Skip to content

Commit

Permalink
perf(core): database access
Browse files Browse the repository at this point in the history
  • Loading branch information
rhunk committed Nov 11, 2023
1 parent da8561c commit 9618392
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 106 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@ package me.rhunk.snapenhance.core.database

import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteDatabase.OpenParams
import android.database.sqlite.SQLiteDatabaseCorruptException
import me.rhunk.snapenhance.common.database.DatabaseObject
import me.rhunk.snapenhance.common.database.impl.ConversationMessage
import me.rhunk.snapenhance.common.database.impl.FriendFeedEntry
import me.rhunk.snapenhance.common.database.impl.FriendInfo
import me.rhunk.snapenhance.common.database.impl.StoryEntry
import me.rhunk.snapenhance.common.database.impl.UserConversationLink
import me.rhunk.snapenhance.common.util.ktx.getIntOrNull
import me.rhunk.snapenhance.common.util.ktx.getInteger
import me.rhunk.snapenhance.common.util.ktx.getStringOrNull
import me.rhunk.snapenhance.core.ModContext
import me.rhunk.snapenhance.core.manager.Manager
import me.rhunk.snapenhance.core.ui.ViewAppearanceHelper
import java.io.File


class DatabaseAccess(
Expand All @@ -25,59 +25,46 @@ class DatabaseAccess(

private inline fun <T> SQLiteDatabase.performOperation(crossinline query: SQLiteDatabase.() -> T?): T? {
return runCatching {
query()
synchronized(this) {
query()
}
}.onFailure {
context.log.error("Database operation failed", it)
}.getOrNull()
}

private var hasShownDatabaseError = false

private fun showDatabaseError(databasePath: String, throwable: Throwable) {
if (hasShownDatabaseError) return
hasShownDatabaseError = true
context.runOnUiThread {
if (context.mainActivity == null) return@runOnUiThread
ViewAppearanceHelper.newAlertDialogBuilder(context.mainActivity)
.setTitle("SnapEnhance")
.setMessage("Failed to query $databasePath database!\n\n${throwable.localizedMessage}\n\nRestarting Snapchat may fix this issue. If the issue persists, try to clean the app data and cache.")
.setPositiveButton("Restart Snapchat") { _, _ ->
File(databasePath).takeIf { it.exists() }?.delete()
context.softRestartApp()
}
.setNegativeButton("Dismiss") { dialog, _ ->
dialog.dismiss()
}.show()
}
}

private fun SQLiteDatabase.safeRawQuery(query: String, args: Array<String>? = null): Cursor? {
return runCatching {
rawQuery(query, args)
}.onFailure {
if (it !is SQLiteDatabaseCorruptException) {
context.log.error("Failed to execute query $query", it)
showDatabaseError(this.path, it)
return@onFailure
}
context.log.warn("Database ${this.path} is corrupted!")
context.longToast("Database ${this.path} is corrupted! Restarting ...")
context.androidContext.deleteDatabase(this.path)
showDatabaseError(this.path, it)
context.crash("Database ${this.path} is corrupted!", it)
}.getOrNull()
}

private val dmOtherParticipantCache by lazy {
(arroyoDb?.performOperation {
safeRawQuery(
"SELECT client_conversation_id, user_id FROM user_conversation WHERE conversation_type = 0 AND user_id != ?",
"SELECT client_conversation_id, conversation_type, user_id FROM user_conversation WHERE user_id != ?",
arrayOf(myUserId)
)?.use { query ->
val participants = mutableMapOf<String, String?>()
if (!query.moveToFirst()) {
return@performOperation null
}
do {
participants[query.getStringOrNull("client_conversation_id")!!] = query.getStringOrNull("user_id")!!
val conversationId = query.getStringOrNull("client_conversation_id") ?: continue
val userId = query.getStringOrNull("user_id") ?: continue
participants[conversationId] = when (query.getIntOrNull("conversation_type")) {
0 -> userId
else -> null
}
participants[userId] = null
} while (query.moveToNext())
participants
}
Expand All @@ -89,13 +76,16 @@ class DatabaseAccess(
if (!dbPath.exists()) return null
return runCatching {
SQLiteDatabase.openDatabase(
dbPath.absolutePath,
null,
SQLiteDatabase.OPEN_READONLY or SQLiteDatabase.NO_LOCALIZED_COLLATORS
dbPath,
OpenParams.Builder()
.setOpenFlags(SQLiteDatabase.OPEN_READONLY)
.setErrorHandler {
context.androidContext.deleteDatabase(dbPath.absolutePath)
context.softRestartApp()
}.build()
)
}.onFailure {
context.log.error("Failed to open database $fileName!", it)
showDatabaseError(dbPath.absolutePath, it)
}.getOrNull()
}

Expand Down Expand Up @@ -137,6 +127,7 @@ class DatabaseAccess(
}

val myUserId by lazy {
context.androidContext.getSharedPreferences("user_session_shared_pref", 0).getString("key_user_id", null) ?:
arroyoDb?.performOperation {
safeRawQuery(buildString {
append("SELECT value FROM required_values WHERE key = 'USERID'")
Expand All @@ -146,7 +137,7 @@ class DatabaseAccess(
}
query.getStringOrNull("value")!!
}
} ?: context.androidContext.getSharedPreferences("user_session_shared_pref", 0).getString("key_user_id", null)!!
}!!
}

fun getFeedEntryByConversationId(conversationId: String): FriendFeedEntry? {
Expand Down Expand Up @@ -241,8 +232,8 @@ class DatabaseAccess(
participants.add(query.getStringOrNull("user_id")!!)
} while (query.moveToNext())
participants.firstOrNull { it != myUserId }
}
}.also { dmOtherParticipantCache[conversationId] = it }
}.also { dmOtherParticipantCache[conversationId] = it }
}
}


Expand All @@ -253,18 +244,28 @@ class DatabaseAccess(
}

fun getConversationParticipants(conversationId: String): List<String>? {
if (dmOtherParticipantCache[conversationId] != null) return dmOtherParticipantCache[conversationId]?.let { listOf(myUserId, it) }
return arroyoDb?.performOperation {
safeRawQuery(
"SELECT user_id FROM user_conversation WHERE client_conversation_id = ?",
"SELECT user_id, conversation_type FROM user_conversation WHERE client_conversation_id = ?",
arrayOf(conversationId)
)?.use {
if (!it.moveToFirst()) {
)?.use { cursor ->
if (!cursor.moveToFirst()) {
return@performOperation null
}
val participants = mutableListOf<String>()
var conversationType = -1
do {
participants.add(it.getStringOrNull("user_id")!!)
} while (it.moveToNext())
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
}
}
Expand Down
14 changes: 9 additions & 5 deletions core/src/main/kotlin/me/rhunk/snapenhance/core/event/EventBus.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ class EventBus(
private val subscribers = mutableMapOf<KClass<out Event>, MutableMap<Int, IListener<out Event>>>()

fun <T : Event> subscribe(event: KClass<T>, listener: IListener<T>, priority: Int? = null) {
if (!subscribers.containsKey(event)) {
subscribers[event] = sortedMapOf()
synchronized(subscribers) {
if (!subscribers.containsKey(event)) {
subscribers[event] = sortedMapOf()
}
val lastSubscriber = subscribers[event]?.keys?.lastOrNull() ?: 0
subscribers[event]?.put(priority ?: (lastSubscriber + 1), listener)
}
val lastSubscriber = subscribers[event]?.keys?.lastOrNull() ?: 0
subscribers[event]?.put(priority ?: (lastSubscriber + 1), listener)
}

inline fun <T : Event> subscribe(event: KClass<T>, priority: Int? = null, crossinline listener: (T) -> Unit) = subscribe(event, { true }, priority, listener)
Expand All @@ -43,7 +45,9 @@ class EventBus(
}

fun <T : Event> unsubscribe(event: KClass<T>, listener: IListener<T>) {
subscribers[event]?.values?.remove(listener)
synchronized(subscribers) {
subscribers[event]?.values?.remove(listener)
}
}

fun <T : Event> post(event: T, afterBlock: T.() -> Unit = {}): T? {
Expand Down
Loading

0 comments on commit 9618392

Please sign in to comment.