fix(database): db cache

This commit is contained in:
rhunk 2024-01-04 23:09:27 +01:00
parent d1c4b4febe
commit a90f4875a7
3 changed files with 116 additions and 108 deletions

View File

@ -261,69 +261,71 @@ class ScopeContent(
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
// e2ee section // e2ee section
SectionTitle(translation["e2ee_title"]) if (context.config.root.experimental.e2eEncryption.globalState == true) {
var hasSecretKey by remember { mutableStateOf(context.e2eeImplementation.friendKeyExists(friend.userId))} SectionTitle(translation["e2ee_title"])
var importDialog by remember { mutableStateOf(false) } var hasSecretKey by remember { mutableStateOf(context.e2eeImplementation.friendKeyExists(friend.userId))}
var importDialog by remember { mutableStateOf(false) }
if (importDialog) { if (importDialog) {
Dialog( Dialog(
onDismissRequest = { importDialog = false } onDismissRequest = { importDialog = false }
) { ) {
dialogs.RawInputDialog(onDismiss = { importDialog = false }, onConfirm = { newKey -> dialogs.RawInputDialog(onDismiss = { importDialog = false }, onConfirm = { newKey ->
importDialog = false importDialog = false
runCatching { runCatching {
val key = Base64.decode(newKey) val key = Base64.decode(newKey)
if (key.size != 32) { if (key.size != 32) {
context.longToast("Invalid key size (must be 32 bytes)") context.longToast("Invalid key size (must be 32 bytes)")
return@runCatching return@runCatching
}
context.e2eeImplementation.storeSharedSecretKey(friend.userId, key)
context.longToast("Successfully imported key")
hasSecretKey = true
}.onFailure {
context.longToast("Failed to import key: ${it.message}")
context.log.error("Failed to import key", it)
} }
})
context.e2eeImplementation.storeSharedSecretKey(friend.userId, key) }
context.longToast("Successfully imported key")
hasSecretKey = true
}.onFailure {
context.longToast("Failed to import key: ${it.message}")
context.log.error("Failed to import key", it)
}
})
} }
}
ContentCard { ContentCard {
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(10.dp) horizontalArrangement = Arrangement.spacedBy(10.dp)
) { ) {
if (hasSecretKey) { if (hasSecretKey) {
OutlinedButton(onClick = { OutlinedButton(onClick = {
val secretKey = Base64.encode(context.e2eeImplementation.getSharedSecretKey(friend.userId) ?: return@OutlinedButton) val secretKey = Base64.encode(context.e2eeImplementation.getSharedSecretKey(friend.userId) ?: return@OutlinedButton)
//TODO: fingerprint auth //TODO: fingerprint auth
context.activity!!.startActivity(Intent.createChooser(Intent().apply { context.activity!!.startActivity(Intent.createChooser(Intent().apply {
action = Intent.ACTION_SEND action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_TEXT, secretKey) putExtra(Intent.EXTRA_TEXT, secretKey)
type = "text/plain" type = "text/plain"
}, "").apply { }, "").apply {
putExtra(Intent.EXTRA_INITIAL_INTENTS, arrayOf( putExtra(Intent.EXTRA_INITIAL_INTENTS, arrayOf(
Intent().apply { Intent().apply {
putExtra(Intent.EXTRA_TEXT, secretKey) putExtra(Intent.EXTRA_TEXT, secretKey)
putExtra(Intent.EXTRA_SUBJECT, secretKey) putExtra(Intent.EXTRA_SUBJECT, secretKey)
}) })
)
})
}) {
Text(
text = "Export Base64",
maxLines = 1
) )
}) }
}) { }
OutlinedButton(onClick = { importDialog = true }) {
Text( Text(
text = "Export Base64", text = "Import Base64",
maxLines = 1 maxLines = 1
) )
} }
} }
OutlinedButton(onClick = { importDialog = true }) {
Text(
text = "Import Base64",
maxLines = 1
)
}
} }
} }
} }

View File

