Use separate tables for mms and sms in LokiMessageDatabase

This commit is contained in:
andrew 2023-10-11 01:51:09 +10:30
parent c86b229200
commit 77f951cadf
10 changed files with 68 additions and 67 deletions

View File

@ -186,7 +186,7 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper)
else DatabaseComponent.get(context).mmsDatabase() else DatabaseComponent.get(context).mmsDatabase()
messagingDatabase.deleteMessage(messageID) messagingDatabase.deleteMessage(messageID)
DatabaseComponent.get(context).lokiMessageDatabase().deleteMessage(messageID, isSms) DatabaseComponent.get(context).lokiMessageDatabase().deleteMessage(messageID, isSms)
DatabaseComponent.get(context).lokiMessageDatabase().deleteMessageServerHash(messageID) DatabaseComponent.get(context).lokiMessageDatabase().deleteMessageServerHash(messageID, mms = !isSms)
} }
override fun deleteMessages(messageIDs: List<Long>, threadId: Long, isSms: Boolean) { override fun deleteMessages(messageIDs: List<Long>, threadId: Long, isSms: Boolean) {
@ -195,7 +195,7 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper)
messagingDatabase.deleteMessages(messageIDs.toLongArray(), threadId) messagingDatabase.deleteMessages(messageIDs.toLongArray(), threadId)
DatabaseComponent.get(context).lokiMessageDatabase().deleteMessages(messageIDs) DatabaseComponent.get(context).lokiMessageDatabase().deleteMessages(messageIDs)
DatabaseComponent.get(context).lokiMessageDatabase().deleteMessageServerHashes(messageIDs) DatabaseComponent.get(context).lokiMessageDatabase().deleteMessageServerHashes(messageIDs, mms = !isSms)
} }
override fun updateMessageAsDeleted(timestamp: Long, author: String): Long? { override fun updateMessageAsDeleted(timestamp: Long, author: String): Long? {
@ -212,15 +212,12 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper)
return message.id return message.id
} }
override fun getServerHashForMessage(messageID: Long): String? { override fun getServerHashForMessage(messageID: Long, mms: Boolean): String? =
val messageDB = DatabaseComponent.get(context).lokiMessageDatabase() DatabaseComponent.get(context).lokiMessageDatabase().getMessageServerHash(messageID, mms)
return messageDB.getMessageServerHash(messageID)
}
override fun getDatabaseAttachment(attachmentId: Long): DatabaseAttachment? { override fun getDatabaseAttachment(attachmentId: Long): DatabaseAttachment? =
val attachmentDatabase = DatabaseComponent.get(context).attachmentDatabase() DatabaseComponent.get(context).attachmentDatabase()
return attachmentDatabase.getAttachment(AttachmentId(attachmentId, 0)) .getAttachment(AttachmentId(attachmentId, 0))
}
private fun scaleAndStripExif(attachmentDatabase: AttachmentDatabase, constraints: MediaConstraints, attachment: Attachment): Attachment? { private fun scaleAndStripExif(attachmentDatabase: AttachmentDatabase, constraints: MediaConstraints, attachment: Attachment): Attachment? {
return try { return try {

View File

@ -1813,7 +1813,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
override fun deleteMessages(messages: Set<MessageRecord>) { override fun deleteMessages(messages: Set<MessageRecord>) {
val recipient = viewModel.recipient ?: return val recipient = viewModel.recipient ?: return
val allSentByCurrentUser = messages.all { it.isOutgoing } val allSentByCurrentUser = messages.all { it.isOutgoing }
val allHasHash = messages.all { lokiMessageDb.getMessageServerHash(it.id) != null } val allHasHash = messages.all { lokiMessageDb.getMessageServerHash(it.id, it.isMms) != null }
if (recipient.isOpenGroupRecipient) { if (recipient.isOpenGroupRecipient) {
val messageCount = 1 val messageCount = 1

View File

@ -207,52 +207,52 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab
messages.add(cursor.getLong(messageID) to cursor.getLong(serverID)) messages.add(cursor.getLong(messageID) to cursor.getLong(serverID))
} }
} }
var deletedCount = 0L
database.beginTransaction() database.beginTransaction()
messages.forEach { (messageId, serverId) -> messages.forEach { (messageId, serverId) ->
deletedCount += database.delete(messageIDTable, "$messageID = ? AND $serverID = ?", arrayOf(messageId.toString(), serverId.toString())) database.delete(messageIDTable, "$messageID = ? AND $serverID = ?", arrayOf(messageId.toString(), serverId.toString()))
} }
val mappingDeleted = database.delete(messageThreadMappingTable, "$threadID = ?", arrayOf(threadId.toString())) database.delete(messageThreadMappingTable, "$threadID = ?", arrayOf(threadId.toString()))
database.setTransactionSuccessful() database.setTransactionSuccessful()
} finally { } finally {
database.endTransaction() database.endTransaction()
} }
} }
fun getMessageServerHash(messageID: Long): String? { fun getMessageServerHash(messageID: Long, mms: Boolean): String? = getMessageTables(mms).firstNotNullOfOrNull {
val database = databaseHelper.readableDatabase databaseHelper.readableDatabase.get(it, "${Companion.messageID} = ?", arrayOf(messageID.toString())) { cursor ->
return database.get(messageHashTable, "${Companion.messageID} = ?", arrayOf(messageID.toString())) { cursor ->
cursor.getString(serverHash) cursor.getString(serverHash)
} }
} }
fun setMessageServerHash(messageID: Long, serverHash: String) { fun setMessageServerHash(messageID: Long, mms: Boolean, serverHash: String) {
val database = databaseHelper.writableDatabase val contentValues = ContentValues(2).apply {
val contentValues = ContentValues(2) put(Companion.messageID, messageID)
contentValues.put(Companion.messageID, messageID) put(Companion.serverHash, serverHash)
contentValues.put(Companion.serverHash, serverHash)
database.insertOrUpdate(messageHashTable, contentValues, "${Companion.messageID} = ?", arrayOf(messageID.toString()))
} }
fun deleteMessageServerHash(messageID: Long) { databaseHelper.writableDatabase.apply {
val database = databaseHelper.writableDatabase insertOrUpdate(getMessageTable(mms), contentValues, "${Companion.messageID} = ?", arrayOf(messageID.toString()))
database.delete(messageHashTable, "${Companion.messageID} = ?", arrayOf(messageID.toString())) }
} }
fun deleteMessageServerHashes(messageIDs: List<Long>) { fun deleteMessageServerHash(messageID: Long, mms: Boolean) {
val database = databaseHelper.writableDatabase getMessageTables(mms).firstOrNull {
database.delete( databaseHelper.writableDatabase.delete(it, "${Companion.messageID} = ?", arrayOf(messageID.toString())) > 0
messageHashTable, }
"${Companion.messageID} IN (${messageIDs.map { "?" }.joinToString(",")})", }
fun deleteMessageServerHashes(messageIDs: List<Long>, mms: Boolean) {
databaseHelper.writableDatabase.delete(
getMessageTable(mms),
"${Companion.messageID} IN (${messageIDs.joinToString(",") { "?" }})",
messageIDs.map { "$it" }.toTypedArray() messageIDs.map { "$it" }.toTypedArray()
) )
} }
fun migrateThreadId(legacyThreadId: Long, newThreadId: Long) { private fun getMessageTables(mms: Boolean) = sequenceOf(
val database = databaseHelper.writableDatabase getMessageTable(mms),
val contentValues = ContentValues(1) messageHashTable
contentValues.put(threadID, newThreadId) )
database.update(messageThreadMappingTable, contentValues, "$threadID = ?", arrayOf(legacyThreadId.toString()))
}
private fun getMessageTable(mms: Boolean) = if (mms) mmsHashTable else smsHashTable
} }

