Add Offline support for tracks

This commit is contained in:
tzugen 2022-04-18 07:31:06 +02:00
parent ee67f4c744
commit 8490f7115d
No known key found for this signature in database
GPG Key ID: 61E9C34BC10EC930
14 changed files with 61 additions and 38 deletions

View File

@ -5,7 +5,7 @@ import androidx.room.PrimaryKey
import java.io.Serializable import java.io.Serializable
import java.util.Date import java.util.Date
@Entity @Entity(tableName = "tracks")
data class Track( data class Track(
@PrimaryKey override var id: String, @PrimaryKey override var id: String,
override var parent: String? = null, override var parent: String? = null,

View File

@ -24,6 +24,7 @@ class ActiveServerProvider(
private val repository: ServerSettingDao private val repository: ServerSettingDao
) : CoroutineScope by CoroutineScope(Dispatchers.IO) { ) : CoroutineScope by CoroutineScope(Dispatchers.IO) {
private var cachedServer: ServerSetting? = null private var cachedServer: ServerSetting? = null
// FIXME cach never set
private var cachedDatabase: MetaDatabase? = null private var cachedDatabase: MetaDatabase? = null
private var cachedServerId: Int? = null private var cachedServerId: Int? = null
@ -110,27 +111,21 @@ 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( cachedDatabase = initDatabase(activeServer)
UApp.applicationContext(),
MetaDatabase::class.java, return cachedDatabase!!
METADATA_DB + cachedServerId
)
.addMigrations(META_MIGRATION_2_1)
.fallbackToDestructiveMigrationOnDowngrade()
.build()
} }
val offlineMetaDatabase: MetaDatabase by lazy { val offlineMetaDatabase: MetaDatabase by lazy {
buildDatabase(OFFLINE_DB_ID) initDatabase(0)
} }
private fun buildDatabase(id: Int?): MetaDatabase { private fun initDatabase(serverId: Int): MetaDatabase {
return Room.databaseBuilder( return Room.databaseBuilder(
UApp.applicationContext(), UApp.applicationContext(),
MetaDatabase::class.java, MetaDatabase::class.java,
METADATA_DB + id METADATA_DB + serverId
) ).fallbackToDestructiveMigration()
.fallbackToDestructiveMigration()
.build() .build()
} }

View File

@ -32,7 +32,7 @@ interface AlbumDao : GenericDao<Album> {
fun clearByArtist(id: String) fun clearByArtist(id: String)
/** /**
* FIXME: Make generic * TODO: Make generic
* Upserts (insert or update) an object to the database * Upserts (insert or update) an object to the database
* *
* @param obj the object to upsert * @param obj the object to upsert

View File

@ -7,7 +7,7 @@ import androidx.room.Query
import org.moire.ultrasonic.domain.Artist import org.moire.ultrasonic.domain.Artist
@Dao @Dao
interface ArtistsDao { interface ArtistDao {
/** /**
* Insert a list in the database. If the item already exists, replace it. * Insert a list in the database. If the item already exists, replace it.
* *

View File

@ -53,6 +53,7 @@ interface IndexDao : GenericDao<Index> {
fun get(musicFolderId: String): List<Index> fun get(musicFolderId: String): List<Index>
/** /**
* TODO: Make generic
* Upserts (insert or update) an object to the database * Upserts (insert or update) an object to the database
* *
* @param obj the object to upsert * @param obj the object to upsert

View File

@ -1,33 +1,36 @@
package org.moire.ultrasonic.data package org.moire.ultrasonic.data
import androidx.room.AutoMigration
import androidx.room.Database import androidx.room.Database
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import androidx.room.TypeConverter import androidx.room.TypeConverter
import androidx.room.TypeConverters import androidx.room.TypeConverters
import androidx.room.migration.Migration import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteDatabase
import org.moire.ultrasonic.domain.Album
import java.util.Date import java.util.Date
import org.moire.ultrasonic.domain.Album
import org.moire.ultrasonic.domain.Artist import org.moire.ultrasonic.domain.Artist
import org.moire.ultrasonic.domain.Index import org.moire.ultrasonic.domain.Index
import org.moire.ultrasonic.domain.MusicFolder import org.moire.ultrasonic.domain.MusicFolder
import org.moire.ultrasonic.domain.Track
/** /**
* This database is used to store and cache the ID3 metadata * This database is used to store and cache the ID3 metadata
*/ */
@Database( @Database(
entities = [Artist::class, Album::class, Index::class, MusicFolder::class], entities = [
version = 2, Artist::class,
autoMigrations = [ Album::class,
AutoMigration(from = 1, to = 2) Track::class,
Index::class,
MusicFolder::class
], ],
version = 2,
exportSchema = true exportSchema = true
) )
@TypeConverters(Converters::class) @TypeConverters(Converters::class)
abstract class MetaDatabase : RoomDatabase() { abstract class MetaDatabase : RoomDatabase() {
abstract fun artistsDao(): ArtistsDao abstract fun artistDao(): ArtistDao
abstract fun albumDao(): AlbumDao abstract fun albumDao(): AlbumDao
@ -50,7 +53,6 @@ class Converters {
} }
} }
// FIXME: Check if correct
val META_MIGRATION_2_1: Migration = object : Migration(2, 1) { val META_MIGRATION_2_1: Migration = object : Migration(2, 1) {
override fun migrate(database: SupportSQLiteDatabase) { override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL( database.execSQL(

View File

@ -1,9 +1,13 @@
package org.moire.ultrasonic.data package org.moire.ultrasonic.data
import androidx.room.Dao
import androidx.room.Entity
import androidx.room.Query import androidx.room.Query
import org.moire.ultrasonic.domain.MusicDirectory import org.moire.ultrasonic.domain.Track
interface TrackDao { @Dao
@Entity(tableName = "tracks")
interface TrackDao : GenericDao<Track> {
/** /**
* Clear the whole database * Clear the whole database
*/ */
@ -14,12 +18,11 @@ interface TrackDao {
* Get all albums * Get all albums
*/ */
@Query("SELECT * FROM tracks") @Query("SELECT * FROM tracks")
fun get(): List<MusicDirectory.Album> fun get(): List<Track>
/** /**
* Get albums by artist * Get albums by artist
*/ */
@Query("SELECT * FROM tracks WHERE albumId LIKE :id") @Query("SELECT * FROM tracks WHERE albumId LIKE :id")
fun byAlbum(id: String): List<MusicDirectory.Entry> fun byAlbum(id: String): List<Track>
}
}

View File

@ -600,7 +600,7 @@ open class TrackCollectionFragment : MultiListFragment<MusicDirectory.Child>() {
listModel.getRandom(albumListSize) listModel.getRandom(albumListSize)
} else { } else {
setTitle(name) setTitle(name)
if (!isOffline() && Settings.shouldUseId3Tags) { if (!isOffline() && Settings.shouldUseId3Tags || Settings.useId3TagsOffline) {
if (isAlbum) { if (isAlbum) {
listModel.getAlbum(refresh2, id!!, name) listModel.getAlbum(refresh2, id!!, name)
} else { } else {

View File

@ -52,8 +52,9 @@ class CachedMusicService(private val musicService: MusicService) : MusicService,
private val cachedGenres = TimeLimitedCache<List<Genre>>(10 * 3600, TimeUnit.SECONDS) private val cachedGenres = TimeLimitedCache<List<Genre>>(10 * 3600, TimeUnit.SECONDS)
// New Room Database // New Room Database
private var cachedArtists = metaDatabase.artistsDao() private var cachedArtists = metaDatabase.artistDao()
private var cachedAlbums = metaDatabase.albumDao() private var cachedAlbums = metaDatabase.albumDao()
private var cachedTracks = metaDatabase.trackDao()
private var cachedIndexes = metaDatabase.indexDao() private var cachedIndexes = metaDatabase.indexDao()
private val cachedMusicFolders = metaDatabase.musicFoldersDao() private val cachedMusicFolders = metaDatabase.musicFoldersDao()
@ -353,7 +354,7 @@ class CachedMusicService(private val musicService: MusicService) : MusicService,
if (!Util.equals(newUrl, restUrl) || !Util.equals(cachedMusicFolderId, newFolderId)) { if (!Util.equals(newUrl, restUrl) || !Util.equals(cachedMusicFolderId, newFolderId)) {
// Switch database // Switch database
metaDatabase = activeServerProvider.getActiveMetaDatabase() metaDatabase = activeServerProvider.getActiveMetaDatabase()
cachedArtists = metaDatabase.artistsDao() cachedArtists = metaDatabase.artistDao()
cachedAlbums = metaDatabase.albumDao() cachedAlbums = metaDatabase.albumDao()
cachedIndexes = metaDatabase.indexDao() cachedIndexes = metaDatabase.indexDao()

View File

@ -44,6 +44,7 @@ import org.moire.ultrasonic.domain.Track
import org.moire.ultrasonic.domain.UserInfo import org.moire.ultrasonic.domain.UserInfo
import org.moire.ultrasonic.util.AbstractFile import org.moire.ultrasonic.util.AbstractFile
import org.moire.ultrasonic.util.Constants import org.moire.ultrasonic.util.Constants
import org.moire.ultrasonic.util.EntryByDiscAndTrackComparator
import org.moire.ultrasonic.util.FileUtil import org.moire.ultrasonic.util.FileUtil
import org.moire.ultrasonic.util.Storage import org.moire.ultrasonic.util.Storage
import org.moire.ultrasonic.util.Util.safeClose import org.moire.ultrasonic.util.Util.safeClose
@ -56,8 +57,9 @@ class OfflineMusicService : MusicService, KoinComponent {
private var metaDatabase: MetaDatabase = activeServerProvider.getActiveMetaDatabase() private var metaDatabase: MetaDatabase = activeServerProvider.getActiveMetaDatabase()
// New Room Database // New Room Database
private var cachedArtists = metaDatabase.artistsDao() private var cachedArtists = metaDatabase.artistDao()
private var cachedAlbums = metaDatabase.albumDao() private var cachedAlbums = metaDatabase.albumDao()
private var cachedTracks = metaDatabase.trackDao()
private var cachedIndexes = metaDatabase.indexDao() private var cachedIndexes = metaDatabase.indexDao()
private val cachedMusicFolders = metaDatabase.musicFoldersDao() private val cachedMusicFolders = metaDatabase.musicFoldersDao()
@ -108,7 +110,7 @@ class OfflineMusicService : MusicService, KoinComponent {
@Throws(OfflineException::class) @Throws(OfflineException::class)
override fun getArtists(refresh: Boolean): List<Artist> { override fun getArtists(refresh: Boolean): List<Artist> {
var result = cachedArtists.get() val result = cachedArtists.get()
if (result.isEmpty()) { if (result.isEmpty()) {
// use indexes? // use indexes?
@ -478,7 +480,15 @@ class OfflineMusicService : MusicService, KoinComponent {
@Throws(OfflineException::class) @Throws(OfflineException::class)
override fun getAlbum(id: String, name: String?, refresh: Boolean): MusicDirectory { override fun getAlbum(id: String, name: String?, refresh: Boolean): MusicDirectory {
throw OfflineException("getAlbum isn't available in offline mode")
val list = cachedTracks
.byAlbum(id)
.sortedWith(EntryByDiscAndTrackComparator())
var dir = MusicDirectory()
dir.addAll(list)
return dir
} }
@Throws(OfflineException::class) @Throws(OfflineException::class)

View File

@ -86,6 +86,7 @@ object Constants {
const val PREFERENCES_KEY_INCREMENT_TIME = "incrementTime" const val PREFERENCES_KEY_INCREMENT_TIME = "incrementTime"
const val PREFERENCES_KEY_SHOW_NOW_PLAYING_DETAILS = "showNowPlayingDetails" const val PREFERENCES_KEY_SHOW_NOW_PLAYING_DETAILS = "showNowPlayingDetails"
const val PREFERENCES_KEY_ID3_TAGS = "useId3Tags" const val PREFERENCES_KEY_ID3_TAGS = "useId3Tags"
const val PREFERENCES_KEY_ID3_TAGS_OFFLINE = "useId3TagsOffline"
const val PREFERENCES_KEY_SHOW_ARTIST_PICTURE = "showArtistPicture" const val PREFERENCES_KEY_SHOW_ARTIST_PICTURE = "showArtistPicture"
const val PREFERENCES_KEY_CHAT_REFRESH_INTERVAL = "chatRefreshInterval" const val PREFERENCES_KEY_CHAT_REFRESH_INTERVAL = "chatRefreshInterval"
const val PREFERENCES_KEY_DIRECTORY_CACHE_TIME = "directoryCacheTime" const val PREFERENCES_KEY_DIRECTORY_CACHE_TIME = "directoryCacheTime"

View File

@ -130,6 +130,7 @@ object Settings {
@JvmStatic @JvmStatic
var mediaButtonsEnabled var mediaButtonsEnabled
by BooleanSetting(Constants.PREFERENCES_KEY_MEDIA_BUTTONS, true) by BooleanSetting(Constants.PREFERENCES_KEY_MEDIA_BUTTONS, true)
var resumePlayOnHeadphonePlug var resumePlayOnHeadphonePlug
by BooleanSetting(R.string.setting_keys_resume_play_on_headphones_plug, true) by BooleanSetting(R.string.setting_keys_resume_play_on_headphones_plug, true)
@ -162,6 +163,9 @@ object Settings {
@JvmStatic @JvmStatic
var shouldUseId3Tags by BooleanSetting(Constants.PREFERENCES_KEY_ID3_TAGS, false) var shouldUseId3Tags by BooleanSetting(Constants.PREFERENCES_KEY_ID3_TAGS, false)
@JvmStatic
var useId3TagsOffline by BooleanSetting(Constants.PREFERENCES_KEY_ID3_TAGS_OFFLINE, false)
var activeServer by IntSetting(Constants.PREFERENCES_KEY_SERVER_INSTANCE, -1) var activeServer by IntSetting(Constants.PREFERENCES_KEY_SERVER_INSTANCE, -1)
var serverScaling by BooleanSetting(Constants.PREFERENCES_KEY_SERVER_SCALING, false) var serverScaling by BooleanSetting(Constants.PREFERENCES_KEY_SERVER_SCALING, false)
@ -244,8 +248,6 @@ object Settings {
var useHwOffload by BooleanSetting(Constants.PREFERENCES_KEY_HARDWARE_OFFLOAD, false) var useHwOffload by BooleanSetting(Constants.PREFERENCES_KEY_HARDWARE_OFFLOAD, false)
var useId3TagsOffline = true
// TODO: Remove in December 2022 // TODO: Remove in December 2022
fun migrateFeatureStorage() { fun migrateFeatureStorage() {
val sp = appContext.getSharedPreferences("feature_flags", Context.MODE_PRIVATE) val sp = appContext.getSharedPreferences("feature_flags", Context.MODE_PRIVATE)

View File

@ -316,6 +316,8 @@
<string name="settings.show_now_playing_details">Show details in Now Playing</string> <string name="settings.show_now_playing_details">Show details in Now Playing</string>
<string name="settings.use_id3">Browse Using ID3 Tags</string> <string name="settings.use_id3">Browse Using ID3 Tags</string>
<string name="settings.use_id3_summary">Use ID3 tag methods instead of file system based methods</string> <string name="settings.use_id3_summary">Use ID3 tag methods instead of file system based methods</string>
<string name="settings.use_id3_offline">Use ID3 method also when offline</string>
<string name="settings.use_id3_offline_summary">(Experimental)</string>
<string name="settings.show_artist_picture">Show artist picture in artist list</string> <string name="settings.show_artist_picture">Show artist picture in artist list</string>
<string name="settings.show_artist_picture_summary">Displays the artist picture in the artist list if available</string> <string name="settings.show_artist_picture_summary">Displays the artist picture in the artist list if available</string>
<string name="main.video" tools:ignore="UnusedResources">Video</string> <string name="main.video" tools:ignore="UnusedResources">Video</string>

View File

@ -59,6 +59,12 @@
a:summary="@string/settings.use_id3_summary" a:summary="@string/settings.use_id3_summary"
a:title="@string/settings.use_id3" a:title="@string/settings.use_id3"
app:iconSpaceReserved="false"/> app:iconSpaceReserved="false"/>
<CheckBoxPreference
a:defaultValue="true"
a:key="useId3TagsOffline"
a:summary="@string/settings.use_id3_offline_summary"
a:title="@string/settings.use_id3_offline"
app:iconSpaceReserved="false"/>
<CheckBoxPreference <CheckBoxPreference
a:defaultValue="true" a:defaultValue="true"
a:key="showArtistPicture" a:key="showArtistPicture"