@ -363,7 +363,7 @@ class ExportMemories : AbstractAction() {
val database = runCatching { val database = runCatching {
SQLiteDatabase.openDatabase( SQLiteDatabase.openDatabase(
context.androidContext.getDatabasePath("memories.db"), context.androidContext.getDatabasePath("memories.db"),
OpenParams.Builder().build(), OpenParams.Builder().setOpenFlags(SQLiteDatabase.OPEN_READONLY).build()
) )
}.getOrNull() }.getOrNull()

View File

@ -19,17 +19,45 @@ import me.rhunk.snapenhance.core.ModContext
import me.rhunk.snapenhance.core.manager.Manager import me.rhunk.snapenhance.core.manager.Manager
enum class DatabaseType(
val fileName: String
) {
MAIN("main.db"),
ARROYO("arroyo.db")
}
class DatabaseAccess( class DatabaseAccess(
private val context: ModContext private val context: ModContext
) : Manager { ) : Manager {
companion object { private val openedDatabases = mutableMapOf<DatabaseType, SQLiteDatabase>()
val DATABASES = mapOf(
"main" to "main.db", private fun useDatabase(database: DatabaseType, writeMode: Boolean = false): SQLiteDatabase? {
"arroyo" to "arroyo.db" if (openedDatabases.containsKey(database) && openedDatabases[database]?.isOpen == true) {
) return openedDatabases[database]
}
val dbPath = context.androidContext.getDatabasePath(database.fileName)
if (!dbPath.exists()) return null
return runCatching {
SQLiteDatabase.openDatabase(
dbPath,
OpenParams.Builder()
.setOpenFlags(
if (writeMode) SQLiteDatabase.OPEN_READWRITE or SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING
else SQLiteDatabase.OPEN_READONLY
)
.setErrorHandler {
context.androidContext.deleteDatabase(dbPath.absolutePath)
context.softRestartApp()
}.build()
)
}.onFailure {
context.log.error("Failed to open database ${database.fileName}!", it)
}.getOrNull()?.also {
openedDatabases[database] = it
}
} }
private val mainDb by lazy { openLocalDatabase("main") }
private val arroyoDb by lazy { openLocalDatabase("arroyo") }
private inline fun <T> SQLiteDatabase.performOperation(crossinline query: SQLiteDatabase.() -> T?): T? { private inline fun <T> SQLiteDatabase.performOperation(crossinline query: SQLiteDatabase.() -> T?): T? {
return runCatching { return runCatching {
@ -56,7 +84,7 @@ class DatabaseAccess(
} }
private val dmOtherParticipantCache by lazy { private val dmOtherParticipantCache by lazy {
(arroyoDb?.performOperation { (useDatabase(DatabaseType.ARROYO)?.performOperation {
safeRawQuery( safeRawQuery(
"SELECT client_conversation_id, conversation_type, user_id FROM user_conversation WHERE user_id != ?", "SELECT client_conversation_id, conversation_type, user_id FROM user_conversation WHERE user_id != ?",
arrayOf(myUserId) arrayOf(myUserId)
@ -79,49 +107,27 @@ class DatabaseAccess(
} ?: emptyMap()).toMutableMap() } ?: emptyMap()).toMutableMap()
} }
private fun openLocalDatabase(databaseName: String, writeMode: Boolean = false): SQLiteDatabase? { fun hasMain(): Boolean = useDatabase(DatabaseType.MAIN)?.isOpen == true
val dbPath = context.androidContext.getDatabasePath(DATABASES[databaseName]!!) fun hasArroyo(): Boolean = useDatabase(DatabaseType.ARROYO)?.isOpen == true
if (!dbPath.exists()) return null
return runCatching {
SQLiteDatabase.openDatabase(
dbPath,
OpenParams.Builder()
.setOpenFlags(
if (writeMode) SQLiteDatabase.OPEN_READWRITE or SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING
else SQLiteDatabase.OPEN_READONLY
)
.setErrorHandler {
context.androidContext.deleteDatabase(dbPath.absolutePath)
context.softRestartApp()
}.build()
)
}.onFailure {
context.log.error("Failed to open database $databaseName!", it)
}.getOrNull()
}
fun hasMain(): Boolean = mainDb?.isOpen == true
fun hasArroyo(): Boolean = arroyoDb?.isOpen == true
override fun init() { override fun init() {
// perform integrity check on databases // perform integrity check on databases
DATABASES.forEach { (name, fileName) -> DatabaseType.entries.forEach { type ->
openLocalDatabase(name, writeMode = true)?.apply { useDatabase(type, writeMode = true)?.apply {
rawQuery("PRAGMA integrity_check", null).use { query -> rawQuery("PRAGMA integrity_check", null).use { query ->
if (!query.moveToFirst() || query.getString(0).lowercase() != "ok") { if (!query.moveToFirst() || query.getString(0).lowercase() != "ok") {
context.log.error("Failed to perform integrity check on $fileName") context.log.error("Failed to perform integrity check on ${type.fileName}")
context.androidContext.deleteDatabase(fileName) context.androidContext.deleteDatabase(type.fileName)
return@apply return@apply
} }
context.log.verbose("database $fileName integrity check passed") context.log.verbose("database ${type.fileName} integrity check passed")
} }
}?.close() }?.close()
} }
} }
fun finalize() { fun finalize() {
mainDb?.close() openedDatabases.values.forEach { it.close() }
arroyoDb?.close()
context.log.verbose("Database closed") context.log.verbose("Database closed")
} }
@ -143,7 +149,7 @@ class DatabaseAccess(
} }
fun getFeedEntryByUserId(userId: String): FriendFeedEntry? { fun getFeedEntryByUserId(userId: String): FriendFeedEntry? {
return mainDb?.performOperation { return useDatabase(DatabaseType.MAIN)?.performOperation {
readDatabaseObject( readDatabaseObject(
FriendFeedEntry(), FriendFeedEntry(),
"FriendsFeedView", "FriendsFeedView",
@ -155,7 +161,7 @@ class DatabaseAccess(
val myUserId by lazy { val myUserId by lazy {
context.androidContext.getSharedPreferences("user_session_shared_pref", 0).getString("key_user_id", null) ?: context.androidContext.getSharedPreferences("user_session_shared_pref", 0).getString("key_user_id", null) ?:
arroyoDb?.performOperation { useDatabase(DatabaseType.ARROYO)?.performOperation {
safeRawQuery(buildString { safeRawQuery(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 ->
@ -168,7 +174,7 @@ class DatabaseAccess(
} }
fun getFeedEntryByConversationId(conversationId: String): FriendFeedEntry? { fun getFeedEntryByConversationId(conversationId: String): FriendFeedEntry? {
return mainDb?.performOperation { return useDatabase(DatabaseType.MAIN)?.performOperation {
readDatabaseObject( readDatabaseObject(
FriendFeedEntry(), FriendFeedEntry(),
"FriendsFeedView", "FriendsFeedView",
@ -179,7 +185,7 @@ class DatabaseAccess(
} }
fun getFriendInfo(userId: String): FriendInfo? { fun getFriendInfo(userId: String): FriendInfo? {
return mainDb?.performOperation { return useDatabase(DatabaseType.MAIN)?.performOperation {
readDatabaseObject( readDatabaseObject(
FriendInfo(), FriendInfo(),
"FriendWithUsername", "FriendWithUsername",
@ -190,7 +196,7 @@ class DatabaseAccess(
} }
fun getAllFriends(): List<FriendInfo> { fun getAllFriends(): List<FriendInfo> {
return mainDb?.performOperation { return useDatabase(DatabaseType.MAIN)?.performOperation {
safeRawQuery( safeRawQuery(
"SELECT * FROM FriendWithUsername", "SELECT * FROM FriendWithUsername",
null null
@ -209,7 +215,7 @@ class DatabaseAccess(
} }
fun getFeedEntries(limit: Int): List<FriendFeedEntry> { fun getFeedEntries(limit: Int): List<FriendFeedEntry> {
return mainDb?.performOperation { return useDatabase(DatabaseType.MAIN)?.performOperation {
safeRawQuery( safeRawQuery(
"SELECT * FROM FriendsFeedView ORDER BY _id LIMIT ?", "SELECT * FROM FriendsFeedView ORDER BY _id LIMIT ?",
arrayOf(limit.toString()) arrayOf(limit.toString())
@ -228,7 +234,7 @@ class DatabaseAccess(
} }
fun getConversationMessageFromId(clientMessageId: Long): ConversationMessage? { fun getConversationMessageFromId(clientMessageId: Long): ConversationMessage? {
return arroyoDb?.performOperation { return useDatabase(DatabaseType.ARROYO)?.performOperation {
readDatabaseObject( readDatabaseObject(
ConversationMessage(), ConversationMessage(),
"conversation_message", "conversation_message",
@ -239,7 +245,7 @@ class DatabaseAccess(
} }
fun getConversationType(conversationId: String): Int? { fun getConversationType(conversationId: String): Int? {
return arroyoDb?.performOperation { return useDatabase(DatabaseType.ARROYO)?.performOperation {
safeRawQuery( safeRawQuery(
"SELECT conversation_type FROM user_conversation WHERE client_conversation_id = ?", "SELECT conversation_type FROM user_conversation WHERE client_conversation_id = ?",
arrayOf(conversationId) arrayOf(conversationId)
@ -253,7 +259,7 @@ class DatabaseAccess(
} }
fun getConversationLinkFromUserId(userId: String): UserConversationLink? { fun getConversationLinkFromUserId(userId: String): UserConversationLink? {
return arroyoDb?.performOperation { return useDatabase(DatabaseType.ARROYO)?.performOperation {
readDatabaseObject( readDatabaseObject(
UserConversationLink(), UserConversationLink(),
"user_conversation", "user_conversation",
@ -265,7 +271,7 @@ class DatabaseAccess(
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 arroyoDb?.performOperation { return useDatabase(DatabaseType.ARROYO)?.performOperation {
safeRawQuery( safeRawQuery(
"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)
@ -284,14 +290,14 @@ class DatabaseAccess(
fun getStoryEntryFromId(storyId: String): StoryEntry? { fun getStoryEntryFromId(storyId: String): StoryEntry? {
return mainDb?.performOperation { return useDatabase(DatabaseType.MAIN)?.performOperation {
readDatabaseObject(StoryEntry(), "Story", "storyId = ?", arrayOf(storyId)) readDatabaseObject(StoryEntry(), "Story", "storyId = ?", arrayOf(storyId))
} }
} }
fun getConversationParticipants(conversationId: String): List<String>? { fun getConversationParticipants(conversationId: String): List<String>? {
if (dmOtherParticipantCache[conversationId] != null) return dmOtherParticipantCache[conversationId]?.let { listOf(myUserId, it) } if (dmOtherParticipantCache[conversationId] != null) return dmOtherParticipantCache[conversationId]?.let { listOf(myUserId, it) }
return arroyoDb?.performOperation { return useDatabase(DatabaseType.ARROYO)?.performOperation {
safeRawQuery( safeRawQuery(
"SELECT user_id, conversation_type FROM user_conversation WHERE client_conversation_id = ?", "SELECT user_id, conversation_type FROM user_conversation WHERE client_conversation_id = ?",
arrayOf(conversationId) arrayOf(conversationId)
@ -321,7 +327,7 @@ class DatabaseAccess(
conversationId: String, conversationId: String,
limit: Int limit: Int
): List<ConversationMessage>? { ): List<ConversationMessage>? {
return arroyoDb?.performOperation { return useDatabase(DatabaseType.ARROYO)?.performOperation {
safeRawQuery( safeRawQuery(
"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())
@ -341,7 +347,7 @@ class DatabaseAccess(
} }
fun getAddSource(userId: String): String? { fun getAddSource(userId: String): String? {
return mainDb?.performOperation { return useDatabase(DatabaseType.MAIN)?.performOperation {
rawQuery( rawQuery(
"SELECT addSource FROM FriendWhoAddedMe WHERE userId = ?", "SELECT addSource FROM FriendWhoAddedMe WHERE userId = ?",
arrayOf(userId) arrayOf(userId)
@ -355,7 +361,7 @@ class DatabaseAccess(
} }
fun markFriendStoriesAsSeen(userId: String) { fun markFriendStoriesAsSeen(userId: String) {
openLocalDatabase("main", writeMode = true)?.apply { useDatabase(DatabaseType.MAIN, writeMode = true)?.apply {
performOperation { performOperation {
execSQL("UPDATE StorySnap SET viewed = 1 WHERE userId = ?", arrayOf(userId)) execSQL("UPDATE StorySnap SET viewed = 1 WHERE userId = ?", arrayOf(userId))
} }
@ -364,7 +370,7 @@ class DatabaseAccess(
} }
fun getAccessTokens(userId: String): Map<String, String>? { fun getAccessTokens(userId: String): Map<String, String>? {
return mainDb?.performOperation { return useDatabase(DatabaseType.MAIN)?.performOperation {
rawQuery( rawQuery(
"SELECT accessTokensPb FROM SnapToken WHERE userId = ?", "SELECT accessTokensPb FROM SnapToken WHERE userId = ?",
arrayOf(userId) arrayOf(userId)