Refactor backup creation failures, add case for file too large.

This commit is contained in:
Greyson Parrelli
2025-11-07 13:52:37 -05:00
committed by Michelle Tang
parent ad0b240550
commit 945453cb81
12 changed files with 154 additions and 177 deletions

View File

@@ -207,10 +207,6 @@ object ArchiveUploadProgress {
resetState()
}
fun onValidationFailure() {
resetState()
}
fun onMainBackupFileUploadFailure() {
resetState()
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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