Group message deletion

This commit is contained in:
SessionHero01 2024-10-25 17:14:12 +11:00
parent 86a9e07f31
commit ea714a60a2
No known key found for this signature in database
7 changed files with 102 additions and 66 deletions

View File

@ -610,13 +610,12 @@ class ConversationViewModel(
}
private fun markAsDeletedForEveryoneGroupsV2(data: DeleteForEveryoneDialogData){
viewModelScope.launch(Dispatchers.IO) {
viewModelScope.launch(Dispatchers.Default) {
// show a loading indicator
_uiState.update { it.copy(showLoader = true) }
//todo GROUPS V2 - uncomment below and use Fanchao's method to delete a group V2
try {
//repository.callMethodFromFanchao(threadId, recipient, data.messages)
repository.deleteGroupV2MessagesRemotely(recipient!!, data.messages)
// the repo will handle the internal logic (calling `/delete` on the swarm
// and sending 'GroupUpdateDeleteMemberContentMessage'
@ -638,7 +637,7 @@ class ConversationViewModel(
).show()
}
} catch (e: Exception) {
Log.w("Loki", "FAILED TO delete messages ${data.messages} ")
Log.e("Loki", "FAILED TO delete messages ${data.messages}", e)
// failed to delete - show a toast and get back on the modal
withContext(Dispatchers.Main) {
Toast.makeText(

View File

@ -3,31 +3,34 @@ package org.thoughtcrime.securesms.database
import android.content.ContentValues
import android.content.Context
import net.zetetic.database.sqlcipher.SQLiteDatabase.CONFLICT_REPLACE
import org.intellij.lang.annotations.Language
import org.json.JSONArray
import org.session.libsession.database.ServerHashToMessageId
import org.session.libsignal.database.LokiMessageDatabaseProtocol
import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
import org.thoughtcrime.securesms.util.asSequence
class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiMessageDatabaseProtocol {
companion object {
private val messageIDTable = "loki_message_friend_request_database"
private val messageThreadMappingTable = "loki_message_thread_mapping_database"
private val errorMessageTable = "loki_error_message_database"
private val messageHashTable = "loki_message_hash_database"
private val smsHashTable = "loki_sms_hash_database"
private val mmsHashTable = "loki_mms_hash_database"
private const val messageIDTable = "loki_message_friend_request_database"
private const val messageThreadMappingTable = "loki_message_thread_mapping_database"
private const val errorMessageTable = "loki_error_message_database"
private const val messageHashTable = "loki_message_hash_database"
private const val smsHashTable = "loki_sms_hash_database"
private const val mmsHashTable = "loki_mms_hash_database"
const val groupInviteTable = "loki_group_invites"
private val groupInviteDeleteTrigger = "group_invite_delete_trigger"
private const val groupInviteDeleteTrigger = "group_invite_delete_trigger"
private val messageID = "message_id"
private val serverID = "server_id"
private val friendRequestStatus = "friend_request_status"
private val threadID = "thread_id"
private val errorMessage = "error_message"
private val messageType = "message_type"
private val serverHash = "server_hash"
private const val messageID = "message_id"
private const val serverID = "server_id"
private const val friendRequestStatus = "friend_request_status"
private const val threadID = "thread_id"
private const val errorMessage = "error_message"
private const val messageType = "message_type"
private const val serverHash = "server_hash"
const val invitingSessionId = "inviting_session_id"
@JvmStatic
@ -236,46 +239,52 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab
}
fun getSendersForHashes(threadId: Long, hashes: Set<String>): List<ServerHashToMessageId> {
val smsQuery = "SELECT ${SmsDatabase.TABLE_NAME}.${MmsSmsColumns.ADDRESS}, $smsHashTable.$serverHash, " +
"${SmsDatabase.TABLE_NAME}.${MmsSmsColumns.ID} FROM $smsHashTable LEFT OUTER JOIN ${SmsDatabase.TABLE_NAME} " +
"ON ${SmsDatabase.TABLE_NAME}.${MmsSmsColumns.ID} = $smsHashTable.$messageID WHERE ${SmsDatabase.TABLE_NAME}.${MmsSmsColumns.THREAD_ID} = ?;"
val mmsQuery = "SELECT ${MmsDatabase.TABLE_NAME}.${MmsSmsColumns.ADDRESS}, $mmsHashTable.$serverHash, " +
"${MmsDatabase.TABLE_NAME}.${MmsSmsColumns.ID} FROM $mmsHashTable LEFT OUTER JOIN ${MmsDatabase.TABLE_NAME} " +
"ON ${MmsDatabase.TABLE_NAME}.${MmsSmsColumns.ID} = $mmsHashTable.$messageID WHERE ${MmsDatabase.TABLE_NAME}.${MmsSmsColumns.THREAD_ID} = ?;"
val smsCursor = databaseHelper.readableDatabase.query(smsQuery, arrayOf(threadId))
val mmsCursor = databaseHelper.readableDatabase.query(mmsQuery, arrayOf(threadId))
@Language("RoomSql")
val query = """
WITH
sender_hash_mapping AS (
SELECT
sms_hash_table.$serverHash AS hash,
sms.${MmsSmsColumns.ID} AS message_id,
sms.${MmsSmsColumns.ADDRESS} AS sender,
sms.${SmsDatabase.TYPE} AS type,
true AS is_sms
FROM $smsHashTable sms_hash_table
LEFT OUTER JOIN ${SmsDatabase.TABLE_NAME} sms ON sms_hash_table.${messageID} = sms.${MmsSmsColumns.ID}
WHERE sms.${MmsSmsColumns.THREAD_ID} = :threadId
val serverHashToMessageIds = mutableListOf<ServerHashToMessageId>()
UNION ALL
smsCursor.use { cursor ->
while (cursor.moveToNext()) {
val hash = cursor.getString(1)
if (hash in hashes) {
serverHashToMessageIds += ServerHashToMessageId(
serverHash = hash,
isSms = true,
sender = cursor.getString(0),
messageId = cursor.getLong(2)
)
}
SELECT
mms_hash_table.$serverHash,
mms.${MmsSmsColumns.ID},
mms.${MmsSmsColumns.ADDRESS},
mms.${MmsDatabase.MESSAGE_TYPE},
false
FROM $mmsHashTable mms_hash_table
LEFT OUTER JOIN ${MmsDatabase.TABLE_NAME} mms ON mms_hash_table.${messageID} = mms.${MmsSmsColumns.ID}
WHERE mms.${MmsSmsColumns.THREAD_ID} = :threadId
)
SELECT * FROM sender_hash_mapping
WHERE hash IN (SELECT value FROM json_each(:hashes))
""".trimIndent()
val result = databaseHelper.readableDatabase.query(query, arrayOf(threadId, JSONArray(hashes).toString()))
.use { cursor ->
cursor.asSequence()
.map {
ServerHashToMessageId(
serverHash = cursor.getString(0),
messageId = cursor.getLong(1),
sender = cursor.getString(2),
isSms = cursor.getInt(4) == 1,
isOutgoing = MmsSmsColumns.Types.isOutgoingMessageType(cursor.getLong(3))
)
}
.toList()
}
}
mmsCursor.use { cursor ->
while (cursor.moveToNext()) {
val hash = cursor.getString(1)
if (hash in hashes) {
serverHashToMessageIds += ServerHashToMessageId(
serverHash = hash,
isSms = false,
sender = cursor.getString(0),
messageId = cursor.getLong(2)
)
}
}
}
return serverHashToMessageIds
return result
}
fun getMessageServerHash(messageID: Long, mms: Boolean): String? = getMessageTables(mms).firstNotNullOfOrNull {

View File

@ -296,15 +296,21 @@ open class Storage @Inject constructor(
closedGroupId: String
): Boolean {
val threadId = getThreadId(fromSerialized(closedGroupId))!!
val senderIsMe = sender == getUserPublicKey()
val info = lokiMessageDatabase.getSendersForHashes(threadId, hashes)
return info.all { it.sender == sender }
if (senderIsMe) {
return info.all { it.isOutgoing }
} else {
return info.all { it.sender == sender }
}
}
override fun deleteMessagesByHash(threadId: Long, hashes: List<String>) {
val info = lokiMessageDatabase.getSendersForHashes(threadId, hashes.toSet())
for ((serverHash, sender, messageIdToDelete, isSms) in info) {
messageDataProvider.deleteMessage(messageIdToDelete, isSms)
if (!messageDataProvider.isOutgoingMessage(messageIdToDelete)) {
for (info in lokiMessageDatabase.getSendersForHashes(threadId, hashes.toSet())) {
messageDataProvider.deleteMessage(info.messageId, info.isSms)
if (!info.isOutgoing) {
notificationManager.updateNotification(context)
}
}

View File

@ -19,6 +19,7 @@ import network.loki.messenger.libsession_util.util.GroupMember
import network.loki.messenger.libsession_util.util.INVITE_STATUS_FAILED
import network.loki.messenger.libsession_util.util.INVITE_STATUS_SENT
import network.loki.messenger.libsession_util.util.UserPic
import org.session.libsession.database.MessageDataProvider
import org.session.libsession.database.StorageProtocol
import org.session.libsession.database.userAuth
import org.session.libsession.messaging.groups.GroupManagerV2
@ -74,6 +75,7 @@ class GroupManagerV2Impl @Inject constructor(
private val profileManager: SSKEnvironment.ProfileManagerProtocol,
@ApplicationContext val application: Context,
private val clock: SnodeClock,
private val messageDataProvider: MessageDataProvider,
) : GroupManagerV2 {
private val dispatcher = Dispatchers.Default
@ -865,7 +867,7 @@ class GroupManagerV2Impl @Inject constructor(
override suspend fun requestMessageDeletion(
groupId: AccountId,
messageHashes: List<String>
messageHashes: Set<String>
): Unit = withContext(dispatcher) {
// To delete messages from a group, there are a few considerations:
// 1. Messages are stored on every member's device, we need a way to ask them to delete their stored messages
@ -883,7 +885,7 @@ class GroupManagerV2Impl @Inject constructor(
check(
group.hasAdminKey() ||
storage.ensureMessageHashesAreSender(
messageHashes.toSet(),
messageHashes,
userPubKey,
groupId.hexString
)
@ -896,7 +898,7 @@ class GroupManagerV2Impl @Inject constructor(
SnodeAPI.deleteMessage(
publicKey = groupId.hexString,
swarmAuth = OwnedSwarmAuth.ofClosedGroup(groupId, adminKey),
serverHashes = messageHashes
serverHashes = messageHashes.toList()
)
}
@ -958,8 +960,8 @@ class GroupManagerV2Impl @Inject constructor(
groupId.hexString
)
) {
// ensure that all message hashes belong to user
// storage delete
// For deleting message by hashes, we'll likely only need to mark
// them as deleted
storage.deleteMessagesByHash(threadId, hashes)
}
}

View File

@ -77,6 +77,8 @@ interface ConversationRepository {
messages: Set<MessageRecord>
)
suspend fun deleteGroupV2MessagesRemotely(recipient: Recipient, messages: Set<MessageRecord>)
suspend fun banUser(threadId: Long, recipient: Recipient): Result<Unit>
suspend fun banAndDeleteAll(threadId: Long, recipient: Recipient): Result<Unit>
suspend fun deleteThread(threadId: Long): Result<Unit>
@ -315,6 +317,20 @@ class DefaultConversationRepository @Inject constructor(
}
}
override suspend fun deleteGroupV2MessagesRemotely(
recipient: Recipient,
messages: Set<MessageRecord>
) {
require(recipient.isGroupV2Recipient) { "Recipient is not a group v2 recipient" }
val groupId = AccountId(recipient.address.serialize())
val hashes = messages.mapNotNullTo(mutableSetOf()) { msg ->
messageDataProvider.getServerHashForMessage(msg.id, msg.isMms)
}
groupManager.requestMessageDeletion(groupId, hashes)
}
override suspend fun deleteNoteToSelfMessagesRemotely(
threadId: Long,
recipient: Recipient,

View File

@ -2,7 +2,11 @@ package org.session.libsession.database
data class ServerHashToMessageId(
val serverHash: String,
/**
* This will only be the "sender" when the message is incoming.
*/
val sender: String,
val messageId: Long,
val isSms: Boolean,
val isOutgoing: Boolean,
)

View File

@ -80,7 +80,7 @@ interface GroupManagerV2 {
* It can be called by a regular member who wishes to delete their own messages.
* It can also called by an admin, who can delete any messages from any member.
*/
suspend fun requestMessageDeletion(groupId: AccountId, messageHashes: List<String>)
suspend fun requestMessageDeletion(groupId: AccountId, messageHashes: Set<String>)
/**
* Handle a request to delete a member's content from the group. This is called when we receive