Merge pull request #719 from ultrasonic/AlbumTrackCachePrepare

Add migration path for coming DB change
This commit is contained in:
tzugen 2022-03-31 12:20:27 +02:00 committed by GitHub
commit e77b5abd3e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 145 additions and 149 deletions

View File

@ -157,9 +157,8 @@ class NavigationActivity : AppCompatActivity() {
setMenuForServerCapabilities() setMenuForServerCapabilities()
} }
// Determine first run and migrate server settings to DB as early as possible // Determine if this is a first run
var showWelcomeScreen = Util.isFirstRun() val showWelcomeScreen = Util.isFirstRun()
val areServersMigrated: Boolean = serverSettingsModel.migrateFromPreferences()
// Migrate Feature storage if needed // Migrate Feature storage if needed
// TODO: Remove in December 2022 // TODO: Remove in December 2022
@ -167,9 +166,6 @@ class NavigationActivity : AppCompatActivity() {
Settings.migrateFeatureStorage() Settings.migrateFeatureStorage()
} }
// If there are any servers in the DB, do not show the welcome screen
showWelcomeScreen = showWelcomeScreen and !areServersMigrated
loadSettings() loadSettings()
// This is a first run with only the demo entry inside the database // This is a first run with only the demo entry inside the database
@ -194,14 +190,14 @@ class NavigationActivity : AppCompatActivity() {
recreate() recreate()
} }
serverRepository.liveServerCount().observe( serverRepository.liveServerCount().observe(this) { count ->
this, cachedServerCount = count ?: 0
{ count -> updateNavigationHeaderForServer()
cachedServerCount = count ?: 0 }
updateNavigationHeaderForServer()
} ActiveServerProvider.liveActiveServerId.observe(this) {
) updateNavigationHeaderForServer()
ActiveServerProvider.liveActiveServerId.observe(this, { updateNavigationHeaderForServer() }) }
} }
private fun updateNavigationHeaderForServer() { private fun updateNavigationHeaderForServer() {

View File

@ -1,4 +1,4 @@
package org.moire.ultrasonic.fragment package org.moire.ultrasonic.adapters
import android.content.Context import android.content.Context
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable

View File

@ -19,8 +19,6 @@ import timber.log.Timber
/** /**
* This class can be used to retrieve the properties of the Active Server * This class can be used to retrieve the properties of the Active Server
* It caches the settings read up from the DB to improve performance. * It caches the settings read up from the DB to improve performance.
*
* TODO: There seems to be some confusion whether offline id is 0 or -1. Clean this up (carefully!)
*/ */
class ActiveServerProvider( class ActiveServerProvider(
private val repository: ServerSettingDao private val repository: ServerSettingDao
@ -35,7 +33,7 @@ class ActiveServerProvider(
*/ */
@JvmOverloads @JvmOverloads
fun getActiveServer(serverId: Int = getActiveServerId()): ServerSetting { fun getActiveServer(serverId: Int = getActiveServerId()): ServerSetting {
if (serverId > 0) { if (serverId > OFFLINE_DB_ID) {
if (cachedServer != null && cachedServer!!.id == serverId) return cachedServer!! if (cachedServer != null && cachedServer!!.id == serverId) return cachedServer!!
// Ideally this is the only call where we block the thread while using the repository // Ideally this is the only call where we block the thread while using the repository
@ -53,22 +51,11 @@ class ActiveServerProvider(
return cachedServer!! return cachedServer!!
} }
setActiveServerId(0) // Fallback to Offline
setActiveServerId(OFFLINE_DB_ID)
} }
return ServerSetting( return OFFLINE_DB
id = -1,
index = 0,
name = UApp.applicationContext().getString(R.string.main_offline),
url = "http://localhost",
userName = "",
password = "",
jukeboxByDefault = false,
allowSelfSignedCertificate = false,
ldapSupport = false,
musicFolderId = "",
minimumApiVersion = null
)
} }
/** /**
@ -77,9 +64,9 @@ class ActiveServerProvider(
*/ */
fun setActiveServerByIndex(index: Int) { fun setActiveServerByIndex(index: Int) {
Timber.d("setActiveServerByIndex $index") Timber.d("setActiveServerByIndex $index")
if (index < 1) { if (index <= OFFLINE_DB_INDEX) {
// Offline mode is selected // Offline mode is selected
setActiveServerId(0) setActiveServerId(OFFLINE_DB_ID)
return return
} }
@ -103,22 +90,20 @@ class ActiveServerProvider(
Timber.i("Switching to new database, id:$activeServer") Timber.i("Switching to new database, id:$activeServer")
cachedServerId = activeServer cachedServerId = activeServer
return Room.databaseBuilder( return buildDatabase(cachedServerId)
UApp.applicationContext(),
MetaDatabase::class.java,
METADATA_DB + cachedServerId
)
.fallbackToDestructiveMigrationOnDowngrade()
.build()
} }
val offlineMetaDatabase: MetaDatabase by lazy { val offlineMetaDatabase: MetaDatabase by lazy {
Room.databaseBuilder( buildDatabase(OFFLINE_DB_ID)
}
private fun buildDatabase(id: Int?): MetaDatabase {
return Room.databaseBuilder(
UApp.applicationContext(), UApp.applicationContext(),
MetaDatabase::class.java, MetaDatabase::class.java,
METADATA_DB + 0 METADATA_DB + id
) )
.fallbackToDestructiveMigrationOnDowngrade() .fallbackToDestructiveMigration()
.build() .build()
} }
@ -177,8 +162,24 @@ class ActiveServerProvider(
} }
companion object { companion object {
const val METADATA_DB = "$DB_FILENAME-meta-" const val METADATA_DB = "$DB_FILENAME-meta-"
const val OFFLINE_DB_ID = -1
const val OFFLINE_DB_INDEX = 0
val OFFLINE_DB = ServerSetting(
id = OFFLINE_DB_ID,
index = OFFLINE_DB_INDEX,
name = UApp.applicationContext().getString(R.string.main_offline),
url = "http://localhost",
userName = "",
password = "",
jukeboxByDefault = false,
allowSelfSignedCertificate = false,
ldapSupport = false,
musicFolderId = "",
minimumApiVersion = null
)
val liveActiveServerId: MutableLiveData<Int> = MutableLiveData(getActiveServerId()) val liveActiveServerId: MutableLiveData<Int> = MutableLiveData(getActiveServerId())
/** /**
@ -186,7 +187,7 @@ class ActiveServerProvider(
* @return True, if the "Offline" mode is selected * @return True, if the "Offline" mode is selected
*/ */
fun isOffline(): Boolean { fun isOffline(): Boolean {
return getActiveServerId() < 1 return getActiveServerId() == OFFLINE_DB_ID
} }
/** /**

View File

@ -9,7 +9,11 @@ import androidx.sqlite.db.SupportSQLiteDatabase
* Room Database to be used to store global data for the whole app. * Room Database to be used to store global data for the whole app.
* This could be settings or data that are not specific to any remote music database * This could be settings or data that are not specific to any remote music database
*/ */
@Database(entities = [ServerSetting::class], version = 4) @Database(
entities = [ServerSetting::class],
version = 4,
exportSchema = true
)
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
/** /**
@ -175,3 +179,89 @@ val MIGRATION_4_3: Migration = object : Migration(4, 3) {
) )
} }
} }
val MIGRATION_4_5: Migration = object : Migration(4, 5) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `_new_ServerSetting` (
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
`index` INTEGER NOT NULL,
`name` TEXT NOT NULL,
`url` TEXT NOT NULL,
`color` INTEGER,
`userName` TEXT NOT NULL,
`password` TEXT NOT NULL,
`jukeboxByDefault` INTEGER NOT NULL,
`allowSelfSignedCertificate` INTEGER NOT NULL,
`ldapSupport` INTEGER NOT NULL,
`musicFolderId` TEXT,
`minimumApiVersion` TEXT,
`chatSupport` INTEGER,
`bookmarkSupport` INTEGER,
`shareSupport` INTEGER,
`podcastSupport` INTEGER
)
""".trimIndent()
)
database.execSQL(
"""
INSERT INTO `_new_ServerSetting` (
`ldapSupport`,`musicFolderId`,`color`,`index`,`userName`,`minimumApiVersion`,
`jukeboxByDefault`,`url`,`password`,`shareSupport`,`bookmarkSupport`,`name`,
`podcastSupport`,`id`,`allowSelfSignedCertificate`,`chatSupport`
)
SELECT `ldapSupport`,`musicFolderId`,`color`,`index`,`userName`,
`minimumApiVersion`,`jukeboxByDefault`,`url`,`password`,`shareSupport`,
`bookmarkSupport`,`name`,`podcastSupport`,`id`,`allowSelfSignedCertificate`,
`chatSupport`
FROM `ServerSetting`
""".trimIndent()
)
database.execSQL("DROP TABLE `ServerSetting`")
database.execSQL("ALTER TABLE `_new_ServerSetting` RENAME TO `ServerSetting`")
}
}
val MIGRATION_5_4: Migration = object : Migration(5, 4) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `_new_ServerSetting` (
`id` INTEGER PRIMARY KEY NOT NULL,
`index` INTEGER NOT NULL,
`name` TEXT NOT NULL,
`url` TEXT NOT NULL,
`color` INTEGER,
`userName` TEXT NOT NULL,
`password` TEXT NOT NULL,
`jukeboxByDefault` INTEGER NOT NULL,
`allowSelfSignedCertificate` INTEGER NOT NULL,
`ldapSupport` INTEGER NOT NULL,
`musicFolderId` TEXT,
`minimumApiVersion` TEXT,
`chatSupport` INTEGER,
`bookmarkSupport` INTEGER,
`shareSupport` INTEGER,
`podcastSupport` INTEGER
)
""".trimIndent()
)
database.execSQL(
"""
INSERT INTO `_new_ServerSetting` (
`ldapSupport`,`musicFolderId`,`color`,`index`,`userName`,`minimumApiVersion`,
`jukeboxByDefault`,`url`,`password`,`shareSupport`,`bookmarkSupport`,`name`,
`podcastSupport`,`id`,`allowSelfSignedCertificate`,`chatSupport`
)
SELECT `ldapSupport`,`musicFolderId`,`color`,`index`,`userName`,
`minimumApiVersion`,`jukeboxByDefault`,`url`,`password`,`shareSupport`,
`bookmarkSupport`,`name`,`podcastSupport`,`id`,`allowSelfSignedCertificate`,
`chatSupport`
FROM `ServerSetting`
""".trimIndent()
)
database.execSQL("DROP TABLE `ServerSetting`")
database.execSQL("ALTER TABLE `_new_ServerSetting` RENAME TO `ServerSetting`")
}
}

View File

@ -39,7 +39,4 @@ data class ServerSetting(
constructor() : this ( constructor() : this (
-1, 0, "", "", null, "", "", false, false, false, null, null -1, 0, "", "", null, "", "", false, false, false, null, null
) )
constructor(name: String, url: String) : this(
-1, 0, name, url, null, "", "", false, false, false, null, null
)
} }

View File

@ -12,6 +12,8 @@ import org.moire.ultrasonic.data.MIGRATION_2_3
import org.moire.ultrasonic.data.MIGRATION_3_2 import org.moire.ultrasonic.data.MIGRATION_3_2
import org.moire.ultrasonic.data.MIGRATION_3_4 import org.moire.ultrasonic.data.MIGRATION_3_4
import org.moire.ultrasonic.data.MIGRATION_4_3 import org.moire.ultrasonic.data.MIGRATION_4_3
import org.moire.ultrasonic.data.MIGRATION_4_5
import org.moire.ultrasonic.data.MIGRATION_5_4
import org.moire.ultrasonic.model.ServerSettingsModel import org.moire.ultrasonic.model.ServerSettingsModel
import org.moire.ultrasonic.util.Settings import org.moire.ultrasonic.util.Settings
@ -36,6 +38,8 @@ val appPermanentStorage = module {
.addMigrations(MIGRATION_3_2) .addMigrations(MIGRATION_3_2)
.addMigrations(MIGRATION_3_4) .addMigrations(MIGRATION_3_4)
.addMigrations(MIGRATION_4_3) .addMigrations(MIGRATION_4_3)
.addMigrations(MIGRATION_4_5)
.addMigrations(MIGRATION_5_4)
.build() .build()
} }

View File

@ -15,6 +15,7 @@ import kotlinx.coroutines.withContext
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
import org.moire.ultrasonic.R import org.moire.ultrasonic.R
import org.moire.ultrasonic.adapters.ServerRowAdapter
import org.moire.ultrasonic.data.ActiveServerProvider import org.moire.ultrasonic.data.ActiveServerProvider
import org.moire.ultrasonic.fragment.EditServerFragment.Companion.EDIT_SERVER_INTENT_INDEX import org.moire.ultrasonic.fragment.EditServerFragment.Companion.EDIT_SERVER_INTENT_INDEX
import org.moire.ultrasonic.model.ServerSettingsModel import org.moire.ultrasonic.model.ServerSettingsModel
@ -103,11 +104,10 @@ class ServerSelectorFragment : Fragment() {
super.onResume() super.onResume()
val serverList = serverSettingsModel.getServerList() val serverList = serverSettingsModel.getServerList()
serverList.observe( serverList.observe(
this, this
{ t -> ) { t ->
serverRowAdapter!!.setData(t.toTypedArray()) serverRowAdapter!!.setData(t.toTypedArray())
} }
)
} }
/** /**

View File

@ -1,11 +1,9 @@
package org.moire.ultrasonic.model package org.moire.ultrasonic.model
import android.app.Application import android.app.Application
import android.content.SharedPreferences
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.preference.PreferenceManager
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
@ -29,46 +27,6 @@ class ServerSettingsModel(
private val appScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) private val appScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
/**
* This function will try and convert settings from the Preferences to the Database
* @return True, if the migration was executed, False otherwise
*/
fun migrateFromPreferences(): Boolean {
var migrated = true
runBlocking {
val rowCount = repository.count()
if (rowCount == null || rowCount == 0) {
// First time load up the server settings from the Preferences
val dbServerList = mutableListOf<ServerSetting>()
val context = getApplication<Application>().applicationContext
val settings = PreferenceManager.getDefaultSharedPreferences(context)
val serverNum = settings.getInt(PREFERENCES_KEY_ACTIVE_SERVERS, 0)
if (serverNum != 0) {
var index = 1
for (x in 1 until serverNum + 1) {
val newServerSetting = loadServerSettingFromPreferences(x, index, settings)
if (newServerSetting != null) {
dbServerList.add(newServerSetting)
repository.insert(newServerSetting)
index++
Timber.i(
"Imported server from Preferences to Database: %s",
newServerSetting.name
)
}
}
} else {
migrated = false
}
}
}
return migrated
}
/** /**
* Retrieves the list of the configured servers from the database. * Retrieves the list of the configured servers from the database.
* This function is asynchronous, uses LiveData to provide the Setting. * This function is asynchronous, uses LiveData to provide the Setting.
@ -192,40 +150,6 @@ class ServerSettingsModel(
return demo.id return demo.id
} }
/**
* Reads up a Server Setting stored in the obsolete Preferences
*/
private fun loadServerSettingFromPreferences(
preferenceId: Int,
serverId: Int,
settings: SharedPreferences
): ServerSetting? {
val url = settings.getString(PREFERENCES_KEY_SERVER_URL + preferenceId, "")
val userName = settings.getString(PREFERENCES_KEY_USERNAME + preferenceId, "")
val isMigrated = settings.getBoolean(PREFERENCES_KEY_SERVER_MIGRATED + preferenceId, false)
if (url.isNullOrEmpty() || userName.isNullOrEmpty() || isMigrated) return null
setServerMigrated(settings, preferenceId)
return ServerSetting(
preferenceId,
serverId,
settings.getString(PREFERENCES_KEY_SERVER_NAME + preferenceId, "")!!,
url,
null,
userName,
settings.getString(PREFERENCES_KEY_PASSWORD + preferenceId, "")!!,
settings.getBoolean(PREFERENCES_KEY_JUKEBOX_BY_DEFAULT + preferenceId, false),
settings.getBoolean(
PREFERENCES_KEY_ALLOW_SELF_SIGNED_CERTIFICATE + preferenceId,
false
),
settings.getBoolean(PREFERENCES_KEY_LDAP_SUPPORT + preferenceId, false),
settings.getString(PREFERENCES_KEY_MUSIC_FOLDER_ID + preferenceId, null),
null
)
}
/** /**
* Checks if there are any missing indexes in the ServerSetting list * Checks if there are any missing indexes in the ServerSetting list
* For displaying the Server Settings in a ListView, it is mandatory that their indexes * For displaying the Server Settings in a ListView, it is mandatory that their indexes
@ -263,25 +187,7 @@ class ServerSettingsModel(
return indexesInDatabase return indexesInDatabase
} }
private fun setServerMigrated(settings: SharedPreferences, preferenceId: Int) {
val editor = settings.edit()
editor.putBoolean(PREFERENCES_KEY_SERVER_MIGRATED + preferenceId, true)
editor.apply()
}
companion object { companion object {
private const val PREFERENCES_KEY_SERVER_MIGRATED = "serverMigrated"
// These constants were removed from Constants.java as they are deprecated and only used here
private const val PREFERENCES_KEY_JUKEBOX_BY_DEFAULT = "jukeboxEnabled"
private const val PREFERENCES_KEY_SERVER_NAME = "serverName"
private const val PREFERENCES_KEY_SERVER_URL = "serverUrl"
private const val PREFERENCES_KEY_ACTIVE_SERVERS = "activeServers"
private const val PREFERENCES_KEY_USERNAME = "username"
private const val PREFERENCES_KEY_PASSWORD = "password"
private const val PREFERENCES_KEY_ALLOW_SELF_SIGNED_CERTIFICATE = "allowSSCertificate"
private const val PREFERENCES_KEY_LDAP_SUPPORT = "enableLdapSupport"
private const val PREFERENCES_KEY_MUSIC_FOLDER_ID = "musicFolderId"
private val DEMO_SERVER_CONFIG = ServerSetting( private val DEMO_SERVER_CONFIG = ServerSetting(
id = 0, id = 0,
index = 0, index = 0,

View File

@ -83,6 +83,8 @@ class PlaybackStateSerializer : KoinComponent {
lock.lock() lock.lock()
deserializeNow(afterDeserialized) deserializeNow(afterDeserialized)
setup.set(true) setup.set(true)
} catch (all: Exception) {
Timber.e(all, "Had a problem deserializing:")
} finally { } finally {
lock.unlock() lock.unlock()
} }