diff --git a/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/Album.kt b/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/Album.kt index 63611fb7..97e5674e 100644 --- a/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/Album.kt +++ b/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/Album.kt @@ -1,8 +1,10 @@ package org.moire.ultrasonic.domain +import androidx.room.Entity import androidx.room.PrimaryKey import java.util.Date +@Entity(tableName = "albums") data class Album( @PrimaryKey override var id: String, override var parent: String? = null, diff --git a/ultrasonic/schemas/org.moire.ultrasonic.data.MetaDatabase/2.json b/ultrasonic/schemas/org.moire.ultrasonic.data.MetaDatabase/2.json new file mode 100644 index 00000000..7096550d --- /dev/null +++ b/ultrasonic/schemas/org.moire.ultrasonic.data.MetaDatabase/2.json @@ -0,0 +1,474 @@ +{ + "formatVersion": 1, + "database": { + "version": 2, + "identityHash": "b6ac795e7857eac4fed2dbbd01f80fb8", + "entities": [ + { + "tableName": "artists", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT, `index` TEXT, `coverArt` TEXT, `albumCount` INTEGER, `closeness` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "index", + "columnName": "index", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "coverArt", + "columnName": "coverArt", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "albumCount", + "columnName": "albumCount", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "closeness", + "columnName": "closeness", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "albums", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `parent` TEXT, `album` TEXT, `title` TEXT, `name` TEXT, `discNumber` INTEGER, `coverArt` TEXT, `songCount` INTEGER, `created` INTEGER, `artist` TEXT, `artistId` TEXT, `duration` INTEGER, `year` INTEGER, `genre` TEXT, `starred` INTEGER NOT NULL, `path` TEXT, `closeness` INTEGER NOT NULL, `isDirectory` INTEGER NOT NULL, `isVideo` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parent", + "columnName": "parent", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "album", + "columnName": "album", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "discNumber", + "columnName": "discNumber", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "coverArt", + "columnName": "coverArt", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "songCount", + "columnName": "songCount", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "created", + "columnName": "created", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "artist", + "columnName": "artist", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "artistId", + "columnName": "artistId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "year", + "columnName": "year", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "genre", + "columnName": "genre", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "starred", + "columnName": "starred", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "closeness", + "columnName": "closeness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDirectory", + "columnName": "isDirectory", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isVideo", + "columnName": "isVideo", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "tracks", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `parent` TEXT, `isDirectory` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `albumId` TEXT, `artist` TEXT, `artistId` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `contentType` TEXT, `suffix` TEXT, `transcodedContentType` TEXT, `transcodedSuffix` TEXT, `coverArt` TEXT, `size` INTEGER, `songCount` INTEGER, `duration` INTEGER, `bitRate` INTEGER, `path` TEXT, `isVideo` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `discNumber` INTEGER, `type` TEXT, `created` INTEGER, `closeness` INTEGER NOT NULL, `bookmarkPosition` INTEGER NOT NULL, `userRating` INTEGER, `averageRating` REAL, `name` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parent", + "columnName": "parent", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isDirectory", + "columnName": "isDirectory", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "album", + "columnName": "album", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "albumId", + "columnName": "albumId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "artist", + "columnName": "artist", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "artistId", + "columnName": "artistId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "track", + "columnName": "track", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "year", + "columnName": "year", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "genre", + "columnName": "genre", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "contentType", + "columnName": "contentType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "suffix", + "columnName": "suffix", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "transcodedContentType", + "columnName": "transcodedContentType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "transcodedSuffix", + "columnName": "transcodedSuffix", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "coverArt", + "columnName": "coverArt", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "size", + "columnName": "size", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "songCount", + "columnName": "songCount", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "bitRate", + "columnName": "bitRate", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isVideo", + "columnName": "isVideo", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "starred", + "columnName": "starred", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "discNumber", + "columnName": "discNumber", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "created", + "columnName": "created", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "closeness", + "columnName": "closeness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "bookmarkPosition", + "columnName": "bookmarkPosition", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userRating", + "columnName": "userRating", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "averageRating", + "columnName": "averageRating", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "indexes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT, `index` TEXT, `coverArt` TEXT, `albumCount` INTEGER, `closeness` INTEGER NOT NULL, `musicFolderId` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "index", + "columnName": "index", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "coverArt", + "columnName": "coverArt", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "albumCount", + "columnName": "albumCount", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "closeness", + "columnName": "closeness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "musicFolderId", + "columnName": "musicFolderId", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "music_folders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'b6ac795e7857eac4fed2dbbd01f80fb8')" + ] + } +} \ No newline at end of file diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/ArtistRowBinder.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/ArtistRowBinder.kt index df9d8cc8..d1cfbc10 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/ArtistRowBinder.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/ArtistRowBinder.kt @@ -19,6 +19,7 @@ import androidx.recyclerview.widget.RecyclerView import com.drakeet.multitype.ItemViewBinder import org.koin.core.component.KoinComponent import org.moire.ultrasonic.R +import org.moire.ultrasonic.data.ActiveServerProvider import org.moire.ultrasonic.domain.ArtistOrIndex import org.moire.ultrasonic.domain.Identifiable import org.moire.ultrasonic.imageloader.ImageLoader @@ -57,7 +58,7 @@ class ArtistRowBinder( holder.coverArtId = item.coverArt - if (Settings.shouldShowArtistPicture) { + if (showArtistPicture()) { holder.coverArt.visibility = View.VISIBLE val key = FileUtil.getArtistArtKey(item.name, false) imageLoader.loadImage( @@ -108,6 +109,12 @@ class ArtistRowBinder( return section.toString() } + private fun showArtistPicture(): Boolean { + val isOffline = ActiveServerProvider.isOffline() + val shouldShowArtistPicture = Settings.shouldShowArtistPicture + return (!isOffline && shouldShowArtistPicture) || Settings.useId3TagsOffline + } + /** * Creates an instance of our ViewHolder class */ diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ActiveServerProvider.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ActiveServerProvider.kt index 1526dd7e..54dba5dd 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ActiveServerProvider.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ActiveServerProvider.kt @@ -110,7 +110,14 @@ class ActiveServerProvider( Timber.i("Switching to new database, id:$activeServer") cachedServerId = activeServer - return buildDatabase(cachedServerId) + return Room.databaseBuilder( + UApp.applicationContext(), + MetaDatabase::class.java, + METADATA_DB + cachedServerId + ) + .addMigrations(META_MIGRATION_2_1) + .fallbackToDestructiveMigrationOnDowngrade() + .build() } val offlineMetaDatabase: MetaDatabase by lazy { diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/AlbumDao.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/AlbumDao.kt new file mode 100644 index 00000000..59b5e7da --- /dev/null +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/AlbumDao.kt @@ -0,0 +1,68 @@ +package org.moire.ultrasonic.data + +import androidx.room.Dao +import androidx.room.Query +import androidx.room.Transaction +import org.moire.ultrasonic.domain.Album + +@Dao +interface AlbumDao : GenericDao { + /** + * Clear the whole database + */ + @Query("DELETE FROM albums") + fun clear() + + /** + * Get all albums + */ + @Query("SELECT * FROM albums") + fun get(): List + + /** + * Get albums by artist + */ + @Query("SELECT * FROM albums WHERE artistId LIKE :id") + fun byArtist(id: String): List + + /** + * Clear albums by artist + */ + @Query("DELETE FROM albums WHERE artistId LIKE :id") + fun clearByArtist(id: String) + + /** + * FIXME: Make generic + * Upserts (insert or update) an object to the database + * + * @param obj the object to upsert + */ + @Transaction + @JvmSuppressWildcards + fun upsert(obj: Album) { + val id = insertIgnoring(obj) + if (id == -1L) { + update(obj) + } + } + + /** + * Upserts (insert or update) a list of objects + * + * @param objList the object to be upserted + */ + @Transaction + @JvmSuppressWildcards + fun upsert(objList: List) { + val insertResult = insertIgnoring(objList) + val updateList: MutableList = ArrayList() + for (i in insertResult.indices) { + if (insertResult[i] == -1L) { + updateList.add(objList[i]) + } + } + if (updateList.isNotEmpty()) { + update(updateList) + } + } +} diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ArtistsDao.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ArtistsDao.kt index a039c1f7..61571285 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ArtistsDao.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ArtistsDao.kt @@ -43,5 +43,5 @@ interface ArtistsDao { * Get artist by id */ @Query("SELECT * FROM artists WHERE id LIKE :id") - fun get(id: String): Artist + fun get(id: String): Artist? } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/MetaDatabase.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/MetaDatabase.kt index cb58ed96..c423b468 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/MetaDatabase.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/MetaDatabase.kt @@ -1,7 +1,14 @@ package org.moire.ultrasonic.data +import androidx.room.AutoMigration import androidx.room.Database import androidx.room.RoomDatabase +import androidx.room.TypeConverter +import androidx.room.TypeConverters +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase +import org.moire.ultrasonic.domain.Album +import java.util.Date import org.moire.ultrasonic.domain.Artist import org.moire.ultrasonic.domain.Index import org.moire.ultrasonic.domain.MusicFolder @@ -11,14 +18,41 @@ import org.moire.ultrasonic.domain.MusicFolder */ @Database( - entities = [Artist::class, Index::class, MusicFolder::class], - version = 1, + entities = [Artist::class, Album::class, Index::class, MusicFolder::class], + version = 2, + autoMigrations = [ + AutoMigration(from = 1, to = 2) + ], exportSchema = true ) +@TypeConverters(Converters::class) abstract class MetaDatabase : RoomDatabase() { abstract fun artistsDao(): ArtistsDao + abstract fun albumDao(): AlbumDao + abstract fun musicFoldersDao(): MusicFoldersDao abstract fun indexDao(): IndexDao } + +class Converters { + @TypeConverter + fun fromTimestamp(value: Long?): Date? { + return value?.let { Date(it) } + } + + @TypeConverter + fun dateToTimestamp(date: Date?): Long? { + return date?.time + } +} + +// FIXME: Check if correct +val META_MIGRATION_2_1: Migration = object : Migration(2, 1) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL( + "DROP TABLE ServerSetting" + ) + } +} diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/model/AlbumListModel.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/model/AlbumListModel.kt index 605973a2..36665ef5 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/model/AlbumListModel.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/model/AlbumListModel.kt @@ -39,7 +39,7 @@ class AlbumListModel(application: Application) : GenericListModel(application) { id: String, name: String? ) { - list.postValue(musicService.getArtist(id, name, refresh)) + list.postValue(musicService.getAlbumsOfArtist(id, name, refresh)) } override fun load( diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/model/ArtistListModel.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/model/ArtistListModel.kt index 6c2de732..76118894 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/model/ArtistListModel.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/model/ArtistListModel.kt @@ -26,6 +26,7 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import java.text.Collator import org.moire.ultrasonic.domain.ArtistOrIndex import org.moire.ultrasonic.service.MusicService +import org.moire.ultrasonic.util.Settings /** * Provides ViewModel which contains the list of available Artists @@ -58,7 +59,7 @@ class ArtistListModel(application: Application) : GenericListModel(application) val result: List - if (!isOffline && useId3Tags) { + if (!isOffline && useId3Tags || Settings.useId3TagsOffline) { result = musicService.getArtists(refresh) } else { result = musicService.getIndexes(musicFolderId, refresh) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/AutoMediaBrowserCallback.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/AutoMediaBrowserCallback.kt index 6797ee53..8fb80cc1 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/AutoMediaBrowserCallback.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/AutoMediaBrowserCallback.kt @@ -635,7 +635,7 @@ class AutoMediaBrowserCallback(var player: Player, val libraryService: MediaLibr return serviceScope.future { val albums = if (!isOffline && useId3Tags) { - callWithErrorHandling { musicService.getArtist(id, name, false) } + callWithErrorHandling { musicService.getAlbumsOfArtist(id, name, false) } } else { callWithErrorHandling { musicService.getMusicDirectory(id, name, false).getAlbums() diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CachedMusicService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CachedMusicService.kt index 13d2a2b2..cf8e432b 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CachedMusicService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CachedMusicService.kt @@ -43,7 +43,6 @@ class CachedMusicService(private val musicService: MusicService) : MusicService, // Old style TimeLimitedCache private val cachedMusicDirectories: LRUCache> - private val cachedArtist: LRUCache>> private val cachedAlbum: LRUCache> private val cachedUserInfo: LRUCache> private val cachedLicenseValid = TimeLimitedCache(120, TimeUnit.SECONDS) @@ -54,6 +53,7 @@ class CachedMusicService(private val musicService: MusicService) : MusicService, // New Room Database private var cachedArtists = metaDatabase.artistsDao() + private var cachedAlbums = metaDatabase.albumDao() private var cachedIndexes = metaDatabase.indexDao() private val cachedMusicFolders = metaDatabase.musicFoldersDao() @@ -103,10 +103,10 @@ class CachedMusicService(private val musicService: MusicService) : MusicService, var indexes: List - if (musicFolderId == null) { - indexes = cachedIndexes.get() + indexes = if (musicFolderId == null) { + cachedIndexes.get() } else { - indexes = cachedIndexes.get(musicFolderId) + cachedIndexes.get(musicFolderId) } if (indexes.isEmpty()) { @@ -117,17 +117,40 @@ class CachedMusicService(private val musicService: MusicService) : MusicService, return indexes } +// FIXME why commented? +// @Throws(Exception::class) +// override fun getArtist(id: String, refresh: Boolean): Artist? { +// +// // Check if we have a cache hit +// var result = cachedArtists.get(id) +// +// if (result == null || refresh) { +// musicService.getArtists(refresh = true) +// } +// //var result = cachedArtists.get() +// +// if (result.isEmpty()) { +// result = getArtists(refresh) +// // FIXME +// // cachedAlbums.clear() +// cachedArtists.set(result) +// } +// return result +// } +// @Throws(Exception::class) override fun getArtists(refresh: Boolean): List { checkSettingsChanged() if (refresh) { cachedArtists.clear() } + // FIXME unnecessary check var result = cachedArtists.get() if (result.isEmpty()) { result = musicService.getArtists(refresh) - cachedArtist.clear() + // FIXME + // cachedAlbums.clear() cachedArtists.set(result) } return result @@ -150,20 +173,24 @@ class CachedMusicService(private val musicService: MusicService) : MusicService, } @Throws(Exception::class) - override fun getArtist(id: String, name: String?, refresh: Boolean): + override fun getAlbumsOfArtist(id: String, name: String?, refresh: Boolean): List { checkSettingsChanged() - var cache = if (refresh) null else cachedArtist[id] - var dir = cache?.get() - if (dir == null) { - dir = musicService.getArtist(id, name, refresh) - cache = TimeLimitedCache( - Settings.directoryCacheTime.toLong(), TimeUnit.SECONDS - ) - cache.set(dir) - cachedArtist.put(id, cache) + + var result: List + + result = if (refresh) { + cachedAlbums.clearByArtist(id) + listOf() + } else { + cachedAlbums.byArtist(id) } - return dir + + if (result.isEmpty()) { + result = musicService.getAlbumsOfArtist(id, name, refresh) + cachedAlbums.upsert(result) + } + return result } @Throws(Exception::class) @@ -327,6 +354,7 @@ class CachedMusicService(private val musicService: MusicService) : MusicService, // Switch database metaDatabase = activeServerProvider.getActiveMetaDatabase() cachedArtists = metaDatabase.artistsDao() + cachedAlbums = metaDatabase.albumDao() cachedIndexes = metaDatabase.indexDao() // Clear in memory caches @@ -335,7 +363,6 @@ class CachedMusicService(private val musicService: MusicService) : MusicService, cachedPlaylists.clear() cachedGenres.clear() cachedAlbum.clear() - cachedArtist.clear() cachedUserInfo.clear() // Set the cache keys @@ -472,7 +499,6 @@ class CachedMusicService(private val musicService: MusicService) : MusicService, init { cachedMusicDirectories = LRUCache(MUSIC_DIR_CACHE_SIZE) - cachedArtist = LRUCache(MUSIC_DIR_CACHE_SIZE) cachedAlbum = LRUCache(MUSIC_DIR_CACHE_SIZE) cachedUserInfo = LRUCache(MUSIC_DIR_CACHE_SIZE) } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MusicService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MusicService.kt index 02bf0fbb..8869943c 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MusicService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MusicService.kt @@ -59,7 +59,7 @@ interface MusicService { fun getMusicDirectory(id: String, name: String?, refresh: Boolean): MusicDirectory @Throws(Exception::class) - fun getArtist(id: String, name: String?, refresh: Boolean): List + fun getAlbumsOfArtist(id: String, name: String?, refresh: Boolean): List @Throws(Exception::class) fun getAlbum(id: String, name: String?, refresh: Boolean): MusicDirectory diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt index 27d84aa9..3f0d00bf 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt @@ -23,6 +23,7 @@ import java.util.regex.Pattern import org.koin.core.component.KoinComponent import org.koin.core.component.inject import org.moire.ultrasonic.data.ActiveServerProvider +import org.moire.ultrasonic.data.MetaDatabase import org.moire.ultrasonic.domain.Album import org.moire.ultrasonic.domain.Artist import org.moire.ultrasonic.domain.ArtistOrIndex @@ -52,6 +53,14 @@ import timber.log.Timber class OfflineMusicService : MusicService, KoinComponent { private val activeServerProvider: ActiveServerProvider by inject() + private var metaDatabase: MetaDatabase = activeServerProvider.getActiveMetaDatabase() + + // New Room Database + private var cachedArtists = metaDatabase.artistsDao() + private var cachedAlbums = metaDatabase.albumDao() + private var cachedIndexes = metaDatabase.indexDao() + private val cachedMusicFolders = metaDatabase.musicFoldersDao() + override fun getIndexes(musicFolderId: String?, refresh: Boolean): List { val indexes: MutableList = ArrayList() val root = FileUtil.musicDirectory @@ -97,6 +106,16 @@ class OfflineMusicService : MusicService, KoinComponent { return indexes } + @Throws(OfflineException::class) + override fun getArtists(refresh: Boolean): List { + var result = cachedArtists.get() + + if (result.isEmpty()) { + // use indexes? + } + return result + } + /* * Especially when dealing with indexes, this method can return Albums, Entries or a mix of both! */ @@ -450,15 +469,11 @@ class OfflineMusicService : MusicService, KoinComponent { override fun isLicenseValid(): Boolean = true - @Throws(OfflineException::class) - override fun getArtists(refresh: Boolean): List { - throw OfflineException("getArtists isn't available in offline mode") - } - - @Throws(OfflineException::class) - override fun getArtist(id: String, name: String?, refresh: Boolean): + @Throws(Exception::class) + override fun getAlbumsOfArtist(id: String, name: String?, refresh: Boolean): List { - throw OfflineException("getArtist isn't available in offline mode") + // FIXME: Add fallback? + return cachedAlbums.byArtist(id) } @Throws(OfflineException::class) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt index 7c313291..e826f0e0 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt @@ -141,7 +141,7 @@ open class RESTMusicService( } @Throws(Exception::class) - override fun getArtist( + override fun getAlbumsOfArtist( id: String, name: String?, refresh: Boolean diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/DownloadHandler.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/DownloadHandler.kt index 3165b58b..bd128263 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/DownloadHandler.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/DownloadHandler.kt @@ -269,7 +269,7 @@ class DownloadHandler( return } val musicService = getMusicService() - val artist = musicService.getArtist(id, "", false) + val artist = musicService.getAlbumsOfArtist(id, "", false) for ((id1) in artist) { val albumDirectory = musicService.getAlbum( id1, diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Settings.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Settings.kt index 356fca5c..152d7ead 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Settings.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Settings.kt @@ -13,7 +13,6 @@ import androidx.preference.PreferenceManager import java.util.regex.Pattern import org.moire.ultrasonic.R import org.moire.ultrasonic.app.UApp -import org.moire.ultrasonic.data.ActiveServerProvider /** * Contains convenience functions for reading and writing preferences @@ -161,8 +160,7 @@ object Settings { by BooleanSetting(Constants.PREFERENCES_KEY_SHOW_NOW_PLAYING_DETAILS, false) @JvmStatic - var shouldUseId3Tags - by BooleanSetting(Constants.PREFERENCES_KEY_ID3_TAGS, false) + var shouldUseId3Tags by BooleanSetting(Constants.PREFERENCES_KEY_ID3_TAGS, false) var activeServer by IntSetting(Constants.PREFERENCES_KEY_SERVER_INSTANCE, -1) @@ -170,15 +168,8 @@ object Settings { var firstRunExecuted by BooleanSetting(Constants.PREFERENCES_KEY_FIRST_RUN_EXECUTED, false) - val shouldShowArtistPicture: Boolean - get() { - val preferences = preferences - val isOffline = ActiveServerProvider.isOffline() - val isId3Enabled = preferences.getBoolean(Constants.PREFERENCES_KEY_ID3_TAGS, false) - val shouldShowArtistPicture = - preferences.getBoolean(Constants.PREFERENCES_KEY_SHOW_ARTIST_PICTURE, false) - return !isOffline && isId3Enabled && shouldShowArtistPicture - } + val shouldShowArtistPicture + by BooleanSetting(Constants.PREFERENCES_KEY_SHOW_ARTIST_PICTURE, false) @JvmStatic var chatRefreshInterval by StringIntSetting( @@ -253,6 +244,8 @@ object Settings { var useHwOffload by BooleanSetting(Constants.PREFERENCES_KEY_HARDWARE_OFFLOAD, false) + var useId3TagsOffline = true + // TODO: Remove in December 2022 fun migrateFeatureStorage() { val sp = appContext.getSharedPreferences("feature_flags", Context.MODE_PRIVATE)