perf(core): database access

This commit is contained in:
rhunk
2023-10-24 00:04:40 +02:00
parent b651094475
commit e82b91eee0
2 changed files with 95 additions and 105 deletions

View File

@ -12,60 +12,66 @@ import me.rhunk.snapenhance.common.util.ktx.getStringOrNull
import me.rhunk.snapenhance.core.ModContext import me.rhunk.snapenhance.core.ModContext
import me.rhunk.snapenhance.core.logger.CoreLogger import me.rhunk.snapenhance.core.logger.CoreLogger
import me.rhunk.snapenhance.core.manager.Manager import me.rhunk.snapenhance.core.manager.Manager
import java.io.File import java.lang.ref.WeakReference
inline fun <T> SQLiteDatabase.performOperation(crossinline query: SQLiteDatabase.() -> T?): T? {
synchronized(this) {
if (!isOpen) {
return null
}
return runCatching {
query()
}.onFailure {
CoreLogger.xposedLog("Database operation failed", it)
}.getOrNull()
}
}
@SuppressLint("Range") @SuppressLint("Range")
class DatabaseAccess(private val context: ModContext) : Manager { class DatabaseAccess(
private val databaseLock = Any() private val context: ModContext
) : Manager {
private val arroyoDatabase: File by lazy {
context.androidContext.getDatabasePath("arroyo.db")
}
private val mainDatabase: File by lazy {
context.androidContext.getDatabasePath("main.db")
}
private val dmOtherParticipantCache by lazy { private val dmOtherParticipantCache by lazy {
getAllDMOtherParticipants().toMutableMap() (openArroyo().performOperation {
} rawQuery(
"SELECT client_conversation_id, user_id FROM user_conversation WHERE conversation_type = 0 AND user_id != ?",
private fun openMain(): SQLiteDatabase { arrayOf(myUserId)
return SQLiteDatabase.openDatabase( ).use { query ->
mainDatabase.absolutePath, val participants = mutableMapOf<String, String?>()
null, if (!query.moveToFirst()) {
SQLiteDatabase.OPEN_READONLY return@performOperation null
)!! }
} do {
participants[query.getString(query.getColumnIndex("client_conversation_id"))] =
private fun openArroyo(): SQLiteDatabase { query.getString(query.getColumnIndex("user_id"))
return SQLiteDatabase.openDatabase( } while (query.moveToNext())
arroyoDatabase.absolutePath, participants
null,
SQLiteDatabase.OPEN_READONLY
)!!
}
fun hasArroyo(): Boolean {
return arroyoDatabase.exists()
}
private fun <T> safeDatabaseOperation(
database: SQLiteDatabase,
query: (SQLiteDatabase) -> T?
): T? {
synchronized(databaseLock) {
if (!database.isOpen) {
return null
} }
return runCatching { } ?: emptyMap()).toMutableMap()
query(database)
}.onFailure {
CoreLogger.xposedLog("Database operation failed", it)
}.getOrNull()
}
} }
private var databaseWeakMap = mutableMapOf<String, WeakReference<SQLiteDatabase>?>()
private fun openLocalDatabase(fileName: String): SQLiteDatabase {
if (databaseWeakMap.containsKey(fileName)) {
val database = databaseWeakMap[fileName]?.get()
if (database != null && database.isOpen) return database
}
return SQLiteDatabase.openDatabase(
context.androidContext.getDatabasePath(fileName).absolutePath,
null,
SQLiteDatabase.OPEN_READONLY
)?.also {
databaseWeakMap[fileName] = WeakReference(it)
} ?: throw IllegalStateException("Failed to open database $fileName")
}
private fun openMain() = openLocalDatabase("main.db")
private fun openArroyo() = openLocalDatabase("arroyo.db")
fun hasArroyo(): Boolean = context.androidContext.getDatabasePath("arroyo.db").exists()
private fun <T : DatabaseObject> readDatabaseObject( private fun <T : DatabaseObject> readDatabaseObject(
obj: T, obj: T,
database: SQLiteDatabase, database: SQLiteDatabase,
@ -85,10 +91,10 @@ class DatabaseAccess(private val context: ModContext) : Manager {
} }
fun getFeedEntryByUserId(userId: String): FriendFeedEntry? { fun getFeedEntryByUserId(userId: String): FriendFeedEntry? {
return safeDatabaseOperation(openMain()) { database -> return openMain().performOperation {
readDatabaseObject( readDatabaseObject(
FriendFeedEntry(), FriendFeedEntry(),
database, this,
"FriendsFeedView", "FriendsFeedView",
"friendUserId = ?", "friendUserId = ?",
arrayOf(userId) arrayOf(userId)
@ -97,23 +103,23 @@ class DatabaseAccess(private val context: ModContext) : Manager {
} }
val myUserId by lazy { val myUserId by lazy {
safeDatabaseOperation(openArroyo()) { arroyoDatabase: SQLiteDatabase -> openArroyo().performOperation {
arroyoDatabase.rawQuery(buildString { rawQuery(buildString {
append("SELECT value FROM required_values WHERE key = 'USERID'") append("SELECT value FROM required_values WHERE key = 'USERID'")
}, null).use { query -> }, null).use { query ->
if (!query.moveToFirst()) { if (!query.moveToFirst()) {
return@safeDatabaseOperation null return@performOperation null
} }
query.getStringOrNull("value")!! query.getStringOrNull("value")!!
} }
}!! } ?: context.androidContext.getSharedPreferences("user_session_shared_pref", 0).getString("key_user_id", null)!!
} }
fun getFeedEntryByConversationId(conversationId: String): FriendFeedEntry? { fun getFeedEntryByConversationId(conversationId: String): FriendFeedEntry? {
return safeDatabaseOperation(openMain()) { return openMain().performOperation {
readDatabaseObject( readDatabaseObject(
FriendFeedEntry(), FriendFeedEntry(),
it, this,
"FriendsFeedView", "FriendsFeedView",
"key = ?", "key = ?",
arrayOf(conversationId) arrayOf(conversationId)
@ -122,10 +128,10 @@ class DatabaseAccess(private val context: ModContext) : Manager {
} }
fun getFriendInfo(userId: String): FriendInfo? { fun getFriendInfo(userId: String): FriendInfo? {
return safeDatabaseOperation(openMain()) { return openMain().performOperation {
readDatabaseObject( readDatabaseObject(
FriendInfo(), FriendInfo(),
it, this,
"FriendWithUsername", "FriendWithUsername",
"userId = ?", "userId = ?",
arrayOf(userId) arrayOf(userId)
@ -134,8 +140,8 @@ class DatabaseAccess(private val context: ModContext) : Manager {
} }
fun getFeedEntries(limit: Int): List<FriendFeedEntry> { fun getFeedEntries(limit: Int): List<FriendFeedEntry> {
return safeDatabaseOperation(openMain()) { database -> return openMain().performOperation {
database.rawQuery( rawQuery(
"SELECT * FROM FriendsFeedView ORDER BY _id LIMIT ?", "SELECT * FROM FriendsFeedView ORDER BY _id LIMIT ?",
arrayOf(limit.toString()) arrayOf(limit.toString())
).use { query -> ).use { query ->
@ -153,10 +159,10 @@ class DatabaseAccess(private val context: ModContext) : Manager {
} }
fun getConversationMessageFromId(clientMessageId: Long): ConversationMessage? { fun getConversationMessageFromId(clientMessageId: Long): ConversationMessage? {
return safeDatabaseOperation(openArroyo()) { return openArroyo().performOperation {
readDatabaseObject( readDatabaseObject(
ConversationMessage(), ConversationMessage(),
it, this,
"conversation_message", "conversation_message",
"client_message_id = ?", "client_message_id = ?",
arrayOf(clientMessageId.toString()) arrayOf(clientMessageId.toString())
@ -165,13 +171,13 @@ class DatabaseAccess(private val context: ModContext) : Manager {
} }
fun getConversationType(conversationId: String): Int? { fun getConversationType(conversationId: String): Int? {
return safeDatabaseOperation(openArroyo()) { database -> return openArroyo().performOperation {
database.rawQuery( rawQuery(
"SELECT conversation_type FROM user_conversation WHERE client_conversation_id = ?", "SELECT conversation_type FROM user_conversation WHERE client_conversation_id = ?",
arrayOf(conversationId) arrayOf(conversationId)
).use { query -> ).use { query ->
if (!query.moveToFirst()) { if (!query.moveToFirst()) {
return@safeDatabaseOperation null return@performOperation null
} }
query.getInt(query.getColumnIndex("conversation_type")) query.getInt(query.getColumnIndex("conversation_type"))
} }
@ -179,10 +185,10 @@ class DatabaseAccess(private val context: ModContext) : Manager {
} }
fun getConversationLinkFromUserId(userId: String): UserConversationLink? { fun getConversationLinkFromUserId(userId: String): UserConversationLink? {
return safeDatabaseOperation(openArroyo()) { return openArroyo().performOperation {
readDatabaseObject( readDatabaseObject(
UserConversationLink(), UserConversationLink(),
it, this,
"user_conversation", "user_conversation",
"user_id = ? AND conversation_type = 0", "user_id = ? AND conversation_type = 0",
arrayOf(userId) arrayOf(userId)
@ -190,35 +196,16 @@ class DatabaseAccess(private val context: ModContext) : Manager {
} }
} }
private fun getAllDMOtherParticipants(): Map<String, String?> {
return safeDatabaseOperation(openArroyo()) { arroyoDatabase: SQLiteDatabase ->
arroyoDatabase.rawQuery(
"SELECT client_conversation_id, user_id FROM user_conversation WHERE conversation_type = 0",
null
).use { query ->
val participants = mutableMapOf<String, String>()
if (!query.moveToFirst()) {
return@safeDatabaseOperation null
}
do {
participants[query.getString(query.getColumnIndex("client_conversation_id"))] =
query.getString(query.getColumnIndex("user_id"))
} while (query.moveToNext())
participants
}
} ?: emptyMap()
}
fun getDMOtherParticipant(conversationId: String): String? { fun getDMOtherParticipant(conversationId: String): String? {
if (dmOtherParticipantCache.containsKey(conversationId)) return dmOtherParticipantCache[conversationId] if (dmOtherParticipantCache.containsKey(conversationId)) return dmOtherParticipantCache[conversationId]
return safeDatabaseOperation(openArroyo()) { cursor -> return openArroyo().performOperation {
cursor.rawQuery( rawQuery(
"SELECT user_id FROM user_conversation WHERE client_conversation_id = ? AND conversation_type = 0", "SELECT user_id FROM user_conversation WHERE client_conversation_id = ? AND conversation_type = 0",
arrayOf(conversationId) arrayOf(conversationId)
).use { query -> ).use { query ->
val participants = mutableListOf<String>() val participants = mutableListOf<String>()
if (!query.moveToFirst()) { if (!query.moveToFirst()) {
return@safeDatabaseOperation null return@performOperation null
} }
do { do {
participants.add(query.getString(query.getColumnIndex("user_id"))) participants.add(query.getString(query.getColumnIndex("user_id")))
@ -230,19 +217,19 @@ class DatabaseAccess(private val context: ModContext) : Manager {
fun getStoryEntryFromId(storyId: String): StoryEntry? { fun getStoryEntryFromId(storyId: String): StoryEntry? {
return safeDatabaseOperation(openMain()) { return openMain().performOperation {
readDatabaseObject(StoryEntry(), it, "Story", "storyId = ?", arrayOf(storyId)) readDatabaseObject(StoryEntry(), this, "Story", "storyId = ?", arrayOf(storyId))
} }
} }
fun getConversationParticipants(conversationId: String): List<String>? { fun getConversationParticipants(conversationId: String): List<String>? {
return safeDatabaseOperation(openArroyo()) { arroyoDatabase: SQLiteDatabase -> return openArroyo().performOperation {
arroyoDatabase.rawQuery( rawQuery(
"SELECT user_id FROM user_conversation WHERE client_conversation_id = ?", "SELECT user_id FROM user_conversation WHERE client_conversation_id = ?",
arrayOf(conversationId) arrayOf(conversationId)
).use { ).use {
if (!it.moveToFirst()) { if (!it.moveToFirst()) {
return@safeDatabaseOperation null return@performOperation null
} }
val participants = mutableListOf<String>() val participants = mutableListOf<String>()
do { do {
@ -257,13 +244,13 @@ class DatabaseAccess(private val context: ModContext) : Manager {
conversationId: String, conversationId: String,
limit: Int limit: Int
): List<ConversationMessage>? { ): List<ConversationMessage>? {
return safeDatabaseOperation(openArroyo()) { arroyoDatabase: SQLiteDatabase -> return openArroyo().performOperation {
arroyoDatabase.rawQuery( rawQuery(
"SELECT * FROM conversation_message WHERE client_conversation_id = ? ORDER BY creation_timestamp DESC LIMIT ?", "SELECT * FROM conversation_message WHERE client_conversation_id = ? ORDER BY creation_timestamp DESC LIMIT ?",
arrayOf(conversationId, limit.toString()) arrayOf(conversationId, limit.toString())
).use { query -> ).use { query ->
if (!query.moveToFirst()) { if (!query.moveToFirst()) {
return@safeDatabaseOperation null return@performOperation null
} }
val messages = mutableListOf<ConversationMessage>() val messages = mutableListOf<ConversationMessage>()
do { do {
@ -277,13 +264,13 @@ class DatabaseAccess(private val context: ModContext) : Manager {
} }
fun getAddSource(userId: String): String? { fun getAddSource(userId: String): String? {
return safeDatabaseOperation(openMain()) { database -> return openMain().performOperation {
database.rawQuery( rawQuery(
"SELECT addSource FROM FriendWhoAddedMe WHERE userId = ?", "SELECT addSource FROM FriendWhoAddedMe WHERE userId = ?",
arrayOf(userId) arrayOf(userId)
).use { ).use {
if (!it.moveToFirst()) { if (!it.moveToFirst()) {
return@safeDatabaseOperation null return@performOperation null
} }
it.getStringOrNull("addSource") it.getStringOrNull("addSource")
} }

View File

@ -116,13 +116,16 @@ class FriendFeedInfoMenu : AbstractMenu() {
messages.forEach { message -> messages.forEach { message ->
val sender = participants[message.senderId] val sender = participants[message.senderId]
val protoReader = ( val messageProtoReader =
messageLogger.takeIf { it.isEnabled }?.getMessageProto(conversationId, message.clientMessageId.toLong()) ?: ProtoReader(message.messageContent ?: return@forEach).followPath(4, 4) messageLogger.takeIf {
) ?: return@forEach it.isEnabled && message.contentType == ContentType.STATUS.id // only process deleted messages
}?.getMessageProto(conversationId, message.clientMessageId.toLong())
?: ProtoReader(message.messageContent ?: return@forEach).followPath(4, 4)
?: return@forEach
val contentType = ContentType.fromMessageContainer(protoReader) ?: ContentType.fromId(message.contentType) val contentType = ContentType.fromMessageContainer(messageProtoReader) ?: ContentType.fromId(message.contentType)
var messageString = if (contentType == ContentType.CHAT) { var messageString = if (contentType == ContentType.CHAT) {
protoReader.getString(2, 1) ?: return@forEach messageProtoReader.getString(2, 1) ?: return@forEach
} else { } else {
contentType.name contentType.name
} }