diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/ArchiveAttachmentReconciliationJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/ArchiveAttachmentReconciliationJob.kt index a982c3c1b3..1f8a8555c4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/ArchiveAttachmentReconciliationJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/ArchiveAttachmentReconciliationJob.kt @@ -36,7 +36,6 @@ import org.thoughtcrime.securesms.util.RemoteConfig import org.whispersystems.signalservice.api.NetworkResult import org.whispersystems.signalservice.api.archive.ArchiveGetMediaItemsResponse import org.whispersystems.signalservice.api.backup.MediaId -import java.lang.RuntimeException import kotlin.time.Duration.Companion.days import kotlin.time.Duration.Companion.hours @@ -66,6 +65,20 @@ class ArchiveAttachmentReconciliationJob private constructor( private const val CDN_FETCH_LIMIT = 10_000 private const val DELETE_BATCH_SIZE = 10_000 + + /** + * Enqueues a reconciliation job if the retry limit hasn't been exceeded. + * + * @param forced If true, forces the job run to bypass any sync interval constraints. + */ + fun enqueueIfRetryAllowed(forced: Boolean) { + if (SignalStore.backup.archiveAttachmentReconciliationAttempts < 3) { + SignalStore.backup.archiveAttachmentReconciliationAttempts++ + AppDependencies.jobManager.add(ArchiveAttachmentReconciliationJob(forced = forced)) + } else { + Log.i(TAG, "Skip enqueueing reconciliation job: attempt limit exceeded.") + } + } } constructor(forced: Boolean = false) : this( @@ -327,6 +340,7 @@ class ArchiveAttachmentReconciliationJob private constructor( return null to Result.failure() } } + is NetworkResult.ApplicationError -> { Log.w(TAG, "Failed to list remote media objects due to a crash.", result.getCause()) return null to Result.fatalFailure(RuntimeException(result.getCause())) diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/CopyAttachmentToArchiveJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/CopyAttachmentToArchiveJob.kt index f0bc624677..c5c5cd0c4b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/CopyAttachmentToArchiveJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/CopyAttachmentToArchiveJob.kt @@ -173,7 +173,7 @@ class CopyAttachmentToArchiveJob private constructor(private val attachmentId: A Log.i(TAG, "[$attachmentId] Remote storage is full, but our local state indicates that once we reconcile our storage, we should have enough. Enqueuing the reconciliation job and retrying.") SignalStore.backup.remoteStorageGarbageCollectionPending = true - AppDependencies.jobManager.add(ArchiveAttachmentReconciliationJob(forced = true)) + ArchiveAttachmentReconciliationJob.enqueueIfRetryAllowed(forced = true) Result.retry(defaultBackoff()) } @@ -206,6 +206,7 @@ class CopyAttachmentToArchiveJob private constructor(private val attachmentId: A } ArchiveUploadProgress.onAttachmentFinished(attachmentId) + SignalStore.backup.archiveAttachmentReconciliationAttempts = 0 } return result @@ -213,7 +214,7 @@ class CopyAttachmentToArchiveJob private constructor(private val attachmentId: A private fun getServerQuota(): ByteSize? { return runBlocking { - BackupRepository.getPaidType().successOrThrow()?.storageAllowanceBytes?.bytes + BackupRepository.getPaidType().successOrThrow().storageAllowanceBytes?.bytes } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/BackupValues.kt b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/BackupValues.kt index 5cf321d73f..86151223cd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/BackupValues.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/BackupValues.kt @@ -82,6 +82,7 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) { private const val KEY_BACKUP_EXPIRED_AND_DOWNGRADED = "backup.expired.and.downgraded" private const val KEY_BACKUP_DELETION_STATE = "backup.deletion.state" private const val KEY_REMOTE_STORAGE_GARBAGE_COLLECTION_PENDING = "backup.remoteStorageGarbageCollectionPending" + private const val KEY_ARCHIVE_ATTACHMENT_RECONCILIATION_ATTEMPTS = "backup.archiveAttachmentReconciliationAttempts" private const val KEY_MEDIA_ROOT_BACKUP_KEY = "backup.mediaRootBackupKey" @@ -393,10 +394,25 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) { var remoteStorageGarbageCollectionPending get() = store.getBoolean(KEY_REMOTE_STORAGE_GARBAGE_COLLECTION_PENDING, false) set(value) { - store.beginWrite().putBoolean(KEY_REMOTE_STORAGE_GARBAGE_COLLECTION_PENDING, value) + store.beginWrite() + .putBoolean(KEY_REMOTE_STORAGE_GARBAGE_COLLECTION_PENDING, value) + .apply() NoRemoteArchiveGarbageCollectionPendingConstraint.Observer.notifyListeners() } + /** + * Tracks archive attachment reconciliation attempts to prevent infinite retries when we disagree with the server about available storage space. + */ + var archiveAttachmentReconciliationAttempts: Int + get() { + return store.getInteger(KEY_ARCHIVE_ATTACHMENT_RECONCILIATION_ATTEMPTS, 0) + } + set(value) { + store.beginWrite() + .putInteger(KEY_ARCHIVE_ATTACHMENT_RECONCILIATION_ATTEMPTS, value) + .apply() + } + /** * When we are told by the server that we are out of storage space, we should show * UX treatment to make the user aware of this. @@ -416,6 +432,8 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) { .putBoolean(KEY_NOT_ENOUGH_REMOTE_STORAGE_SPACE, false) .putBoolean(KEY_NOT_ENOUGH_REMOTE_STORAGE_SPACE_DISPLAY_SHEET, false) .apply() + + archiveAttachmentReconciliationAttempts = 0 } /**