From 78130adac7473776eec558f4527e76f35640dfcb Mon Sep 17 00:00:00 2001 From: Pete Walters Date: Tue, 2 Jun 2026 14:00:50 -0500 Subject: [PATCH] Add internal setting to force regeneration of backup thumbnails --- ...InternalBackupSettingsViewController.swift | 11 ++++++ .../AttachmentOffloadingManager.swift | 37 +++++++++++++++---- .../Settings/BackupSettingsStore.swift | 15 ++++++++ 3 files changed, 56 insertions(+), 7 deletions(-) diff --git a/Signal/src/ViewControllers/AppSettings/Internal/InternalBackupSettingsViewController.swift b/Signal/src/ViewControllers/AppSettings/Internal/InternalBackupSettingsViewController.swift index 0e06c0fd33..7b18acdcae 100644 --- a/Signal/src/ViewControllers/AppSettings/Internal/InternalBackupSettingsViewController.swift +++ b/Signal/src/ViewControllers/AppSettings/Internal/InternalBackupSettingsViewController.swift @@ -65,6 +65,17 @@ class InternalBackupSettingsViewController: OWSTableViewController2 { self?.navigationController?.pushViewController(vc, animated: true) }) if RemoteConfig.current.isOptimizeStorageEnabled { + section.add(.switch( + withText: "Regenerate backup thumbnails", + subtitle: "Regenerate backup thumbnails on next offloading run", + isOn: { db.read(block: backupSettingsStore.shouldGenerateThumbnailsOnNextOffloading(tx:)) }, + actionBlock: { _ in + db.write { tx in + let currentValue = backupSettingsStore.shouldGenerateThumbnailsOnNextOffloading(tx: tx) + backupSettingsStore.setShouldGenerateThumbnailsOnNextOffloading(!currentValue, tx: tx) + } + }, + )) section.add(.switch( withText: "Aggressive optimize media", subtitle: "Don't keep recent attachments when optimize media is enabled", diff --git a/SignalServiceKit/Backups/Attachments/AttachmentOffloadingManager.swift b/SignalServiceKit/Backups/Attachments/AttachmentOffloadingManager.swift index d2cd01233d..82ebd0b431 100644 --- a/SignalServiceKit/Backups/Attachments/AttachmentOffloadingManager.swift +++ b/SignalServiceKit/Backups/Attachments/AttachmentOffloadingManager.swift @@ -124,6 +124,8 @@ public class AttachmentOffloadingManagerImpl: AttachmentOffloadingManager { throw NeedsListMediaError() } + let forceThumbnailGeneration = db.read(block: backupSettingsStore.shouldGenerateThumbnailsOnNextOffloading(tx:)) + let (candidateAttachments, didHitEnd) = try db.read { tx -> ([Attachment], Bool) in guard offloadingIsAllowed(tx: tx) else { return ([], false) @@ -176,7 +178,12 @@ public class AttachmentOffloadingManagerImpl: AttachmentOffloadingManager { ) { attachments.append(attachment) - if self.thumbnailableAttachment(attachment) != nil { + if + self.thumbnailableAttachment( + attachment, + ignoreExisting: forceThumbnailGeneration, + ) != nil + { numAttachmentsNeedingThumbnail += 1 } } @@ -201,10 +208,15 @@ public class AttachmentOffloadingManagerImpl: AttachmentOffloadingManager { return nil } - try await downloadExistingThumbnails(candidateAttachments) + if !forceThumbnailGeneration { + try await downloadExistingThumbnails(candidateAttachments) + } // Generate if we can't download - let pendingThumbnails = try await generateThumbnails(candidateAttachments) + let pendingThumbnails = try await generateThumbnails( + candidateAttachments, + ignoreExisting: forceThumbnailGeneration, + ) await db.awaitableWrite { tx in guard offloadingIsAllowed(tx: tx) else { @@ -278,6 +290,9 @@ public class AttachmentOffloadingManagerImpl: AttachmentOffloadingManager { } if didHitEnd { + await db.awaitableWrite { tx in + backupSettingsStore.setShouldGenerateThumbnailsOnNextOffloading(false, tx: tx) + } return nil } else { return candidateAttachments.last?.id @@ -358,9 +373,12 @@ public class AttachmentOffloadingManagerImpl: AttachmentOffloadingManager { } /// Returns nil if the attachment cannot or does not need to be thumbnailed. - private func thumbnailableAttachment(_ attachment: Attachment) -> ThumbnailableAttachment? { + private func thumbnailableAttachment( + _ attachment: Attachment, + ignoreExisting: Bool, + ) -> ThumbnailableAttachment? { guard - attachment.localRelativeFilePathThumbnail == nil, + (ignoreExisting || attachment.localRelativeFilePathThumbnail == nil), AttachmentBackupThumbnail.canBeThumbnailed(attachment), let stream = attachment.asStream(), let mediaName = attachment.mediaName @@ -403,8 +421,13 @@ public class AttachmentOffloadingManagerImpl: AttachmentOffloadingManager { } } - private func generateThumbnails(_ attachments: [Attachment]) async throws -> [Attachment.IDType: PendingThumbnail] { - let attachments = attachments.compactMap(self.thumbnailableAttachment(_:)) + private func generateThumbnails( + _ attachments: [Attachment], + ignoreExisting: Bool, + ) async throws -> [Attachment.IDType: PendingThumbnail] { + let attachments = attachments.compactMap { + self.thumbnailableAttachment($0, ignoreExisting: ignoreExisting) + } if attachments.isEmpty { return [:] } diff --git a/SignalServiceKit/Backups/Settings/BackupSettingsStore.swift b/SignalServiceKit/Backups/Settings/BackupSettingsStore.swift index ab5460681f..dbf9517835 100644 --- a/SignalServiceKit/Backups/Settings/BackupSettingsStore.swift +++ b/SignalServiceKit/Backups/Settings/BackupSettingsStore.swift @@ -60,6 +60,7 @@ public struct BackupSettingsStore { private enum Keys { static let haveEverBeenEnabled = "haveEverBeenEnabledKey2" static let shouldOverrideShowBackupsOnboarding = "shouldOverrideShowBackupsOnboarding" + static let shouldGenerateThumbnailsOnNextOffloading = "shouldGenerateThumbnailsOnNextOffloading" static let plan = "planKey2" static let firstBackupDate = "firstBackupDate" static let lastBackupDate = "lastBackupDate" @@ -113,6 +114,20 @@ public struct BackupSettingsStore { kvStore.setBool(value, key: Keys.shouldOverrideShowBackupsOnboarding, transaction: tx) } + /// Whether to force generation/upload of new thumbnails during the next offloading of attachment. + /// + /// Not intended for production use. + public func shouldGenerateThumbnailsOnNextOffloading(tx: DBReadTransaction) -> Bool { + return kvStore.getBool(Keys.shouldGenerateThumbnailsOnNextOffloading, defaultValue: false, transaction: tx) + } + + /// Set an override to force generation/upload of new thumbnails during the next offloading of attachment. + /// + /// Not intended for production use. + public func setShouldGenerateThumbnailsOnNextOffloading(_ value: Bool, tx: DBWriteTransaction) { + kvStore.setBool(value, key: Keys.shouldGenerateThumbnailsOnNextOffloading, transaction: tx) + } + // MARK: - BackupPlan /// This device's view of the user's current Backup plan. A return value of