Skip to content

Commit

Permalink
refactor: room
Browse files Browse the repository at this point in the history
  • Loading branch information
rhunk committed May 11, 2024
1 parent 23f16b8 commit cbbf841
Show file tree
Hide file tree
Showing 21 changed files with 433 additions and 404 deletions.
6 changes: 6 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import java.io.ByteArrayOutputStream
plugins {
alias(libs.plugins.androidApplication)
alias(libs.plugins.kotlinAndroid)
alias(libs.plugins.ksp)
id("kotlin-parcelize")
}

Expand Down Expand Up @@ -151,6 +152,11 @@ dependencies {
fullImplementation(libs.coil.video)
fullImplementation(libs.colorpicker.compose)
fullImplementation(libs.androidx.ui.tooling.preview)

implementation(libs.androidx.room.runtime)
annotationProcessor(libs.androidx.room.compiler)
ksp(libs.androidx.room.compiler)

properties["debug_flavor"]?.let {
debugImplementation(libs.androidx.ui.tooling)
}
Expand Down
8 changes: 8 additions & 0 deletions app/src/main/kotlin/me/rhunk/snapenhance/RemoteSideContext.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.core.app.CoreComponentFactory
import androidx.documentfile.provider.DocumentFile
import androidx.room.Room
import coil.ImageLoader
import coil.decode.VideoFrameDecoder
import coil.disk.DiskCache
Expand All @@ -32,6 +33,7 @@ import me.rhunk.snapenhance.e2ee.E2EEImplementation
import me.rhunk.snapenhance.messaging.ModDatabase
import me.rhunk.snapenhance.messaging.StreaksReminder
import me.rhunk.snapenhance.scripting.RemoteScriptManager
import me.rhunk.snapenhance.storage.AppDatabase
import me.rhunk.snapenhance.task.TaskManager
import me.rhunk.snapenhance.ui.manager.MainActivity
import me.rhunk.snapenhance.ui.manager.data.InstallationSummary
Expand Down Expand Up @@ -65,6 +67,7 @@ class RemoteSideContext(
val translation = LocaleWrapper()
val mappings = MappingsWrapper()
val taskManager = TaskManager(this)
@Deprecated("Use RemoteSideContext.database instead")
val modDatabase = ModDatabase(this)
val streaksReminder = StreaksReminder(this)
val log = LogManager(this)
Expand All @@ -74,6 +77,7 @@ class RemoteSideContext(
val messageLogger by lazy { LoggerWrapper(androidContext.getDatabasePath(BridgeFileType.MESSAGE_LOGGER_DATABASE.fileName)) }
val tracker = RemoteTracker(this)
val accountStorage = RemoteAccountStorage(this)
lateinit var database: AppDatabase

//used to load bitmoji selfies and download previews
val imageLoader by lazy {
Expand Down Expand Up @@ -114,6 +118,10 @@ class RemoteSideContext(
}
}
launch {
database = Room
.databaseBuilder(androidContext, AppDatabase::class.java, "snapenhance")
.fallbackToDestructiveMigration()
.build()
modDatabase.init()
streaksReminder.init()
}
Expand Down
48 changes: 26 additions & 22 deletions app/src/main/kotlin/me/rhunk/snapenhance/bridge/BridgeService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ import me.rhunk.snapenhance.bridge.snapclient.MessagingBridge
import me.rhunk.snapenhance.common.bridge.types.BridgeFileType
import me.rhunk.snapenhance.common.bridge.types.FileActionType
import me.rhunk.snapenhance.common.bridge.wrapper.LocaleWrapper
import me.rhunk.snapenhance.common.data.MessagingFriendInfo
import me.rhunk.snapenhance.common.data.MessagingGroupInfo
import me.rhunk.snapenhance.common.data.Friend
import me.rhunk.snapenhance.common.data.Group
import me.rhunk.snapenhance.common.data.SocialScope
import me.rhunk.snapenhance.common.data.SyncResult
import me.rhunk.snapenhance.common.logger.LogLevel
import me.rhunk.snapenhance.common.util.toParcelable
import me.rhunk.snapenhance.download.DownloadProcessor
import me.rhunk.snapenhance.download.FFMpegProcessor
import me.rhunk.snapenhance.storage.MessagingRule
import me.rhunk.snapenhance.task.Task
import me.rhunk.snapenhance.task.TaskType
import java.io.File
Expand Down Expand Up @@ -47,34 +49,33 @@ class BridgeService : Service() {

fun triggerScopeSync(scope: SocialScope, id: String, updateOnly: Boolean = false) {
runCatching {
val modDatabase = remoteSideContext.modDatabase
val syncedObject = when (scope) {
val database = remoteSideContext.database
val syncResult = toParcelable<SyncResult>(when (scope) {
SocialScope.FRIEND -> {
if (updateOnly && modDatabase.getFriendInfo(id) == null) return
if (updateOnly && database.friendDao().getByUserId(id) == null) return
syncCallback.syncFriend(id)
}
SocialScope.GROUP -> {
if (updateOnly && modDatabase.getGroupInfo(id) == null) return
if (updateOnly && database.groupDao().getByConversationId(id) == null) return
syncCallback.syncGroup(id)
}
else -> null
}

if (syncedObject == null) {
} ?: run {
remoteSideContext.log.warn("Failed to sync $scope $id")
return
}
})

when (scope) {
SocialScope.FRIEND -> {
toParcelable<MessagingFriendInfo>(syncedObject)?.let {
modDatabase.syncFriend(it)
database.friendDao().insertOrUpdate(syncResult?.friend ?: return)
if (syncResult.friendStreaks != null) {
database.friendStreaksDao().insertOrUpdate(syncResult.friendStreaks!!)
} else {
database.friendStreaksDao().delete(id)
}
}
SocialScope.GROUP -> {
toParcelable<MessagingGroupInfo>(syncedObject)?.let {
modDatabase.syncGroupInfo(it)
}
database.groupDao().insert(syncResult?.group ?: return)
}
}
}.onFailure {
Expand Down Expand Up @@ -194,24 +195,27 @@ class BridgeService : Service() {
}

override fun getRules(uuid: String): List<String> {
return remoteSideContext.modDatabase.getRules(uuid).map { it.key }
return remoteSideContext.database.messagingRuleDao().getAll(uuid).map { it.type }
}

override fun getRuleIds(type: String): MutableList<String> {
return remoteSideContext.modDatabase.getRuleIds(type)
return remoteSideContext.database.messagingRuleDao().getIds(type).toMutableList()
}

override fun setRule(uuid: String, rule: String, state: Boolean) {
remoteSideContext.modDatabase.setRule(uuid, rule, state)
remoteSideContext.database.messagingRuleDao().setState(
MessagingRule(type = rule, targetUuid = uuid),
state
)
}

override fun sync(callback: SyncCallback) {
syncCallback = callback
measureTimeMillis {
remoteSideContext.modDatabase.getFriends().map { it.userId } .forEach { friendId ->
remoteSideContext.database.friendDao().getAll().map { it.userId } .forEach { friendId ->
triggerScopeSync(SocialScope.FRIEND, friendId, true)
}
remoteSideContext.modDatabase.getGroups().map { it.conversationId }.forEach { groupId ->
remoteSideContext.database.groupDao().getAll().map { it.conversationId }.forEach { groupId ->
triggerScopeSync(SocialScope.GROUP, groupId, true)
}
}.also {
Expand All @@ -230,8 +234,8 @@ class BridgeService : Service() {
) {
remoteSideContext.log.verbose("Received ${groups.size} groups and ${friends.size} friends")
remoteSideContext.modDatabase.receiveMessagingDataCallback(
friends.mapNotNull { toParcelable<MessagingFriendInfo>(it) },
groups.mapNotNull { toParcelable<MessagingGroupInfo>(it) }
friends.mapNotNull { toParcelable<Friend>(it) },
groups.mapNotNull { toParcelable<Group>(it) }
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package me.rhunk.snapenhance.download

import android.annotation.SuppressLint
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.BitmapFactory
Expand Down
196 changes: 1 addition & 195 deletions app/src/main/kotlin/me/rhunk/snapenhance/messaging/ModDatabase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class ModDatabase(
private val executor = Executors.newSingleThreadExecutor()
private lateinit var database: SQLiteDatabase

var receiveMessagingDataCallback: (friends: List<MessagingFriendInfo>, groups: List<MessagingGroupInfo>) -> Unit = { _, _ -> }
var receiveMessagingDataCallback: (friends: List<Friend>, groups: List<Group>) -> Unit = { _, _ -> }

fun executeAsync(block: () -> Unit) {
executor.execute {
Expand All @@ -37,32 +37,6 @@ class ModDatabase(
fun init() {
database = context.androidContext.openOrCreateDatabase("main.db", 0, null)
SQLiteDatabaseHelper.createTablesFromSchema(database, mapOf(
"friends" to listOf(
"id INTEGER PRIMARY KEY AUTOINCREMENT",
"userId CHAR(36) UNIQUE",
"dmConversationId VARCHAR(36)",
"displayName VARCHAR",
"mutableUsername VARCHAR",
"bitmojiId VARCHAR",
"selfieId VARCHAR"
),
"groups" to listOf(
"id INTEGER PRIMARY KEY AUTOINCREMENT",
"conversationId CHAR(36) UNIQUE",
"name VARCHAR",
"participantsCount INTEGER"
),
"rules" to listOf(
"id INTEGER PRIMARY KEY AUTOINCREMENT",
"type VARCHAR",
"targetUuid VARCHAR"
),
"streaks" to listOf(
"id VARCHAR PRIMARY KEY",
"notify BOOLEAN",
"expirationTimestamp BIGINT",
"length INTEGER"
),
"scripts" to listOf(
"id INTEGER PRIMARY KEY AUTOINCREMENT",
"name VARCHAR NOT NULL",
Expand Down Expand Up @@ -97,174 +71,6 @@ class ModDatabase(
))
}

fun getGroups(): List<MessagingGroupInfo> {
return database.rawQuery("SELECT * FROM groups", null).use { cursor ->
val groups = mutableListOf<MessagingGroupInfo>()
while (cursor.moveToNext()) {
groups.add(MessagingGroupInfo.fromCursor(cursor))
}
groups
}
}

fun getFriends(descOrder: Boolean = false): List<MessagingFriendInfo> {
return database.rawQuery("SELECT * FROM friends LEFT OUTER JOIN streaks ON friends.userId = streaks.id ORDER BY id ${if (descOrder) "DESC" else "ASC"}", null).use { cursor ->
val friends = mutableListOf<MessagingFriendInfo>()
while (cursor.moveToNext()) {
runCatching {
friends.add(MessagingFriendInfo.fromCursor(cursor))
}.onFailure {
context.log.error("Failed to parse friend", it)
}
}
friends
}
}


fun syncGroupInfo(conversationInfo: MessagingGroupInfo) {
executeAsync {
try {
database.execSQL("INSERT OR REPLACE INTO groups (conversationId, name, participantsCount) VALUES (?, ?, ?)", arrayOf(
conversationInfo.conversationId,
conversationInfo.name,
conversationInfo.participantsCount
))
} catch (e: Exception) {
throw e
}
}
}

fun syncFriend(friend: MessagingFriendInfo) {
executeAsync {
try {
database.execSQL(
"INSERT OR REPLACE INTO friends (userId, dmConversationId, displayName, mutableUsername, bitmojiId, selfieId) VALUES (?, ?, ?, ?, ?, ?)",
arrayOf(
friend.userId,
friend.dmConversationId,
friend.displayName,
friend.mutableUsername,
friend.bitmojiId,
friend.selfieId
)
)
//sync streaks
friend.streaks?.takeIf { it.length > 0 }?.let {
val streaks = getFriendStreaks(friend.userId)

database.execSQL("INSERT OR REPLACE INTO streaks (id, notify, expirationTimestamp, length) VALUES (?, ?, ?, ?)", arrayOf(
friend.userId,
streaks?.notify ?: true,
it.expirationTimestamp,
it.length
))
} ?: database.execSQL("DELETE FROM streaks WHERE id = ?", arrayOf(friend.userId))
} catch (e: Exception) {
throw e
}
}
}

fun getRules(targetUuid: String): List<MessagingRuleType> {
return database.rawQuery("SELECT type FROM rules WHERE targetUuid = ?", arrayOf(
targetUuid
)).use { cursor ->
val rules = mutableListOf<MessagingRuleType>()
while (cursor.moveToNext()) {
runCatching {
rules.add(MessagingRuleType.getByName(cursor.getStringOrNull("type")!!) ?: return@runCatching)
}.onFailure {
context.log.error("Failed to parse rule", it)
}
}
rules
}
}

fun setRule(targetUuid: String, type: String, enabled: Boolean) {
executeAsync {
if (enabled) {
database.execSQL("INSERT OR REPLACE INTO rules (targetUuid, type) VALUES (?, ?)", arrayOf(
targetUuid,
type
))
} else {
database.execSQL("DELETE FROM rules WHERE targetUuid = ? AND type = ?", arrayOf(
targetUuid,
type
))
}
}
}

fun getFriendInfo(userId: String): MessagingFriendInfo? {
return database.rawQuery("SELECT * FROM friends LEFT OUTER JOIN streaks ON friends.userId = streaks.id WHERE userId = ?", arrayOf(userId)).use { cursor ->
if (!cursor.moveToFirst()) return@use null
MessagingFriendInfo.fromCursor(cursor)
}
}

fun findFriend(conversationId: String): MessagingFriendInfo? {
return database.rawQuery("SELECT * FROM friends WHERE dmConversationId = ?", arrayOf(conversationId)).use { cursor ->
if (!cursor.moveToFirst()) return@use null
MessagingFriendInfo.fromCursor(cursor)
}
}

fun deleteFriend(userId: String) {
executeAsync {
database.execSQL("DELETE FROM friends WHERE userId = ?", arrayOf(userId))
database.execSQL("DELETE FROM streaks WHERE id = ?", arrayOf(userId))
database.execSQL("DELETE FROM rules WHERE targetUuid = ?", arrayOf(userId))
}
}

fun deleteGroup(conversationId: String) {
executeAsync {
database.execSQL("DELETE FROM groups WHERE conversationId = ?", arrayOf(conversationId))
database.execSQL("DELETE FROM rules WHERE targetUuid = ?", arrayOf(conversationId))
}
}

fun getGroupInfo(conversationId: String): MessagingGroupInfo? {
return database.rawQuery("SELECT * FROM groups WHERE conversationId = ?", arrayOf(conversationId)).use { cursor ->
if (!cursor.moveToFirst()) return@use null
MessagingGroupInfo.fromCursor(cursor)
}
}

fun getFriendStreaks(userId: String): FriendStreaks? {
return database.rawQuery("SELECT * FROM streaks WHERE id = ?", arrayOf(userId)).use { cursor ->
if (!cursor.moveToFirst()) return@use null
FriendStreaks(
notify = cursor.getInteger("notify") == 1,
expirationTimestamp = cursor.getLongOrNull("expirationTimestamp") ?: 0L,
length = cursor.getInteger("length")
)
}
}

fun setFriendStreaksNotify(userId: String, notify: Boolean) {
executeAsync {
database.execSQL("UPDATE streaks SET notify = ? WHERE id = ?", arrayOf(
if (notify) 1 else 0,
userId
))
}
}

fun getRuleIds(type: String): MutableList<String> {
return database.rawQuery("SELECT targetUuid FROM rules WHERE type = ?", arrayOf(type)).use { cursor ->
val ruleIds = mutableListOf<String>()
while (cursor.moveToNext()) {
ruleIds.add(cursor.getStringOrNull("targetUuid")!!)
}
ruleIds
}
}

fun getScripts(): List<ModuleInfo> {
return database.rawQuery("SELECT * FROM scripts ORDER BY id DESC", null).use { cursor ->
val scripts = mutableListOf<ModuleInfo>()
Expand Down
Loading

0 comments on commit cbbf841

Please sign in to comment.