mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-24 04:58:45 +00:00
Refactor backup creation failures, add case for file too large.
This commit is contained in:
committed by
Michelle Tang
parent
ad0b240550
commit
945453cb81
@@ -207,10 +207,6 @@ object ArchiveUploadProgress {
|
||||
resetState()
|
||||
}
|
||||
|
||||
fun onValidationFailure() {
|
||||
resetState()
|
||||
}
|
||||
|
||||
fun onMainBackupFileUploadFailure() {
|
||||
resetState()
|
||||
}
|
||||
|
||||
@@ -116,6 +116,7 @@ import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob
|
||||
import org.thoughtcrime.securesms.jobs.StorageForcePushJob
|
||||
import org.thoughtcrime.securesms.jobs.Svr2MirrorJob
|
||||
import org.thoughtcrime.securesms.jobs.UploadAttachmentToArchiveJob
|
||||
import org.thoughtcrime.securesms.keyvalue.BackupValues
|
||||
import org.thoughtcrime.securesms.keyvalue.BackupValues.ArchiveServiceCredentials
|
||||
import org.thoughtcrime.securesms.keyvalue.KeyValueStore
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
@@ -389,8 +390,8 @@ object BackupRepository {
|
||||
CancelRestoreMediaJob.enqueue()
|
||||
}
|
||||
|
||||
fun markBackupFailure() {
|
||||
SignalStore.backup.markMessageBackupFailure()
|
||||
fun markBackupCreationFailed(error: BackupValues.BackupCreationError) {
|
||||
SignalStore.backup.markBackupCreationFailed(error)
|
||||
ArchiveUploadProgress.onMainBackupFileUploadFailure()
|
||||
|
||||
if (!SignalStore.backup.hasBackupBeenUploaded) {
|
||||
@@ -416,7 +417,7 @@ object BackupRepository {
|
||||
}
|
||||
|
||||
fun clearBackupFailure() {
|
||||
SignalStore.backup.clearMessageBackupFailure()
|
||||
SignalStore.backup.clearBackupCreationFailed()
|
||||
ServiceUtil.getNotificationManager(AppDependencies.application).cancel(NotificationIds.INITIAL_BACKUP_FAILED)
|
||||
}
|
||||
|
||||
@@ -467,7 +468,7 @@ object BackupRepository {
|
||||
*/
|
||||
@JvmStatic
|
||||
fun shouldDisplayBackupFailedIndicator(): Boolean {
|
||||
if (shouldNotDisplayBackupFailedMessaging() || !SignalStore.backup.hasBackupFailure) {
|
||||
if (shouldNotDisplayBackupFailedMessaging() || !SignalStore.backup.hasBackupCreationError) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -482,30 +483,6 @@ object BackupRepository {
|
||||
return !(shouldNotDisplayBackupFailedMessaging() || !SignalStore.backup.hasBackupAlreadyRedeemedError)
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the "Backup Failed" row should be displayed in settings.
|
||||
* Shown when the initial backup creation has failed
|
||||
*/
|
||||
fun shouldDisplayBackupFailedSettingsRow(): Boolean {
|
||||
if (shouldNotDisplayBackupFailedMessaging()) {
|
||||
return false
|
||||
}
|
||||
|
||||
return !SignalStore.backup.hasBackupBeenUploaded && SignalStore.backup.hasBackupFailure
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the "Could not complete backup" row should be displayed in settings.
|
||||
* Shown when a new backup could not be created but there is an existing one already
|
||||
*/
|
||||
fun shouldDisplayCouldNotCompleteBackupSettingsRow(): Boolean {
|
||||
if (shouldNotDisplayBackupFailedMessaging()) {
|
||||
return false
|
||||
}
|
||||
|
||||
return SignalStore.backup.hasBackupBeenUploaded && SignalStore.backup.hasBackupFailure
|
||||
}
|
||||
|
||||
/**
|
||||
* Displayed when the user falls out of the grace period for backups after their subscription
|
||||
* expires.
|
||||
@@ -554,7 +531,7 @@ object BackupRepository {
|
||||
return false
|
||||
}
|
||||
|
||||
return (!SignalStore.backup.hasBackupBeenUploaded || SignalStore.backup.hasValidationError) && SignalStore.backup.hasBackupFailure && System.currentTimeMillis().milliseconds > SignalStore.backup.nextBackupFailureSheetSnoozeTime
|
||||
return SignalStore.backup.hasBackupCreationError && SignalStore.backup.backupCreationError != BackupValues.BackupCreationError.TRANSIENT && System.currentTimeMillis().milliseconds > SignalStore.backup.nextBackupFailureSheetSnoozeTime
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -675,7 +652,7 @@ object BackupRepository {
|
||||
}
|
||||
}
|
||||
|
||||
private fun shouldNotDisplayBackupFailedMessaging(): Boolean {
|
||||
fun shouldNotDisplayBackupFailedMessaging(): Boolean {
|
||||
return !SignalStore.account.isRegistered || !SignalStore.backup.areBackupsEnabled
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ package org.thoughtcrime.securesms.backup.v2.ui.status
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
@@ -19,7 +21,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.AnnotatedString.Builder
|
||||
import androidx.compose.ui.text.LinkAnnotation
|
||||
import androidx.compose.ui.text.Placeholder
|
||||
import androidx.compose.ui.text.PlaceholderVerticalAlign
|
||||
@@ -32,6 +34,7 @@ import androidx.compose.ui.unit.sp
|
||||
import org.signal.core.ui.compose.DayNightPreviews
|
||||
import org.signal.core.ui.compose.Previews
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.keyvalue.BackupValues
|
||||
import org.signal.core.ui.R as CoreUiR
|
||||
|
||||
private val YELLOW_DOT = Color(0xFFFFCC00)
|
||||
@@ -41,27 +44,18 @@ private val YELLOW_DOT = Color(0xFFFFCC00)
|
||||
*/
|
||||
@Composable
|
||||
fun BackupCreateErrorRow(
|
||||
showCouldNotComplete: Boolean,
|
||||
showBackupFailed: Boolean,
|
||||
error: BackupValues.BackupCreationError,
|
||||
onLearnMoreClick: () -> Unit = {}
|
||||
) {
|
||||
if (showBackupFailed) {
|
||||
val inlineContentMap = mapOf(
|
||||
"yellow_bullet" to InlineTextContent(
|
||||
Placeholder(20.sp, 12.sp, PlaceholderVerticalAlign.TextCenter)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(12.dp)
|
||||
.background(color = YELLOW_DOT, shape = CircleShape)
|
||||
)
|
||||
when (error) {
|
||||
BackupValues.BackupCreationError.TRANSIENT -> {
|
||||
BackupAlertText {
|
||||
append(stringResource(R.string.BackupStatusRow__your_last_backup))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
BackupAlertText(
|
||||
text = buildAnnotatedString {
|
||||
appendInlineContent("yellow_bullet")
|
||||
append(" ")
|
||||
BackupValues.BackupCreationError.VALIDATION -> {
|
||||
BackupAlertText {
|
||||
append(stringResource(R.string.BackupStatusRow__your_last_backup_latest_version))
|
||||
append(" ")
|
||||
withLink(
|
||||
@@ -74,11 +68,29 @@ fun BackupCreateErrorRow(
|
||||
) {
|
||||
append(stringResource(R.string.BackupStatusRow__learn_more))
|
||||
}
|
||||
},
|
||||
inlineContent = inlineContentMap
|
||||
)
|
||||
} else if (showCouldNotComplete) {
|
||||
val inlineContentMap = mapOf(
|
||||
}
|
||||
}
|
||||
|
||||
BackupValues.BackupCreationError.BACKUP_FILE_TOO_LARGE -> {
|
||||
BackupAlertText {
|
||||
append(stringResource(R.string.BackupStatusRow__backup_file_too_large))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BackupAlertText(stringBuilder: @Composable Builder.() -> Unit) {
|
||||
Text(
|
||||
text = buildAnnotatedString {
|
||||
appendInlineContent("yellow_bullet")
|
||||
append(" ")
|
||||
stringBuilder()
|
||||
},
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.padding(horizontal = dimensionResource(CoreUiR.dimen.gutter)),
|
||||
inlineContent = mapOf(
|
||||
"yellow_bullet" to InlineTextContent(
|
||||
Placeholder(20.sp, 12.sp, PlaceholderVerticalAlign.TextCenter)
|
||||
) {
|
||||
@@ -89,26 +101,6 @@ fun BackupCreateErrorRow(
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
BackupAlertText(
|
||||
text = buildAnnotatedString {
|
||||
appendInlineContent("yellow_bullet")
|
||||
append(" ")
|
||||
append(stringResource(R.string.BackupStatusRow__your_last_backup))
|
||||
},
|
||||
inlineContent = inlineContentMap
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BackupAlertText(text: AnnotatedString, inlineContent: Map<String, InlineTextContent>) {
|
||||
Text(
|
||||
text = text,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.padding(horizontal = dimensionResource(CoreUiR.dimen.gutter)),
|
||||
inlineContent = inlineContent
|
||||
)
|
||||
}
|
||||
|
||||
@@ -116,14 +108,12 @@ private fun BackupAlertText(text: AnnotatedString, inlineContent: Map<String, In
|
||||
@Composable
|
||||
fun BackupStatusRowCouldNotCompleteBackupPreview() {
|
||||
Previews.Preview {
|
||||
BackupCreateErrorRow(showCouldNotComplete = true, showBackupFailed = false)
|
||||
}
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
fun BackupStatusRowBackupFailedPreview() {
|
||||
Previews.Preview {
|
||||
BackupCreateErrorRow(showCouldNotComplete = false, showBackupFailed = true)
|
||||
Column {
|
||||
for (error in BackupValues.BackupCreationError.entries) {
|
||||
Text(error.name)
|
||||
BackupCreateErrorRow(error = error, onLearnMoreClick = {})
|
||||
Spacer(modifier = Modifier.size(8.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import androidx.lifecycle.map
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||
import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||
import org.thoughtcrime.securesms.backup.v2.BackupRepository
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppDonations
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.RecurringInAppPaymentRepository
|
||||
import org.thoughtcrime.securesms.conversationlist.model.UnreadPaymentsLiveData
|
||||
@@ -72,18 +71,13 @@ class AppSettingsViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
private fun getBackupFailureState(): BackupFailureState {
|
||||
return if (BackupRepository.shouldDisplayOutOfRemoteStorageSpaceUx()) {
|
||||
BackupFailureState.OUT_OF_STORAGE_SPACE
|
||||
} else if (BackupRepository.shouldDisplayBackupFailedSettingsRow()) {
|
||||
BackupFailureState.BACKUP_FAILED
|
||||
} else if (BackupRepository.shouldDisplayCouldNotCompleteBackupSettingsRow()) {
|
||||
BackupFailureState.COULD_NOT_COMPLETE_BACKUP
|
||||
} else if (SignalStore.backup.subscriptionStateMismatchDetected) {
|
||||
BackupFailureState.SUBSCRIPTION_STATE_MISMATCH
|
||||
} else if (SignalStore.backup.hasBackupAlreadyRedeemedError) {
|
||||
BackupFailureState.ALREADY_REDEEMED
|
||||
} else {
|
||||
BackupFailureState.NONE
|
||||
return when {
|
||||
!SignalStore.account.isRegistered || !SignalStore.backup.areBackupsEnabled -> BackupFailureState.NONE
|
||||
SignalStore.backup.isNotEnoughRemoteStorageSpace -> BackupFailureState.OUT_OF_STORAGE_SPACE
|
||||
SignalStore.backup.hasBackupCreationError -> BackupFailureState.COULD_NOT_COMPLETE_BACKUP
|
||||
SignalStore.backup.subscriptionStateMismatchDetected -> BackupFailureState.SUBSCRIPTION_STATE_MISMATCH
|
||||
SignalStore.backup.hasBackupAlreadyRedeemedError -> BackupFailureState.ALREADY_REDEEMED
|
||||
else -> BackupFailureState.NONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -539,11 +539,10 @@ private fun RemoteBackupsSettingsContent(
|
||||
contentCallbacks = contentCallbacks
|
||||
)
|
||||
} else {
|
||||
if (state.showBackupCreateFailedError || state.showBackupCreateCouldNotCompleteError) {
|
||||
if (state.backupCreationError != null) {
|
||||
item {
|
||||
BackupCreateErrorRow(
|
||||
showCouldNotComplete = state.showBackupCreateCouldNotCompleteError,
|
||||
showBackupFailed = state.showBackupCreateFailedError,
|
||||
error = state.backupCreationError,
|
||||
onLearnMoreClick = contentCallbacks::onLearnMoreAboutBackupFailure
|
||||
)
|
||||
}
|
||||
@@ -888,11 +887,10 @@ private fun LazyListScope.appendBackupDetailsItems(
|
||||
}
|
||||
}
|
||||
|
||||
if (state.showBackupCreateFailedError || state.showBackupCreateCouldNotCompleteError) {
|
||||
if (state.backupCreationError != null) {
|
||||
item {
|
||||
BackupCreateErrorRow(
|
||||
showCouldNotComplete = state.showBackupCreateCouldNotCompleteError,
|
||||
showBackupFailed = state.showBackupCreateFailedError,
|
||||
error = state.backupCreationError,
|
||||
onLearnMoreClick = contentCallbacks::onLearnMoreAboutBackupFailure
|
||||
)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ package org.thoughtcrime.securesms.components.settings.app.backups.remote
|
||||
import org.signal.core.util.ByteSize
|
||||
import org.thoughtcrime.securesms.backup.v2.MessageBackupTier
|
||||
import org.thoughtcrime.securesms.components.settings.app.backups.BackupState
|
||||
import org.thoughtcrime.securesms.keyvalue.BackupValues
|
||||
|
||||
/**
|
||||
* @param includeDebuglog The state for whether or not we should include a debuglog in the backup. If `null`, hide the setting.
|
||||
@@ -30,8 +31,7 @@ data class RemoteBackupsSettingsState(
|
||||
val includeDebuglog: Boolean? = null,
|
||||
val canBackupMessagesJobRun: Boolean = false,
|
||||
val backupMediaDetails: BackupMediaDetails? = null,
|
||||
val showBackupCreateFailedError: Boolean = false,
|
||||
val showBackupCreateCouldNotCompleteError: Boolean = false,
|
||||
val backupCreationError: BackupValues.BackupCreationError? = null,
|
||||
val freeTierMediaRetentionDays: Int = -1,
|
||||
val isGooglePlayServicesAvailable: Boolean = false
|
||||
) {
|
||||
|
||||
@@ -77,8 +77,7 @@ class RemoteBackupsSettingsViewModel : ViewModel() {
|
||||
canBackUpUsingCellular = SignalStore.backup.backupWithCellular,
|
||||
canRestoreUsingCellular = SignalStore.backup.restoreWithCellular,
|
||||
includeDebuglog = SignalStore.internal.includeDebuglogInBackup.takeIf { RemoteConfig.internalUser },
|
||||
showBackupCreateFailedError = BackupRepository.shouldDisplayBackupFailedSettingsRow(),
|
||||
showBackupCreateCouldNotCompleteError = BackupRepository.shouldDisplayCouldNotCompleteBackupSettingsRow()
|
||||
backupCreationError = SignalStore.backup.backupCreationError
|
||||
)
|
||||
)
|
||||
|
||||
@@ -349,8 +348,7 @@ class RemoteBackupsSettingsViewModel : ViewModel() {
|
||||
canRestoreUsingCellular = SignalStore.backup.restoreWithCellular,
|
||||
isOutOfStorageSpace = BackupRepository.shouldDisplayOutOfRemoteStorageSpaceUx(),
|
||||
hasRedemptionError = lastPurchase?.data?.error?.data_ == "409",
|
||||
showBackupCreateFailedError = BackupRepository.shouldDisplayBackupFailedSettingsRow(),
|
||||
showBackupCreateCouldNotCompleteError = BackupRepository.shouldDisplayCouldNotCompleteBackupSettingsRow()
|
||||
backupCreationError = SignalStore.backup.backupCreationError
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@ import org.thoughtcrime.securesms.jobs.ArchiveAttachmentReconciliationJob
|
||||
import org.thoughtcrime.securesms.jobs.ArchiveThumbnailBackfillJob
|
||||
import org.thoughtcrime.securesms.jobs.BackupRestoreMediaJob
|
||||
import org.thoughtcrime.securesms.jobs.LocalBackupJob
|
||||
import org.thoughtcrime.securesms.keyvalue.BackupValues
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
|
||||
@@ -561,10 +562,10 @@ fun Screen(
|
||||
)
|
||||
|
||||
Rows.TextRow(
|
||||
text = "Mark backup failure",
|
||||
text = "Mark backup validation failure",
|
||||
label = "This will display the error sheet when returning to the chats list.",
|
||||
onClick = {
|
||||
BackupRepository.markBackupFailure()
|
||||
BackupRepository.markBackupCreationFailed(BackupValues.BackupCreationError.VALIDATION)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.BackupMessagesConstraint
|
||||
import org.thoughtcrime.securesms.jobs.protos.BackupMessagesJobData
|
||||
import org.thoughtcrime.securesms.keyvalue.BackupValues
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.keyvalue.isDecisionPending
|
||||
import org.thoughtcrime.securesms.logsubmit.SubmitDebugLogActivity
|
||||
@@ -120,6 +121,8 @@ class BackupMessagesJob private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private var backupErrorHandled = false
|
||||
|
||||
constructor() : this(
|
||||
syncTime = 0L,
|
||||
dataFile = "",
|
||||
@@ -145,9 +148,9 @@ class BackupMessagesJob private constructor(
|
||||
}
|
||||
|
||||
override fun onFailure() {
|
||||
if (!isCanceled) {
|
||||
if (!isCanceled && !backupErrorHandled) {
|
||||
Log.w(TAG, "Failed to backup user messages. Marking failure state.", true)
|
||||
BackupRepository.markBackupFailure()
|
||||
BackupRepository.markBackupCreationFailed(BackupValues.BackupCreationError.TRANSIENT)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,13 +297,15 @@ class BackupMessagesJob private constructor(
|
||||
Log.i(TAG, "Backup file is too large! Size: ${tempBackupFile.length()} bytes", result.getCause(), true)
|
||||
tempBackupFile.delete()
|
||||
this.dataFile = ""
|
||||
// TODO [backup] Need to show the user an error
|
||||
BackupRepository.markBackupCreationFailed(BackupValues.BackupCreationError.BACKUP_FILE_TOO_LARGE)
|
||||
backupErrorHandled = true
|
||||
return Result.failure()
|
||||
}
|
||||
else -> {
|
||||
Log.i(TAG, "Status code failure", result.getCause(), true)
|
||||
return Result.retry(defaultBackoff())
|
||||
}
|
||||
}
|
||||
return Result.retry(defaultBackoff())
|
||||
}
|
||||
|
||||
is NetworkResult.ApplicationError -> throw result.throwable
|
||||
@@ -458,7 +463,6 @@ class BackupMessagesJob private constructor(
|
||||
|
||||
when (val result = ArchiveValidator.validateSignalBackup(tempBackupFile, backupKey, forwardSecrecyToken)) {
|
||||
ArchiveValidator.ValidationResult.Success -> {
|
||||
SignalStore.backup.hasValidationError = false
|
||||
Log.d(TAG, "Successfully passed validation.", true)
|
||||
}
|
||||
|
||||
@@ -471,8 +475,8 @@ class BackupMessagesJob private constructor(
|
||||
Log.w(TAG, "The backup file fails validation! Message: ${result.exception.message}, Details: ${result.messageDetails}", true)
|
||||
tempBackupFile.delete()
|
||||
this.dataFile = ""
|
||||
SignalStore.backup.hasValidationError = true
|
||||
ArchiveUploadProgress.onValidationFailure()
|
||||
BackupRepository.markBackupCreationFailed(BackupValues.BackupCreationError.VALIDATION)
|
||||
backupErrorHandled = true
|
||||
return BackupFileResult.Failure
|
||||
}
|
||||
|
||||
@@ -481,8 +485,8 @@ class BackupMessagesJob private constructor(
|
||||
tempBackupFile.delete()
|
||||
this.dataFile = ""
|
||||
AppDependencies.jobManager.add(E164FormattingJob())
|
||||
SignalStore.backup.hasValidationError = true
|
||||
ArchiveUploadProgress.onValidationFailure()
|
||||
BackupRepository.markBackupCreationFailed(BackupValues.BackupCreationError.VALIDATION)
|
||||
backupErrorHandled = true
|
||||
return BackupFileResult.Failure
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import okio.withLock
|
||||
import org.signal.core.util.LongSerializer
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.backup.DeletionState
|
||||
import org.thoughtcrime.securesms.backup.RestoreState
|
||||
@@ -67,7 +68,7 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) {
|
||||
private const val KEY_BACKUP_UPLOADED = "backup.backupUploaded"
|
||||
private const val KEY_SUBSCRIPTION_STATE_MISMATCH = "backup.subscriptionStateMismatch"
|
||||
|
||||
private const val KEY_BACKUP_FAIL = "backup.failed"
|
||||
private const val KEY_BACKUP_CREATION_ERROR = "backup.creationError"
|
||||
private const val KEY_BACKUP_FAIL_ACKNOWLEDGED_SNOOZE_TIME = "backup.failed.acknowledged.snooze.time"
|
||||
private const val KEY_BACKUP_FAIL_ACKNOWLEDGED_SNOOZE_COUNT = "backup.failed.acknowledged.snooze.count"
|
||||
private const val KEY_BACKUP_FAIL_SHEET_SNOOZE_TIME = "backup.failed.sheet.snooze"
|
||||
@@ -77,7 +78,6 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) {
|
||||
private const val KEY_NOT_ENOUGH_REMOTE_STORAGE_SPACE = "backup.not.enough.remote.storage.space"
|
||||
private const val KEY_NOT_ENOUGH_REMOTE_STORAGE_SPACE_DISPLAY_SHEET = "backup.not.enough.remote.storage.space.display.sheet"
|
||||
private const val KEY_MANUAL_NO_BACKUP_NOTIFIED = "backup.manual.no.backup.notified"
|
||||
private const val KEY_VALIDATION_ERROR = "backup.validation.error"
|
||||
|
||||
private const val KEY_USER_MANUALLY_SKIPPED_MEDIA_RESTORE = "backup.user.manually.skipped.media.restore"
|
||||
private const val KEY_BACKUP_EXPIRED_AND_DOWNGRADED = "backup.expired.and.downgraded"
|
||||
@@ -259,7 +259,7 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) {
|
||||
|
||||
if (storedValue != value) {
|
||||
clearNotEnoughRemoteStorageSpace()
|
||||
clearMessageBackupFailure()
|
||||
clearBackupCreationFailed()
|
||||
clearMessageBackupFailureSheetWatermark()
|
||||
}
|
||||
|
||||
@@ -302,10 +302,8 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) {
|
||||
/** True if we believe we have successfully uploaded a backup, otherwise false. */
|
||||
var hasBackupBeenUploaded: Boolean by booleanValue(KEY_BACKUP_UPLOADED, false)
|
||||
|
||||
/** Set when we fail to validate a user's backup during the export process */
|
||||
var hasValidationError: Boolean by booleanValue(KEY_VALIDATION_ERROR, false)
|
||||
|
||||
val hasBackupFailure: Boolean get() = getBoolean(KEY_BACKUP_FAIL, false)
|
||||
val hasBackupCreationError: Boolean get() = backupCreationError != null
|
||||
val backupCreationError: BackupCreationError? by enumValue(KEY_BACKUP_CREATION_ERROR, null, BackupCreationError.serializer)
|
||||
val nextBackupFailureSnoozeTime: Duration get() = getLong(KEY_BACKUP_FAIL_ACKNOWLEDGED_SNOOZE_TIME, 0L).milliseconds
|
||||
val nextBackupFailureSheetSnoozeTime: Duration get() = getLong(KEY_BACKUP_FAIL_SHEET_SNOOZE_TIME, getNextBackupFailureSheetSnoozeTime(lastBackupTime.milliseconds).inWholeMilliseconds).milliseconds
|
||||
|
||||
@@ -343,11 +341,6 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) {
|
||||
backupDownloadNotifierState = null
|
||||
}
|
||||
|
||||
fun internalSetBackupFailedErrorState() {
|
||||
markMessageBackupFailure()
|
||||
putLong(KEY_BACKUP_FAIL_SHEET_SNOOZE_TIME, 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Call when the user disables backups. Clears/resets all relevant fields.
|
||||
*/
|
||||
@@ -458,9 +451,9 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) {
|
||||
.apply()
|
||||
}
|
||||
|
||||
fun markMessageBackupFailure() {
|
||||
fun markBackupCreationFailed(error: BackupCreationError) {
|
||||
store.beginWrite()
|
||||
.putBoolean(KEY_BACKUP_FAIL, true)
|
||||
.putLong(KEY_BACKUP_CREATION_ERROR, error.value)
|
||||
.putLong(KEY_BACKUP_FAIL_ACKNOWLEDGED_SNOOZE_TIME, System.currentTimeMillis())
|
||||
.putLong(KEY_BACKUP_FAIL_ACKNOWLEDGED_SNOOZE_COUNT, 0)
|
||||
.putLong(KEY_BACKUP_FAIL_SHEET_SNOOZE_TIME, System.currentTimeMillis())
|
||||
@@ -468,7 +461,7 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) {
|
||||
}
|
||||
|
||||
fun updateMessageBackupFailureWatermark() {
|
||||
if (!hasBackupFailure) {
|
||||
if (!hasBackupCreationError) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -485,8 +478,8 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) {
|
||||
.apply()
|
||||
}
|
||||
|
||||
fun clearMessageBackupFailure() {
|
||||
putBoolean(KEY_BACKUP_FAIL, false)
|
||||
fun clearBackupCreationFailed() {
|
||||
putLong(KEY_BACKUP_CREATION_ERROR, -1)
|
||||
}
|
||||
|
||||
fun updateMessageBackupFailureSheetWatermark() {
|
||||
@@ -587,4 +580,28 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) {
|
||||
putLong(cdnTimestampKey, System.currentTimeMillis())
|
||||
}
|
||||
}
|
||||
|
||||
enum class BackupCreationError(val value: Long) {
|
||||
/** A temporary failure, usually cause by poor network. */
|
||||
TRANSIENT(1),
|
||||
|
||||
/** The validation of the backup file failed. This likely cannot be fixed without an app update. */
|
||||
VALIDATION(2),
|
||||
|
||||
/** The backup file itself is too large. The only resolution would be for the user to delete some number of messages. */
|
||||
BACKUP_FILE_TOO_LARGE(3);
|
||||
|
||||
companion object {
|
||||
|
||||
val serializer = object : LongSerializer<BackupCreationError?> {
|
||||
override fun serialize(data: BackupCreationError?): Long {
|
||||
return data?.value ?: -1
|
||||
}
|
||||
|
||||
override fun deserialize(input: Long): BackupCreationError? {
|
||||
return entries.firstOrNull { it.value == input }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,22 +27,22 @@ class LogSectionRemoteBackups : LogSection {
|
||||
val output = StringBuilder()
|
||||
|
||||
output.append("-- Backup State\n")
|
||||
output.append("Enabled: ${SignalStore.backup.areBackupsEnabled}\n")
|
||||
output.append("Current tier: ${SignalStore.backup.backupTier}\n")
|
||||
output.append("Latest tier: ${SignalStore.backup.latestBackupTier}\n")
|
||||
output.append("Backup override tier: ${SignalStore.backup.backupTierInternalOverride}\n")
|
||||
output.append("Last backup time: ${SignalStore.backup.lastBackupTime}\n")
|
||||
output.append("Last check-in: ${SignalStore.backup.lastCheckInMillis}\n")
|
||||
output.append("Last reconciliation time: ${SignalStore.backup.lastAttachmentReconciliationTime}\n")
|
||||
output.append("Days since last backup: ${SignalStore.backup.daysSinceLastBackup}\n")
|
||||
output.append("User manually skipped media restore: ${SignalStore.backup.userManuallySkippedMediaRestore}\n")
|
||||
output.append("Can backup with cellular: ${SignalStore.backup.backupWithCellular}\n")
|
||||
output.append("Has backup been uploaded: ${SignalStore.backup.hasBackupBeenUploaded}\n")
|
||||
output.append("Has backup failure: ${SignalStore.backup.hasBackupFailure}\n")
|
||||
output.append("Optimize storage: ${SignalStore.backup.optimizeStorage}\n")
|
||||
output.append("Enabled : ${SignalStore.backup.areBackupsEnabled}\n")
|
||||
output.append("Current tier : ${SignalStore.backup.backupTier}\n")
|
||||
output.append("Latest tier : ${SignalStore.backup.latestBackupTier}\n")
|
||||
output.append("Backup override tier : ${SignalStore.backup.backupTierInternalOverride}\n")
|
||||
output.append("Last backup time : ${SignalStore.backup.lastBackupTime}\n")
|
||||
output.append("Last check-in : ${SignalStore.backup.lastCheckInMillis}\n")
|
||||
output.append("Last reconciliation time : ${SignalStore.backup.lastAttachmentReconciliationTime}\n")
|
||||
output.append("Days since last backup : ${SignalStore.backup.daysSinceLastBackup}\n")
|
||||
output.append("User manually skipped media restore : ${SignalStore.backup.userManuallySkippedMediaRestore}\n")
|
||||
output.append("Can backup with cellular : ${SignalStore.backup.backupWithCellular}\n")
|
||||
output.append("Has backup been uploaded : ${SignalStore.backup.hasBackupBeenUploaded}\n")
|
||||
output.append("Backup failure state : ${SignalStore.backup.backupCreationError?.name ?: "None"}\n")
|
||||
output.append("Optimize storage : ${SignalStore.backup.optimizeStorage}\n")
|
||||
output.append("Detected subscription state mismatch: ${SignalStore.backup.subscriptionStateMismatchDetected}\n")
|
||||
output.append("Last verified key time: ${SignalStore.backup.lastVerifyKeyTime}\n")
|
||||
output.append("Restore state: ${ArchiveRestoreProgress.state}\n")
|
||||
output.append("Last verified key time : ${SignalStore.backup.lastVerifyKeyTime}\n")
|
||||
output.append("Restore state : ${ArchiveRestoreProgress.state}\n")
|
||||
output.append("\n -- Subscription State\n")
|
||||
|
||||
val backupSubscriptionId = InAppPaymentsRepository.getSubscriber(InAppPaymentSubscriberRecord.Type.BACKUP)
|
||||
@@ -50,21 +50,21 @@ class LogSectionRemoteBackups : LogSection {
|
||||
val googlePlayServicesAvailability = GooglePlayServicesAvailability.fromCode(GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context))
|
||||
val inAppPayment = SignalDatabase.inAppPayments.getLatestInAppPaymentByType(InAppPaymentType.RECURRING_BACKUP)
|
||||
|
||||
output.append("Has backup subscription id: ${backupSubscriptionId != null}\n")
|
||||
output.append("Google Play Billing state: $googlePlayBillingAccess\n")
|
||||
output.append("Google Play Services state: $googlePlayServicesAvailability\n\n")
|
||||
output.append("Has backup subscription id : ${backupSubscriptionId != null}\n")
|
||||
output.append("Google Play Billing state : $googlePlayBillingAccess\n")
|
||||
output.append("Google Play Services state : $googlePlayServicesAvailability\n\n")
|
||||
|
||||
if (inAppPayment != null) {
|
||||
output.append("IAP end of period (seconds): ${inAppPayment.endOfPeriodSeconds}\n")
|
||||
output.append("IAP state: ${inAppPayment.state.name}\n")
|
||||
output.append("IAP inserted at (seconds): ${inAppPayment.insertedAt.inWholeSeconds}\n")
|
||||
output.append("IAP updated at (seconds): ${inAppPayment.updatedAt.inWholeSeconds}\n")
|
||||
output.append("IAP notified flag: ${inAppPayment.notified}\n")
|
||||
output.append("IAP level: ${inAppPayment.data.level}\n")
|
||||
output.append("IAP redemption stage (or null): ${inAppPayment.data.redemption?.stage}\n")
|
||||
output.append("IAP error type (or null): ${inAppPayment.data.error?.type}\n")
|
||||
output.append("IAP end of period (seconds) : ${inAppPayment.endOfPeriodSeconds}\n")
|
||||
output.append("IAP state : ${inAppPayment.state.name}\n")
|
||||
output.append("IAP inserted at (seconds) : ${inAppPayment.insertedAt.inWholeSeconds}\n")
|
||||
output.append("IAP updated at (seconds) : ${inAppPayment.updatedAt.inWholeSeconds}\n")
|
||||
output.append("IAP notified flag : ${inAppPayment.notified}\n")
|
||||
output.append("IAP level : ${inAppPayment.data.level}\n")
|
||||
output.append("IAP redemption stage (or null) : ${inAppPayment.data.redemption?.stage}\n")
|
||||
output.append("IAP error type (or null) : ${inAppPayment.data.error?.type}\n")
|
||||
output.append("IAP cancellation reason (or null): ${inAppPayment.data.cancellation?.reason}\n")
|
||||
output.append("IAP price: ${inAppPayment.data.amount?.toFiatMoney()?.let { FiatMoneyUtil.format(context.resources, it)} ?: "Not available" }\n")
|
||||
output.append("IAP price : ${inAppPayment.data.amount?.toFiatMoney()?.let { FiatMoneyUtil.format(context.resources, it)} ?: "Not available" }\n")
|
||||
} else {
|
||||
output.append("No in-app payment data available.\n")
|
||||
}
|
||||
@@ -90,7 +90,7 @@ class LogSectionRemoteBackups : LogSection {
|
||||
output.append(SignalStore.backup.archiveUploadState!!.toPrettyString())
|
||||
|
||||
if (SignalStore.backup.archiveUploadState!!.state !in setOf(ArchiveUploadProgressState.State.None, ArchiveUploadProgressState.State.UserCanceled)) {
|
||||
output.append("Pending bytes: ${SignalDatabase.attachments.getPendingArchiveUploadBytes()}\n")
|
||||
output.append("Pending bytes : ${SignalDatabase.attachments.getPendingArchiveUploadBytes()}\n")
|
||||
}
|
||||
} else {
|
||||
output.append("None\n")
|
||||
@@ -105,28 +105,28 @@ class LogSectionRemoteBackups : LogSection {
|
||||
|
||||
private fun ArchiveUploadProgressState.toPrettyString(): String {
|
||||
return buildString {
|
||||
appendLine("state: ${state.name}")
|
||||
appendLine("backupPhase: ${backupPhase.name}")
|
||||
appendLine("frameExportCount: $frameExportCount")
|
||||
appendLine("frameTotalCount: $frameTotalCount")
|
||||
appendLine("state : ${state.name}")
|
||||
appendLine("backupPhase : ${backupPhase.name}")
|
||||
appendLine("frameExportCount : $frameExportCount")
|
||||
appendLine("frameTotalCount : $frameTotalCount")
|
||||
appendLine("backupFileUploadedBytes: $backupFileUploadedBytes")
|
||||
appendLine("backupFileTotalBytes: $backupFileTotalBytes")
|
||||
appendLine("mediaUploadedBytes: $mediaUploadedBytes")
|
||||
appendLine("mediaTotalBytes: $mediaTotalBytes")
|
||||
appendLine("backupFileTotalBytes : $backupFileTotalBytes")
|
||||
appendLine("mediaUploadedBytes : $mediaUploadedBytes")
|
||||
appendLine("mediaTotalBytes : $mediaTotalBytes")
|
||||
|
||||
if (frameTotalCount > 0) {
|
||||
val frameProgress = (frameExportCount.toDouble() / frameTotalCount * 100).toInt()
|
||||
appendLine("Frame export progress: $frameProgress%")
|
||||
appendLine("Frame export progress : $frameProgress%")
|
||||
}
|
||||
|
||||
if (backupFileTotalBytes > 0) {
|
||||
val backupFileProgress = (backupFileUploadedBytes.toDouble() / backupFileTotalBytes * 100).toInt()
|
||||
appendLine("Backup file upload progress: $backupFileProgress%")
|
||||
appendLine("Backup file upload progress : $backupFileProgress%")
|
||||
}
|
||||
|
||||
if (mediaTotalBytes > 0) {
|
||||
val mediaProgress = (mediaUploadedBytes.toDouble() / mediaTotalBytes * 100).toInt()
|
||||
appendLine("Media upload progress: $mediaProgress%")
|
||||
appendLine("Media upload progress : $mediaProgress%")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8050,6 +8050,8 @@
|
||||
<string name="BackupStatusRow__your_last_backup">Your last backup couldn\'t be completed. Make sure your phone is connected to Wi-Fi and tap \"Back up now\" to try again.</string>
|
||||
<!-- Text displayed when a backup could not be completed and to check that they are on the latest version of Signal -->
|
||||
<string name="BackupStatusRow__your_last_backup_latest_version">Your last backup couldn\'t be completed. Make sure you\'re on the latest version of Signal and try again.</string>
|
||||
<!-- Text displayed when a backup file exceeds the storage limit (HTTP 413 error) -->
|
||||
<string name="BackupStatusRow__backup_file_too_large">The number of messages you have exceeds the storage limit. Delete some messages and try again.</string>
|
||||
<!-- Text displayed in a row to learn more about why a backup failed -->
|
||||
<string name="BackupStatusRow__learn_more">Learn more</string>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user