mirror of
https://github.com/ultrasonic/ultrasonic
synced 2025-02-16 11:41:16 +01:00
Add Offline support for tracks
This commit is contained in:
parent
ee67f4c744
commit
8490f7115d
@ -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,
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
*
|
*
|
@ -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
|
||||||
|
@ -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(
|
||||||
|
@ -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>
|
||||||
|
}
|
||||||
}
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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"
|
||||||
|
@ -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)
|
||||||
|
@ -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>
|
||||||
|
@ -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"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user