View File

@ -376,7 +376,7 @@ open class Storage(
} }
message.serverHash?.let { serverHash -> message.serverHash?.let { serverHash ->
messageID?.let { id -> messageID?.let { id ->
DatabaseComponent.get(context).lokiMessageDatabase().setMessageServerHash(id, serverHash) DatabaseComponent.get(context).lokiMessageDatabase().setMessageServerHash(id, message.isMediaMessage(), serverHash)
} }
} }
if (expiryMode is ExpiryMode.AfterSend) { if (expiryMode is ExpiryMode.AfterSend) {
@ -756,10 +756,10 @@ open class Storage(
SessionMetaProtocol.removeTimestamps(timestamps) SessionMetaProtocol.removeTimestamps(timestamps)
} }
override fun getMessageIdInDatabase(timestamp: Long, author: String): Long? { override fun getMessageIdInDatabase(timestamp: Long, author: String): Pair<Long, Boolean>? {
val database = DatabaseComponent.get(context).mmsSmsDatabase() val database = DatabaseComponent.get(context).mmsSmsDatabase()
val address = fromSerialized(author) val address = fromSerialized(author)
return database.getMessageFor(timestamp, address)?.getId() return database.getMessageFor(timestamp, address)?.run { getId() to isMms }
} }
override fun updateSentTimestamp( override fun updateSentTimestamp(
@ -878,8 +878,8 @@ open class Storage(
db.clearErrorMessage(messageID) db.clearErrorMessage(messageID)
} }
override fun setMessageServerHash(messageID: Long, serverHash: String) { override fun setMessageServerHash(messageID: Long, mms: Boolean, serverHash: String) {
DatabaseComponent.get(context).lokiMessageDatabase().setMessageServerHash(messageID, serverHash) DatabaseComponent.get(context).lokiMessageDatabase().setMessageServerHash(messageID, mms, serverHash)
} }
override fun getGroup(groupID: String): GroupRecord? { override fun getGroup(groupID: String): GroupRecord? {

View File

@ -61,7 +61,9 @@ class MarkReadReceiver : BroadcastReceiver() {
val loki = DatabaseComponent.get(context).lokiMessageDatabase() val loki = DatabaseComponent.get(context).lokiMessageDatabase()
task { task {
val hashToInfo = markedReadMessages.associateByNotNull { loki.getMessageServerHash(it.expirationInfo.id) } val hashToInfo = markedReadMessages.associateByNotNull {
it.expirationInfo.run { loki.getMessageServerHash(id, isMms) }
}
if (hashToInfo.isEmpty()) return@task if (hashToInfo.isEmpty()) return@task
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")

View File

@ -202,7 +202,7 @@ class DefaultConversationRepository @Inject constructor(
} }
} else { } else {
messageDataProvider.deleteMessage(message.id, !message.isMms) messageDataProvider.deleteMessage(message.id, !message.isMms)
messageDataProvider.getServerHashForMessage(message.id)?.let { serverHash -> messageDataProvider.getServerHashForMessage(message.id, message.isMms)?.let { serverHash ->
var publicKey = recipient.address.serialize() var publicKey = recipient.address.serialize()
if (recipient.isClosedGroupRecipient) { if (recipient.isClosedGroupRecipient) {
publicKey = GroupUtil.doubleDecodeGroupID(publicKey).toHexString() publicKey = GroupUtil.doubleDecodeGroupID(publicKey).toHexString()
@ -219,16 +219,15 @@ class DefaultConversationRepository @Inject constructor(
override fun buildUnsendRequest(recipient: Recipient, message: MessageRecord): UnsendRequest? { override fun buildUnsendRequest(recipient: Recipient, message: MessageRecord): UnsendRequest? {
if (recipient.isOpenGroupRecipient) return null if (recipient.isOpenGroupRecipient) return null
messageDataProvider.getServerHashForMessage(message.id) ?: return null messageDataProvider.getServerHashForMessage(message.id, message.isMms) ?: return null
val unsendRequest = UnsendRequest() return UnsendRequest().apply {
if (message.isOutgoing) { author = if (message.isOutgoing) {
unsendRequest.author = textSecurePreferences.getLocalNumber() textSecurePreferences.getLocalNumber()
} else { } else {
unsendRequest.author = message.individualRecipient.address.contactIdentifier() message.individualRecipient.address.contactIdentifier()
}
timestamp = message.timestamp
} }
unsendRequest.timestamp = message.timestamp
return unsendRequest
} }
override suspend fun deleteMessageWithoutUnsendRequest( override suspend fun deleteMessageWithoutUnsendRequest(
@ -243,7 +242,7 @@ class DefaultConversationRepository @Inject constructor(
lokiMessageDb.getServerID(message.id, !message.isMms) ?: continue lokiMessageDb.getServerID(message.id, !message.isMms) ?: continue
messageServerIDs[messageServerID] = message messageServerIDs[messageServerID] = message
} }
for ((messageServerID, message) in messageServerIDs) { messageServerIDs.forEach { (messageServerID, message) ->
OpenGroupApi.deleteMessage(messageServerID, openGroup.room, openGroup.server) OpenGroupApi.deleteMessage(messageServerID, openGroup.room, openGroup.server)
.success { .success {
messageDataProvider.deleteMessage(message.id, !message.isMms) messageDataProvider.deleteMessage(message.id, !message.isMms)

View File

@ -24,7 +24,7 @@ interface MessageDataProvider {
fun deleteMessage(messageID: Long, isSms: Boolean) fun deleteMessage(messageID: Long, isSms: Boolean)
fun deleteMessages(messageIDs: List<Long>, threadId: Long, isSms: Boolean) fun deleteMessages(messageIDs: List<Long>, threadId: Long, isSms: Boolean)
fun updateMessageAsDeleted(timestamp: Long, author: String): Long? fun updateMessageAsDeleted(timestamp: Long, author: String): Long?
fun getServerHashForMessage(messageID: Long): String? fun getServerHashForMessage(messageID: Long, mms: Boolean): String?
fun getDatabaseAttachment(attachmentId: Long): DatabaseAttachment? fun getDatabaseAttachment(attachmentId: Long): DatabaseAttachment?
fun getAttachmentStream(attachmentId: Long): SessionServiceAttachmentStream? fun getAttachmentStream(attachmentId: Long): SessionServiceAttachmentStream?
fun getAttachmentPointer(attachmentId: Long): SessionServiceAttachmentPointer? fun getAttachmentPointer(attachmentId: Long): SessionServiceAttachmentPointer?

View File

@ -114,7 +114,7 @@ interface StorageProtocol {
*/ */
fun persistAttachments(messageID: Long, attachments: List<Attachment>): List<Long> fun persistAttachments(messageID: Long, attachments: List<Attachment>): List<Long>
fun getAttachmentsForMessage(messageID: Long): List<DatabaseAttachment> fun getAttachmentsForMessage(messageID: Long): List<DatabaseAttachment>
fun getMessageIdInDatabase(timestamp: Long, author: String): Long? // TODO: This is a weird name fun getMessageIdInDatabase(timestamp: Long, author: String): Pair<Long, Boolean>? // TODO: This is a weird name
fun updateSentTimestamp(messageID: Long, isMms: Boolean, openGroupSentTimestamp: Long, threadId: Long) fun updateSentTimestamp(messageID: Long, isMms: Boolean, openGroupSentTimestamp: Long, threadId: Long)
fun markAsResyncing(timestamp: Long, author: String) fun markAsResyncing(timestamp: Long, author: String)
fun markAsSyncing(timestamp: Long, author: String) fun markAsSyncing(timestamp: Long, author: String)
@ -124,7 +124,7 @@ interface StorageProtocol {
fun markAsSyncFailed(timestamp: Long, author: String, error: Exception) fun markAsSyncFailed(timestamp: Long, author: String, error: Exception)
fun markAsSentFailed(timestamp: Long, author: String, error: Exception) fun markAsSentFailed(timestamp: Long, author: String, error: Exception)
fun clearErrorMessage(messageID: Long) fun clearErrorMessage(messageID: Long)
fun setMessageServerHash(messageID: Long, serverHash: String) fun setMessageServerHash(messageID: Long, mms: Boolean, serverHash: String)
// Closed Groups // Closed Groups
fun getGroup(groupID: String): GroupRecord? fun getGroup(groupID: String): GroupRecord?

View File

@ -372,20 +372,23 @@ object MessageSender {
fun handleSuccessfulMessageSend(message: Message, destination: Destination, isSyncMessage: Boolean = false, openGroupSentTimestamp: Long = -1) { fun handleSuccessfulMessageSend(message: Message, destination: Destination, isSyncMessage: Boolean = false, openGroupSentTimestamp: Long = -1) {
val storage = MessagingModuleConfiguration.shared.storage val storage = MessagingModuleConfiguration.shared.storage
val userPublicKey = storage.getUserPublicKey()!! val userPublicKey = storage.getUserPublicKey()!!
val timestamp = message.sentTimestamp!!
// Ignore future self-sends // Ignore future self-sends
storage.addReceivedMessageTimestamp(message.sentTimestamp!!) storage.addReceivedMessageTimestamp(timestamp)
storage.getMessageIdInDatabase(message.sentTimestamp!!, userPublicKey)?.let { messageID -> storage.getMessageIdInDatabase(timestamp, userPublicKey)?.let { (messageID, mms) ->
if (openGroupSentTimestamp != -1L && message is VisibleMessage) { if (openGroupSentTimestamp != -1L && message is VisibleMessage) {
storage.addReceivedMessageTimestamp(openGroupSentTimestamp) storage.addReceivedMessageTimestamp(openGroupSentTimestamp)
storage.updateSentTimestamp(messageID, message.isMediaMessage(), openGroupSentTimestamp, message.threadID!!) storage.updateSentTimestamp(messageID, message.isMediaMessage(), openGroupSentTimestamp, message.threadID!!)
message.sentTimestamp = openGroupSentTimestamp message.sentTimestamp = openGroupSentTimestamp
} }
// When the sync message is successfully sent, the hash value of this TSOutgoingMessage // When the sync message is successfully sent, the hash value of this TSOutgoingMessage
// will be replaced by the hash value of the sync message. Since the hash value of the // will be replaced by the hash value of the sync message. Since the hash value of the
// real message has no use when we delete a message. It is OK to let it be. // real message has no use when we delete a message. It is OK to let it be.
message.serverHash?.let { message.serverHash?.let {
storage.setMessageServerHash(messageID, it) storage.setMessageServerHash(messageID, mms, it)
} }
// in case any errors from previous sends // in case any errors from previous sends
storage.clearErrorMessage(messageID) storage.clearErrorMessage(messageID)
// Track the open group server message ID // Track the open group server message ID
@ -412,11 +415,11 @@ object MessageSender {
} }
} }
// Mark the message as sent // Mark the message as sent
storage.markAsSent(message.sentTimestamp!!, userPublicKey) storage.markAsSent(timestamp, userPublicKey)
storage.markUnidentified(message.sentTimestamp!!, userPublicKey) storage.markUnidentified(timestamp, userPublicKey)
// Start the disappearing messages timer if needed // Start the disappearing messages timer if needed
if (message.recipient == userPublicKey || !isSyncMessage) { if (message.recipient == userPublicKey || !isSyncMessage) {
SSKEnvironment.shared.messageExpirationManager.startAnyExpiration(message.sentTimestamp!!, userPublicKey, System.currentTimeMillis()) SSKEnvironment.shared.messageExpirationManager.startAnyExpiration(timestamp, userPublicKey, System.currentTimeMillis())
} }
} ?: run { } ?: run {
storage.updateReactionIfNeeded(message, message.sender?:userPublicKey, openGroupSentTimestamp) storage.updateReactionIfNeeded(message, message.sender?:userPublicKey, openGroupSentTimestamp)
@ -429,7 +432,7 @@ object MessageSender {
if (message is VisibleMessage) message.syncTarget = destination.publicKey if (message is VisibleMessage) message.syncTarget = destination.publicKey
if (message is ExpirationTimerUpdate) message.syncTarget = destination.publicKey if (message is ExpirationTimerUpdate) message.syncTarget = destination.publicKey
storage.markAsSyncing(message.sentTimestamp!!, userPublicKey) storage.markAsSyncing(timestamp, userPublicKey)
sendToSnodeDestination(Destination.Contact(userPublicKey), message, true) sendToSnodeDestination(Destination.Contact(userPublicKey), message, true)
} }
} }
@ -456,7 +459,7 @@ object MessageSender {
message.linkPreview?.let { linkPreview -> message.linkPreview?.let { linkPreview ->
if (linkPreview.attachmentID == null) { if (linkPreview.attachmentID == null) {
messageDataProvider.getLinkPreviewAttachmentIDFor(message.id!!)?.let { attachmentID -> messageDataProvider.getLinkPreviewAttachmentIDFor(message.id!!)?.let { attachmentID ->
message.linkPreview!!.attachmentID = attachmentID linkPreview.attachmentID = attachmentID
message.attachmentIDs.remove(attachmentID) message.attachmentIDs.remove(attachmentID)
} }
} }

View File

@ -259,8 +259,8 @@ fun MessageReceiver.handleUnsendRequest(message: UnsendRequest): Long? {
val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider
val timestamp = message.timestamp ?: return null val timestamp = message.timestamp ?: return null
val author = message.author ?: return null val author = message.author ?: return null
val messageIdToDelete = storage.getMessageIdInDatabase(timestamp, author) ?: return null val (messageIdToDelete, mms) = storage.getMessageIdInDatabase(timestamp, author) ?: return null
messageDataProvider.getServerHashForMessage(messageIdToDelete)?.let { serverHash -> messageDataProvider.getServerHashForMessage(messageIdToDelete, mms)?.let { serverHash ->
SnodeAPI.deleteMessage(author, listOf(serverHash)) SnodeAPI.deleteMessage(author, listOf(serverHash))
} }
val deletedMessageId = messageDataProvider.updateMessageAsDeleted(timestamp, author) val deletedMessageId = messageDataProvider.updateMessageAsDeleted(timestamp, author)