diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/SnapEnhance.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/SnapEnhance.kt index 76cba92f..1e081537 100644 --- a/core/src/main/kotlin/me/rhunk/snapenhance/core/SnapEnhance.kt +++ b/core/src/main/kotlin/me/rhunk/snapenhance/core/SnapEnhance.kt @@ -325,7 +325,7 @@ class SnapEnhance { MessagingFriendInfo( friendUserId, - appContext.database.getConversationLinkFromUserId(friendUserId)?.clientConversationId, + it.key, friend.displayName, friend.mutableUsername ?: friend.usernameForSorting!!, friend.bitmojiAvatarId, @@ -344,7 +344,7 @@ class SnapEnhance { return appContext.database.getFriendInfo(uuid)?.let { MessagingFriendInfo( userId = it.userId!!, - dmConversationId = appContext.database.getConversationLinkFromUserId(it.userId!!)?.clientConversationId, + dmConversationId = null, displayName = it.displayName, mutableUsername = it.mutableUsername!!, bitmojiId = it.bitmojiAvatarId, diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/action/impl/BulkMessagingAction.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/action/impl/BulkMessagingAction.kt index 25dd1722..3473dd05 100644 --- a/core/src/main/kotlin/me/rhunk/snapenhance/core/action/impl/BulkMessagingAction.kt +++ b/core/src/main/kotlin/me/rhunk/snapenhance/core/action/impl/BulkMessagingAction.kt @@ -54,7 +54,6 @@ import me.rhunk.snapenhance.core.ui.ViewAppearanceHelper import me.rhunk.snapenhance.core.util.EvictingMap import me.rhunk.snapenhance.core.util.dataBuilder import me.rhunk.snapenhance.mapper.impl.FriendRelationshipChangerMapper -import java.net.URL import java.text.DateFormat import java.util.Date import kotlin.random.Random @@ -184,7 +183,7 @@ class BulkMessagingAction : AbstractAction() { } private fun getDMLastMessage(userId: String?): ConversationMessage? { - return context.database.getConversationLinkFromUserId(userId ?: return null)?.clientConversationId?.let { + return context.database.getDMConversationId(userId ?: return null)?.let { context.database.getMessagesFromConversationId(it, 1) }?.firstOrNull() } diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/database/DatabaseAccess.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/database/DatabaseAccess.kt index a7bb0be8..5fb51d63 100644 --- a/core/src/main/kotlin/me/rhunk/snapenhance/core/database/DatabaseAccess.kt +++ b/core/src/main/kotlin/me/rhunk/snapenhance/core/database/DatabaseAccess.kt @@ -13,6 +13,7 @@ import me.rhunk.snapenhance.common.util.ktx.getInteger import me.rhunk.snapenhance.common.util.ktx.getStringOrNull import me.rhunk.snapenhance.common.util.protobuf.ProtoReader import me.rhunk.snapenhance.core.ModContext +import me.rhunk.snapenhance.core.wrapper.impl.toSnapUUID import me.rhunk.snapenhance.nativelib.NativeLib @@ -30,6 +31,14 @@ class DatabaseAccess( ) { private val openedDatabases = mutableMapOf() + private val hasArroyoConversationTable by lazy { + useDatabase(DatabaseType.ARROYO)?.performOperation { + safeRawQuery("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'conversation'")?.use { query -> + query.moveToFirst() && query.getStringOrNull("name") == "conversation" + } + } == true + } + private fun useDatabase(database: DatabaseType, writeMode: Boolean = false): SQLiteDatabase? { // only cache read-only databases if (!writeMode && openedDatabases.containsKey(database) && openedDatabases[database]?.isOpen == true) { @@ -88,7 +97,44 @@ class DatabaseAccess( }.getOrNull() } + private val friendDMsCache by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { + getFeedEntries(Int.MAX_VALUE) + .filter { it.conversationType == 0 && it.participantsSize == 2 } + .associate { it.participants?.firstOrNull { it != myUserId } to it.key } + .toMutableMap() + } + private val dmOtherParticipantCache by lazy { + if (hasArroyoConversationTable) { + return@lazy useDatabase(DatabaseType.ARROYO)?.performOperation { + safeRawQuery( + "SELECT client_conversation_id, conversation_metadata FROM conversation", + )?.use { query -> + val result = mutableMapOf() + if (!query.moveToFirst()) { + return@performOperation null + } + do { + val conversationId = query.getStringOrNull("client_conversation_id") ?: continue + val conversationMetadata = ProtoReader(query.getBlobOrNull("conversation_metadata") ?: continue) + + val participants = mutableListOf() + conversationMetadata.eachBuffer(3) { + participants.add(getByteArray(1, 1)?.toSnapUUID()?.toString() ?: return@eachBuffer) + } + + result[conversationId] = if (participants.size == 2) { + participants.firstOrNull { it != myUserId }?.also { + result[it] = null + } + } else null + } while (query.moveToNext()) + + result + } + }?.toMutableMap() ?: mutableMapOf() + } + (useDatabase(DatabaseType.ARROYO)?.performOperation { safeRawQuery( "SELECT client_conversation_id, conversation_type, user_id FROM user_conversation WHERE user_id != ?", @@ -293,6 +339,10 @@ class DatabaseAccess( } fun getConversationType(conversationId: String): Int? { + if (hasArroyoConversationTable) { + return getFeedEntryByConversationId(conversationId)?.conversationType + } + return useDatabase(DatabaseType.ARROYO)?.performOperation { safeRawQuery( "SELECT conversation_type FROM user_conversation WHERE client_conversation_id = ?", @@ -306,37 +356,68 @@ class DatabaseAccess( } } - fun getConversationLinkFromUserId(userId: String): UserConversationLink? { + fun getDMConversationId(userId: String): String? { + if (hasArroyoConversationTable) { + return friendDMsCache[userId] + } + return useDatabase(DatabaseType.ARROYO)?.performOperation { readDatabaseObject( UserConversationLink(), "user_conversation", "user_id = ? AND conversation_type = 0", arrayOf(userId) - ) + )?.clientConversationId + } + } + + private fun getConversationParticipantsRaw(conversationId: String): List? { + if (hasArroyoConversationTable) { + return useDatabase(DatabaseType.ARROYO)?.performOperation { + safeRawQuery( + "SELECT conversation_metadata FROM conversation WHERE client_conversation_id = ?", + arrayOf(conversationId) + )?.use { query -> + val participants = mutableListOf() + if (!query.moveToFirst()) { + return@performOperation null + } + val conversationMetadata = ProtoReader(query.getBlobOrNull("conversation_metadata") ?: return@performOperation null) + + conversationMetadata.eachBuffer(3) { + participants.add(getByteArray(1, 1)?.toSnapUUID()?.toString() ?: return@eachBuffer) + } + + participants + } + } + } + + return useDatabase(DatabaseType.ARROYO)?.performOperation { + safeRawQuery( + "SELECT user_id FROM user_conversation WHERE client_conversation_id = ?", + arrayOf(conversationId) + )?.use { query -> + if (!query.moveToFirst()) { + return@performOperation emptyList() + } + val participants = mutableListOf() + do { + query.getStringOrNull("user_id")?.let { participants.add(it) } + } while (query.moveToNext()) + participants + } } } fun getDMOtherParticipant(conversationId: String): String? { if (dmOtherParticipantCache.containsKey(conversationId)) return dmOtherParticipantCache[conversationId] - return useDatabase(DatabaseType.ARROYO)?.performOperation { - safeRawQuery( - "SELECT user_id FROM user_conversation WHERE client_conversation_id = ? AND conversation_type = 0", - arrayOf(conversationId) - )?.use { query -> - val participants = mutableListOf() - if (!query.moveToFirst()) { - return@performOperation null - } - do { - participants.add(query.getStringOrNull("user_id")!!) - } while (query.moveToNext()) - participants.firstOrNull { it != myUserId } - }.also { dmOtherParticipantCache[conversationId] = it } + + return getConversationParticipantsRaw(conversationId)?.takeIf { it.size == 2 }?.firstOrNull { it != myUserId }.also { + dmOtherParticipantCache[conversationId] = it } } - fun getStoryEntryFromId(storyId: String): StoryEntry? { return useDatabase(DatabaseType.MAIN)?.performOperation { readDatabaseObject(StoryEntry(), "Story", "storyId = ?", arrayOf(storyId)) @@ -345,28 +426,10 @@ class DatabaseAccess( fun getConversationParticipants(conversationId: String, useCache: Boolean = true): List? { if (dmOtherParticipantCache[conversationId] != null && useCache) return dmOtherParticipantCache[conversationId]?.let { listOf(myUserId, it) } - return useDatabase(DatabaseType.ARROYO)?.performOperation { - safeRawQuery( - "SELECT user_id, conversation_type FROM user_conversation WHERE client_conversation_id = ?", - arrayOf(conversationId) - )?.use { cursor -> - if (!cursor.moveToFirst()) { - return@performOperation null - } - val participants = mutableListOf() - var conversationType = -1 - do { - if (conversationType == -1) conversationType = cursor.getInteger("conversation_type") - participants.add(cursor.getStringOrNull("user_id")!!) - } while (cursor.moveToNext()) - if (!dmOtherParticipantCache.containsKey(conversationId)) { - dmOtherParticipantCache[conversationId] = when (conversationType) { - 0 -> participants.firstOrNull { it != myUserId } - else -> null - } - } - participants + return getConversationParticipantsRaw(conversationId)?.also { + if (!dmOtherParticipantCache.containsKey(conversationId)) { + dmOtherParticipantCache[conversationId] = it.firstOrNull { it != myUserId } } } } diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/messaging/CoreMessagingBridge.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/messaging/CoreMessagingBridge.kt index 2e34dbb8..e8d13ce5 100644 --- a/core/src/main/kotlin/me/rhunk/snapenhance/core/messaging/CoreMessagingBridge.kt +++ b/core/src/main/kotlin/me/rhunk/snapenhance/core/messaging/CoreMessagingBridge.kt @@ -116,5 +116,5 @@ class CoreMessagingBridge( } } - override fun getOneToOneConversationId(userId: String) = context.database.getConversationLinkFromUserId(userId)?.clientConversationId + override fun getOneToOneConversationId(userId: String) = context.database.getDMConversationId(userId) } \ No newline at end of file