Filter messages that will expire within 24hrs from including attachments in backups.

This commit is contained in:
Alex Hart
2025-07-22 16:28:28 -03:00
committed by Michelle Tang
parent 635aa8791f
commit 16776ad843
8 changed files with 162 additions and 10 deletions

View File

@@ -7,7 +7,9 @@ import androidx.test.filters.FlakyTest
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import assertk.assertThat import assertk.assertThat
import assertk.assertions.hasSize import assertk.assertions.hasSize
import assertk.assertions.isEmpty
import assertk.assertions.isEqualTo import assertk.assertions.isEqualTo
import assertk.assertions.isNotEmpty
import assertk.assertions.isNotEqualTo import assertk.assertions.isNotEqualTo
import assertk.assertions.isTrue import assertk.assertions.isTrue
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
@@ -42,7 +44,9 @@ import java.util.UUID
import kotlin.random.Random import kotlin.random.Random
import kotlin.time.Duration import kotlin.time.Duration
import kotlin.time.Duration.Companion.days import kotlin.time.Duration.Companion.days
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
@@ -246,14 +250,90 @@ class AttachmentTableTest {
} }
} }
@Test
fun givenAnAttachmentWithAMessageThatExpiresIn5Minutes_whenIGetAttachmentsThatNeedArchiveUpload_thenIDoNotExpectThatAttachment() {
// GIVEN
val uncompressData = byteArrayOf(1, 2, 3, 4, 5)
val blobUncompressed = BlobProvider.getInstance().forData(uncompressData).createForSingleSessionInMemory()
val attachment = createAttachment(1, blobUncompressed, AttachmentTable.TransformProperties.empty())
val message = createIncomingMessage(serverTime = 0.days, attachment = attachment, expiresIn = 5.minutes)
val messageId = SignalDatabase.messages.insertMessageInbox(message).map { it.messageId }.get()
SignalDatabase.attachments.setArchiveTransferState(AttachmentId(1L), AttachmentTable.ArchiveTransferState.NONE)
SignalDatabase.attachments.setTransferState(messageId, AttachmentId(1L), AttachmentTable.TRANSFER_PROGRESS_DONE)
// WHEN
val attachments = SignalDatabase.attachments.getAttachmentsThatNeedArchiveUpload()
// THEN
assertThat(attachments).isEmpty()
}
@Test
fun givenAnAttachmentWithAMessageThatExpiresIn5Days_whenIGetAttachmentsThatNeedArchiveUpload_thenIDoExpectThatAttachment() {
// GIVEN
val uncompressData = byteArrayOf(1, 2, 3, 4, 5)
val blobUncompressed = BlobProvider.getInstance().forData(uncompressData).createForSingleSessionInMemory()
val attachment = createAttachment(1, blobUncompressed, AttachmentTable.TransformProperties.empty())
val message = createIncomingMessage(serverTime = 0.days, attachment = attachment, expiresIn = 5.days)
val messageId = SignalDatabase.messages.insertMessageInbox(message).map { it.messageId }.get()
SignalDatabase.attachments.setArchiveTransferState(AttachmentId(1L), AttachmentTable.ArchiveTransferState.NONE)
SignalDatabase.attachments.setTransferState(messageId, AttachmentId(1L), AttachmentTable.TRANSFER_PROGRESS_DONE)
// WHEN
val attachments = SignalDatabase.attachments.getAttachmentsThatNeedArchiveUpload()
// THEN
assertThat(attachments).isNotEmpty()
}
@Test
fun givenAnAttachmentWithAMessageWithExpirationStartedThatExpiresIn5Minutes_whenIGetAttachmentsThatNeedArchiveUpload_thenIDoNotExpectThatAttachment() {
// GIVEN
val uncompressData = byteArrayOf(1, 2, 3, 4, 5)
val blobUncompressed = BlobProvider.getInstance().forData(uncompressData).createForSingleSessionInMemory()
val attachment = createAttachment(1, blobUncompressed, AttachmentTable.TransformProperties.empty())
val message = createIncomingMessage(serverTime = 0.days, attachment = attachment, expiresIn = 5.days)
val messageId = SignalDatabase.messages.insertMessageInbox(message).map { it.messageId }.get()
SignalDatabase.messages.markExpireStarted(messageId, startedTimestamp = System.currentTimeMillis() - (4.days + 12.hours).inWholeMilliseconds)
SignalDatabase.attachments.setArchiveTransferState(AttachmentId(1L), AttachmentTable.ArchiveTransferState.NONE)
SignalDatabase.attachments.setTransferState(messageId, AttachmentId(1L), AttachmentTable.TRANSFER_PROGRESS_DONE)
// WHEN
val attachments = SignalDatabase.attachments.getAttachmentsThatNeedArchiveUpload()
// THEN
assertThat(attachments).isEmpty()
}
@Test
fun givenAnAttachmentWithAMessageWithExpirationStartedThatExpiresIn5Days_whenIGetAttachmentsThatNeedArchiveUpload_thenIDoExpectThatAttachment() {
// GIVEN
val uncompressData = byteArrayOf(1, 2, 3, 4, 5)
val blobUncompressed = BlobProvider.getInstance().forData(uncompressData).createForSingleSessionInMemory()
val attachment = createAttachment(1, blobUncompressed, AttachmentTable.TransformProperties.empty())
val message = createIncomingMessage(serverTime = 0.days, attachment = attachment, expiresIn = 5.days)
val messageId = SignalDatabase.messages.insertMessageInbox(message).map { it.messageId }.get()
SignalDatabase.messages.markExpireStarted(messageId)
SignalDatabase.attachments.setArchiveTransferState(AttachmentId(1L), AttachmentTable.ArchiveTransferState.NONE)
SignalDatabase.attachments.setTransferState(messageId, AttachmentId(1L), AttachmentTable.TRANSFER_PROGRESS_DONE)
// WHEN
val attachments = SignalDatabase.attachments.getAttachmentsThatNeedArchiveUpload()
// THEN
assertThat(attachments).isNotEmpty()
}
private fun createIncomingMessage( private fun createIncomingMessage(
serverTime: Duration, serverTime: Duration,
attachment: Attachment attachment: Attachment,
expiresIn: Duration = Duration.ZERO
): IncomingMessage { ): IncomingMessage {
return IncomingMessage( return IncomingMessage(
type = MessageType.NORMAL, type = MessageType.NORMAL,
from = harness.others[0], from = harness.others[0],
body = null, body = null,
expiresIn = expiresIn.inWholeMilliseconds,
sentTimeMillis = serverTime.inWholeMilliseconds, sentTimeMillis = serverTime.inWholeMilliseconds,
serverTimeMillis = serverTime.inWholeMilliseconds, serverTimeMillis = serverTime.inWholeMilliseconds,
receivedTimeMillis = serverTime.inWholeMilliseconds, receivedTimeMillis = serverTime.inWholeMilliseconds,

View File

@@ -38,7 +38,6 @@ import org.signal.core.util.getAllIndexDefinitions
import org.signal.core.util.getAllTableDefinitions import org.signal.core.util.getAllTableDefinitions
import org.signal.core.util.getAllTriggerDefinitions import org.signal.core.util.getAllTriggerDefinitions
import org.signal.core.util.getForeignKeyViolations import org.signal.core.util.getForeignKeyViolations
import org.signal.core.util.isNotEmpty
import org.signal.core.util.logging.Log import org.signal.core.util.logging.Log
import org.signal.core.util.money.FiatMoney import org.signal.core.util.money.FiatMoney
import org.signal.core.util.requireIntOrNull import org.signal.core.util.requireIntOrNull
@@ -1425,6 +1424,7 @@ object BackupRepository {
!DatabaseAttachmentArchiveUtil.hadIntegrityCheckPerformed(attachment) -> false !DatabaseAttachmentArchiveUtil.hadIntegrityCheckPerformed(attachment) -> false
messageId == AttachmentTable.PREUPLOAD_MESSAGE_ID -> false messageId == AttachmentTable.PREUPLOAD_MESSAGE_ID -> false
SignalDatabase.messages.isStory(messageId) -> false SignalDatabase.messages.isStory(messageId) -> false
SignalDatabase.messages.willMessageExpireBeforeCutoff(messageId) -> false
else -> true else -> true
} }
} }

View File

@@ -77,6 +77,11 @@ import org.thoughtcrime.securesms.crypto.AttachmentSecret
import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream
import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream
import org.thoughtcrime.securesms.database.AttachmentTable.ArchiveTransferState.COPY_PENDING
import org.thoughtcrime.securesms.database.AttachmentTable.ArchiveTransferState.FINISHED
import org.thoughtcrime.securesms.database.AttachmentTable.ArchiveTransferState.NONE
import org.thoughtcrime.securesms.database.AttachmentTable.ArchiveTransferState.PERMANENT_FAILURE
import org.thoughtcrime.securesms.database.AttachmentTable.ArchiveTransferState.UPLOAD_IN_PROGRESS
import org.thoughtcrime.securesms.database.AttachmentTable.Companion.DATA_FILE import org.thoughtcrime.securesms.database.AttachmentTable.Companion.DATA_FILE
import org.thoughtcrime.securesms.database.AttachmentTable.Companion.DATA_HASH_END import org.thoughtcrime.securesms.database.AttachmentTable.Companion.DATA_HASH_END
import org.thoughtcrime.securesms.database.AttachmentTable.Companion.PREUPLOAD_MESSAGE_ID import org.thoughtcrime.securesms.database.AttachmentTable.Companion.PREUPLOAD_MESSAGE_ID
@@ -625,6 +630,12 @@ class AttachmentTable(
.readToSingleLong() .readToSingleLong()
} }
private fun getMessageDoesNotExpireWithinTimeoutClause(): String {
val messageHasExpiration = "${MessageTable.TABLE_NAME}.${MessageTable.EXPIRES_IN} > 0"
val messageExpiresInOneDayAfterViewing = "$messageHasExpiration AND ${MessageTable.TABLE_NAME}.${MessageTable.EXPIRES_IN} < ${1.days.inWholeMilliseconds}"
return "NOT $messageExpiresInOneDayAfterViewing"
}
/** /**
* Finds all of the attachmentIds of attachments that need to be uploaded to the archive cdn. * Finds all of the attachmentIds of attachments that need to be uploaded to the archive cdn.
*/ */
@@ -632,7 +643,11 @@ class AttachmentTable(
return readableDatabase return readableDatabase
.select("$TABLE_NAME.$ID") .select("$TABLE_NAME.$ID")
.from("$TABLE_NAME LEFT JOIN ${MessageTable.TABLE_NAME} ON $TABLE_NAME.$MESSAGE_ID = ${MessageTable.TABLE_NAME}.${MessageTable.ID}") .from("$TABLE_NAME LEFT JOIN ${MessageTable.TABLE_NAME} ON $TABLE_NAME.$MESSAGE_ID = ${MessageTable.TABLE_NAME}.${MessageTable.ID}")
.where("($ARCHIVE_TRANSFER_STATE = ? or $ARCHIVE_TRANSFER_STATE = ?) AND $DATA_FILE NOT NULL AND $TRANSFER_STATE = $TRANSFER_PROGRESS_DONE AND (${MessageTable.STORY_TYPE} = 0 OR ${MessageTable.STORY_TYPE} IS NULL)", ArchiveTransferState.NONE.value, ArchiveTransferState.TEMPORARY_FAILURE.value) .where(
"($ARCHIVE_TRANSFER_STATE = ? or $ARCHIVE_TRANSFER_STATE = ?) AND $DATA_FILE NOT NULL AND $TRANSFER_STATE = $TRANSFER_PROGRESS_DONE AND (${MessageTable.STORY_TYPE} = 0 OR ${MessageTable.STORY_TYPE} IS NULL) AND ${getMessageDoesNotExpireWithinTimeoutClause()}",
ArchiveTransferState.NONE.value,
ArchiveTransferState.TEMPORARY_FAILURE.value
)
.orderBy("$TABLE_NAME.$ID DESC") .orderBy("$TABLE_NAME.$ID DESC")
.run() .run()
.readToList { AttachmentId(it.requireLong(ID)) } .readToList { AttachmentId(it.requireLong(ID)) }
@@ -679,7 +694,11 @@ class AttachmentTable(
fun doAnyAttachmentsNeedArchiveUpload(): Boolean { fun doAnyAttachmentsNeedArchiveUpload(): Boolean {
return readableDatabase return readableDatabase
.exists("$TABLE_NAME LEFT JOIN ${MessageTable.TABLE_NAME} ON $TABLE_NAME.$MESSAGE_ID = ${MessageTable.TABLE_NAME}.${MessageTable.ID}") .exists("$TABLE_NAME LEFT JOIN ${MessageTable.TABLE_NAME} ON $TABLE_NAME.$MESSAGE_ID = ${MessageTable.TABLE_NAME}.${MessageTable.ID}")
.where("($ARCHIVE_TRANSFER_STATE = ? OR $ARCHIVE_TRANSFER_STATE = ?) AND $DATA_FILE NOT NULL AND $TRANSFER_STATE = $TRANSFER_PROGRESS_DONE AND (${MessageTable.STORY_TYPE} = 0 OR ${MessageTable.STORY_TYPE} IS NULL)", ArchiveTransferState.NONE.value, ArchiveTransferState.TEMPORARY_FAILURE.value) .where(
"($ARCHIVE_TRANSFER_STATE = ? OR $ARCHIVE_TRANSFER_STATE = ?) AND $DATA_FILE NOT NULL AND $TRANSFER_STATE = $TRANSFER_PROGRESS_DONE AND (${MessageTable.STORY_TYPE} = 0 OR ${MessageTable.STORY_TYPE} IS NULL) AND ${getMessageDoesNotExpireWithinTimeoutClause()}",
ArchiveTransferState.NONE.value,
ArchiveTransferState.TEMPORARY_FAILURE.value
)
.run() .run()
} }
@@ -836,7 +855,8 @@ class AttachmentTable(
$DATA_HASH_END NOT NULL AND $DATA_HASH_END NOT NULL AND
$REMOTE_KEY NOT NULL AND $REMOTE_KEY NOT NULL AND
$ARCHIVE_TRANSFER_STATE NOT IN (${ArchiveTransferState.FINISHED.value}, ${ArchiveTransferState.PERMANENT_FAILURE.value}) AND $ARCHIVE_TRANSFER_STATE NOT IN (${ArchiveTransferState.FINISHED.value}, ${ArchiveTransferState.PERMANENT_FAILURE.value}) AND
(${MessageTable.STORY_TYPE} = 0 OR ${MessageTable.STORY_TYPE} IS NULL) (${MessageTable.STORY_TYPE} = 0 OR ${MessageTable.STORY_TYPE} IS NULL) AND
${getMessageDoesNotExpireWithinTimeoutClause()}
) )
""".trimIndent() """.trimIndent()
) )
@@ -1153,6 +1173,7 @@ class AttachmentTable(
ThumbnailRestoreState.IN_PROGRESS -> { ThumbnailRestoreState.IN_PROGRESS -> {
"($THUMBNAIL_RESTORE_STATE = ${ThumbnailRestoreState.NEEDS_RESTORE.value} OR $THUMBNAIL_RESTORE_STATE = ${ThumbnailRestoreState.IN_PROGRESS.value}) AND" "($THUMBNAIL_RESTORE_STATE = ${ThumbnailRestoreState.NEEDS_RESTORE.value} OR $THUMBNAIL_RESTORE_STATE = ${ThumbnailRestoreState.IN_PROGRESS.value}) AND"
} }
else -> "" else -> ""
} }
@@ -2336,9 +2357,11 @@ class AttachmentTable(
hashMatch.hashStart -> { hashMatch.hashStart -> {
Log.i(TAG, "[insertAttachmentWithData] Found that the new attachment hash matches the DATA_HASH_START of ${hashMatch.id}. Using all of it's fields. (MessageId: $messageId, ${attachment.uri})") Log.i(TAG, "[insertAttachmentWithData] Found that the new attachment hash matches the DATA_HASH_START of ${hashMatch.id}. Using all of it's fields. (MessageId: $messageId, ${attachment.uri})")
} }
hashMatch.hashEnd -> { hashMatch.hashEnd -> {
Log.i(TAG, "[insertAttachmentWithData] Found that the new attachment hash matches the DATA_HASH_END of ${hashMatch.id}. Using all of it's fields. (MessageId: $messageId, ${attachment.uri})") Log.i(TAG, "[insertAttachmentWithData] Found that the new attachment hash matches the DATA_HASH_END of ${hashMatch.id}. Using all of it's fields. (MessageId: $messageId, ${attachment.uri})")
} }
else -> { else -> {
throw IllegalStateException("Should not be possible based on query.") throw IllegalStateException("Should not be possible based on query.")
} }
@@ -2380,7 +2403,10 @@ class AttachmentTable(
} }
if (uploadTemplate != null) { if (uploadTemplate != null) {
Log.i(TAG, "[insertAttachmentWithData] Found a valid template we could use to skip upload. Template: ${uploadTemplate.attachmentId}, TemplateUploadTimestamp: ${hashMatch?.uploadTimestamp}, CurrentTime: ${System.currentTimeMillis()}, InsertingAttachment: (MessageId: $messageId, ${attachment.uri})") Log.i(
TAG,
"[insertAttachmentWithData] Found a valid template we could use to skip upload. Template: ${uploadTemplate.attachmentId}, TemplateUploadTimestamp: ${hashMatch?.uploadTimestamp}, CurrentTime: ${System.currentTimeMillis()}, InsertingAttachment: (MessageId: $messageId, ${attachment.uri})"
)
transformProperties = (uploadTemplate.transformProperties ?: transformProperties).copy(skipTransform = true) transformProperties = (uploadTemplate.transformProperties ?: transformProperties).copy(skipTransform = true)
} }
@@ -2647,11 +2673,19 @@ class AttachmentTable(
.associate { it to readableDatabase.count().from(TABLE_NAME).where("$ARCHIVE_TRANSFER_STATE = ${it.value} AND $DATA_HASH_END NOT NULL AND $REMOTE_KEY NOT NULL").run().readToSingleLong(-1L) } .associate { it to readableDatabase.count().from(TABLE_NAME).where("$ARCHIVE_TRANSFER_STATE = ${it.value} AND $DATA_HASH_END NOT NULL AND $REMOTE_KEY NOT NULL").run().readToSingleLong(-1L) }
val attachmentFileCount = readableDatabase.query("SELECT COUNT(DISTINCT $DATA_FILE) FROM $TABLE_NAME WHERE $DATA_FILE NOT NULL AND $DATA_HASH_END NOT NULL AND $REMOTE_KEY NOT NULL").readToSingleLong(-1L) val attachmentFileCount = readableDatabase.query("SELECT COUNT(DISTINCT $DATA_FILE) FROM $TABLE_NAME WHERE $DATA_FILE NOT NULL AND $DATA_HASH_END NOT NULL AND $REMOTE_KEY NOT NULL").readToSingleLong(-1L)
val finishedAttachmentFileCount = readableDatabase.query("SELECT COUNT(DISTINCT $DATA_FILE) FROM $TABLE_NAME WHERE $DATA_FILE NOT NULL AND $DATA_HASH_END NOT NULL AND $REMOTE_KEY NOT NULL AND $ARCHIVE_TRANSFER_STATE = ${ArchiveTransferState.FINISHED.value}").readToSingleLong(-1L) val finishedAttachmentFileCount =
val attachmentPlaintextHashAndKeyCount = readableDatabase.query("SELECT COUNT(*) FROM (SELECT DISTINCT $DATA_HASH_END, $REMOTE_KEY FROM $TABLE_NAME WHERE $DATA_HASH_END NOT NULL AND $REMOTE_KEY NOT NULL AND $TRANSFER_STATE in ($TRANSFER_PROGRESS_DONE, $TRANSFER_RESTORE_OFFLOADED, $TRANSFER_RESTORE_IN_PROGRESS, $TRANSFER_NEEDS_RESTORE))").readToSingleLong(-1L) readableDatabase.query("SELECT COUNT(DISTINCT $DATA_FILE) FROM $TABLE_NAME WHERE $DATA_FILE NOT NULL AND $DATA_HASH_END NOT NULL AND $REMOTE_KEY NOT NULL AND $ARCHIVE_TRANSFER_STATE = ${ArchiveTransferState.FINISHED.value}")
val finishedAttachmentDigestCount = readableDatabase.query("SELECT COUNT(*) FROM (SELECT DISTINCT $DATA_HASH_END, $REMOTE_KEY FROM $TABLE_NAME WHERE $DATA_HASH_END NOT NULL AND $REMOTE_KEY NOT NULL AND $ARCHIVE_TRANSFER_STATE = ${ArchiveTransferState.FINISHED.value})").readToSingleLong(-1L) .readToSingleLong(-1L)
val attachmentPlaintextHashAndKeyCount =
readableDatabase.query("SELECT COUNT(*) FROM (SELECT DISTINCT $DATA_HASH_END, $REMOTE_KEY FROM $TABLE_NAME WHERE $DATA_HASH_END NOT NULL AND $REMOTE_KEY NOT NULL AND $TRANSFER_STATE in ($TRANSFER_PROGRESS_DONE, $TRANSFER_RESTORE_OFFLOADED, $TRANSFER_RESTORE_IN_PROGRESS, $TRANSFER_NEEDS_RESTORE))")
.readToSingleLong(-1L)
val finishedAttachmentDigestCount =
readableDatabase.query("SELECT COUNT(*) FROM (SELECT DISTINCT $DATA_HASH_END, $REMOTE_KEY FROM $TABLE_NAME WHERE $DATA_HASH_END NOT NULL AND $REMOTE_KEY NOT NULL AND $ARCHIVE_TRANSFER_STATE = ${ArchiveTransferState.FINISHED.value})")
.readToSingleLong(-1L)
val thumbnailFileCount = readableDatabase.query("SELECT COUNT(DISTINCT $THUMBNAIL_FILE) FROM $TABLE_NAME WHERE $THUMBNAIL_FILE IS NOT NULL").readToSingleLong(-1L) val thumbnailFileCount = readableDatabase.query("SELECT COUNT(DISTINCT $THUMBNAIL_FILE) FROM $TABLE_NAME WHERE $THUMBNAIL_FILE IS NOT NULL").readToSingleLong(-1L)
val estimatedThumbnailCount = readableDatabase.query("SELECT COUNT(*) FROM (SELECT DISTINCT $DATA_HASH_END, $REMOTE_KEY FROM $TABLE_NAME WHERE $ARCHIVE_TRANSFER_STATE = ${ArchiveTransferState.FINISHED.value} AND $DATA_HASH_END NOT NULL AND $REMOTE_KEY NOT NULL AND ($CONTENT_TYPE LIKE 'image/%' OR $CONTENT_TYPE LIKE 'video/%'))").readToSingleLong(-1L) val estimatedThumbnailCount =
readableDatabase.query("SELECT COUNT(*) FROM (SELECT DISTINCT $DATA_HASH_END, $REMOTE_KEY FROM $TABLE_NAME WHERE $ARCHIVE_TRANSFER_STATE = ${ArchiveTransferState.FINISHED.value} AND $DATA_HASH_END NOT NULL AND $REMOTE_KEY NOT NULL AND ($CONTENT_TYPE LIKE 'image/%' OR $CONTENT_TYPE LIKE 'video/%'))")
.readToSingleLong(-1L)
val pendingUploadBytes = getPendingArchiveUploadBytes() val pendingUploadBytes = getPendingArchiveUploadBytes()
val uploadedAttachmentBytes = readableDatabase val uploadedAttachmentBytes = readableDatabase

View File

@@ -68,6 +68,7 @@ import org.thoughtcrime.securesms.attachments.Attachment
import org.thoughtcrime.securesms.attachments.AttachmentId import org.thoughtcrime.securesms.attachments.AttachmentId
import org.thoughtcrime.securesms.attachments.DatabaseAttachment import org.thoughtcrime.securesms.attachments.DatabaseAttachment
import org.thoughtcrime.securesms.attachments.DatabaseAttachment.DisplayOrderComparator import org.thoughtcrime.securesms.attachments.DatabaseAttachment.DisplayOrderComparator
import org.thoughtcrime.securesms.backup.v2.exporters.ChatItemArchiveExporter
import org.thoughtcrime.securesms.contactshare.Contact import org.thoughtcrime.securesms.contactshare.Contact
import org.thoughtcrime.securesms.conversation.MessageStyler import org.thoughtcrime.securesms.conversation.MessageStyler
import org.thoughtcrime.securesms.database.EarlyDeliveryReceiptCache.Receipt import org.thoughtcrime.securesms.database.EarlyDeliveryReceiptCache.Receipt
@@ -632,6 +633,23 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
return rawQueryWithAttachments(where, null) return rawQueryWithAttachments(where, null)
} }
/**
* Returns true iff
* - the message will expire within [ChatItemArchiveExporter.EXPIRATION_CUTOFF] once viewed
*/
fun willMessageExpireBeforeCutoff(messageId: Long): Boolean {
val expiresIn = readableDatabase
.select(EXPIRES_IN)
.from(TABLE_NAME)
.where(ID_WHERE, messageId)
.run()
.readToSingleObject {
it.requireLong(EXPIRES_IN)
} ?: 0L
return expiresIn > 0L && expiresIn < ChatItemArchiveExporter.EXPIRATION_CUTOFF.inWholeMilliseconds
}
fun getMessagesBySentTimestamp(sentTimestamp: Long): List<MessageRecord> { fun getMessagesBySentTimestamp(sentTimestamp: Long): List<MessageRecord> {
return readableDatabase return readableDatabase
.select(*MMS_PROJECTION) .select(*MMS_PROJECTION)

View File

@@ -235,6 +235,10 @@ class AttachmentDownloadJob private constructor(
Log.i(TAG, "[$attachmentId] Attachment is a story. Skipping.") Log.i(TAG, "[$attachmentId] Attachment is a story. Skipping.")
} }
SignalDatabase.messages.willMessageExpireBeforeCutoff(messageId) -> {
Log.i(TAG, "[$attachmentId] Message will expire within 24hrs. Skipping.")
}
else -> { else -> {
Log.i(TAG, "[$attachmentId] Enqueuing job to copy to archive.") Log.i(TAG, "[$attachmentId] Enqueuing job to copy to archive.")
AppDependencies.jobManager.add(CopyAttachmentToArchiveJob(attachmentId)) AppDependencies.jobManager.add(CopyAttachmentToArchiveJob(attachmentId))

View File

@@ -198,6 +198,10 @@ class AttachmentUploadJob private constructor(
messageId == AttachmentTable.PREUPLOAD_MESSAGE_ID -> { messageId == AttachmentTable.PREUPLOAD_MESSAGE_ID -> {
Log.i(TAG, "[$attachmentId] Avoid uploading preuploaded attachments to archive. Skipping.") Log.i(TAG, "[$attachmentId] Avoid uploading preuploaded attachments to archive. Skipping.")
} }
SignalDatabase.messages.willMessageExpireBeforeCutoff(messageId) -> {
Log.i(TAG, "[$attachmentId] Message will expire within 24hrs. Skipping.")
}
else -> { else -> {
Log.i(TAG, "[$attachmentId] Enqueuing job to copy to archive.") Log.i(TAG, "[$attachmentId] Enqueuing job to copy to archive.")
AppDependencies.jobManager.add(CopyAttachmentToArchiveJob(attachmentId)) AppDependencies.jobManager.add(CopyAttachmentToArchiveJob(attachmentId))

View File

@@ -96,6 +96,12 @@ class CopyAttachmentToArchiveJob private constructor(private val attachmentId: A
return Result.success() 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)
return Result.success()
}
if (isCanceled) { if (isCanceled) {
Log.w(TAG, "[$attachmentId] Canceled. Refusing to proceed.") Log.w(TAG, "[$attachmentId] Canceled. Refusing to proceed.")
return Result.failure() return Result.failure()

View File

@@ -131,6 +131,12 @@ class UploadAttachmentToArchiveJob private constructor(
return Result.success() 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)
return Result.success()
}
if (attachment.remoteKey == null) { if (attachment.remoteKey == null) {
Log.w(TAG, "[$attachmentId] Attachment is missing remote key! Cannot upload.") Log.w(TAG, "[$attachmentId] Attachment is missing remote key! Cannot upload.")
return Result.failure() return Result.failure()