Improve delete sync coverage for partial expiring threads.

This commit is contained in:
Cody Henthorne 2024-06-18 11:33:04 -04:00 committed by Greyson Parrelli
parent 070174fee6
commit 6659700a1c
13 changed files with 127 additions and 51 deletions

View file

@ -260,7 +260,7 @@ class SyncMessageProcessorTest_synchronizeDeleteForMe {
}
messageHelper.syncDeleteForMeConversation(
DeleteForMeSync(conversationId = messageHelper.alice, randomFutureMessages, true)
DeleteForMeSync(conversationId = messageHelper.alice, randomFutureMessages, isFullDelete = true)
)
// THEN
@ -271,6 +271,38 @@ class SyncMessageProcessorTest_synchronizeDeleteForMe {
harness.inMemoryLogger.entries().filter { it.message?.contains("Unable to find most recent received at timestamp") == true }.size assertIs 1
}
@Test
fun singleConversationNoRecentsFoundNonExpiringRecentsFoundDelete() {
// GIVEN
val messages = mutableListOf<MessageTable.SyncMessageId>()
for (i in 0 until 10) {
messages += MessageTable.SyncMessageId(messageHelper.alice, messageHelper.incomingText().timestamp)
messages += MessageTable.SyncMessageId(harness.self.id, messageHelper.outgoingText().timestamp)
}
val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 20
// WHEN
val nonExpiringMessages = messages.takeLast(5).map { it.recipientId to it.timetamp }
val randomFutureMessages = (1..5).map {
messageHelper.alice to messageHelper.nextStartTime(it)
}
messageHelper.syncDeleteForMeConversation(
DeleteForMeSync(conversationId = messageHelper.alice, randomFutureMessages, nonExpiringMessages, true)
)
// THEN
SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 0
SignalDatabase.threads.getThreadRecord(threadId) assertIs null
harness.inMemoryLogger.flush()
harness.inMemoryLogger.entries().filter { it.message?.contains("Using backup non-expiring messages") == true }.size assertIs 1
}
@Test
fun localOnlyRemainingAfterConversationDeleteWithFullDelete() {
// GIVEN
@ -389,8 +421,8 @@ class SyncMessageProcessorTest_synchronizeDeleteForMe {
// WHEN
messageHelper.syncDeleteForMeConversation(
DeleteForMeSync(conversationId = messageHelper.alice, allMessages[messageHelper.alice]!!.takeLast(5).map { it.recipientId to it.timetamp }, true),
DeleteForMeSync(conversationId = messageHelper.bob, allMessages[messageHelper.bob]!!.takeLast(5).map { it.recipientId to it.timetamp }, true)
DeleteForMeSync(conversationId = messageHelper.alice, allMessages[messageHelper.alice]!!.takeLast(5).map { it.recipientId to it.timetamp }, isFullDelete = true),
DeleteForMeSync(conversationId = messageHelper.bob, allMessages[messageHelper.bob]!!.takeLast(5).map { it.recipientId to it.timetamp }, isFullDelete = true)
)
// THEN

View file

@ -187,8 +187,8 @@ object MessageContentFuzzer {
.syncMessage(
SyncMessage(
deleteForMe = SyncMessage.DeleteForMe(
conversationDeletes = allDeletes.map { (conversationId, conversationDeletes, isFullDelete) ->
val conversation = Recipient.resolved(conversationId)
conversationDeletes = allDeletes.map { delete ->
val conversation = Recipient.resolved(delete.conversationId)
SyncMessage.DeleteForMe.ConversationDelete(
conversation = if (conversation.isGroup) {
SyncMessage.DeleteForMe.ConversationIdentifier(threadGroupId = conversation.requireGroupId().decodedId.toByteString())
@ -196,14 +196,21 @@ object MessageContentFuzzer {
SyncMessage.DeleteForMe.ConversationIdentifier(threadServiceId = conversation.requireAci().toString())
},
mostRecentMessages = conversationDeletes.map { (author, timestamp) ->
mostRecentMessages = delete.messages.map { (author, timestamp) ->
SyncMessage.DeleteForMe.AddressableMessage(
authorServiceId = Recipient.resolved(author).requireAci().toString(),
sentTimestamp = timestamp
)
},
isFullDelete = isFullDelete
mostRecentNonExpiringMessages = delete.nonExpiringMessages.map { (author, timestamp) ->
SyncMessage.DeleteForMe.AddressableMessage(
authorServiceId = Recipient.resolved(author).requireAci().toString(),
sentTimestamp = timestamp
)
},
isFullDelete = delete.isFullDelete
)
}
)
@ -405,6 +412,7 @@ object MessageContentFuzzer {
data class DeleteForMeSync(
val conversationId: RecipientId,
val messages: List<Pair<RecipientId, Long>>,
val nonExpiringMessages: List<Pair<RecipientId, Long>> = emptyList(),
val isFullDelete: Boolean = true,
val attachments: List<Pair<Long, AttachmentTable.SyncAttachmentId>> = emptyList()
) {

View file

@ -209,6 +209,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
const val QUOTE_NOT_PRESENT_ID = 0L
const val QUOTE_TARGET_MISSING_ID = -1L
const val ADDRESSABLE_MESSAGE_LIMIT = 5
const val CREATE_TABLE = """
CREATE TABLE $TABLE_NAME (
$ID INTEGER PRIMARY KEY AUTOINCREMENT,
@ -4972,26 +4974,26 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
return !hasMessages
}
fun getMostRecentAddressableMessages(threadId: Long): Set<MessageRecord> {
fun getMostRecentAddressableMessages(threadId: Long, excludeExpiring: Boolean): Set<MessageRecord> {
return readableDatabase
.select(*MMS_PROJECTION)
.from(TABLE_NAME)
.where("$IS_ADDRESSABLE_CLAUSE AND $THREAD_ID = ?", threadId)
.where("$IS_ADDRESSABLE_CLAUSE AND $THREAD_ID = ? ${if (excludeExpiring) "AND $EXPIRES_IN = 0" else ""}", threadId)
.orderBy("$DATE_RECEIVED DESC")
.limit(5)
.limit(ADDRESSABLE_MESSAGE_LIMIT)
.run()
.use {
MmsReader(it).toSet()
}
}
fun getAddressableMessagesBefore(threadId: Long, beforeTimestamp: Long): Set<MessageRecord> {
fun getAddressableMessagesBefore(threadId: Long, beforeTimestamp: Long, excludeExpiring: Boolean): Set<MessageRecord> {
return readableDatabase
.select(*MMS_PROJECTION)
.from(TABLE_NAME)
.where("$IS_ADDRESSABLE_CLAUSE AND $THREAD_ID = ? AND $DATE_RECEIVED < ?", threadId, beforeTimestamp)
.where("$IS_ADDRESSABLE_CLAUSE AND $THREAD_ID = ? AND $DATE_RECEIVED < ? ${if (excludeExpiring) "AND $EXPIRES_IN = 0" else ""}", threadId, beforeTimestamp)
.orderBy("$DATE_RECEIVED DESC")
.limit(5)
.limit(ADDRESSABLE_MESSAGE_LIMIT)
.run()
.use {
MmsReader(it).toSet()

View file

@ -50,7 +50,7 @@ import org.thoughtcrime.securesms.database.model.serialize
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.groups.BadGroupIdException
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.jobs.MultiDeviceDeleteSendSyncJob
import org.thoughtcrime.securesms.jobs.MultiDeviceDeleteSyncJob
import org.thoughtcrime.securesms.jobs.OptimizeMessageSearchIndexJob
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.mms.SlideDeck
@ -326,7 +326,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
}
val syncThreadTrimDeletes = SignalStore.settings().shouldSyncThreadTrimDeletes() && Recipient.self().deleteSyncCapability.isSupported
val threadTrimsToSync = mutableListOf<Pair<Long, Set<MessageRecord>>>()
val threadTrimsToSync = mutableListOf<ThreadDeleteSyncInfo>()
readableDatabase
.select(ID)
@ -358,7 +358,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
}
if (syncThreadTrimDeletes && threadTrimsToSync.isNotEmpty()) {
MultiDeviceDeleteSendSyncJob.enqueueThreadDeletes(threadTrimsToSync, isFullDelete = false)
MultiDeviceDeleteSyncJob.enqueueThreadDeletes(threadTrimsToSync, isFullDelete = false)
}
notifyAttachmentListeners()
@ -377,7 +377,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
return
}
var threadTrimToSync: Pair<Long, Set<MessageRecord>>? = null
var threadTrimToSync: ThreadDeleteSyncInfo? = null
val deletes = writableDatabase.withinTransaction {
threadTrimToSync = trimThreadInternal(threadId, syncThreadTrimDeletes, length, trimBeforeDate, inclusive)
messages.deleteAbandonedMessages()
@ -392,7 +392,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
}
if (syncThreadTrimDeletes && threadTrimToSync != null) {
MultiDeviceDeleteSendSyncJob.enqueueThreadDeletes(listOf(threadTrimToSync!!), isFullDelete = false)
MultiDeviceDeleteSyncJob.enqueueThreadDeletes(listOf(threadTrimToSync!!), isFullDelete = false)
}
notifyAttachmentListeners()
@ -406,7 +406,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
length: Int,
trimBeforeDate: Long,
inclusive: Boolean = false
): Pair<Long, Set<MessageRecord>>? {
): ThreadDeleteSyncInfo? {
if (length == NO_TRIM_MESSAGE_COUNT_SET && trimBeforeDate == NO_TRIM_BEFORE_DATE_SET) {
return null
}
@ -427,7 +427,18 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
if (finalTrimBeforeDate != NO_TRIM_BEFORE_DATE_SET) {
Log.i(TAG, "Trimming thread: $threadId before: $finalTrimBeforeDate inclusive: $inclusive")
val addressableMessages: Set<MessageRecord> = if (syncThreadTrimDeletes) messages.getAddressableMessagesBefore(threadId, finalTrimBeforeDate) else emptySet()
val addressableMessages: Set<MessageRecord> = if (syncThreadTrimDeletes) {
messages.getAddressableMessagesBefore(threadId, finalTrimBeforeDate, excludeExpiring = false)
} else {
emptySet()
}
val nonExpiringAddressableMessages: Set<MessageRecord> = if (syncThreadTrimDeletes && addressableMessages.size == MessageTable.ADDRESSABLE_MESSAGE_LIMIT && addressableMessages.any { it.expiresIn > 0 }) {
messages.getAddressableMessagesBefore(threadId, finalTrimBeforeDate, excludeExpiring = true)
} else {
emptySet()
}
val deletes = messages.deleteMessagesInThreadBeforeDate(threadId, finalTrimBeforeDate, inclusive)
if (deletes > 0) {
@ -438,7 +449,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
SignalDatabase.calls.updateCallEventDeletionTimestamps()
return if (syncThreadTrimDeletes && (threadDeleted || addressableMessages.isNotEmpty())) {
threadId to addressableMessages
ThreadDeleteSyncInfo(threadId, addressableMessages, nonExpiringAddressableMessages)
} else {
null
}
@ -1132,13 +1143,20 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
fun deleteConversations(selectedConversations: Set<Long>, syncThreadDeletes: Boolean = true) {
val recipientIds = getRecipientIdsForThreadIds(selectedConversations)
val addressableMessages = mutableListOf<Pair<Long, Set<MessageRecord>>>()
val addressableMessages = mutableListOf<ThreadDeleteSyncInfo>()
val queries: List<SqlUtil.Query> = SqlUtil.buildCollectionQuery(ID, selectedConversations)
writableDatabase.withinTransaction { db ->
if (syncThreadDeletes && Recipient.self().deleteSyncCapability.isSupported) {
for (threadId in selectedConversations) {
addressableMessages += threadId to messages.getMostRecentAddressableMessages(threadId)
val mostRecentMessages = messages.getMostRecentAddressableMessages(threadId, excludeExpiring = false)
val mostRecentNonExpiring = if (mostRecentMessages.size == MessageTable.ADDRESSABLE_MESSAGE_LIMIT && mostRecentMessages.any { it.expiresIn > 0 }) {
messages.getMostRecentAddressableMessages(threadId, excludeExpiring = true)
} else {
emptySet()
}
addressableMessages += ThreadDeleteSyncInfo(threadId, mostRecentMessages, mostRecentNonExpiring)
}
}
@ -1160,7 +1178,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
}
if (syncThreadDeletes) {
MultiDeviceDeleteSendSyncJob.enqueueThreadDeletes(addressableMessages, isFullDelete = true)
MultiDeviceDeleteSyncJob.enqueueThreadDeletes(addressableMessages, isFullDelete = true)
}
notifyConversationListListeners()
@ -2206,4 +2224,6 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
val threadId: Long,
val newlyCreated: Boolean
)
data class ThreadDeleteSyncInfo(val threadId: Long, val addressableMessages: Set<MessageRecord>, val nonExpiringAddressableMessages: Set<MessageRecord>)
}

View file

@ -164,7 +164,7 @@ public final class JobManagerFactories {
put(MultiDeviceConfigurationUpdateJob.KEY, new MultiDeviceConfigurationUpdateJob.Factory());
put(MultiDeviceContactSyncJob.KEY, new MultiDeviceContactSyncJob.Factory());
put(MultiDeviceContactUpdateJob.KEY, new MultiDeviceContactUpdateJob.Factory());
put(MultiDeviceDeleteSendSyncJob.KEY, new MultiDeviceDeleteSendSyncJob.Factory());
put(MultiDeviceDeleteSyncJob.KEY, new MultiDeviceDeleteSyncJob.Factory());
put(MultiDeviceKeysUpdateJob.KEY, new MultiDeviceKeysUpdateJob.Factory());
put(MultiDeviceMessageRequestResponseJob.KEY, new MultiDeviceMessageRequestResponseJob.Factory());
put(MultiDeviceOutgoingPaymentSyncJob.KEY, new MultiDeviceOutgoingPaymentSyncJob.Factory());

View file

@ -13,6 +13,7 @@ import org.signal.core.util.logging.Log
import org.signal.core.util.orNull
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.ThreadTable
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.jobmanager.Job
@ -37,7 +38,7 @@ import kotlin.time.Duration.Companion.days
/**
* Send delete for me sync messages for the various type of delete syncs.
*/
class MultiDeviceDeleteSendSyncJob private constructor(
class MultiDeviceDeleteSyncJob private constructor(
private var data: DeleteSyncJobData,
parameters: Parameters = Parameters.Builder()
.addConstraint(NetworkConstraint.KEY)
@ -48,7 +49,7 @@ class MultiDeviceDeleteSendSyncJob private constructor(
companion object {
const val KEY = "MultiDeviceDeleteSendSyncJob"
private val TAG = Log.tag(MultiDeviceDeleteSendSyncJob::class.java)
private val TAG = Log.tag(MultiDeviceDeleteSyncJob::class.java)
private const val CHUNK_SIZE = 500
private const val THREAD_CHUNK_SIZE = CHUNK_SIZE / 5
@ -68,7 +69,7 @@ class MultiDeviceDeleteSendSyncJob private constructor(
messageRecords.chunked(CHUNK_SIZE).forEach { chunk ->
val deletes = createMessageDeletes(chunk)
if (deletes.isNotEmpty()) {
AppDependencies.jobManager.add(MultiDeviceDeleteSendSyncJob(messages = deletes))
AppDependencies.jobManager.add(MultiDeviceDeleteSyncJob(messages = deletes))
} else {
Log.i(TAG, "No valid message deletes to sync")
}
@ -89,14 +90,14 @@ class MultiDeviceDeleteSendSyncJob private constructor(
val delete = createAttachmentDelete(message, attachment)
if (delete != null) {
AppDependencies.jobManager.add(MultiDeviceDeleteSendSyncJob(attachments = listOf(delete)))
AppDependencies.jobManager.add(MultiDeviceDeleteSyncJob(attachments = listOf(delete)))
} else {
Log.i(TAG, "No valid attachment deletes to sync attachment:${attachment.attachmentId}")
}
}
@WorkerThread
fun enqueueThreadDeletes(threads: List<Pair<Long, Set<MessageRecord>>>, isFullDelete: Boolean) {
fun enqueueThreadDeletes(threads: List<ThreadTable.ThreadDeleteSyncInfo>, isFullDelete: Boolean) {
if (!TextSecurePreferences.isMultiDevice(AppDependencies.application)) {
return
}
@ -110,7 +111,7 @@ class MultiDeviceDeleteSendSyncJob private constructor(
val threadDeletes = createThreadDeletes(chunk, isFullDelete)
if (threadDeletes.isNotEmpty()) {
AppDependencies.jobManager.add(
MultiDeviceDeleteSendSyncJob(
MultiDeviceDeleteSyncJob(
threads = threadDeletes.filter { it.messages.isNotEmpty() },
localOnlyThreads = threadDeletes.filter { it.messages.isEmpty() }
)
@ -186,8 +187,8 @@ class MultiDeviceDeleteSendSyncJob private constructor(
}
@WorkerThread
private fun createThreadDeletes(threads: List<Pair<Long, Set<MessageRecord>>>, isFullDelete: Boolean): List<ThreadDelete> {
return threads.mapNotNull { (threadId, messages) ->
private fun createThreadDeletes(threads: List<ThreadTable.ThreadDeleteSyncInfo>, isFullDelete: Boolean): List<ThreadDelete> {
return threads.mapNotNull { (threadId, messages, nonExpiringMessages) ->
val threadRecipient = SignalDatabase.threads.getRecipientForThreadId(threadId)
if (threadRecipient == null) {
Log.w(TAG, "Unable to find thread recipient for thread: $threadId")
@ -206,6 +207,12 @@ class MultiDeviceDeleteSendSyncJob private constructor(
sentTimestamp = it.dateSent,
authorRecipientId = it.fromRecipient.id.toLong()
)
},
nonExpiringMessages = nonExpiringMessages.map {
AddressableMessage(
sentTimestamp = it.dateSent,
authorRecipientId = it.fromRecipient.id.toLong()
)
}
)
}
@ -269,16 +276,17 @@ class MultiDeviceDeleteSendSyncJob private constructor(
if (data.threadDeletes.isNotEmpty()) {
val success = syncDelete(
DeleteForMe(
conversationDeletes = data.threadDeletes.mapNotNull {
val conversation = Recipient.resolved(RecipientId.from(it.threadRecipientId)).toDeleteSyncConversationId()
conversationDeletes = data.threadDeletes.mapNotNull { threadDelete ->
val conversation = Recipient.resolved(RecipientId.from(threadDelete.threadRecipientId)).toDeleteSyncConversationId()
if (conversation != null) {
DeleteForMe.ConversationDelete(
conversation = conversation,
mostRecentMessages = it.messages.mapNotNull { m -> m.toDeleteSyncMessage() },
isFullDelete = it.isFullDelete
mostRecentMessages = threadDelete.messages.mapNotNull { it.toDeleteSyncMessage() },
isFullDelete = threadDelete.isFullDelete,
mostRecentNonExpiringMessages = threadDelete.messages.mapNotNull { it.toDeleteSyncMessage() }
)
} else {
Log.w(TAG, "Unable to resolve ${it.threadRecipientId} to conversation id")
Log.w(TAG, "Unable to resolve ${threadDelete.threadRecipientId} to conversation id")
null
}
}
@ -408,9 +416,9 @@ class MultiDeviceDeleteSendSyncJob private constructor(
}
}
class Factory : Job.Factory<MultiDeviceDeleteSendSyncJob> {
override fun create(parameters: Parameters, serializedData: ByteArray?): MultiDeviceDeleteSendSyncJob {
return MultiDeviceDeleteSendSyncJob(DeleteSyncJobData.ADAPTER.decode(serializedData!!), parameters)
class Factory : Job.Factory<MultiDeviceDeleteSyncJob> {
override fun create(parameters: Parameters, serializedData: ByteArray?): MultiDeviceDeleteSyncJob {
return MultiDeviceDeleteSyncJob(DeleteSyncJobData.ADAPTER.decode(serializedData!!), parameters)
}
}
}

View file

@ -14,7 +14,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.MediaTable;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.jobs.MultiDeviceDeleteSendSyncJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceDeleteSyncJob;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.AttachmentUtil;
@ -87,7 +87,7 @@ final class MediaActions {
}
if (Recipient.self().getDeleteSyncCapability().isSupported() && Util.hasItems(deletedMessageRecords)) {
MultiDeviceDeleteSendSyncJob.enqueueMessageDeletes(deletedMessageRecords);
MultiDeviceDeleteSyncJob.enqueueMessageDeletes(deletedMessageRecords);
}
return null;

View file

@ -21,7 +21,7 @@ import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.media
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.jobs.MultiDeviceDeleteSendSyncJob
import org.thoughtcrime.securesms.jobs.MultiDeviceDeleteSyncJob
import org.thoughtcrime.securesms.longmessage.resolveBody
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
@ -86,7 +86,7 @@ class MediaPreviewRepository {
return Completable.fromRunnable {
val deletedMessageRecord = AttachmentUtil.deleteAttachment(attachment)
if (deletedMessageRecord != null && Recipient.self().deleteSyncCapability.isSupported) {
MultiDeviceDeleteSendSyncJob.enqueueMessageDeletes(setOf(deletedMessageRecord))
MultiDeviceDeleteSyncJob.enqueueMessageDeletes(setOf(deletedMessageRecord))
}
}.subscribeOn(Schedulers.io())
}

View file

@ -1536,8 +1536,12 @@ object SyncMessageProcessor {
continue
}
val mostRecentMessagesToDelete: List<MessageTable.SyncMessageId> = delete.mostRecentMessages.mapNotNull { it.toSyncMessageId(envelopeTimestamp) }
val latestReceivedAt = SignalDatabase.messages.getLatestReceivedAt(threadId, mostRecentMessagesToDelete)
var latestReceivedAt = SignalDatabase.messages.getLatestReceivedAt(threadId, delete.mostRecentMessages.mapNotNull { it.toSyncMessageId(envelopeTimestamp) })
if (latestReceivedAt == null && delete.mostRecentNonExpiringMessages.isNotEmpty()) {
log(envelopeTimestamp, "[handleSynchronizeDeleteForMe] Using backup non-expiring messages")
latestReceivedAt = SignalDatabase.messages.getLatestReceivedAt(threadId, delete.mostRecentNonExpiringMessages.mapNotNull { it.toSyncMessageId(envelopeTimestamp) })
}
if (latestReceivedAt != null) {
SignalDatabase.threads.trimThread(threadId = threadId, syncThreadTrimDeletes = false, trimBeforeDate = latestReceivedAt, inclusive = true)

View file

@ -17,7 +17,7 @@ import org.thoughtcrime.securesms.database.NoSuchMessageException;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.jobmanager.impl.NotInCallConstraint;
import org.thoughtcrime.securesms.jobs.MultiDeviceDeleteSendSyncJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceDeleteSyncJob;
import org.thoughtcrime.securesms.recipients.Recipient;
import java.util.Collections;
@ -105,7 +105,7 @@ public class AttachmentUtil {
} else {
SignalDatabase.attachments().deleteAttachment(attachmentId);
if (Recipient.self().getDeleteSyncCapability().isSupported()) {
MultiDeviceDeleteSendSyncJob.enqueueAttachmentDelete(SignalDatabase.messages().getMessageRecordOrNull(mmsId), attachment);
MultiDeviceDeleteSyncJob.enqueueAttachmentDelete(SignalDatabase.messages().getMessageRecordOrNull(mmsId), attachment);
}
}

View file

@ -8,7 +8,7 @@ import org.signal.core.util.concurrent.SignalExecutors
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.jobs.MultiDeviceDeleteSendSyncJob
import org.thoughtcrime.securesms.jobs.MultiDeviceDeleteSyncJob
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.sms.MessageSender
@ -122,7 +122,7 @@ object DeleteDialog {
}
if (Recipient.self().deleteSyncCapability.isSupported) {
MultiDeviceDeleteSendSyncJob.enqueueMessageDeletes(messageRecords)
MultiDeviceDeleteSyncJob.enqueueMessageDeletes(messageRecords)
}
return threadDeleted

View file

@ -100,6 +100,7 @@ message DeleteSyncJobData {
uint64 threadRecipientId = 1;
repeated AddressableMessage messages = 2;
bool isFullDelete = 3;
repeated AddressableMessage nonExpiringMessages = 4;
}
repeated AddressableMessage messageDeletes = 1;

View file

@ -687,6 +687,7 @@ message SyncMessage {
message ConversationDelete {
optional ConversationIdentifier conversation = 1;
repeated AddressableMessage mostRecentMessages = 2;
repeated AddressableMessage mostRecentNonExpiringMessages = 4;
optional bool isFullDelete = 3;
}