From d7714a20672cf026a902d31ebb80d5d3ee65c454 Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Thu, 14 Aug 2025 10:52:33 -0400 Subject: [PATCH] Do not archive view-once media. --- .../securesms/backup/v2/BackupRepository.kt | 1 + .../backup/v2/DatabaseAttachmentArchiveUtil.kt | 4 ++++ .../thoughtcrime/securesms/database/AttachmentTable.kt | 10 +++++++--- .../thoughtcrime/securesms/database/MessageTable.kt | 7 +++++++ .../securesms/jobs/ArchiveThumbnailUploadJob.kt | 4 ++-- .../securesms/jobs/AttachmentDownloadJob.kt | 4 ++++ .../thoughtcrime/securesms/jobs/AttachmentUploadJob.kt | 10 ++++++---- .../thoughtcrime/securesms/jobs/BackupMessagesJob.kt | 2 +- .../securesms/jobs/CopyAttachmentToArchiveJob.kt | 6 ++++++ .../securesms/jobs/UploadAttachmentToArchiveJob.kt | 6 ++++++ 10 files changed, 44 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt index dcf0dd3a56..be2b840613 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt @@ -1590,6 +1590,7 @@ object BackupRepository { !DatabaseAttachmentArchiveUtil.hadIntegrityCheckPerformed(attachment) -> false messageId == AttachmentTable.PREUPLOAD_MESSAGE_ID -> false SignalDatabase.messages.isStory(messageId) -> false + SignalDatabase.messages.isViewOnce(messageId) -> false SignalDatabase.messages.willMessageExpireBeforeCutoff(messageId) -> false else -> true } diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/DatabaseAttachmentArchiveUtil.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/DatabaseAttachmentArchiveUtil.kt index d0853a1156..89a693b280 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/DatabaseAttachmentArchiveUtil.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/DatabaseAttachmentArchiveUtil.kt @@ -90,6 +90,10 @@ fun DatabaseAttachment.requireThumbnailMediaName(): MediaName { return DatabaseAttachmentArchiveUtil.requireThumbnailMediaName(this) } +fun DatabaseAttachment.hadIntegrityCheckPerformed(): Boolean { + return DatabaseAttachmentArchiveUtil.hadIntegrityCheckPerformed(this) +} + /** * Creates a [SignalServiceAttachmentPointer] for the archived attachment of the given [DatabaseAttachment]. */ diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentTable.kt index dcd78c3c15..fa6f5949ee 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentTable.kt @@ -687,7 +687,7 @@ class AttachmentTable( /** * Similar to [getAttachmentsThatNeedArchiveUpload], but returns if the list would be non-null in a more efficient way. */ - fun doAnyAttachmentsNeedArchiveUpload(currentTime: Long): Boolean { + fun doAnyAttachmentsNeedArchiveUpload(): Boolean { return readableDatabase .exists("$TABLE_NAME LEFT JOIN ${MessageTable.TABLE_NAME} ON $TABLE_NAME.$MESSAGE_ID = ${MessageTable.TABLE_NAME}.${MessageTable.ID}") .where(buildAttachmentsThatNeedUploadQuery()) @@ -833,6 +833,8 @@ class AttachmentTable( /** * Returns sum of the file sizes of attachments that are not fully uploaded to the archive CDN. + * + * Should be the same or subset of that returned by [getAttachmentsThatNeedArchiveUpload]. */ fun getPendingArchiveUploadBytes(): Long { return readableDatabase @@ -849,7 +851,8 @@ class AttachmentTable( $ARCHIVE_TRANSFER_STATE NOT IN (${ArchiveTransferState.FINISHED.value}, ${ArchiveTransferState.PERMANENT_FAILURE.value}) AND $CONTENT_TYPE != '${MediaUtil.LONG_TEXT}' AND (${MessageTable.STORY_TYPE} = 0 OR ${MessageTable.STORY_TYPE} IS NULL) AND - (${MessageTable.EXPIRES_IN} = 0 OR ${MessageTable.EXPIRES_IN} > ${ChatItemArchiveExporter.EXPIRATION_CUTOFF.inWholeMilliseconds}) + (${MessageTable.EXPIRES_IN} = 0 OR ${MessageTable.EXPIRES_IN} > ${ChatItemArchiveExporter.EXPIRATION_CUTOFF.inWholeMilliseconds}) AND + ${MessageTable.TABLE_NAME}.${MessageTable.VIEW_ONCE} = 0 ) """.trimIndent() ) @@ -2592,7 +2595,8 @@ class AttachmentTable( $TRANSFER_STATE = $TRANSFER_PROGRESS_DONE AND (${MessageTable.STORY_TYPE} = 0 OR ${MessageTable.STORY_TYPE} IS NULL) AND (${MessageTable.TABLE_NAME}.${MessageTable.EXPIRES_IN} <= 0 OR ${MessageTable.TABLE_NAME}.${MessageTable.EXPIRES_IN} > ${ChatItemArchiveExporter.EXPIRATION_CUTOFF.inWholeMilliseconds}) AND - $CONTENT_TYPE != '${MediaUtil.LONG_TEXT}' + $CONTENT_TYPE != '${MediaUtil.LONG_TEXT}' AND + ${MessageTable.TABLE_NAME}.${MessageTable.VIEW_ONCE} = 0 """ } private fun getAttachment(cursor: Cursor): DatabaseAttachment { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt index 0421e66dab..32bdbe8ef7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt @@ -1348,6 +1348,13 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat databaseHelper.signalWritableDatabase } + fun isViewOnce(messageId: Long): Boolean { + return readableDatabase + .exists(TABLE_NAME) + .where("$ID = ? AND $VIEW_ONCE > 0", messageId) + .run() + } + fun isStory(messageId: Long): Boolean { return readableDatabase .exists(TABLE_NAME) diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/ArchiveThumbnailUploadJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/ArchiveThumbnailUploadJob.kt index 13bce87d96..30943d4663 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/ArchiveThumbnailUploadJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/ArchiveThumbnailUploadJob.kt @@ -12,6 +12,7 @@ import org.thoughtcrime.securesms.attachments.AttachmentId import org.thoughtcrime.securesms.attachments.DatabaseAttachment import org.thoughtcrime.securesms.attachments.PointerAttachment import org.thoughtcrime.securesms.backup.v2.BackupRepository +import org.thoughtcrime.securesms.backup.v2.hadIntegrityCheckPerformed import org.thoughtcrime.securesms.backup.v2.requireThumbnailMediaName import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.dependencies.AppDependencies @@ -97,12 +98,11 @@ class ArchiveThumbnailUploadJob private constructor( return Result.success() } - if (attachment.remoteDigest == null && attachment.dataHash == null) { + if (attachment.remoteDigest == null && attachment.dataHash == null && attachment.hadIntegrityCheckPerformed()) { Log.w(TAG, "$attachmentId has no integrity check! Cannot proceed.") return Result.success() } - // TODO [backups] Decide if we fail a job when associated attachment not already backed up // TODO [backups] Determine if we actually need to upload or are reusing a thumbnail from another attachment val thumbnailResult = generateThumbnailIfPossible(attachment) diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.kt index 1fc8e2e1af..0c59eabdee 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.kt @@ -235,6 +235,10 @@ class AttachmentDownloadJob private constructor( Log.i(TAG, "[$attachmentId] Attachment is a story. Skipping.") } + SignalDatabase.messages.isViewOnce(messageId) -> { + Log.i(TAG, "[$attachmentId] View-once. Skipping.") + } + SignalDatabase.messages.willMessageExpireBeforeCutoff(messageId) -> { Log.i(TAG, "[$attachmentId] Message will expire within 24hrs. Skipping.") } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.kt index 920d310aaf..9f9be96181 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.kt @@ -187,17 +187,19 @@ class AttachmentUploadJob private constructor( SignalDatabase.attachments.finalizeAttachmentAfterUpload(databaseAttachment.attachmentId, uploadResult) if (SignalStore.backup.backsUpMedia) { val messageId = SignalDatabase.attachments.getMessageId(databaseAttachment.attachmentId) - val isStory = SignalDatabase.messages.isStory(messageId) when { databaseAttachment.archiveTransferState == AttachmentTable.ArchiveTransferState.FINISHED -> { Log.i(TAG, "[$attachmentId] Already archived. Skipping.") } - isStory -> { - Log.i(TAG, "[$attachmentId] Attachment is a story. Skipping.") - } messageId == AttachmentTable.PREUPLOAD_MESSAGE_ID -> { Log.i(TAG, "[$attachmentId] Avoid uploading preuploaded attachments to archive. Skipping.") } + SignalDatabase.messages.isStory(messageId) -> { + Log.i(TAG, "[$attachmentId] Attachment is a story. Skipping.") + } + SignalDatabase.messages.isViewOnce(messageId) -> { + Log.i(TAG, "[$attachmentId] Attachment is view-once. Skipping.") + } SignalDatabase.messages.willMessageExpireBeforeCutoff(messageId) -> { Log.i(TAG, "[$attachmentId] Message will expire within 24hrs. Skipping.") } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/BackupMessagesJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/BackupMessagesJob.kt index d870e62db7..3d68bbf7b1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/BackupMessagesJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/BackupMessagesJob.kt @@ -290,7 +290,7 @@ class BackupMessagesJob private constructor( return Result.failure() } - if (SignalStore.backup.backsUpMedia && SignalDatabase.attachments.doAnyAttachmentsNeedArchiveUpload(System.currentTimeMillis())) { + if (SignalStore.backup.backsUpMedia && SignalDatabase.attachments.doAnyAttachmentsNeedArchiveUpload()) { Log.i(TAG, "Enqueuing attachment backfill job.") AppDependencies.jobManager.add(ArchiveAttachmentBackfillJob()) } else { 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 82b3c48a78..a2b5ffef19 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/CopyAttachmentToArchiveJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/CopyAttachmentToArchiveJob.kt @@ -103,6 +103,12 @@ class CopyAttachmentToArchiveJob private constructor(private val attachmentId: A return Result.success() } + if (SignalDatabase.messages.isViewOnce(attachment.mmsId)) { + Log.i(TAG, "[$attachmentId] Attachment is view-once. Resetting transfer state to none and skipping.") + SignalDatabase.attachments.setArchiveTransferState(attachmentId, AttachmentTable.ArchiveTransferState.NONE) + return Result.success() + } + if (SignalDatabase.messages.willMessageExpireBeforeCutoff(attachment.mmsId)) { Log.i(TAG, "[$attachmentId] Message will expire in less than 24 hours. Resetting transfer state to none and skipping.") SignalDatabase.attachments.setArchiveTransferState(attachmentId, AttachmentTable.ArchiveTransferState.NONE) diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/UploadAttachmentToArchiveJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/UploadAttachmentToArchiveJob.kt index 6a66a0c2d1..cd14a232f4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/UploadAttachmentToArchiveJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/UploadAttachmentToArchiveJob.kt @@ -134,6 +134,12 @@ class UploadAttachmentToArchiveJob private constructor( return Result.success() } + if (SignalDatabase.messages.isViewOnce(attachment.mmsId)) { + Log.i(TAG, "[$attachmentId] Attachment is a view-once. Resetting transfer state to none and skipping.") + SignalDatabase.attachments.setArchiveTransferState(attachmentId, AttachmentTable.ArchiveTransferState.NONE) + return Result.success() + } + if (SignalDatabase.messages.willMessageExpireBeforeCutoff(attachment.mmsId)) { Log.i(TAG, "[$attachmentId] Message will expire within 24 hours. Resetting transfer state to none and skipping.") SignalDatabase.attachments.setArchiveTransferState(attachmentId, AttachmentTable.ArchiveTransferState.NONE)