Own metadata databases by server ID

This commit is contained in:
birdbird committed 2026-10-07 09:04:47 +02:00
1 parent 0a3d5b1c02
commit dd95c554b7
3 files changed
+82 -37

No files matched your search

@@ -7,7 +7,6 @@
package org.moire.ultrasonic.data
import androidx.room.Room
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -40,8 +39,7 @@ import timber.log.Timber
class ActiveServerProvider(private val repository: ServerSettingDao) :
CoroutineScope by CoroutinePatterns.loggingScope(Dispatchers.IO) {
private var cachedDatabase: MetaDatabase? = null
private var cachedServerId: Int? = null
private val metadataDatabases = MetaDatabaseRegistry()
private val _accessState = MutableStateFlow(
LibraryAccessState(Settings.activeServer, Settings.explicitOffline)
@@ -308,45 +306,18 @@ class ActiveServerProvider(private val repository: ServerSettingDao) :
* this rather than the effective, offline database.
*/
@Synchronized
fun getSelectedMetaDatabase(): MetaDatabase {
val selectedServer = getSelectedServerId()
fun getSelectedMetaDatabase(): MetaDatabase = getMetaDatabaseForServer(getSelectedServerId())
if (selectedServer == cachedServerId && cachedDatabase != null) {
return cachedDatabase!!
}
/** Metadata storage owned by a server, independent of the current UI selection. */
@Synchronized
fun getMetaDatabaseForServer(serverId: Int): MetaDatabase =
metadataDatabases.forServer(serverId)
if (selectedServer < 1) {
return offlineMetaDatabase
}
Timber.i("Switching to new database, id:$selectedServer")
cachedServerId = selectedServer
cachedDatabase = initDatabase(selectedServer)
return cachedDatabase!!
}
val offlineMetaDatabase: MetaDatabase by lazy {
initDatabase(0)
}
private fun initDatabase(serverId: Int): MetaDatabase = Room.databaseBuilder(
UApp.applicationContext(),
MetaDatabase::class.java,
METADATA_DB + serverId
)
.addMigrations(META_MIGRATION_2_3, META_MIGRATION_3_4)
.fallbackToDestructiveMigrationOnDowngrade(true)
.build()
val offlineMetaDatabase: MetaDatabase get() = metadataDatabases.forServer(0)
@Synchronized
fun deleteMetaDatabase(id: Int) {
if (cachedServerId == id) {
cachedDatabase?.close()
cachedDatabase = null
cachedServerId = null
}
UApp.applicationContext().deleteDatabase(METADATA_DB + id)
metadataDatabases.delete(id)
Timber.i("Deleted metadataBase, id:$id")
}
@@ -0,0 +1,39 @@
package org.moire.ultrasonic.data
import androidx.room.Room
import org.moire.ultrasonic.app.UApp
import timber.log.Timber
/** Owns one Room handle per server for the lifetime of the application process. */
internal class MetaDatabaseRegistry(
private val open: (Int) -> MetaDatabase = { serverId ->
Room.databaseBuilder(
UApp.applicationContext(),
MetaDatabase::class.java,
ActiveServerProvider.METADATA_DB + serverId
)
.addMigrations(META_MIGRATION_2_3, META_MIGRATION_3_4)
.fallbackToDestructiveMigrationOnDowngrade(true)
.build()
},
private val deleteFile: (Int) -> Unit = { serverId ->
UApp.applicationContext().deleteDatabase(ActiveServerProvider.METADATA_DB + serverId)
}
) {
private val databases = mutableMapOf<Int, MetaDatabase>()
@Synchronized
fun forServer(serverId: Int): MetaDatabase {
val databaseId = serverId.coerceAtLeast(0)
return databases.getOrPut(databaseId) {
Timber.i("Opening metadata database, id:$databaseId")
open(databaseId)
}
}
@Synchronized
fun delete(serverId: Int) {
databases.remove(serverId)?.close()
deleteFile(serverId)
}
}
@@ -0,0 +1,35 @@
package org.moire.ultrasonic.data
import org.amshove.kluent.shouldBeEqualTo
import org.junit.Test
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
class MetaDatabaseRegistryTest {
@Test
fun `server switch reuses handles and deletion closes only its server`() {
val first: MetaDatabase = mock()
val second: MetaDatabase = mock()
val opened = mutableListOf<Int>()
val deleted = mutableListOf<Int>()
val registry = MetaDatabaseRegistry(
open = { id ->
opened += id
if (id == 1) first else second
},
deleteFile = { deleted += it }
)
registry.forServer(1) shouldBeEqualTo first
registry.forServer(2) shouldBeEqualTo second
registry.forServer(1) shouldBeEqualTo first
opened shouldBeEqualTo listOf(1, 2)
registry.delete(1)
verify(first).close()
deleted shouldBeEqualTo listOf(1)
registry.forServer(2) shouldBeEqualTo second
opened shouldBeEqualTo listOf(1, 2)
}
}