Add additional delete sync support.

This commit is contained in:
Cody Henthorne
2024-06-13 10:11:29 -04:00
committed by Greyson Parrelli
parent d22d18da47
commit c80ccd70ec
37 changed files with 183 additions and 333 deletions

View File

@@ -462,8 +462,11 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
* - Not an encryption message
* - Not a report spam message
* - Not a message rqeuest accepted message
* - Not be a story
* - Have a valid sent timestamp
* - Be a normal message or direct (1:1) story reply
*
* Changes should be reflected in [MmsMessageRecord.canDeleteSync].
*/
private const val IS_ADDRESSABLE_CLAUSE = """
(($TYPE & ${MessageTypes.BASE_TYPE_MASK}) = ${MessageTypes.BASE_SENT_TYPE} OR ($TYPE & ${MessageTypes.BASE_TYPE_MASK}) = ${MessageTypes.BASE_INBOX_TYPE}) AND
@@ -472,7 +475,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
($TYPE & ${MessageTypes.KEY_EXCHANGE_MASK}) = 0 AND
($TYPE & ${MessageTypes.ENCRYPTION_MASK}) = 0 AND
($TYPE & ${MessageTypes.SPECIAL_TYPES_MASK}) != ${MessageTypes.SPECIAL_TYPE_REPORTED_SPAM} AND
($TYPE & ${MessageTypes.SPECIAL_TYPES_MASK}) != ${MessageTypes.SPECIAL_TYPE_MESSAGE_REQUEST_ACCEPTED} AND
($TYPE & ${MessageTypes.SPECIAL_TYPES_MASK}) != ${MessageTypes.SPECIAL_TYPE_MESSAGE_REQUEST_ACCEPTED} AND
$STORY_TYPE = 0 AND
$DATE_SENT > 0 AND
$PARENT_STORY_ID <= 0
"""
@@ -3277,7 +3281,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
threads.setLastScrolled(threadId, 0)
val threadDeleted = if (updateThread) {
threads.update(threadId, false)
threads.update(threadId, unarchive = false, syncThreadDelete = false)
} else {
false
}
@@ -3332,11 +3336,6 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
}
}
fun deleteThread(threadId: Long) {
Log.d(TAG, "deleteThread($threadId)")
deleteThreads(setOf(threadId))
}
private fun getSerializedSharedContacts(insertedAttachmentIds: Map<Attachment, AttachmentId>, contacts: List<Contact>): String? {
if (contacts.isEmpty()) {
return null
@@ -3434,27 +3433,6 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
return ids
}
private fun deleteThreads(threadIds: Set<Long>) {
Log.d(TAG, "deleteThreads(count: ${threadIds.size})")
writableDatabase.withinTransaction { db ->
SqlUtil.buildCollectionQuery(THREAD_ID, threadIds).forEach { query ->
db.select(ID, THREAD_ID)
.from(TABLE_NAME)
.where(query.where, query.whereArgs)
.run()
.forEach { cursor ->
deleteMessage(cursor.requireLong(ID), cursor.requireLong(THREAD_ID), notify = false, updateThread = false)
}
}
}
notifyConversationListeners(threadIds)
notifyStickerListeners()
notifyStickerPackListeners()
OptimizeMessageSearchIndexJob.enqueue()
}
fun deleteMessagesInThreadBeforeDate(threadId: Long, date: Long, inclusive: Boolean): Int {
val condition = if (inclusive) "<=" else "<"

View File

@@ -410,6 +410,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
fun maskCapabilitiesToLong(capabilities: SignalServiceProfile.Capabilities): Long {
var value: Long = 0
value = Bitmask.update(value, Capabilities.PAYMENT_ACTIVATION, Capabilities.BIT_LENGTH, Recipient.Capability.fromBoolean(capabilities.isPaymentActivation).serialize().toLong())
value = Bitmask.update(value, Capabilities.DELETE_SYNC, Capabilities.BIT_LENGTH, Recipient.Capability.fromBoolean(capabilities.isDeleteSync).serialize().toLong())
return value
}
}
@@ -4577,6 +4578,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
// const val GIFT_BADGES = 6
// const val PNP = 7
const val PAYMENT_ACTIVATION = 8
const val DELETE_SYNC = 9
}
enum class VibrateState(val id: Int) {

View File

@@ -175,7 +175,8 @@ object RecipientTableCursorUtil {
val capabilities = cursor.requireLong(RecipientTable.CAPABILITIES)
return RecipientRecord.Capabilities(
rawBits = capabilities,
paymentActivation = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.PAYMENT_ACTIVATION, Capabilities.BIT_LENGTH).toInt())
paymentActivation = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.PAYMENT_ACTIVATION, Capabilities.BIT_LENGTH).toInt()),
deleteSync = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.DELETE_SYNC, Capabilities.BIT_LENGTH).toInt())
)
}

