feat(core/room): swappable databases

This commit is contained in:
Cilly Leang 2026-04-11 21:33:33 +10:00
parent e7caeca356
commit 959b022cf9
Signed by: cilly
GPG key ID: 6500251E087653C9
8 changed files with 117 additions and 73 deletions

View file

@ -0,0 +1,22 @@
package moe.lava.banksia.core.room
import android.content.Context
import androidx.room.Room
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
import org.koin.core.component.inject
actual class DatabaseManager actual constructor() : KoinComponent {
private val ctx by inject<Context>()
actual val database by lazy {
val ctx = get<Context>().applicationContext
val dbFile = ctx.getDatabasePath("room.db")
val builder = Room.databaseBuilder<Database>(
context = ctx,
name = dbFile.absolutePath,
)
Database.build(builder)
}
}

View file

@ -1,21 +0,0 @@
package moe.lava.banksia.core.room
import android.content.Context
import androidx.room.Room
import androidx.room.RoomDatabase
import org.koin.core.parameter.ParametersHolder
import org.koin.core.scope.Scope
class AndroidDatabaseBuilder(val ctx: Context) : PlatformDatabaseBuilder {
override fun getBuilder(): RoomDatabase.Builder<Database> {
val appContext = ctx.applicationContext
val dbFile = appContext.getDatabasePath("room.db")
return Room.databaseBuilder<Database>(
context = appContext,
name = dbFile.absolutePath
)
}
}
internal actual fun Scope.provideDatabaseBuilder(p: ParametersHolder): PlatformDatabaseBuilder =
AndroidDatabaseBuilder(get())

View file

@ -0,0 +1,7 @@
package moe.lava.banksia.core.room
import org.koin.core.component.KoinComponent
expect class DatabaseManager() : KoinComponent {
val database: Database
}

View file

@ -1,26 +1,18 @@
package moe.lava.banksia.core.room package moe.lava.banksia.core.room
import androidx.room.RoomDatabase import org.koin.core.module.dsl.singleOf
import org.koin.core.parameter.ParametersHolder
import org.koin.core.scope.Scope
import org.koin.dsl.module import org.koin.dsl.module
val roomDiModule = module { val roomDiModule = module {
single { provideDatabaseBuilder(it) } singleOf(::DatabaseManager)
single { Database.build(get<PlatformDatabaseBuilder>().getBuilder()) } factory { get<DatabaseManager>().database }
single { get<Database>().versionMetadataDao } factory { get<Database>().versionMetadataDao }
single { get<Database>().routeDao } factory { get<Database>().routeDao }
single { get<Database>().serviceDao } factory { get<Database>().serviceDao }
single { get<Database>().serviceExceptionDao } factory { get<Database>().serviceExceptionDao }
single { get<Database>().shapeDao } factory { get<Database>().shapeDao }
single { get<Database>().stopDao } factory { get<Database>().stopDao }
single { get<Database>().stopTimeDao } factory { get<Database>().stopTimeDao }
single { get<Database>().tripDao } factory { get<Database>().tripDao }
} }
internal interface PlatformDatabaseBuilder {
fun getBuilder(): RoomDatabase.Builder<Database>
}
internal expect fun Scope.provideDatabaseBuilder(p: ParametersHolder): PlatformDatabaseBuilder

View file

@ -0,0 +1,8 @@
package moe.lava.banksia.core.room
import org.koin.core.component.KoinComponent
actual class DatabaseManager actual constructor() : KoinComponent {
actual val database: Database
get() = TODO("Not yet implemented")
}

View file

@ -1,14 +0,0 @@
package moe.lava.banksia.core.room
import androidx.room.RoomDatabase
import org.koin.core.parameter.ParametersHolder
import org.koin.core.scope.Scope
class IosDatabaseBuilder() : PlatformDatabaseBuilder {
override fun getBuilder(): RoomDatabase.Builder<Database> {
TODO("Not yet implemented")
}
}
internal actual fun Scope.provideDatabaseBuilder(p: ParametersHolder): PlatformDatabaseBuilder =
IosDatabaseBuilder()

View file

@ -0,0 +1,69 @@
package moe.lava.banksia.core.room
import androidx.room.Room
import androidx.room.RoomDatabase
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import moe.lava.banksia.core.util.error
import org.koin.core.component.KoinComponent
import java.io.File
import kotlin.system.exitProcess
actual class DatabaseManager : KoinComponent {
private var liveDatabase: Database = Database.build(getBuilder())
actual val database get() = liveDatabase
private fun getBuilder(path: String = "./data/room.db"): RoomDatabase.Builder<Database> {
val dbFile = File(path)
return Room.databaseBuilder<Database>(
name = dbFile.absolutePath,
).setJournalMode(RoomDatabase.JournalMode.TRUNCATE)
}
fun makeAlt() = Database.build(getBuilder("./data/room_alt.db"))
private fun deleteAll(file: File): Boolean {
val r1 = file.delete()
val r2 = File(file.parentFile, file.name + ".lck").delete()
val r3 = File(file.parentFile, file.name + "-journal").delete()
return r1 && r2 && r3
}
private fun renameAll(from: File, to: File): Boolean {
val r1 = from.renameTo(to)
val r2 = File(from.parentFile, from.name + ".lck").renameTo(File(to.parentFile, to.name + ".lck"))
val r3 = File(from.parentFile, from.name + "-journal").renameTo(File(to.parentFile, to.name + "-journal"))
return r1 && r2 && r3
}
fun swap(scope: CoroutineScope = CoroutineScope(Dispatchers.IO)) {
val live = File("./data/room.db")
val alt = File("./data/room_alt.db")
val old = File("./data/room_old.db")
if (!renameAll(live, old)) {
error("DatabaseManager", "Failed to rename database from live to old (${live.absolutePath} -> ${old.absolutePath})")
return
}
if (!renameAll(alt, live)) {
error("DatabaseManager", "Failed to rename database from alt to live, trying to undo.. (${alt.absolutePath} -> ${live.absolutePath})")
if (!live.renameTo(old)) {
error("DatabaseManager", "Failed to undo, critical failure, exiting..")
exitProcess(1)
}
return
}
val oldDatabase = liveDatabase
liveDatabase = Database.build(getBuilder())
scope.launch {
delay(5000)
if (!deleteAll(old)) {
error("DatabaseManager", "Failed to unlink old database, stray files! (${old.absolutePath})")
}
oldDatabase.close()
}
}
}

View file

@ -1,19 +0,0 @@
package moe.lava.banksia.core.room
import androidx.room.Room
import androidx.room.RoomDatabase
import org.koin.core.parameter.ParametersHolder
import org.koin.core.scope.Scope
import java.io.File
class JvmDatabaseBuilder() : PlatformDatabaseBuilder {
override fun getBuilder(): RoomDatabase.Builder<Database> {
val dbFile = File("./data/room.db")
return Room.databaseBuilder<Database>(
name = dbFile.absolutePath,
)
}
}
internal actual fun Scope.provideDatabaseBuilder(p: ParametersHolder): PlatformDatabaseBuilder =
JvmDatabaseBuilder()