commit a90f4875a73a7ae1a985be183bf51f3bfd4a3d75
parent d1c4b4febeb8300fa7fbf6adb762134ba415a8f5
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date: Thu, 4 Jan 2024 23:09:27 +0100
fix(database): db cache
Diffstat:
3 files changed, 116 insertions(+), 108 deletions(-)
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/sections/social/ScopeContent.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/sections/social/ScopeContent.kt
@@ -261,69 +261,71 @@ class ScopeContent(
Spacer(modifier = Modifier.height(16.dp))
// e2ee section
- SectionTitle(translation["e2ee_title"])
- var hasSecretKey by remember { mutableStateOf(context.e2eeImplementation.friendKeyExists(friend.userId))}
- var importDialog by remember { mutableStateOf(false) }
+ if (context.config.root.experimental.e2eEncryption.globalState == true) {
+ SectionTitle(translation["e2ee_title"])
+ var hasSecretKey by remember { mutableStateOf(context.e2eeImplementation.friendKeyExists(friend.userId))}
+ var importDialog by remember { mutableStateOf(false) }
- if (importDialog) {
- Dialog(
- onDismissRequest = { importDialog = false }
- ) {
- dialogs.RawInputDialog(onDismiss = { importDialog = false }, onConfirm = { newKey ->
- importDialog = false
- runCatching {
- val key = Base64.decode(newKey)
- if (key.size != 32) {
- context.longToast("Invalid key size (must be 32 bytes)")
- return@runCatching
- }
+ if (importDialog) {
+ Dialog(
+ onDismissRequest = { importDialog = false }
+ ) {
+ dialogs.RawInputDialog(onDismiss = { importDialog = false }, onConfirm = { newKey ->
+ importDialog = false
+ runCatching {
+ val key = Base64.decode(newKey)
+ if (key.size != 32) {
+ context.longToast("Invalid key size (must be 32 bytes)")
+ 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 {
- Row(
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.spacedBy(10.dp)
- ) {
- if (hasSecretKey) {
- OutlinedButton(onClick = {
- val secretKey = Base64.encode(context.e2eeImplementation.getSharedSecretKey(friend.userId) ?: return@OutlinedButton)
- //TODO: fingerprint auth
- context.activity!!.startActivity(Intent.createChooser(Intent().apply {
- action = Intent.ACTION_SEND
- putExtra(Intent.EXTRA_TEXT, secretKey)
- type = "text/plain"
- }, "").apply {
- putExtra(Intent.EXTRA_INITIAL_INTENTS, arrayOf(
- Intent().apply {
- putExtra(Intent.EXTRA_TEXT, secretKey)
- putExtra(Intent.EXTRA_SUBJECT, secretKey)
- })
+ ContentCard {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(10.dp)
+ ) {
+ if (hasSecretKey) {
+ OutlinedButton(onClick = {
+ val secretKey = Base64.encode(context.e2eeImplementation.getSharedSecretKey(friend.userId) ?: return@OutlinedButton)
+ //TODO: fingerprint auth
+ context.activity!!.startActivity(Intent.createChooser(Intent().apply {
+ action = Intent.ACTION_SEND
+ putExtra(Intent.EXTRA_TEXT, secretKey)
+ type = "text/plain"
+ }, "").apply {
+ putExtra(Intent.EXTRA_INITIAL_INTENTS, arrayOf(
+ Intent().apply {
+ putExtra(Intent.EXTRA_TEXT, secretKey)
+ putExtra(Intent.EXTRA_SUBJECT, secretKey)
+ })
+ )
+ })
+ }) {
+ Text(
+ text = "Export Base64",
+ maxLines = 1
)
- })
- }) {
+ }
+ }
+
+ OutlinedButton(onClick = { importDialog = true }) {
Text(
- text = "Export Base64",
+ text = "Import Base64",
maxLines = 1
)
}
}
-
- OutlinedButton(onClick = { importDialog = true }) {
- Text(
- text = "Import Base64",
- maxLines = 1
- )
- }
}
}
}
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/action/impl/ExportMemories.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/action/impl/ExportMemories.kt
@@ -363,7 +363,7 @@ class ExportMemories : AbstractAction() {
val database = runCatching {
SQLiteDatabase.openDatabase(
context.androidContext.getDatabasePath("memories.db"),
- OpenParams.Builder().build(),
+ OpenParams.Builder().setOpenFlags(SQLiteDatabase.OPEN_READONLY).build()
)
}.getOrNull()
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
@@ -19,17 +19,45 @@ import me.rhunk.snapenhance.core.ModContext
import me.rhunk.snapenhance.core.manager.Manager
+enum class DatabaseType(
+ val fileName: String
+) {
+ MAIN("main.db"),
+ ARROYO("arroyo.db")
+}
+
class DatabaseAccess(
private val context: ModContext
) : Manager {
- companion object {
- val DATABASES = mapOf(
- "main" to "main.db",
- "arroyo" to "arroyo.db"
- )
+ private val openedDatabases = mutableMapOf<DatabaseType, SQLiteDatabase>()
+
+ private fun useDatabase(database: DatabaseType, writeMode: Boolean = false): SQLiteDatabase? {
+ 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? {
return runCatching {
@@ -56,7 +84,7 @@ class DatabaseAccess(
}
private val dmOtherParticipantCache by lazy {
- (arroyoDb?.performOperation {
+ (useDatabase(DatabaseType.ARROYO)?.performOperation {
safeRawQuery(
"SELECT client_conversation_id, conversation_type, user_id FROM user_conversation WHERE user_id != ?",
arrayOf(myUserId)
@@ -79,49 +107,27 @@ class DatabaseAccess(
} ?: emptyMap()).toMutableMap()
}
- private fun openLocalDatabase(databaseName: String, writeMode: Boolean = false): SQLiteDatabase? {
- val dbPath = context.androidContext.getDatabasePath(DATABASES[databaseName]!!)
- 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
+ fun hasMain(): Boolean = useDatabase(DatabaseType.MAIN)?.isOpen == true
+ fun hasArroyo(): Boolean = useDatabase(DatabaseType.ARROYO)?.isOpen == true
override fun init() {
// perform integrity check on databases
- DATABASES.forEach { (name, fileName) ->
- openLocalDatabase(name, writeMode = true)?.apply {
+ DatabaseType.entries.forEach { type ->
+ useDatabase(type, writeMode = true)?.apply {
rawQuery("PRAGMA integrity_check", null).use { query ->
if (!query.moveToFirst() || query.getString(0).lowercase() != "ok") {
- context.log.error("Failed to perform integrity check on $fileName")
- context.androidContext.deleteDatabase(fileName)
+ context.log.error("Failed to perform integrity check on ${type.fileName}")
+ context.androidContext.deleteDatabase(type.fileName)
return@apply
}
- context.log.verbose("database $fileName integrity check passed")
+ context.log.verbose("database ${type.fileName} integrity check passed")
}
}?.close()
}
}
fun finalize() {
- mainDb?.close()
- arroyoDb?.close()
+ openedDatabases.values.forEach { it.close() }
context.log.verbose("Database closed")
}
@@ -143,7 +149,7 @@ class DatabaseAccess(
}
fun getFeedEntryByUserId(userId: String): FriendFeedEntry? {
- return mainDb?.performOperation {
+ return useDatabase(DatabaseType.MAIN)?.performOperation {
readDatabaseObject(
FriendFeedEntry(),
"FriendsFeedView",
@@ -155,7 +161,7 @@ class DatabaseAccess(
val myUserId by lazy {
context.androidContext.getSharedPreferences("user_session_shared_pref", 0).getString("key_user_id", null) ?:
- arroyoDb?.performOperation {
+ useDatabase(DatabaseType.ARROYO)?.performOperation {
safeRawQuery(buildString {
append("SELECT value FROM required_values WHERE key = 'USERID'")
}, null)?.use { query ->
@@ -168,7 +174,7 @@ class DatabaseAccess(
}
fun getFeedEntryByConversationId(conversationId: String): FriendFeedEntry? {
- return mainDb?.performOperation {
+ return useDatabase(DatabaseType.MAIN)?.performOperation {
readDatabaseObject(
FriendFeedEntry(),
"FriendsFeedView",
@@ -179,7 +185,7 @@ class DatabaseAccess(
}
fun getFriendInfo(userId: String): FriendInfo? {
- return mainDb?.performOperation {
+ return useDatabase(DatabaseType.MAIN)?.performOperation {
readDatabaseObject(
FriendInfo(),
"FriendWithUsername",
@@ -190,7 +196,7 @@ class DatabaseAccess(
}
fun getAllFriends(): List<FriendInfo> {
- return mainDb?.performOperation {
+ return useDatabase(DatabaseType.MAIN)?.performOperation {
safeRawQuery(
"SELECT * FROM FriendWithUsername",
null
@@ -209,7 +215,7 @@ class DatabaseAccess(
}
fun getFeedEntries(limit: Int): List<FriendFeedEntry> {
- return mainDb?.performOperation {
+ return useDatabase(DatabaseType.MAIN)?.performOperation {
safeRawQuery(
"SELECT * FROM FriendsFeedView ORDER BY _id LIMIT ?",
arrayOf(limit.toString())
@@ -228,7 +234,7 @@ class DatabaseAccess(
}
fun getConversationMessageFromId(clientMessageId: Long): ConversationMessage? {
- return arroyoDb?.performOperation {
+ return useDatabase(DatabaseType.ARROYO)?.performOperation {
readDatabaseObject(
ConversationMessage(),
"conversation_message",
@@ -239,7 +245,7 @@ class DatabaseAccess(
}
fun getConversationType(conversationId: String): Int? {
- return arroyoDb?.performOperation {
+ return useDatabase(DatabaseType.ARROYO)?.performOperation {
safeRawQuery(
"SELECT conversation_type FROM user_conversation WHERE client_conversation_id = ?",
arrayOf(conversationId)
@@ -253,7 +259,7 @@ class DatabaseAccess(
}
fun getConversationLinkFromUserId(userId: String): UserConversationLink? {
- return arroyoDb?.performOperation {
+ return useDatabase(DatabaseType.ARROYO)?.performOperation {
readDatabaseObject(
UserConversationLink(),
"user_conversation",
@@ -265,7 +271,7 @@ class DatabaseAccess(
fun getDMOtherParticipant(conversationId: String): String? {
if (dmOtherParticipantCache.containsKey(conversationId)) return dmOtherParticipantCache[conversationId]
- return arroyoDb?.performOperation {
+ return useDatabase(DatabaseType.ARROYO)?.performOperation {
safeRawQuery(
"SELECT user_id FROM user_conversation WHERE client_conversation_id = ? AND conversation_type = 0",
arrayOf(conversationId)
@@ -284,14 +290,14 @@ class DatabaseAccess(
fun getStoryEntryFromId(storyId: String): StoryEntry? {
- return mainDb?.performOperation {
+ return useDatabase(DatabaseType.MAIN)?.performOperation {
readDatabaseObject(StoryEntry(), "Story", "storyId = ?", arrayOf(storyId))
}
}
fun getConversationParticipants(conversationId: String): List<String>? {
if (dmOtherParticipantCache[conversationId] != null) return dmOtherParticipantCache[conversationId]?.let { listOf(myUserId, it) }
- return arroyoDb?.performOperation {
+ return useDatabase(DatabaseType.ARROYO)?.performOperation {
safeRawQuery(
"SELECT user_id, conversation_type FROM user_conversation WHERE client_conversation_id = ?",
arrayOf(conversationId)
@@ -321,7 +327,7 @@ class DatabaseAccess(
conversationId: String,
limit: Int
): List<ConversationMessage>? {
- return arroyoDb?.performOperation {
+ return useDatabase(DatabaseType.ARROYO)?.performOperation {
safeRawQuery(
"SELECT * FROM conversation_message WHERE client_conversation_id = ? ORDER BY creation_timestamp DESC LIMIT ?",
arrayOf(conversationId, limit.toString())
@@ -341,7 +347,7 @@ class DatabaseAccess(
}
fun getAddSource(userId: String): String? {
- return mainDb?.performOperation {
+ return useDatabase(DatabaseType.MAIN)?.performOperation {
rawQuery(
"SELECT addSource FROM FriendWhoAddedMe WHERE userId = ?",
arrayOf(userId)
@@ -355,7 +361,7 @@ class DatabaseAccess(
}
fun markFriendStoriesAsSeen(userId: String) {
- openLocalDatabase("main", writeMode = true)?.apply {
+ useDatabase(DatabaseType.MAIN, writeMode = true)?.apply {
performOperation {
execSQL("UPDATE StorySnap SET viewed = 1 WHERE userId = ?", arrayOf(userId))
}
@@ -364,7 +370,7 @@ class DatabaseAccess(
}
fun getAccessTokens(userId: String): Map<String, String>? {
- return mainDb?.performOperation {
+ return useDatabase(DatabaseType.MAIN)?.performOperation {
rawQuery(
"SELECT accessTokensPb FROM SnapToken WHERE userId = ?",
arrayOf(userId)