View File

@@ -65,7 +65,6 @@ import org.thoughtcrime.securesms.util.ConversationUtil
import org.thoughtcrime.securesms.util.JsonUtils
import org.thoughtcrime.securesms.util.JsonUtils.SaneJSONObject
import org.thoughtcrime.securesms.util.LRUCache
import org.thoughtcrime.securesms.util.RemoteConfig
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.isScheduled
import org.whispersystems.signalservice.api.storage.SignalAccountRecord
@@ -326,7 +325,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
return
}
val syncThreadTrimDeletes = SignalStore.settings().shouldSyncThreadTrimDeletes() && RemoteConfig.deleteSyncEnabled
val syncThreadTrimDeletes = SignalStore.settings().shouldSyncThreadTrimDeletes() && Recipient.self().deleteSyncCapability.isSupported
val threadTrimsToSync = mutableListOf<Pair<Long, Set<MessageRecord>>>()
readableDatabase
@@ -1119,57 +1118,34 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
if (containsAddressable || isEmpty) {
false
} else {
deleteConversation(threadId, syncThreadDeletes = false)
deleteConversation(threadId, syncThreadDelete = false)
true
}
}
}
@JvmOverloads
fun deleteConversation(threadId: Long, syncThreadDeletes: Boolean = true) {
val recipientIdForThreadId = getRecipientIdForThreadId(threadId)
var addressableMessages: Set<MessageRecord> = emptySet()
writableDatabase.withinTransaction { db ->
if (syncThreadDeletes && RemoteConfig.deleteSyncEnabled) {
addressableMessages = messages.getMostRecentAddressableMessages(threadId)
}
messages.deleteThread(threadId)
drafts.clearDrafts(threadId)
db.deactivateThread(threadId)
synchronized(threadIdCache) {
threadIdCache.remove(recipientIdForThreadId)
}
}
if (syncThreadDeletes) {
MultiDeviceDeleteSendSyncJob.enqueueThreadDeletes(listOf(threadId to addressableMessages), isFullDelete = true)
}
notifyConversationListListeners()
notifyConversationListeners(threadId)
AppDependencies.databaseObserver.notifyConversationDeleteListeners(threadId)
ConversationUtil.clearShortcuts(context, setOf(recipientIdForThreadId))
fun deleteConversation(threadId: Long, syncThreadDelete: Boolean = true) {
deleteConversations(setOf(threadId), syncThreadDelete)
}
fun deleteConversations(selectedConversations: Set<Long>) {
fun deleteConversations(selectedConversations: Set<Long>, syncThreadDeletes: Boolean = true) {
val recipientIds = getRecipientIdsForThreadIds(selectedConversations)
val addressableMessages = mutableListOf<Pair<Long, Set<MessageRecord>>>()
val queries: List<SqlUtil.Query> = SqlUtil.buildCollectionQuery(ID, selectedConversations)
writableDatabase.withinTransaction { db ->
for (query in queries) {
db.deactivateThread(query)
}
if (RemoteConfig.deleteSyncEnabled) {
if (syncThreadDeletes && Recipient.self().deleteSyncCapability.isSupported) {
for (threadId in selectedConversations) {
addressableMessages += threadId to messages.getMostRecentAddressableMessages(threadId)
}
}
for (query in queries) {
db.deactivateThread(query)
}
messages.deleteAbandonedMessages()
attachments.trimAllAbandonedAttachments()
groupReceipts.deleteAbandonedRows()
@@ -1183,12 +1159,19 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
}
}
MultiDeviceDeleteSendSyncJob.enqueueThreadDeletes(addressableMessages, isFullDelete = true)
if (syncThreadDeletes) {
MultiDeviceDeleteSendSyncJob.enqueueThreadDeletes(addressableMessages, isFullDelete = true)
}
notifyConversationListListeners()
notifyConversationListeners(selectedConversations)
notifyStickerListeners()
notifyStickerPackListeners()
AppDependencies.databaseObserver.notifyConversationDeleteListeners(selectedConversations)
ConversationUtil.clearShortcuts(context, recipientIds)
OptimizeMessageSearchIndexJob.enqueue()
}
@SuppressLint("DiscouragedApi")
@@ -1485,12 +1468,13 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
.run()
}
fun update(threadId: Long, unarchive: Boolean): Boolean {
fun update(threadId: Long, unarchive: Boolean, syncThreadDelete: Boolean = true): Boolean {
return update(
threadId = threadId,
unarchive = unarchive,
allowDeletion = true,
notifyListeners = true
notifyListeners = true,
syncThreadDelete = syncThreadDelete
)
}
@@ -1499,16 +1483,18 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
threadId = threadId,
unarchive = unarchive,
allowDeletion = true,
notifyListeners = false
notifyListeners = false,
syncThreadDelete = true
)
}
fun update(threadId: Long, unarchive: Boolean, allowDeletion: Boolean): Boolean {
fun update(threadId: Long, unarchive: Boolean, allowDeletion: Boolean, syncThreadDelete: Boolean = true): Boolean {
return update(
threadId = threadId,
unarchive = unarchive,
allowDeletion = allowDeletion,
notifyListeners = true
notifyListeners = true,
syncThreadDelete = syncThreadDelete
)
}
@@ -1543,7 +1529,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
stopwatch?.split("thread-update")
}
private fun update(threadId: Long, unarchive: Boolean, allowDeletion: Boolean, notifyListeners: Boolean): Boolean {
private fun update(threadId: Long, unarchive: Boolean, allowDeletion: Boolean, notifyListeners: Boolean, syncThreadDelete: Boolean): Boolean {
if (threadId == -1L) {
Log.d(TAG, "Skipping update for threadId -1")
return false
@@ -1558,7 +1544,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
if (!meaningfulMessages) {
if (shouldDelete) {
Log.d(TAG, "Deleting thread $threadId because it has no meaningful messages.")
deleteConversation(threadId)
deleteConversation(threadId, syncThreadDelete = syncThreadDelete)
return@withinTransaction true
} else if (!isPinned) {
return@withinTransaction false

View File

@@ -823,6 +823,13 @@ public abstract class MessageRecord extends DisplayRecord {
return revisionNumber;
}
/**
* A message that can be correctly identified and delete sync'd across devices.
*/
public boolean canDeleteSync() {
return false;
}
public static final class InviteAddState {
private final boolean invited;

View File

@@ -308,6 +308,19 @@ public class MmsMessageRecord extends MessageRecord {
return latestRevisionId;
}
@Override
public boolean canDeleteSync() {
return (isSent() || MessageTypes.isInboxType(type)) &&
(isSecure() || isPush()) &&
(type & MessageTypes.GROUP_MASK) == 0 &&
(type & MessageTypes.KEY_EXCHANGE_MASK) == 0 &&
!isReportedSpam() &&
!isMessageRequestAccepted() &&
storyType == StoryType.NONE &&
getDateSent() > 0 &&
(parentStoryId == null || parentStoryId.isDirectReply());
}
public @NonNull MmsMessageRecord withReactions(@NonNull List<ReactionRecord> reactions) {
return new MmsMessageRecord(getId(), getFromRecipient(), getFromDeviceId(), getToRecipient(), getDateSent(), getDateReceived(), getServerTimestamp(), hasDeliveryReceipt(), getThreadId(), getBody(), getSlideDeck(),
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),

View File

@@ -119,12 +119,14 @@ data class RecipientRecord(
data class Capabilities(
val rawBits: Long,
val paymentActivation: Recipient.Capability
val paymentActivation: Recipient.Capability,
val deleteSync: Recipient.Capability
) {
companion object {
@JvmField
val UNKNOWN = Capabilities(
0,
Recipient.Capability.UNKNOWN,
Recipient.Capability.UNKNOWN
)
}