From 3a3bd10fdb1441f3c633c426986c8fddd4f62640 Mon Sep 17 00:00:00 2001 From: tzugen Date: Mon, 18 Apr 2022 07:12:31 +0200 Subject: [PATCH 01/17] Add AlbumDao, rename getArtist to getAlbumsOfArtist --- .../org/moire/ultrasonic/domain/Album.kt | 2 + .../2.json | 474 ++++++++++++++++++ .../ultrasonic/adapters/ArtistRowBinder.kt | 9 +- .../ultrasonic/data/ActiveServerProvider.kt | 9 +- .../org/moire/ultrasonic/data/AlbumDao.kt | 68 +++ .../org/moire/ultrasonic/data/ArtistsDao.kt | 2 +- .../org/moire/ultrasonic/data/MetaDatabase.kt | 38 +- .../moire/ultrasonic/model/AlbumListModel.kt | 2 +- .../moire/ultrasonic/model/ArtistListModel.kt | 3 +- .../playback/AutoMediaBrowserCallback.kt | 2 +- .../ultrasonic/service/CachedMusicService.kt | 62 ++- .../moire/ultrasonic/service/MusicService.kt | 2 +- .../ultrasonic/service/OfflineMusicService.kt | 31 +- .../ultrasonic/service/RESTMusicService.kt | 2 +- .../ultrasonic/subsonic/DownloadHandler.kt | 2 +- .../org/moire/ultrasonic/util/Settings.kt | 17 +- 16 files changed, 676 insertions(+), 49 deletions(-) create mode 100644 ultrasonic/schemas/org.moire.ultrasonic.data.MetaDatabase/2.json create mode 100644 ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/AlbumDao.kt 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) From ee67f4c7446e3f16ac7ac10a48c952515e66fd1a Mon Sep 17 00:00:00 2001 From: tzugen Date: Mon, 18 Apr 2022 07:20:19 +0200 Subject: [PATCH 02/17] Add track Dao --- .../org/moire/ultrasonic/data/MetaDatabase.kt | 2 ++ .../org/moire/ultrasonic/data/TrackDao.kt | 25 +++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/TrackDao.kt 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 c423b468..afbf8e76 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/MetaDatabase.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/MetaDatabase.kt @@ -31,6 +31,8 @@ abstract class MetaDatabase : RoomDatabase() { abstract fun albumDao(): AlbumDao + abstract fun trackDao(): AlbumDao + abstract fun musicFoldersDao(): MusicFoldersDao abstract fun indexDao(): IndexDao diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/TrackDao.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/TrackDao.kt new file mode 100644 index 00000000..662ba1b9 --- /dev/null +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/TrackDao.kt @@ -0,0 +1,25 @@ +package org.moire.ultrasonic.data + +import androidx.room.Query +import org.moire.ultrasonic.domain.MusicDirectory + +interface TrackDao { + /** + * Clear the whole database + */ + @Query("DELETE FROM tracks") + fun clear() + + /** + * Get all albums + */ + @Query("SELECT * FROM tracks") + fun get(): List + + /** + * Get albums by artist + */ + @Query("SELECT * FROM tracks WHERE albumId LIKE :id") + fun byAlbum(id: String): List + +} \ No newline at end of file From 8490f7115d3fb97d5e153f0bc2a2f24c20e5a993 Mon Sep 17 00:00:00 2001 From: tzugen Date: Mon, 18 Apr 2022 07:31:06 +0200 Subject: [PATCH 03/17] Add Offline support for tracks --- .../org/moire/ultrasonic/domain/Track.kt | 2 +- .../ultrasonic/data/ActiveServerProvider.kt | 21 +++++++------------ .../org/moire/ultrasonic/data/AlbumDao.kt | 2 +- .../data/{ArtistsDao.kt => ArtistDao.kt} | 2 +- .../org/moire/ultrasonic/data/BasicDaos.kt | 1 + .../org/moire/ultrasonic/data/MetaDatabase.kt | 18 +++++++++------- .../org/moire/ultrasonic/data/TrackDao.kt | 15 +++++++------ .../fragment/TrackCollectionFragment.kt | 2 +- .../ultrasonic/service/CachedMusicService.kt | 5 +++-- .../ultrasonic/service/OfflineMusicService.kt | 16 +++++++++++--- .../org/moire/ultrasonic/util/Constants.kt | 1 + .../org/moire/ultrasonic/util/Settings.kt | 6 ++++-- ultrasonic/src/main/res/values/strings.xml | 2 ++ ultrasonic/src/main/res/xml/settings.xml | 6 ++++++ 14 files changed, 61 insertions(+), 38 deletions(-) rename ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/{ArtistsDao.kt => ArtistDao.kt} (97%) diff --git a/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/Track.kt b/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/Track.kt index 3a57296f..4c737bec 100644 --- a/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/Track.kt +++ b/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/Track.kt @@ -5,7 +5,7 @@ import androidx.room.PrimaryKey import java.io.Serializable import java.util.Date -@Entity +@Entity(tableName = "tracks") data class Track( @PrimaryKey override var id: String, override var parent: String? = null, 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 54dba5dd..2483a7e8 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ActiveServerProvider.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ActiveServerProvider.kt @@ -24,6 +24,7 @@ class ActiveServerProvider( private val repository: ServerSettingDao ) : CoroutineScope by CoroutineScope(Dispatchers.IO) { private var cachedServer: ServerSetting? = null + // FIXME cach never set private var cachedDatabase: MetaDatabase? = null private var cachedServerId: Int? = null @@ -110,27 +111,21 @@ class ActiveServerProvider( Timber.i("Switching to new database, id:$activeServer") cachedServerId = activeServer - return Room.databaseBuilder( - UApp.applicationContext(), - MetaDatabase::class.java, - METADATA_DB + cachedServerId - ) - .addMigrations(META_MIGRATION_2_1) - .fallbackToDestructiveMigrationOnDowngrade() - .build() + cachedDatabase = initDatabase(activeServer) + + return cachedDatabase!! } 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( UApp.applicationContext(), MetaDatabase::class.java, - METADATA_DB + id - ) - .fallbackToDestructiveMigration() + METADATA_DB + serverId + ).fallbackToDestructiveMigration() .build() } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/AlbumDao.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/AlbumDao.kt index 59b5e7da..8eccef40 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/AlbumDao.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/AlbumDao.kt @@ -32,7 +32,7 @@ interface AlbumDao : GenericDao { fun clearByArtist(id: String) /** - * FIXME: Make generic + * TODO: Make generic * Upserts (insert or update) an object to the database * * @param obj the object to upsert diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ArtistsDao.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ArtistDao.kt similarity index 97% rename from ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ArtistsDao.kt rename to ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ArtistDao.kt index 61571285..9b2dc395 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ArtistsDao.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ArtistDao.kt @@ -7,7 +7,7 @@ import androidx.room.Query import org.moire.ultrasonic.domain.Artist @Dao -interface ArtistsDao { +interface ArtistDao { /** * Insert a list in the database. If the item already exists, replace it. * diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/BasicDaos.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/BasicDaos.kt index 1f3e77f5..9078f911 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/BasicDaos.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/BasicDaos.kt @@ -53,6 +53,7 @@ interface IndexDao : GenericDao { fun get(musicFolderId: String): List /** + * TODO: Make generic * Upserts (insert or update) an object to the database * * @param obj the object to upsert 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 afbf8e76..dc377118 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/MetaDatabase.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/MetaDatabase.kt @@ -1,33 +1,36 @@ 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.Album import org.moire.ultrasonic.domain.Artist import org.moire.ultrasonic.domain.Index import org.moire.ultrasonic.domain.MusicFolder +import org.moire.ultrasonic.domain.Track /** * This database is used to store and cache the ID3 metadata */ @Database( - entities = [Artist::class, Album::class, Index::class, MusicFolder::class], - version = 2, - autoMigrations = [ - AutoMigration(from = 1, to = 2) + entities = [ + Artist::class, + Album::class, + Track::class, + Index::class, + MusicFolder::class ], + version = 2, exportSchema = true ) @TypeConverters(Converters::class) abstract class MetaDatabase : RoomDatabase() { - abstract fun artistsDao(): ArtistsDao + abstract fun artistDao(): ArtistDao abstract fun albumDao(): AlbumDao @@ -50,7 +53,6 @@ class Converters { } } -// FIXME: Check if correct val META_MIGRATION_2_1: Migration = object : Migration(2, 1) { override fun migrate(database: SupportSQLiteDatabase) { database.execSQL( diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/TrackDao.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/TrackDao.kt index 662ba1b9..7700d1a9 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/TrackDao.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/TrackDao.kt @@ -1,9 +1,13 @@ package org.moire.ultrasonic.data +import androidx.room.Dao +import androidx.room.Entity 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 { /** * Clear the whole database */ @@ -14,12 +18,11 @@ interface TrackDao { * Get all albums */ @Query("SELECT * FROM tracks") - fun get(): List + fun get(): List /** * Get albums by artist */ @Query("SELECT * FROM tracks WHERE albumId LIKE :id") - fun byAlbum(id: String): List - -} \ No newline at end of file + fun byAlbum(id: String): List +} diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt index 3c9fcb39..74b00ae5 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt @@ -600,7 +600,7 @@ open class TrackCollectionFragment : MultiListFragment() { listModel.getRandom(albumListSize) } else { setTitle(name) - if (!isOffline() && Settings.shouldUseId3Tags) { + if (!isOffline() && Settings.shouldUseId3Tags || Settings.useId3TagsOffline) { if (isAlbum) { listModel.getAlbum(refresh2, id!!, name) } else { 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 cf8e432b..9b2775dd 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CachedMusicService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CachedMusicService.kt @@ -52,8 +52,9 @@ class CachedMusicService(private val musicService: MusicService) : MusicService, private val cachedGenres = TimeLimitedCache>(10 * 3600, TimeUnit.SECONDS) // New Room Database - private var cachedArtists = metaDatabase.artistsDao() + private var cachedArtists = metaDatabase.artistDao() private var cachedAlbums = metaDatabase.albumDao() + private var cachedTracks = metaDatabase.trackDao() private var cachedIndexes = metaDatabase.indexDao() 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)) { // Switch database metaDatabase = activeServerProvider.getActiveMetaDatabase() - cachedArtists = metaDatabase.artistsDao() + cachedArtists = metaDatabase.artistDao() cachedAlbums = metaDatabase.albumDao() cachedIndexes = metaDatabase.indexDao() 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 3f0d00bf..11eaeb10 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt @@ -44,6 +44,7 @@ import org.moire.ultrasonic.domain.Track import org.moire.ultrasonic.domain.UserInfo import org.moire.ultrasonic.util.AbstractFile import org.moire.ultrasonic.util.Constants +import org.moire.ultrasonic.util.EntryByDiscAndTrackComparator import org.moire.ultrasonic.util.FileUtil import org.moire.ultrasonic.util.Storage import org.moire.ultrasonic.util.Util.safeClose @@ -56,8 +57,9 @@ class OfflineMusicService : MusicService, KoinComponent { private var metaDatabase: MetaDatabase = activeServerProvider.getActiveMetaDatabase() // New Room Database - private var cachedArtists = metaDatabase.artistsDao() + private var cachedArtists = metaDatabase.artistDao() private var cachedAlbums = metaDatabase.albumDao() + private var cachedTracks = metaDatabase.trackDao() private var cachedIndexes = metaDatabase.indexDao() private val cachedMusicFolders = metaDatabase.musicFoldersDao() @@ -108,7 +110,7 @@ class OfflineMusicService : MusicService, KoinComponent { @Throws(OfflineException::class) override fun getArtists(refresh: Boolean): List { - var result = cachedArtists.get() + val result = cachedArtists.get() if (result.isEmpty()) { // use indexes? @@ -478,7 +480,15 @@ class OfflineMusicService : MusicService, KoinComponent { @Throws(OfflineException::class) 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) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Constants.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Constants.kt index 75b349b4..9f44202b 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Constants.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Constants.kt @@ -86,6 +86,7 @@ object Constants { const val PREFERENCES_KEY_INCREMENT_TIME = "incrementTime" const val PREFERENCES_KEY_SHOW_NOW_PLAYING_DETAILS = "showNowPlayingDetails" 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_CHAT_REFRESH_INTERVAL = "chatRefreshInterval" const val PREFERENCES_KEY_DIRECTORY_CACHE_TIME = "directoryCacheTime" 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 152d7ead..ce559913 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Settings.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Settings.kt @@ -130,6 +130,7 @@ object Settings { @JvmStatic var mediaButtonsEnabled by BooleanSetting(Constants.PREFERENCES_KEY_MEDIA_BUTTONS, true) + var resumePlayOnHeadphonePlug by BooleanSetting(R.string.setting_keys_resume_play_on_headphones_plug, true) @@ -162,6 +163,9 @@ object Settings { @JvmStatic 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 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 useId3TagsOffline = true - // TODO: Remove in December 2022 fun migrateFeatureStorage() { val sp = appContext.getSharedPreferences("feature_flags", Context.MODE_PRIVATE) diff --git a/ultrasonic/src/main/res/values/strings.xml b/ultrasonic/src/main/res/values/strings.xml index 4ace333f..b4e81c76 100644 --- a/ultrasonic/src/main/res/values/strings.xml +++ b/ultrasonic/src/main/res/values/strings.xml @@ -316,6 +316,8 @@ Show details in Now Playing Browse Using ID3 Tags Use ID3 tag methods instead of file system based methods + Use ID3 method also when offline + (Experimental) Show artist picture in artist list Displays the artist picture in the artist list if available Video diff --git a/ultrasonic/src/main/res/xml/settings.xml b/ultrasonic/src/main/res/xml/settings.xml index 04f81008..8cd5fea1 100644 --- a/ultrasonic/src/main/res/xml/settings.xml +++ b/ultrasonic/src/main/res/xml/settings.xml @@ -59,6 +59,12 @@ a:summary="@string/settings.use_id3_summary" a:title="@string/settings.use_id3" app:iconSpaceReserved="false"/> + Date: Thu, 16 Jun 2022 19:51:45 +0200 Subject: [PATCH 04/17] Add code to Downloader --- .../moire/ultrasonic/service/Downloader.kt | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/Downloader.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/Downloader.kt index 100a8fdd..38b7fdb9 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/Downloader.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/Downloader.kt @@ -457,8 +457,10 @@ class Downloader( ) } - if (downloadFile.track.artistId != null) { - cacheMetadata(downloadFile.track.artistId!!) + try { + downloadFile.track.cacheMetadata() + } catch (ignore: Exception) { + Timber.w(ignore) } downloadAndSaveCoverArt() @@ -510,13 +512,13 @@ class Downloader( return String.format(Locale.ROOT, "DownloadTask (%s)", downloadFile.track) } - private fun cacheMetadata(artistId: String) { - // TODO: Right now it's caching the track artist. - // Once the albums are cached in db, we should retrieve the album, - // and then cache the album artist. - if (artistId.isEmpty()) return - var artist: Artist? = - activeServerProvider.getActiveMetaDatabase().artistsDao().get(artistId) + private fun Track.cacheMetadata() { + if (artistId.isNullOrEmpty()) return + + val onlineDB = activeServerProvider.getActiveMetaDatabase() + val offlineDB = activeServerProvider.offlineMetaDatabase + + var artist: Artist? = onlineDB.artistDao().get(artistId!!) // If we are downloading a new album, and the user has not visited the Artists list // recently, then the artist won't be in the database. @@ -527,10 +529,23 @@ class Downloader( } } - // If we have found an artist, catch it. + // If we have found an artist, cache it. if (artist != null) { - activeServerProvider.offlineMetaDatabase.artistsDao().insert(artist) + offlineDB.artistDao().insert(artist) } + + // Now cache the album + if (albumId?.isNotEmpty() == true) { + val albums = musicService.getAlbumsOfArtist(artistId!!, null, false) + val album = albums.find { it.id == albumId } + + if (album != null) { + offlineDB.albumDao().insert(album) + } + } + + // Now cache the track data + offlineDB.trackDao().insert(this) } private fun downloadAndSaveCoverArt() { From 241e51015f1c485f553a4d58d543e9543d89c7f7 Mon Sep 17 00:00:00 2001 From: tzugen Date: Sun, 19 Jun 2022 12:59:26 +0200 Subject: [PATCH 05/17] Clean & formatting Update room 2.4.0 -> 2.4.2 --- .../moire/ultrasonic/adapters/BaseAdapter.kt | 13 ++++--- .../ultrasonic/data/ActiveServerProvider.kt | 1 - .../fragment/TrackCollectionFragment.kt | 36 ++++++++++--------- .../ultrasonic/service/CachedMusicService.kt | 31 ++++------------ .../moire/ultrasonic/service/Downloader.kt | 1 + .../ultrasonic/service/OfflineMusicService.kt | 8 ++--- 6 files changed, 37 insertions(+), 53 deletions(-) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/BaseAdapter.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/BaseAdapter.kt index 55f17f0c..4758fd53 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/BaseAdapter.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/BaseAdapter.kt @@ -59,7 +59,7 @@ class BaseAdapter : MultiTypeAdapter(), FastScrollRecyclerView throw IllegalAccessException("You must use submitList() to add data to the Adapter") } - var mDiffer: AsyncListDiffer = AsyncListDiffer( + private var mDiffer: AsyncListDiffer = AsyncListDiffer( AdapterListUpdateCallback(this), AsyncDifferConfig.Builder(diffCallback).build() ) @@ -182,12 +182,11 @@ class BaseAdapter : MultiTypeAdapter(), FastScrollRecyclerView // Select them all getCurrentList().mapNotNullTo( - selectedSet, - { entry -> - // Exclude any -1 ids, eg. headers and other UI elements - entry.longId.takeIf { it != -1L } - } - ) + selectedSet + ) { entry -> + // Exclude any -1 ids, eg. headers and other UI elements + entry.longId.takeIf { it != -1L } + } return selectedSet.count() } 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 2483a7e8..1cc760f7 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ActiveServerProvider.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ActiveServerProvider.kt @@ -24,7 +24,6 @@ class ActiveServerProvider( private val repository: ServerSettingDao ) : CoroutineScope by CoroutineScope(Dispatchers.IO) { private var cachedServer: ServerSetting? = null - // FIXME cach never set private var cachedDatabase: MetaDatabase? = null private var cachedServerId: Int? = null diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt index 74b00ae5..b9b9f549 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt @@ -47,6 +47,7 @@ import org.moire.ultrasonic.util.Constants import org.moire.ultrasonic.util.EntryByDiscAndTrackComparator import org.moire.ultrasonic.util.Settings import org.moire.ultrasonic.util.Util +import timber.log.Timber /** * Displays a group of tracks, eg. the songs of an album, of a playlist etc. @@ -61,11 +62,11 @@ import org.moire.ultrasonic.util.Util open class TrackCollectionFragment : MultiListFragment() { private var albumButtons: View? = null - internal var selectButton: ImageView? = null + private var selectButton: ImageView? = null internal var playNowButton: ImageView? = null private var playNextButton: ImageView? = null private var playLastButton: ImageView? = null - internal var pinButton: ImageView? = null + private var pinButton: ImageView? = null private var unpinButton: ImageView? = null private var downloadButton: ImageView? = null private var deleteButton: ImageView? = null @@ -144,11 +145,10 @@ open class TrackCollectionFragment : MultiListFragment() { // Update the buttons when the selection has changed viewAdapter.selectionRevision.observe( - viewLifecycleOwner, - { - enableButtons() - } - ) + viewLifecycleOwner + ) { + enableButtons() + } } internal open fun setupButtons(view: View) { @@ -267,10 +267,10 @@ open class TrackCollectionFragment : MultiListFragment() { private val childCount: Int get() { val count = viewAdapter.getCurrentList().count() - if (listModel.showHeader) { - return count - 1 + return if (listModel.showHeader) { + count - 1 } else { - return count + count } } @@ -320,13 +320,13 @@ open class TrackCollectionFragment : MultiListFragment() { } as List } - internal fun selectAllOrNone() { + private fun selectAllOrNone() { val someUnselected = viewAdapter.selectedSet.size < childCount selectAll(someUnselected, true) } - internal fun selectAll(selected: Boolean, toast: Boolean) { + private fun selectAll(selected: Boolean, toast: Boolean) { var selectedCount = viewAdapter.selectedSet.size * -1 selectedCount += viewAdapter.setSelectionStatusOfAll(selected) @@ -366,7 +366,7 @@ open class TrackCollectionFragment : MultiListFragment() { deleteButton?.isVisible = (enabled && deleteEnabled) } - internal fun downloadBackground(save: Boolean) { + private fun downloadBackground(save: Boolean) { var songs = getSelectedSongs() if (songs.isEmpty()) { @@ -426,6 +426,7 @@ open class TrackCollectionFragment : MultiListFragment() { override val defaultObserver: (List) -> Unit = { + Timber.i("Received list") val entryList: MutableList = it.toMutableList() if (listModel.currentListIsSortable && Settings.shouldSortByDisc) { @@ -454,9 +455,9 @@ open class TrackCollectionFragment : MultiListFragment() { moreButton!!.visibility = View.GONE } else { moreButton!!.visibility = View.VISIBLE - if (arguments?.getInt(Constants.INTENT_RANDOM, 0) ?: 0 > 0) { + if ((arguments?.getInt(Constants.INTENT_RANDOM, 0) ?: 0) > 0) { moreRandomTracks() - } else if (arguments?.getString(Constants.INTENT_GENRE_NAME, "") ?: "" != "") { + } else if ((arguments?.getString(Constants.INTENT_GENRE_NAME, "") ?: "") != "") { moreSongsForGenre() } } @@ -497,6 +498,8 @@ open class TrackCollectionFragment : MultiListFragment() { } listModel.currentListIsSortable = true + + Timber.i("Processed list") } private fun moreSongsForGenre(args: Bundle = requireArguments()) { @@ -556,6 +559,7 @@ open class TrackCollectionFragment : MultiListFragment() { args: Bundle?, refresh: Boolean ): LiveData> { + Timber.i("Starting gathering track collection data...") if (args == null) return listModel.currentList val id = args.getString(Constants.INTENT_ID) val isAlbum = args.getBoolean(Constants.INTENT_IS_ALBUM, false) @@ -669,7 +673,7 @@ open class TrackCollectionFragment : MultiListFragment() { return true } - internal fun getClickedSong(item: MusicDirectory.Child): List { + private fun getClickedSong(item: MusicDirectory.Child): List { // This can probably be done better return viewAdapter.getCurrentList().mapNotNull { if (it is Track && (it.id == item.id)) 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 9b2775dd..07cbf383 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CachedMusicService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CachedMusicService.kt @@ -54,7 +54,6 @@ class CachedMusicService(private val musicService: MusicService) : MusicService, // New Room Database private var cachedArtists = metaDatabase.artistDao() private var cachedAlbums = metaDatabase.albumDao() - private var cachedTracks = metaDatabase.trackDao() private var cachedIndexes = metaDatabase.indexDao() private val cachedMusicFolders = metaDatabase.musicFoldersDao() @@ -118,40 +117,18 @@ 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) - // FIXME - // cachedAlbums.clear() cachedArtists.set(result) } return result @@ -173,6 +150,10 @@ class CachedMusicService(private val musicService: MusicService) : MusicService, return dir } + /* + * Retrieves all albums of the provided artist. + * Cached in the RoomDB + */ @Throws(Exception::class) override fun getAlbumsOfArtist(id: String, name: String?, refresh: Boolean): List { diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/Downloader.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/Downloader.kt index 38b7fdb9..d7cad0e7 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/Downloader.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/Downloader.kt @@ -536,6 +536,7 @@ class Downloader( // Now cache the album if (albumId?.isNotEmpty() == true) { + // This is a cached call val albums = musicService.getAlbumsOfArtist(artistId!!, null, false) val album = albums.find { it.id == albumId } 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 11eaeb10..97530118 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt @@ -60,8 +60,6 @@ class OfflineMusicService : MusicService, KoinComponent { private var cachedArtists = metaDatabase.artistDao() private var cachedAlbums = metaDatabase.albumDao() private var cachedTracks = metaDatabase.trackDao() - private var cachedIndexes = metaDatabase.indexDao() - private val cachedMusicFolders = metaDatabase.musicFoldersDao() override fun getIndexes(musicFolderId: String?, refresh: Boolean): List { val indexes: MutableList = ArrayList() @@ -474,20 +472,22 @@ class OfflineMusicService : MusicService, KoinComponent { @Throws(Exception::class) override fun getAlbumsOfArtist(id: String, name: String?, refresh: Boolean): List { - // FIXME: Add fallback? return cachedAlbums.byArtist(id) } @Throws(OfflineException::class) override fun getAlbum(id: String, name: String?, refresh: Boolean): MusicDirectory { + Timber.i("Starting album query...") + val list = cachedTracks .byAlbum(id) .sortedWith(EntryByDiscAndTrackComparator()) - var dir = MusicDirectory() + val dir = MusicDirectory() dir.addAll(list) + Timber.i("Returning query.") return dir } From 177329abcf28ee8949af37bf86e1aec58831be71 Mon Sep 17 00:00:00 2001 From: tzugen Date: Sun, 19 Jun 2022 13:24:05 +0200 Subject: [PATCH 06/17] Add Migration --- .../ultrasonic/data/ActiveServerProvider.kt | 2 +- .../org/moire/ultrasonic/data/MetaDatabase.kt | 23 ++++++++----------- 2 files changed, 11 insertions(+), 14 deletions(-) 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 1cc760f7..cacecd14 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ActiveServerProvider.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ActiveServerProvider.kt @@ -124,7 +124,7 @@ class ActiveServerProvider( UApp.applicationContext(), MetaDatabase::class.java, METADATA_DB + serverId - ).fallbackToDestructiveMigration() + ).fallbackToDestructiveMigrationOnDowngrade() .build() } 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 dc377118..c50629fb 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/MetaDatabase.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/MetaDatabase.kt @@ -1,11 +1,10 @@ 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 java.util.Date import org.moire.ultrasonic.domain.Album import org.moire.ultrasonic.domain.Artist @@ -25,8 +24,14 @@ import org.moire.ultrasonic.domain.Track Index::class, MusicFolder::class ], - version = 2, - exportSchema = true + autoMigrations = [ + AutoMigration( + from = 1, + to = 2 + ), + ], + exportSchema = true, + version = 2 ) @TypeConverters(Converters::class) abstract class MetaDatabase : RoomDatabase() { @@ -34,7 +39,7 @@ abstract class MetaDatabase : RoomDatabase() { abstract fun albumDao(): AlbumDao - abstract fun trackDao(): AlbumDao + abstract fun trackDao(): TrackDao abstract fun musicFoldersDao(): MusicFoldersDao @@ -52,11 +57,3 @@ class Converters { return date?.time } } - -val META_MIGRATION_2_1: Migration = object : Migration(2, 1) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL( - "DROP TABLE ServerSetting" - ) - } -} From ad54db5bcba117be6228d777c47e95d60e0f8126 Mon Sep 17 00:00:00 2001 From: tzugen Date: Mon, 4 Jul 2022 17:31:58 +0200 Subject: [PATCH 07/17] Make Ids composite of Item Id + Server Id --- .../org/moire/ultrasonic/domain/Album.kt | 14 +- .../org/moire/ultrasonic/domain/Artist.kt | 16 +- .../moire/ultrasonic/domain/ArtistOrIndex.kt | 17 +- .../org/moire/ultrasonic/domain/Index.kt | 16 +- .../moire/ultrasonic/domain/MusicDirectory.kt | 8 + .../moire/ultrasonic/domain/MusicFolder.kt | 17 +- .../org/moire/ultrasonic/domain/Track.kt | 14 +- .../3.json | 514 ++++++++++++++++++ .../ultrasonic/data/ActiveServerProvider.kt | 11 +- .../org/moire/ultrasonic/data/MetaDatabase.kt | 28 +- .../ultrasonic/domain/APIAlbumConverter.kt | 18 +- .../ultrasonic/domain/APIArtistConverter.kt | 21 +- .../ultrasonic/domain/APIBookmarkConverter.kt | 15 +- .../ultrasonic/domain/APIIndexesConverter.kt | 53 +- .../domain/APIMusicDirectoryConverter.kt | 20 +- .../domain/APIMusicFolderConverter.kt | 21 +- .../ultrasonic/domain/APIPlaylistConverter.kt | 21 +- .../ultrasonic/domain/APISearchConverter.kt | 27 +- .../ultrasonic/domain/APIShareConverter.kt | 15 +- .../ultrasonic/service/OfflineMusicService.kt | 4 +- .../ultrasonic/service/RESTMusicService.kt | 52 +- .../domain/APIAlbumConverterTest.kt | 13 +- .../domain/APIArtistConverterTest.kt | 8 +- .../domain/APIBookmarkConverterTest.kt | 15 +- .../domain/APIMusicDirectoryConverterTest.kt | 15 +- .../domain/APIMusicFolderConverterTest.kt | 6 +- .../domain/APIPlaylistConverterTest.kt | 9 +- .../domain/APISearchConverterTest.kt | 23 +- .../org/moire/ultrasonic/domain/BaseTest.kt | 12 + 29 files changed, 870 insertions(+), 153 deletions(-) create mode 100644 ultrasonic/schemas/org.moire.ultrasonic.data.MetaDatabase/3.json create mode 100644 ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/BaseTest.kt 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 97e5674e..1ddf435b 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,12 +1,22 @@ +/* + * Album.kt + * Copyright (C) 2009-2022 Ultrasonic developers + * + * Distributed under terms of the GNU GPLv3 license. + */ + package org.moire.ultrasonic.domain +import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey import java.util.Date -@Entity(tableName = "albums") +@Entity(tableName = "albums", primaryKeys = ["id", "serverId"]) data class Album( - @PrimaryKey override var id: String, + override var id: String, + @ColumnInfo(defaultValue = "-1") + override var serverId: Int = -1, override var parent: String? = null, override var album: String? = null, override var title: String? = null, diff --git a/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/Artist.kt b/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/Artist.kt index 3da622c6..b9d70777 100644 --- a/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/Artist.kt +++ b/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/Artist.kt @@ -1,14 +1,24 @@ +/* + * Artist.kt + * Copyright (C) 2009-2022 Ultrasonic developers + * + * Distributed under terms of the GNU GPLv3 license. + */ + package org.moire.ultrasonic.domain +import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey -@Entity(tableName = "artists") +@Entity(tableName = "artists", primaryKeys = ["id", "serverId"]) data class Artist( - @PrimaryKey override var id: String, + override var id: String, + @ColumnInfo(defaultValue = "-1") + override var serverId: Int = -1, override var name: String? = null, override var index: String? = null, override var coverArt: String? = null, override var albumCount: Long? = null, override var closeness: Int = 0 -) : ArtistOrIndex(id) +) : ArtistOrIndex(id, serverId) diff --git a/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/ArtistOrIndex.kt b/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/ArtistOrIndex.kt index 602cae66..d61dc17c 100644 --- a/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/ArtistOrIndex.kt +++ b/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/ArtistOrIndex.kt @@ -1,3 +1,10 @@ +/* + * ArtistOrIndex.kt + * Copyright (C) 2009-2022 Ultrasonic developers + * + * Distributed under terms of the GNU GPLv3 license. + */ + package org.moire.ultrasonic.domain import androidx.room.Ignore @@ -6,6 +13,8 @@ abstract class ArtistOrIndex( @Ignore override var id: String, @Ignore + open var serverId: Int, + @Ignore override var name: String? = null, @Ignore open var index: String? = null, @@ -18,15 +27,15 @@ abstract class ArtistOrIndex( ) : GenericEntry() { fun compareTo(other: ArtistOrIndex): Int { - when { + return when { this.closeness == other.closeness -> { - return 0 + 0 } this.closeness > other.closeness -> { - return -1 + -1 } else -> { - return 1 + 1 } } } diff --git a/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/Index.kt b/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/Index.kt index e56399b1..9d9431e7 100644 --- a/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/Index.kt +++ b/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/Index.kt @@ -1,15 +1,25 @@ +/* + * Index.kt + * Copyright (C) 2009-2022 Ultrasonic developers + * + * Distributed under terms of the GNU GPLv3 license. + */ + package org.moire.ultrasonic.domain +import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey -@Entity(tableName = "indexes") +@Entity(tableName = "indexes", primaryKeys = ["id", "serverId"]) data class Index( - @PrimaryKey override var id: String, + override var id: String, + @ColumnInfo(defaultValue = "-1") + override var serverId: Int = -1, override var name: String? = null, override var index: String? = null, override var coverArt: String? = null, override var albumCount: Long? = null, override var closeness: Int = 0, var musicFolderId: String? = null -) : ArtistOrIndex(id) +) : ArtistOrIndex(id, serverId) diff --git a/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/MusicDirectory.kt b/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/MusicDirectory.kt index 1d937700..23ba8663 100644 --- a/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/MusicDirectory.kt +++ b/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/MusicDirectory.kt @@ -1,3 +1,10 @@ +/* + * MusicDirectory.kt + * Copyright (C) 2009-2022 Ultrasonic developers + * + * Distributed under terms of the GNU GPLv3 license. + */ + package org.moire.ultrasonic.domain import java.util.Date @@ -31,6 +38,7 @@ class MusicDirectory : ArrayList() { abstract class Child : GenericEntry() { abstract override var id: String + abstract var serverId: Int abstract var parent: String? abstract var isDirectory: Boolean abstract var album: String? diff --git a/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/MusicFolder.kt b/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/MusicFolder.kt index c4f94fb6..14873928 100644 --- a/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/MusicFolder.kt +++ b/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/MusicFolder.kt @@ -1,13 +1,22 @@ +/* + * MusicFolder.kt + * Copyright (C) 2009-2022 Ultrasonic developers + * + * Distributed under terms of the GNU GPLv3 license. + */ + package org.moire.ultrasonic.domain +import androidx.room.ColumnInfo import androidx.room.Entity -import androidx.room.PrimaryKey /** * Represents a top level directory in which music or other media is stored. */ -@Entity(tableName = "music_folders") +@Entity(tableName = "music_folders", primaryKeys = ["id", "serverId"]) data class MusicFolder( - @PrimaryKey override val id: String, - override val name: String + override val id: String, + override val name: String, + @ColumnInfo(defaultValue = "-1") + var serverId: Int ) : GenericEntry() diff --git a/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/Track.kt b/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/Track.kt index 4c737bec..9a17edee 100644 --- a/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/Track.kt +++ b/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/Track.kt @@ -1,13 +1,23 @@ +/* + * Track.kt + * Copyright (C) 2009-2022 Ultrasonic developers + * + * Distributed under terms of the GNU GPLv3 license. + */ + package org.moire.ultrasonic.domain +import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey import java.io.Serializable import java.util.Date -@Entity(tableName = "tracks") +@Entity(tableName = "tracks", primaryKeys = ["id", "serverId"]) data class Track( - @PrimaryKey override var id: String, + override var id: String, + @ColumnInfo(defaultValue = "-1") + override var serverId: Int = -1, override var parent: String? = null, override var isDirectory: Boolean = false, override var title: String? = null, diff --git a/ultrasonic/schemas/org.moire.ultrasonic.data.MetaDatabase/3.json b/ultrasonic/schemas/org.moire.ultrasonic.data.MetaDatabase/3.json new file mode 100644 index 00000000..e0df55f4 --- /dev/null +++ b/ultrasonic/schemas/org.moire.ultrasonic.data.MetaDatabase/3.json @@ -0,0 +1,514 @@ +{ + "formatVersion": 1, + "database": { + "version": 3, + "identityHash": "95e83d6663a862c03ac46f9567453ded", + "entities": [ + { + "tableName": "artists", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `serverId` INTEGER NOT NULL DEFAULT -1, `name` TEXT, `index` TEXT, `coverArt` TEXT, `albumCount` INTEGER, `closeness` INTEGER NOT NULL, PRIMARY KEY(`id`, `serverId`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serverId", + "columnName": "serverId", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "-1" + }, + { + "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", + "serverId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "albums", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `serverId` INTEGER NOT NULL DEFAULT -1, `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`, `serverId`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serverId", + "columnName": "serverId", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "-1" + }, + { + "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", + "serverId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "tracks", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `serverId` INTEGER NOT NULL DEFAULT -1, `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`, `serverId`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serverId", + "columnName": "serverId", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "-1" + }, + { + "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", + "serverId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "indexes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `serverId` INTEGER NOT NULL DEFAULT -1, `name` TEXT, `index` TEXT, `coverArt` TEXT, `albumCount` INTEGER, `closeness` INTEGER NOT NULL, `musicFolderId` TEXT, PRIMARY KEY(`id`, `serverId`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serverId", + "columnName": "serverId", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "-1" + }, + { + "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", + "serverId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "music_folders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `serverId` INTEGER NOT NULL DEFAULT -1, `name` TEXT NOT NULL, PRIMARY KEY(`id`, `serverId`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serverId", + "columnName": "serverId", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "-1" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id", + "serverId" + ], + "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, '95e83d6663a862c03ac46f9567453ded')" + ] + } +} \ No newline at end of file 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 cacecd14..a63e9f0d 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ActiveServerProvider.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ActiveServerProvider.kt @@ -1,3 +1,10 @@ +/* + * ActiveServerProvider.kt + * Copyright (C) 2009-2022 Ultrasonic developers + * + * Distributed under terms of the GNU GPLv3 license. + */ + package org.moire.ultrasonic.data import androidx.room.Room @@ -124,7 +131,9 @@ class ActiveServerProvider( UApp.applicationContext(), MetaDatabase::class.java, METADATA_DB + serverId - ).fallbackToDestructiveMigrationOnDowngrade() + ) + .addMigrations(META_MIGRATION_2_3) + .fallbackToDestructiveMigrationOnDowngrade() .build() } 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 c50629fb..8f67634c 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/MetaDatabase.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/MetaDatabase.kt @@ -1,3 +1,10 @@ +/* + * MetaDatabase.kt + * Copyright (C) 2009-2022 Ultrasonic developers + * + * Distributed under terms of the GNU GPLv3 license. + */ + package org.moire.ultrasonic.data import androidx.room.AutoMigration @@ -5,12 +12,14 @@ import androidx.room.Database import androidx.room.RoomDatabase import androidx.room.TypeConverter import androidx.room.TypeConverters -import java.util.Date +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase import org.moire.ultrasonic.domain.Album import org.moire.ultrasonic.domain.Artist import org.moire.ultrasonic.domain.Index import org.moire.ultrasonic.domain.MusicFolder import org.moire.ultrasonic.domain.Track +import java.util.Date /** * This database is used to store and cache the ID3 metadata @@ -31,7 +40,7 @@ import org.moire.ultrasonic.domain.Track ), ], exportSchema = true, - version = 2 + version = 3 ) @TypeConverters(Converters::class) abstract class MetaDatabase : RoomDatabase() { @@ -57,3 +66,18 @@ class Converters { return date?.time } } + +val META_MIGRATION_2_3: Migration = object : Migration(2,3) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("DROP TABLE `albums`") + database.execSQL("DROP TABLE `indexes`") + database.execSQL("DROP TABLE `artists`") + database.execSQL("DROP TABLE `tracks`") + database.execSQL("DROP TABLE `music_folders`") + database.execSQL("CREATE TABLE IF NOT EXISTS `albums` (`id` TEXT NOT NULL, `serverId` INTEGER NOT NULL DEFAULT -1, `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`, `serverId`))") + database.execSQL("CREATE TABLE IF NOT EXISTS `indexes` (`id` TEXT NOT NULL, `serverId` INTEGER NOT NULL DEFAULT -1, `name` TEXT, `index` TEXT, `coverArt` TEXT, `albumCount` INTEGER, `closeness` INTEGER NOT NULL, `musicFolderId` TEXT, PRIMARY KEY(`id`, `serverId`))") + database.execSQL("CREATE TABLE IF NOT EXISTS `artists` (`id` TEXT NOT NULL, `serverId` INTEGER NOT NULL DEFAULT -1, `name` TEXT, `index` TEXT, `coverArt` TEXT, `albumCount` INTEGER, `closeness` INTEGER NOT NULL, PRIMARY KEY(`id`, `serverId`))") + database.execSQL("CREATE TABLE IF NOT EXISTS `music_folders` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `serverId` INTEGER NOT NULL DEFAULT -1, PRIMARY KEY(`id`, `serverId`))") + database.execSQL("CREATE TABLE IF NOT EXISTS `tracks` (`id` TEXT NOT NULL, `serverId` INTEGER NOT NULL DEFAULT -1, `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`, `serverId`))") + } +} diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIAlbumConverter.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIAlbumConverter.kt index 1f614254..a0754803 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIAlbumConverter.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIAlbumConverter.kt @@ -1,3 +1,10 @@ +/* + * APIAlbumConverter.kt + * Copyright (C) 2009-2022 Ultrasonic developers + * + * Distributed under terms of the GNU GPLv3 license. + */ + // Converts Album entity from [org.moire.ultrasonic.api.subsonic.SubsonicAPIClient] // to app domain entities. @file:JvmName("APIAlbumConverter") @@ -6,8 +13,9 @@ package org.moire.ultrasonic.domain import org.moire.ultrasonic.api.subsonic.models.Album typealias DomainAlbum = org.moire.ultrasonic.domain.Album -fun Album.toDomainEntity(): DomainAlbum = Album( +fun Album.toDomainEntity(serverId: Int): DomainAlbum = Album( id = this@toDomainEntity.id, + serverId = serverId, title = this@toDomainEntity.name ?: this@toDomainEntity.title, album = this@toDomainEntity.album, coverArt = this@toDomainEntity.coverArt, @@ -21,8 +29,10 @@ fun Album.toDomainEntity(): DomainAlbum = Album( starred = this@toDomainEntity.starredDate.isNotEmpty() ) -fun Album.toMusicDirectoryDomainEntity(): MusicDirectory = MusicDirectory().apply { - addAll(this@toMusicDirectoryDomainEntity.songList.map { it.toTrackEntity() }) +fun Album.toMusicDirectoryDomainEntity(serverId: Int): MusicDirectory = MusicDirectory().apply { + addAll(this@toMusicDirectoryDomainEntity.songList.map { it.toTrackEntity(serverId) }) } -fun List.toDomainEntityList(): List = this.map { it.toDomainEntity() } +fun List.toDomainEntityList(serverId: Int): List = this.map { + it.toDomainEntity(serverId) +} diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIArtistConverter.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIArtistConverter.kt index 7d31b6a1..497eae75 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIArtistConverter.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIArtistConverter.kt @@ -1,3 +1,10 @@ +/* + * APIArtistConverter.kt + * Copyright (C) 2009-2022 Ultrasonic developers + * + * Distributed under terms of the GNU GPLv3 license. + */ + // Converts Artist entity from [org.moire.ultrasonic.api.subsonic.SubsonicAPIClient] // to app domain entities. @file:JvmName("APIArtistConverter") @@ -6,24 +13,26 @@ package org.moire.ultrasonic.domain import org.moire.ultrasonic.api.subsonic.models.Artist as APIArtist // When we like to convert to an Artist -fun APIArtist.toDomainEntity(): Artist = Artist( +fun APIArtist.toDomainEntity(serverId: Int): Artist = Artist( id = this@toDomainEntity.id, + serverId = serverId, coverArt = this@toDomainEntity.coverArt, name = this@toDomainEntity.name ) // When we like to convert to an index (eg. a single directory). -fun APIArtist.toIndexEntity(): Index = Index( +fun APIArtist.toIndexEntity(serverId: Int): Index = Index( id = this@toIndexEntity.id, + serverId = serverId, coverArt = this@toIndexEntity.coverArt, name = this@toIndexEntity.name ) -fun APIArtist.toMusicDirectoryDomainEntity(): MusicDirectory = MusicDirectory().apply { +fun APIArtist.toMusicDirectoryDomainEntity(serverId: Int): MusicDirectory = MusicDirectory().apply { name = this@toMusicDirectoryDomainEntity.name - addAll(this@toMusicDirectoryDomainEntity.albumsList.map { it.toDomainEntity() }) + addAll(this@toMusicDirectoryDomainEntity.albumsList.map { it.toDomainEntity(serverId) }) } -fun APIArtist.toDomainEntityList(): List { - return this.albumsList.map { it.toDomainEntity() } +fun APIArtist.toDomainEntityList(serverId: Int): List { + return this.albumsList.map { it.toDomainEntity(serverId) } } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIBookmarkConverter.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIBookmarkConverter.kt index 7759290e..ce7e2598 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIBookmarkConverter.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIBookmarkConverter.kt @@ -1,16 +1,25 @@ +/* + * APIBookmarkConverter.kt + * Copyright (C) 2009-2022 Ultrasonic developers + * + * Distributed under terms of the GNU GPLv3 license. + */ + // Contains helper functions to convert api Bookmark entity to domain entity @file:JvmName("APIBookmarkConverter") + package org.moire.ultrasonic.domain import org.moire.ultrasonic.api.subsonic.models.Bookmark as ApiBookmark -fun ApiBookmark.toDomainEntity(): Bookmark = Bookmark( +fun ApiBookmark.toDomainEntity(serverId: Int): Bookmark = Bookmark( position = this@toDomainEntity.position.toInt(), username = this@toDomainEntity.username, comment = this@toDomainEntity.comment, created = this@toDomainEntity.created?.time, changed = this@toDomainEntity.changed?.time, - track = this@toDomainEntity.entry.toTrackEntity() + track = this@toDomainEntity.entry.toTrackEntity(serverId) ) -fun List.toDomainEntitiesList(): List = map { it.toDomainEntity() } +fun List.toDomainEntitiesList(serverId: Int): List = + map { it.toDomainEntity(serverId) } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIIndexesConverter.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIIndexesConverter.kt index 2567ee67..cdbc4453 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIIndexesConverter.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIIndexesConverter.kt @@ -1,14 +1,22 @@ +/* + * APIIndexesConverter.kt + * Copyright (C) 2009-2022 Ultrasonic developers + * + * Distributed under terms of the GNU GPLv3 license. + */ + // Converts Indexes entity from [org.moire.ultrasonic.api.subsonic.SubsonicAPIClient] // to app domain entities. @file:JvmName("APIIndexesConverter") + package org.moire.ultrasonic.domain import org.moire.ultrasonic.api.subsonic.models.Index as APIIndex import org.moire.ultrasonic.api.subsonic.models.Indexes as APIIndexes -fun APIIndexes.toArtistList(): List { - val shortcuts = this.shortcutList.map { it.toDomainEntity() }.toMutableList() - val indexes = this.indexList.foldIndexToArtistList() +fun APIIndexes.toArtistList(serverId: Int): List { + val shortcuts = this.shortcutList.map { it.toDomainEntity(serverId) }.toMutableList() + val indexes = this.indexList.foldIndexToArtistList(serverId) indexes.forEach { if (!shortcuts.contains(it)) { @@ -19,9 +27,9 @@ fun APIIndexes.toArtistList(): List { return shortcuts } -fun APIIndexes.toIndexList(musicFolderId: String?): List { - val shortcuts = this.shortcutList.map { it.toIndexEntity() }.toMutableList() - val indexes = this.indexList.foldIndexToIndexList(musicFolderId) +fun APIIndexes.toIndexList(serverId: Int, musicFolderId: String?): List { + val shortcuts = this.shortcutList.map { it.toIndexEntity(serverId) }.toMutableList() + val indexes = this.indexList.foldIndexToIndexList(musicFolderId, serverId) indexes.forEach { if (!shortcuts.contains(it)) { @@ -32,22 +40,23 @@ fun APIIndexes.toIndexList(musicFolderId: String?): List { return shortcuts } -private fun List.foldIndexToArtistList(): List = this.fold( - listOf(), - { acc, index -> - acc + index.artists.map { - it.toDomainEntity() - } +private fun List.foldIndexToArtistList(serverId: Int): List = this.fold( + listOf() +) { acc, index -> + acc + index.artists.map { + it.toDomainEntity(serverId) } -) +} -private fun List.foldIndexToIndexList(musicFolderId: String?): List = this.fold( - listOf(), - { acc, index -> - acc + index.artists.map { - val ret = it.toIndexEntity() - ret.musicFolderId = musicFolderId - ret - } +private fun List.foldIndexToIndexList( + musicFolderId: String?, + serverId: Int +): List = this.fold( + listOf() +) { acc, index -> + acc + index.artists.map { + val ret = it.toIndexEntity(serverId) + ret.musicFolderId = musicFolderId + ret } -) +} diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIMusicDirectoryConverter.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIMusicDirectoryConverter.kt index 260eab66..9d800369 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIMusicDirectoryConverter.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIMusicDirectoryConverter.kt @@ -1,6 +1,6 @@ /* * APIMusicDirectoryConverter.kt - * Copyright (C) 2009-2021 Ultrasonic developers + * Copyright (C) 2009-2022 Ultrasonic developers * * Distributed under terms of the GNU GPLv3 license. */ @@ -26,12 +26,12 @@ internal val dateFormat: DateFormat by lazy { SimpleDateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.getDefault()) } -fun MusicDirectoryChild.toTrackEntity(): Track = Track(id).apply { +fun MusicDirectoryChild.toTrackEntity(serverId: Int): Track = Track(id, serverId).apply { populateCommonProps(this, this@toTrackEntity) populateTrackProps(this, this@toTrackEntity) } -fun MusicDirectoryChild.toAlbumEntity(): Album = Album(id).apply { +fun MusicDirectoryChild.toAlbumEntity(serverId: Int): Album = Album(id, serverId).apply { populateCommonProps(this, this@toAlbumEntity) } @@ -80,24 +80,24 @@ private fun populateTrackProps( track.averageRating = source.averageRating } -fun List.toDomainEntityList(): List { +fun List.toDomainEntityList(serverId: Int): List { val newList: MutableList = mutableListOf() forEach { if (it.isDir) - newList.add(it.toAlbumEntity()) + newList.add(it.toAlbumEntity(serverId)) else - newList.add(it.toTrackEntity()) + newList.add(it.toTrackEntity(serverId)) } return newList } -fun List.toTrackList(): List = this.map { - it.toTrackEntity() +fun List.toTrackList(serverId: Int): List = this.map { + it.toTrackEntity(serverId) } -fun APIMusicDirectory.toDomainEntity(): MusicDirectory = MusicDirectory().apply { +fun APIMusicDirectory.toDomainEntity(serverId: Int): MusicDirectory = MusicDirectory().apply { name = this@toDomainEntity.name - addAll(this@toDomainEntity.childList.toDomainEntityList()) + addAll(this@toDomainEntity.childList.toDomainEntityList(serverId)) } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIMusicFolderConverter.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIMusicFolderConverter.kt index a969032e..e0cbd6f9 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIMusicFolderConverter.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIMusicFolderConverter.kt @@ -1,3 +1,10 @@ +/* + * APIMusicFolderConverter.kt + * Copyright (C) 2009-2022 Ultrasonic developers + * + * Distributed under terms of the GNU GPLv3 license. + */ + // Converts MusicFolder entity from [org.moire.ultrasonic.api.subsonic.SubsonicAPIClient] // to app domain entities. @file:JvmName("APIMusicFolderConverter") @@ -5,7 +12,15 @@ package org.moire.ultrasonic.domain import org.moire.ultrasonic.api.subsonic.models.MusicFolder as APIMusicFolder -fun APIMusicFolder.toDomainEntity(): MusicFolder = MusicFolder(this.id, this.name) +fun APIMusicFolder.toDomainEntity(serverId: Int): MusicFolder = MusicFolder( + id = this.id, + serverId = serverId, + name = this.name +) -fun List.toDomainEntityList(): List = - this.map { it.toDomainEntity() } +fun List.toDomainEntityList(serverId: Int): List = + this.map { + val item = it.toDomainEntity(serverId) + item.serverId = serverId + item + } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIPlaylistConverter.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIPlaylistConverter.kt index 7286163a..e9c279ac 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIPlaylistConverter.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIPlaylistConverter.kt @@ -1,6 +1,14 @@ +/* + * APIPlaylistConverter.kt + * Copyright (C) 2009-2022 Ultrasonic developers + * + * Distributed under terms of the GNU GPLv3 license. + */ + // Converts Playlist entity from [org.moire.ultrasonic.api.subsonic.SubsonicAPIClient] // to app domain entities. @file:JvmName("APIPlaylistConverter") + package org.moire.ultrasonic.domain import java.text.SimpleDateFormat @@ -10,10 +18,15 @@ import org.moire.ultrasonic.util.Util.ifNotNull internal val playlistDateFormat by lazy(NONE) { SimpleDateFormat.getInstance() } -fun APIPlaylist.toMusicDirectoryDomainEntity(): MusicDirectory = MusicDirectory().apply { - name = this@toMusicDirectoryDomainEntity.name - addAll(this@toMusicDirectoryDomainEntity.entriesList.map { it.toTrackEntity() }) -} +fun APIPlaylist.toMusicDirectoryDomainEntity(serverId: Int): MusicDirectory = + MusicDirectory().apply { + name = this@toMusicDirectoryDomainEntity.name + addAll(this@toMusicDirectoryDomainEntity.entriesList.map { + val item = it.toTrackEntity(serverId) + item.serverId = serverId + item + }) + } fun APIPlaylist.toDomainEntity(): Playlist = Playlist( this.id, this.name, this.owner, diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APISearchConverter.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APISearchConverter.kt index ff9ffd74..ed1af67d 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APISearchConverter.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APISearchConverter.kt @@ -1,3 +1,10 @@ +/* + * APISearchConverter.kt + * Copyright (C) 2009-2022 Ultrasonic developers + * + * Distributed under terms of the GNU GPLv3 license. + */ + // Converts SearchResult entities from [org.moire.ultrasonic.api.subsonic.SubsonicAPIClient] // to app domain entities. @file:JvmName("APISearchConverter") @@ -7,19 +14,19 @@ import org.moire.ultrasonic.api.subsonic.models.SearchResult as APISearchResult import org.moire.ultrasonic.api.subsonic.models.SearchThreeResult import org.moire.ultrasonic.api.subsonic.models.SearchTwoResult -fun APISearchResult.toDomainEntity(): SearchResult = SearchResult( +fun APISearchResult.toDomainEntity(serverId: Int): SearchResult = SearchResult( emptyList(), emptyList(), - this.matchList.map { it.toTrackEntity() } + this.matchList.map { it.toTrackEntity(serverId) } ) -fun SearchTwoResult.toDomainEntity(): SearchResult = SearchResult( - this.artistList.map { it.toIndexEntity() }, - this.albumList.map { it.toDomainEntity() }, - this.songList.map { it.toTrackEntity() } +fun SearchTwoResult.toDomainEntity(serverId: Int): SearchResult = SearchResult( + this.artistList.map { it.toIndexEntity(serverId) }, + this.albumList.map { it.toDomainEntity(serverId) }, + this.songList.map { it.toTrackEntity(serverId) } ) -fun SearchThreeResult.toDomainEntity(): SearchResult = SearchResult( - this.artistList.map { it.toDomainEntity() }, - this.albumList.map { it.toDomainEntity() }, - this.songList.map { it.toTrackEntity() } +fun SearchThreeResult.toDomainEntity(serverId: Int): SearchResult = SearchResult( + this.artistList.map { it.toDomainEntity(serverId) }, + this.albumList.map { it.toDomainEntity(serverId) }, + this.songList.map { it.toTrackEntity(serverId) } ) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIShareConverter.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIShareConverter.kt index 2a121ae3..7fb8a9a4 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIShareConverter.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIShareConverter.kt @@ -1,3 +1,10 @@ +/* + * APIShareConverter.kt + * Copyright (C) 2009-2022 Ultrasonic developers + * + * Distributed under terms of the GNU GPLv3 license. + */ + // Contains helper method to convert subsonic api share to domain model @file:JvmName("APIShareConverter") package org.moire.ultrasonic.domain @@ -9,11 +16,11 @@ import org.moire.ultrasonic.util.Util.ifNotNull internal val shareTimeFormat by lazy(NONE) { SimpleDateFormat.getInstance() } -fun List.toDomainEntitiesList(): List = this.map { - it.toDomainEntity() +fun List.toDomainEntitiesList(serverId: Int): List = this.map { + it.toDomainEntity(serverId) } -fun APIShare.toDomainEntity(): Share = Share( +fun APIShare.toDomainEntity(serverId: Int): Share = Share( created = this@toDomainEntity.created.ifNotNull { shareTimeFormat.format(it.time) }, description = this@toDomainEntity.description, expires = this@toDomainEntity.expires.ifNotNull { shareTimeFormat.format(it.time) }, @@ -22,5 +29,5 @@ fun APIShare.toDomainEntity(): Share = Share( url = this@toDomainEntity.url, username = this@toDomainEntity.username, visitCount = this@toDomainEntity.visitCount.toLong(), - tracks = this@toDomainEntity.items.toTrackList().toMutableList() + tracks = this@toDomainEntity.items.toTrackList(serverId).toMutableList() ) 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 97530118..9af9f9ed 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt @@ -1,6 +1,6 @@ /* * OfflineMusicService.kt - * Copyright (C) 2009-2021 Ultrasonic developers + * Copyright (C) 2009-2022 Ultrasonic developers * * Distributed under terms of the GNU GPLv3 license. */ @@ -66,7 +66,7 @@ class OfflineMusicService : MusicService, KoinComponent { val root = FileUtil.musicDirectory for (file in FileUtil.listFiles(root)) { if (file.isDirectory) { - val index = Index(file.path) + val index = Index(id = file.path) index.id = file.path index.index = file.name.substring(0, 1) index.name = file.name 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 e826f0e0..1faec462 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt @@ -1,6 +1,6 @@ /* - * RestMusicService.kt - * Copyright (C) 2009-2021 Ultrasonic developers + * RESTMusicService.kt + * Copyright (C) 2009-2022 Ultrasonic developers * * Distributed under terms of the GNU GPLv3 license. */ @@ -78,7 +78,7 @@ open class RESTMusicService( ): List { val response = API.getMusicFolders().execute().throwOnFailure() - return response.body()!!.musicFolders.toDomainEntityList() + return response.body()!!.musicFolders.toDomainEntityList(activeServerId) } /** @@ -91,7 +91,10 @@ open class RESTMusicService( ): List { val response = API.getIndexes(musicFolderId, null).execute().throwOnFailure() - return response.body()!!.indexes.toIndexList(musicFolderId) + return response.body()!!.indexes.toIndexList( + ActiveServerProvider.getActiveServerId(), + musicFolderId + ) } @Throws(Exception::class) @@ -100,7 +103,7 @@ open class RESTMusicService( ): List { val response = API.getArtists(null).execute().throwOnFailure() - return response.body()!!.indexes.toArtistList() + return response.body()!!.indexes.toArtistList(activeServerId) } @Throws(Exception::class) @@ -137,7 +140,7 @@ open class RESTMusicService( ): MusicDirectory { val response = API.getMusicDirectory(id).execute().throwOnFailure() - return response.body()!!.musicDirectory.toDomainEntity() + return response.body()!!.musicDirectory.toDomainEntity(activeServerId) } @Throws(Exception::class) @@ -148,7 +151,7 @@ open class RESTMusicService( ): List { val response = API.getArtist(id).execute().throwOnFailure() - return response.body()!!.artist.toDomainEntityList() + return response.body()!!.artist.toDomainEntityList(activeServerId) } @Throws(Exception::class) @@ -159,7 +162,7 @@ open class RESTMusicService( ): MusicDirectory { val response = API.getAlbum(id).execute().throwOnFailure() - return response.body()!!.album.toMusicDirectoryDomainEntity() + return response.body()!!.album.toMusicDirectoryDomainEntity(activeServerId) } @Throws(Exception::class) @@ -189,7 +192,7 @@ open class RESTMusicService( API.search(null, null, null, criteria.query, criteria.songCount, null, null) .execute().throwOnFailure() - return response.body()!!.searchResult.toDomainEntity() + return response.body()!!.searchResult.toDomainEntity(activeServerId) } /** @@ -205,7 +208,7 @@ open class RESTMusicService( criteria.songCount, null ).execute().throwOnFailure() - return response.body()!!.searchResult.toDomainEntity() + return response.body()!!.searchResult.toDomainEntity(activeServerId) } @Throws(Exception::class) @@ -218,7 +221,7 @@ open class RESTMusicService( criteria.songCount, null ).execute().throwOnFailure() - return response.body()!!.searchResult.toDomainEntity() + return response.body()!!.searchResult.toDomainEntity(activeServerId) } @Throws(Exception::class) @@ -228,7 +231,7 @@ open class RESTMusicService( ): MusicDirectory { val response = API.getPlaylist(id).execute().throwOnFailure() - val playlist = response.body()!!.playlist.toMusicDirectoryDomainEntity() + val playlist = response.body()!!.playlist.toMusicDirectoryDomainEntity(activeServerId) savePlaylist(name, playlist) return playlist @@ -319,7 +322,7 @@ open class RESTMusicService( "skipped" != podcastEntry.status && "error" != podcastEntry.status ) { - val entry = podcastEntry.toTrackEntity() + val entry = podcastEntry.toTrackEntity(activeServerId) entry.track = null musicDirectory.add(entry) } @@ -363,7 +366,7 @@ open class RESTMusicService( musicFolderId ).execute().throwOnFailure() - return response.body()!!.albumList.toDomainEntityList() + return response.body()!!.albumList.toDomainEntityList(activeServerId) } @Throws(Exception::class) @@ -383,7 +386,7 @@ open class RESTMusicService( musicFolderId ).execute().throwOnFailure() - return response.body()!!.albumList.toDomainEntityList() + return response.body()!!.albumList.toDomainEntityList(activeServerId) } @Throws(Exception::class) @@ -399,7 +402,7 @@ open class RESTMusicService( ).execute().throwOnFailure() val result = MusicDirectory() - result.addAll(response.body()!!.songsList.toDomainEntityList()) + result.addAll(response.body()!!.songsList.toDomainEntityList(activeServerId)) return result } @@ -408,14 +411,14 @@ open class RESTMusicService( override fun getStarred(): SearchResult { val response = API.getStarred(null).execute().throwOnFailure() - return response.body()!!.starred.toDomainEntity() + return response.body()!!.starred.toDomainEntity(activeServerId) } @Throws(Exception::class) override fun getStarred2(): SearchResult { val response = API.getStarred2(null).execute().throwOnFailure() - return response.body()!!.starred2.toDomainEntity() + return response.body()!!.starred2.toDomainEntity(activeServerId) } @Throws(Exception::class) @@ -546,7 +549,7 @@ open class RESTMusicService( ): List { val response = API.getShares().execute().throwOnFailure() - return response.body()!!.shares.toDomainEntitiesList() + return response.body()!!.shares.toDomainEntitiesList(activeServerId) } @Throws(Exception::class) @@ -567,7 +570,7 @@ open class RESTMusicService( val response = API.getSongsByGenre(genre, count, offset, null).execute().throwOnFailure() val result = MusicDirectory() - result.addAll(response.body()!!.songsList.toDomainEntityList()) + result.addAll(response.body()!!.songsList.toDomainEntityList(activeServerId)) return result } @@ -601,7 +604,7 @@ open class RESTMusicService( override fun getBookmarks(): List { val response = API.getBookmarks().execute().throwOnFailure() - return response.body()!!.bookmarkList.toDomainEntitiesList() + return response.body()!!.bookmarkList.toDomainEntitiesList(activeServerId) } @Throws(Exception::class) @@ -626,7 +629,7 @@ open class RESTMusicService( val response = API.getVideos().execute().throwOnFailure() val musicDirectory = MusicDirectory() - musicDirectory.addAll(response.body()!!.videosList.toDomainEntityList()) + musicDirectory.addAll(response.body()!!.videosList.toDomainEntityList(activeServerId)) return musicDirectory } @@ -639,7 +642,7 @@ open class RESTMusicService( ): List { val response = API.createShare(ids, description, expires).execute().throwOnFailure() - return response.body()!!.shares.toDomainEntitiesList() + return response.body()!!.shares.toDomainEntitiesList(activeServerId) } @Throws(Exception::class) @@ -663,6 +666,9 @@ open class RESTMusicService( API.updateShare(id, description, expiresValue).execute().throwOnFailure() } + private val activeServerId: Int + get() = ActiveServerProvider.getActiveServerId() + init { // The client will notice if the minimum supported API version has changed // By registering a callback we ensure this info is saved in the database as well diff --git a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIAlbumConverterTest.kt b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIAlbumConverterTest.kt index b4b39932..3f094844 100644 --- a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIAlbumConverterTest.kt +++ b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIAlbumConverterTest.kt @@ -7,11 +7,14 @@ import org.amshove.kluent.`should be equal to` import org.junit.Test import org.moire.ultrasonic.api.subsonic.models.Album import org.moire.ultrasonic.api.subsonic.models.MusicDirectoryChild +import org.moire.ultrasonic.data.ActiveServerProvider /** * Unit test for extension functions in [APIAlbumConverter.kt] file. */ class APIAlbumConverterTest { + private val serverId = -1 + @Test fun `Should convert Album to domain entity`() { val entity = Album( @@ -20,7 +23,7 @@ class APIAlbumConverterTest { created = Calendar.getInstance(), year = 2017, genre = "some-genre" ) - val convertedEntity = entity.toDomainEntity() + val convertedEntity = entity.toDomainEntity(serverId) with(convertedEntity) { id `should be equal to` entity.id @@ -46,12 +49,12 @@ class APIAlbumConverterTest { songList = listOf(MusicDirectoryChild()) ) - val convertedEntity = entity.toMusicDirectoryDomainEntity() + val convertedEntity = entity.toMusicDirectoryDomainEntity(serverId) with(convertedEntity) { name `should be equal to` null size `should be equal to` entity.songList.size - this[0] `should be equal to` entity.songList[0].toTrackEntity() + this[0] `should be equal to` entity.songList[0].toTrackEntity(serverId) } } @@ -59,12 +62,12 @@ class APIAlbumConverterTest { fun `Should convert list of Album entities to domain list entities`() { val entityList = listOf(Album(id = "455"), Album(id = "1"), Album(id = "1000")) - val convertedList = entityList.toDomainEntityList() + val convertedList = entityList.toDomainEntityList(ActiveServerProvider.getActiveServerId()) with(convertedList) { size `should be equal to` entityList.size forEachIndexed { index, entry -> - entry `should be equal to` entityList[index].toDomainEntity() + entry `should be equal to` entityList[index].toDomainEntity(serverId) } } } diff --git a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIArtistConverterTest.kt b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIArtistConverterTest.kt index 4ae7528b..5920744e 100644 --- a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIArtistConverterTest.kt +++ b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIArtistConverterTest.kt @@ -11,12 +11,12 @@ import org.moire.ultrasonic.api.subsonic.models.Artist /** * Unit test for extension functions in APIArtistConverter.kt file. */ -class APIArtistConverterTest { +class APIArtistConverterTest : BaseTest() { @Test fun `Should convert artist entity`() { val entity = Artist(id = "10", name = "artist-name", starred = Calendar.getInstance()) - val convertedEntity = entity.toDomainEntity() + val convertedEntity = entity.toDomainEntity(serverId) with(convertedEntity) { id `should be equal to` entity.id @@ -38,12 +38,12 @@ class APIArtistConverterTest { ) ) - val convertedEntity = entity.toMusicDirectoryDomainEntity() + val convertedEntity = entity.toMusicDirectoryDomainEntity(serverId) with(convertedEntity) { name `should be equal to` entity.name getChildren() `should be equal to` entity.albumsList - .map { it.toDomainEntity() }.toMutableList() + .map { it.toDomainEntity(serverId) }.toMutableList() } } } diff --git a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIBookmarkConverterTest.kt b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIBookmarkConverterTest.kt index 2b2abecb..fe03805c 100644 --- a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIBookmarkConverterTest.kt +++ b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIBookmarkConverterTest.kt @@ -11,7 +11,8 @@ import org.moire.ultrasonic.api.subsonic.models.MusicDirectoryChild /** * Unit test for function that converts [Bookmark] api entity to domain. */ -class APIBookmarkConverterTest { +class APIBookmarkConverterTest : BaseTest() { + @Test fun `Should convert to domain entity`() { val entity = Bookmark( @@ -19,7 +20,7 @@ class APIBookmarkConverterTest { Calendar.getInstance(), MusicDirectoryChild(id = "12333") ) - val domainEntity = entity.toDomainEntity() + val domainEntity = entity.toDomainEntity(serverId) with(domainEntity) { position `should be equal to` entity.position.toInt() @@ -27,7 +28,7 @@ class APIBookmarkConverterTest { comment `should be equal to` entity.comment created `should be equal to` entity.created?.time changed `should be equal to` entity.changed?.time - track `should be equal to` entity.entry.toTrackEntity() + track `should be equal to` entity.entry.toTrackEntity(serverId) } } @@ -35,11 +36,11 @@ class APIBookmarkConverterTest { fun `Should convert list of entities to domain entities`() { val entitiesList = listOf(Bookmark(443L), Bookmark(444L)) - val domainEntitiesList = entitiesList.toDomainEntitiesList() + val domainEntitiesList = entitiesList.toDomainEntitiesList(serverId) domainEntitiesList.size `should be equal to` entitiesList.size - domainEntitiesList.forEachIndexed({ index, bookmark -> - bookmark `should be equal to` entitiesList[index].toDomainEntity() - }) + domainEntitiesList.forEachIndexed { index, bookmark -> + bookmark `should be equal to` entitiesList[index].toDomainEntity(serverId) + } } } diff --git a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIMusicDirectoryConverterTest.kt b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIMusicDirectoryConverterTest.kt index 3bb559ca..7f47c1a5 100644 --- a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIMusicDirectoryConverterTest.kt +++ b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIMusicDirectoryConverterTest.kt @@ -7,11 +7,12 @@ import org.amshove.kluent.`should be equal to` import org.junit.Test import org.moire.ultrasonic.api.subsonic.models.MusicDirectory import org.moire.ultrasonic.api.subsonic.models.MusicDirectoryChild +import org.moire.ultrasonic.data.ActiveServerProvider /** * Unit test for extension functions in APIMusicDirectoryConverter.kt file. */ -class APIMusicDirectoryConverterTest { +class APIMusicDirectoryConverterTest : BaseTest() { @Test fun `Should convert MusicDirectory entity`() { val entity = MusicDirectory( @@ -20,13 +21,13 @@ class APIMusicDirectoryConverterTest { childList = listOf(MusicDirectoryChild("1"), MusicDirectoryChild("2")) ) - val convertedEntity = entity.toDomainEntity() + val convertedEntity = entity.toDomainEntity(ActiveServerProvider.getActiveServerId()) with(convertedEntity) { name `should be equal to` entity.name size `should be equal to` entity.childList.size getChildren() `should be equal to` entity.childList - .map { it.toTrackEntity() }.toMutableList() + .map { it.toTrackEntity(serverId) }.toMutableList() } } @@ -44,7 +45,7 @@ class APIMusicDirectoryConverterTest { starred = Calendar.getInstance(), userRating = 3, averageRating = 2.99F ) - val convertedEntity = entity.toTrackEntity() + val convertedEntity = entity.toTrackEntity(serverId) with(convertedEntity) { id `should be equal to` entity.id @@ -84,7 +85,7 @@ class APIMusicDirectoryConverterTest { artist = "some-artist", publishDate = Calendar.getInstance() ) - val convertedEntity = entity.toTrackEntity() + val convertedEntity = entity.toTrackEntity(serverId) with(convertedEntity) { id `should be equal to` entity.streamId @@ -96,11 +97,11 @@ class APIMusicDirectoryConverterTest { fun `Should convert list of MusicDirectoryChild to domain entity list`() { val entitiesList = listOf(MusicDirectoryChild(id = "45"), MusicDirectoryChild(id = "34")) - val domainList = entitiesList.toDomainEntityList() + val domainList = entitiesList.toDomainEntityList(serverId) domainList.size `should be equal to` entitiesList.size domainList.forEachIndexed { index, entry -> - entry `should be equal to` entitiesList[index].toTrackEntity() + entry `should be equal to` entitiesList[index].toTrackEntity(serverId) } } } diff --git a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIMusicFolderConverterTest.kt b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIMusicFolderConverterTest.kt index 0605be39..85721482 100644 --- a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIMusicFolderConverterTest.kt +++ b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIMusicFolderConverterTest.kt @@ -9,12 +9,12 @@ import org.moire.ultrasonic.api.subsonic.models.MusicFolder /** * Unit test for extension functions in file APIMusicFolderConverter.kt. */ -class APIMusicFolderConverterTest { +class APIMusicFolderConverterTest : BaseTest() { @Test fun `Should convert MusicFolder entity`() { val entity = MusicFolder(id = "10", name = "some-name") - val convertedEntity = entity.toDomainEntity() + val convertedEntity = entity.toDomainEntity(serverId) convertedEntity.name `should be equal to` entity.name convertedEntity.id `should be equal to` entity.id @@ -27,7 +27,7 @@ class APIMusicFolderConverterTest { MusicFolder(id = "4", name = "some-name-4") ) - val convertedList = entityList.toDomainEntityList() + val convertedList = entityList.toDomainEntityList(serverId) with(convertedList) { size `should be equal to` entityList.size diff --git a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIPlaylistConverterTest.kt b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIPlaylistConverterTest.kt index 9756af80..35a0add5 100644 --- a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIPlaylistConverterTest.kt +++ b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIPlaylistConverterTest.kt @@ -7,11 +7,12 @@ import org.amshove.kluent.`should be equal to` import org.junit.Test import org.moire.ultrasonic.api.subsonic.models.MusicDirectoryChild import org.moire.ultrasonic.api.subsonic.models.Playlist +import org.moire.ultrasonic.data.ActiveServerProvider /** * Unit test for extension functions that converts api playlist entity to domain. */ -class APIPlaylistConverterTest { +class APIPlaylistConverterTest : BaseTest() { @Test fun `Should convert Playlist to MusicDirectory domain entity`() { val entity = Playlist( @@ -22,13 +23,13 @@ class APIPlaylistConverterTest { ) ) - val convertedEntity = entity.toMusicDirectoryDomainEntity() + val convertedEntity = entity.toMusicDirectoryDomainEntity(ActiveServerProvider.getActiveServerId()) with(convertedEntity) { name `should be equal to` entity.name size `should be equal to` entity.entriesList.size - this[0] `should be equal to` entity.entriesList[0].toTrackEntity() - this[1] `should be equal to` entity.entriesList[1].toTrackEntity() + this[0] `should be equal to` entity.entriesList[0].toTrackEntity(serverId) + this[1] `should be equal to` entity.entriesList[1].toTrackEntity(serverId) } } diff --git a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APISearchConverterTest.kt b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APISearchConverterTest.kt index dd2e7d84..10171cf8 100644 --- a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APISearchConverterTest.kt +++ b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APISearchConverterTest.kt @@ -11,11 +11,12 @@ import org.moire.ultrasonic.api.subsonic.models.MusicDirectoryChild import org.moire.ultrasonic.api.subsonic.models.SearchResult import org.moire.ultrasonic.api.subsonic.models.SearchThreeResult import org.moire.ultrasonic.api.subsonic.models.SearchTwoResult +import org.moire.ultrasonic.data.ActiveServerProvider /** * Unit test for extension function in APISearchConverter.kt file. */ -class APISearchConverterTest { +class APISearchConverterTest : BaseTest() { @Test fun `Should convert SearchResult to domain entity`() { val entity = SearchResult( @@ -26,7 +27,7 @@ class APISearchConverterTest { ) ) - val convertedEntity = entity.toDomainEntity() + val convertedEntity = entity.toDomainEntity(serverId) with(convertedEntity) { albums `should not be equal to` null @@ -34,7 +35,7 @@ class APISearchConverterTest { artists `should not be equal to` null artists.size `should be equal to` 0 songs.size `should be equal to` entity.matchList.size - songs[0] `should be equal to` entity.matchList[0].toTrackEntity() + songs[0] `should be equal to` entity.matchList[0].toTrackEntity(serverId) } } @@ -46,15 +47,15 @@ class APISearchConverterTest { listOf(MusicDirectoryChild(id = "9118", parent = "112")) ) - val convertedEntity = entity.toDomainEntity() + val convertedEntity = entity.toDomainEntity(ActiveServerProvider.getActiveServerId()) with(convertedEntity) { artists.size `should be equal to` entity.artistList.size - artists[0] `should be equal to` entity.artistList[0].toIndexEntity() + artists[0] `should be equal to` entity.artistList[0].toIndexEntity(serverId) albums.size `should be equal to` entity.albumList.size - albums[0] `should be equal to` entity.albumList[0].toDomainEntity() + albums[0] `should be equal to` entity.albumList[0].toDomainEntity(serverId) songs.size `should be equal to` entity.songList.size - songs[0] `should be equal to` entity.songList[0].toTrackEntity() + songs[0] `should be equal to` entity.songList[0].toTrackEntity(serverId) } } @@ -66,15 +67,15 @@ class APISearchConverterTest { songList = listOf(MusicDirectoryChild(id = "7123", title = "song1")) ) - val convertedEntity = entity.toDomainEntity() + val convertedEntity = entity.toDomainEntity(serverId) with(convertedEntity) { artists.size `should be equal to` entity.artistList.size - artists[0] `should be equal to` entity.artistList[0].toDomainEntity() + artists[0] `should be equal to` entity.artistList[0].toDomainEntity(serverId) albums.size `should be equal to` entity.albumList.size - albums[0] `should be equal to` entity.albumList[0].toDomainEntity() + albums[0] `should be equal to` entity.albumList[0].toDomainEntity(serverId) songs.size `should be equal to` entity.songList.size - songs[0] `should be equal to` entity.songList[0].toTrackEntity() + songs[0] `should be equal to` entity.songList[0].toTrackEntity(serverId) } } } diff --git a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/BaseTest.kt b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/BaseTest.kt new file mode 100644 index 00000000..4a141e88 --- /dev/null +++ b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/BaseTest.kt @@ -0,0 +1,12 @@ +/* + * BaseTest.kt + * Copyright (C) 2009-2022 Ultrasonic developers + * + * Distributed under terms of the GNU GPLv3 license. + */ + +package org.moire.ultrasonic.domain + +open class BaseTest { + internal val serverId = -1 +} From 53a1a5545ae554499ea07fefd3d3a293ff7e9a4a Mon Sep 17 00:00:00 2001 From: tzugen Date: Tue, 5 Jul 2022 15:30:41 +0200 Subject: [PATCH 08/17] Formatting. Disable line length check in detekt. It's being guarded by KtLint already. --- .../main/kotlin/org/moire/ultrasonic/domain/Album.kt | 1 - .../kotlin/org/moire/ultrasonic/domain/Artist.kt | 1 - .../org/moire/ultrasonic/domain/ArtistOrIndex.kt | 1 + .../main/kotlin/org/moire/ultrasonic/domain/Index.kt | 1 - .../main/kotlin/org/moire/ultrasonic/domain/Track.kt | 1 - detekt-config.yml | 5 +---- .../kotlin/org/moire/ultrasonic/data/MetaDatabase.kt | 6 ++++-- .../moire/ultrasonic/domain/APIPlaylistConverter.kt | 12 +++++++----- .../ultrasonic/domain/APIPlaylistConverterTest.kt | 3 +-- 9 files changed, 14 insertions(+), 17 deletions(-) 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 1ddf435b..28af9da0 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 @@ -9,7 +9,6 @@ package org.moire.ultrasonic.domain import androidx.room.ColumnInfo import androidx.room.Entity -import androidx.room.PrimaryKey import java.util.Date @Entity(tableName = "albums", primaryKeys = ["id", "serverId"]) diff --git a/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/Artist.kt b/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/Artist.kt index b9d70777..7c002e58 100644 --- a/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/Artist.kt +++ b/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/Artist.kt @@ -9,7 +9,6 @@ package org.moire.ultrasonic.domain import androidx.room.ColumnInfo import androidx.room.Entity -import androidx.room.PrimaryKey @Entity(tableName = "artists", primaryKeys = ["id", "serverId"]) data class Artist( diff --git a/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/ArtistOrIndex.kt b/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/ArtistOrIndex.kt index d61dc17c..91040e98 100644 --- a/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/ArtistOrIndex.kt +++ b/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/ArtistOrIndex.kt @@ -9,6 +9,7 @@ package org.moire.ultrasonic.domain import androidx.room.Ignore +@Suppress("LongParameterList") abstract class ArtistOrIndex( @Ignore override var id: String, diff --git a/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/Index.kt b/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/Index.kt index 9d9431e7..d3190f25 100644 --- a/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/Index.kt +++ b/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/Index.kt @@ -9,7 +9,6 @@ package org.moire.ultrasonic.domain import androidx.room.ColumnInfo import androidx.room.Entity -import androidx.room.PrimaryKey @Entity(tableName = "indexes", primaryKeys = ["id", "serverId"]) data class Index( diff --git a/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/Track.kt b/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/Track.kt index 9a17edee..c0e6afd8 100644 --- a/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/Track.kt +++ b/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/Track.kt @@ -9,7 +9,6 @@ package org.moire.ultrasonic.domain import androidx.room.ColumnInfo import androidx.room.Entity -import androidx.room.PrimaryKey import java.io.Serializable import java.util.Date diff --git a/detekt-config.yml b/detekt-config.yml index b59e4ec3..df3b8a00 100644 --- a/detekt-config.yml +++ b/detekt-config.yml @@ -64,10 +64,7 @@ style: WildcardImport: active: true MaxLineLength: - active: true - maxLineLength: 120 - excludePackageStatements: false - excludeImportStatements: false + active: false MagicNumber: # 100 common in percentage, 1000 in milliseconds ignoreNumbers: ['-1', '0', '1', '2', '5', '10', '100', '256', '512', '1000', '1024', '4096'] 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 8f67634c..3ecbe54d 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/MetaDatabase.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/MetaDatabase.kt @@ -14,12 +14,12 @@ import androidx.room.TypeConverter import androidx.room.TypeConverters import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase +import java.util.Date import org.moire.ultrasonic.domain.Album import org.moire.ultrasonic.domain.Artist import org.moire.ultrasonic.domain.Index import org.moire.ultrasonic.domain.MusicFolder import org.moire.ultrasonic.domain.Track -import java.util.Date /** * This database is used to store and cache the ID3 metadata @@ -67,7 +67,8 @@ class Converters { } } -val META_MIGRATION_2_3: Migration = object : Migration(2,3) { +/* ktlint-disable max-line-length */ +val META_MIGRATION_2_3: Migration = object : Migration(2, 3) { override fun migrate(database: SupportSQLiteDatabase) { database.execSQL("DROP TABLE `albums`") database.execSQL("DROP TABLE `indexes`") @@ -81,3 +82,4 @@ val META_MIGRATION_2_3: Migration = object : Migration(2,3) { database.execSQL("CREATE TABLE IF NOT EXISTS `tracks` (`id` TEXT NOT NULL, `serverId` INTEGER NOT NULL DEFAULT -1, `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`, `serverId`))") } } +/* ktlint-enable max-line-length */ diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIPlaylistConverter.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIPlaylistConverter.kt index e9c279ac..3be0e3f1 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIPlaylistConverter.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIPlaylistConverter.kt @@ -21,11 +21,13 @@ internal val playlistDateFormat by lazy(NONE) { SimpleDateFormat.getInstance() } fun APIPlaylist.toMusicDirectoryDomainEntity(serverId: Int): MusicDirectory = MusicDirectory().apply { name = this@toMusicDirectoryDomainEntity.name - addAll(this@toMusicDirectoryDomainEntity.entriesList.map { - val item = it.toTrackEntity(serverId) - item.serverId = serverId - item - }) + addAll( + this@toMusicDirectoryDomainEntity.entriesList.map { + val item = it.toTrackEntity(serverId) + item.serverId = serverId + item + } + ) } fun APIPlaylist.toDomainEntity(): Playlist = Playlist( diff --git a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIPlaylistConverterTest.kt b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIPlaylistConverterTest.kt index 35a0add5..c34de1fd 100644 --- a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIPlaylistConverterTest.kt +++ b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIPlaylistConverterTest.kt @@ -7,7 +7,6 @@ import org.amshove.kluent.`should be equal to` import org.junit.Test import org.moire.ultrasonic.api.subsonic.models.MusicDirectoryChild import org.moire.ultrasonic.api.subsonic.models.Playlist -import org.moire.ultrasonic.data.ActiveServerProvider /** * Unit test for extension functions that converts api playlist entity to domain. @@ -23,7 +22,7 @@ class APIPlaylistConverterTest : BaseTest() { ) ) - val convertedEntity = entity.toMusicDirectoryDomainEntity(ActiveServerProvider.getActiveServerId()) + val convertedEntity = entity.toMusicDirectoryDomainEntity(serverId) with(convertedEntity) { name `should be equal to` entity.name From 152b1d261a1484c69f1d83fefdd0b7e8bcf3954c Mon Sep 17 00:00:00 2001 From: tzugen Date: Tue, 5 Jul 2022 15:44:34 +0200 Subject: [PATCH 09/17] Fix two tests. --- .../moire/ultrasonic/domain/APIIndexConverterTest.kt | 9 ++++++--- .../moire/ultrasonic/domain/APIShareConverterTest.kt | 12 ++++++------ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIIndexConverterTest.kt b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIIndexConverterTest.kt index 06e2cbf9..a785a9ce 100644 --- a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIIndexConverterTest.kt +++ b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIIndexConverterTest.kt @@ -11,7 +11,7 @@ import org.moire.ultrasonic.api.subsonic.models.Indexes /** * Unit tests for extension functions in [APIIndexesConverter.kt]. */ -class APIIndexConverterTest { +class APIIndexConverterTest : BaseTest() { @Test fun `Should convert Indexes entity`() { val artistsA = listOf( @@ -31,9 +31,12 @@ class APIIndexConverterTest { shortcutList = artistsA ) - val convertedEntity = entity.toArtistList() + val convertedEntity = entity.toArtistList(serverId) + + val expectedArtists = (artistsA + artistsT).map { + it.toDomainEntity(serverId) + }.toMutableList() - val expectedArtists = (artistsA + artistsT).map { it.toDomainEntity() }.toMutableList() with(convertedEntity) { size `should be equal to` expectedArtists.size this `should be equal to` expectedArtists diff --git a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIShareConverterTest.kt b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIShareConverterTest.kt index 42c85333..76f523e8 100644 --- a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIShareConverterTest.kt +++ b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIShareConverterTest.kt @@ -11,12 +11,12 @@ import org.moire.ultrasonic.api.subsonic.models.Share /** * Unit test for api to domain share entity converter functions. */ -class APIShareConverterTest { +class APIShareConverterTest : BaseTest() { @Test fun `Should convert share entity to domain`() { val entity = createFakeShare() - val domainEntity = entity.toDomainEntity() + val domainEntity = entity.toDomainEntity(serverId) with(domainEntity) { id `should be equal to` entity.id @@ -27,7 +27,7 @@ class APIShareConverterTest { lastVisited `should be equal to` shareTimeFormat.format(entity.lastVisited!!.time) expires `should be equal to` shareTimeFormat.format(entity.expires!!.time) visitCount `should be equal to` entity.visitCount.toLong() - this.getEntries() `should be equal to` entity.items.toDomainEntityList() + this.getEntries() `should be equal to` entity.items.toDomainEntityList(serverId) } } @@ -47,10 +47,10 @@ class APIShareConverterTest { createFakeShare().copy(id = "554", lastVisited = null) ) - val domainEntityList = entityList.toDomainEntitiesList() + val domainEntityList = entityList.toDomainEntitiesList(serverId) domainEntityList.size `should be equal to` entityList.size - domainEntityList[0] `should be equal to` entityList[0].toDomainEntity() - domainEntityList[1] `should be equal to` entityList[1].toDomainEntity() + domainEntityList[0] `should be equal to` entityList[0].toDomainEntity(serverId) + domainEntityList[1] `should be equal to` entityList[1].toDomainEntity(serverId) } } From 5b03b632fd5574a7ab0939e6440e3eb6e23c3eff Mon Sep 17 00:00:00 2001 From: tzugen Date: Tue, 5 Jul 2022 15:54:18 +0200 Subject: [PATCH 10/17] Fix three tests. --- .../kotlin/org/moire/ultrasonic/domain/APIAlbumConverterTest.kt | 2 +- .../moire/ultrasonic/domain/APIMusicDirectoryConverterTest.kt | 2 +- .../org/moire/ultrasonic/domain/APISearchConverterTest.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIAlbumConverterTest.kt b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIAlbumConverterTest.kt index 3f094844..51f7e760 100644 --- a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIAlbumConverterTest.kt +++ b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIAlbumConverterTest.kt @@ -62,7 +62,7 @@ class APIAlbumConverterTest { fun `Should convert list of Album entities to domain list entities`() { val entityList = listOf(Album(id = "455"), Album(id = "1"), Album(id = "1000")) - val convertedList = entityList.toDomainEntityList(ActiveServerProvider.getActiveServerId()) + val convertedList = entityList.toDomainEntityList(serverId) with(convertedList) { size `should be equal to` entityList.size diff --git a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIMusicDirectoryConverterTest.kt b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIMusicDirectoryConverterTest.kt index 7f47c1a5..3b0dcdce 100644 --- a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIMusicDirectoryConverterTest.kt +++ b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIMusicDirectoryConverterTest.kt @@ -21,7 +21,7 @@ class APIMusicDirectoryConverterTest : BaseTest() { childList = listOf(MusicDirectoryChild("1"), MusicDirectoryChild("2")) ) - val convertedEntity = entity.toDomainEntity(ActiveServerProvider.getActiveServerId()) + val convertedEntity = entity.toDomainEntity(serverId) with(convertedEntity) { name `should be equal to` entity.name diff --git a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APISearchConverterTest.kt b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APISearchConverterTest.kt index 10171cf8..fb23b47e 100644 --- a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APISearchConverterTest.kt +++ b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APISearchConverterTest.kt @@ -47,7 +47,7 @@ class APISearchConverterTest : BaseTest() { listOf(MusicDirectoryChild(id = "9118", parent = "112")) ) - val convertedEntity = entity.toDomainEntity(ActiveServerProvider.getActiveServerId()) + val convertedEntity = entity.toDomainEntity(serverId) with(convertedEntity) { artists.size `should be equal to` entity.artistList.size From 31a1fdace1367e02b7c138afba03f84d1e896157 Mon Sep 17 00:00:00 2001 From: tzugen Date: Tue, 5 Jul 2022 16:41:53 +0200 Subject: [PATCH 11/17] Formatting --- .../kotlin/org/moire/ultrasonic/domain/APIAlbumConverterTest.kt | 1 - .../moire/ultrasonic/domain/APIMusicDirectoryConverterTest.kt | 1 - .../kotlin/org/moire/ultrasonic/domain/APISearchConverterTest.kt | 1 - 3 files changed, 3 deletions(-) diff --git a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIAlbumConverterTest.kt b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIAlbumConverterTest.kt index 51f7e760..0234b92d 100644 --- a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIAlbumConverterTest.kt +++ b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIAlbumConverterTest.kt @@ -7,7 +7,6 @@ import org.amshove.kluent.`should be equal to` import org.junit.Test import org.moire.ultrasonic.api.subsonic.models.Album import org.moire.ultrasonic.api.subsonic.models.MusicDirectoryChild -import org.moire.ultrasonic.data.ActiveServerProvider /** * Unit test for extension functions in [APIAlbumConverter.kt] file. diff --git a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIMusicDirectoryConverterTest.kt b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIMusicDirectoryConverterTest.kt index 3b0dcdce..55bb07b1 100644 --- a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIMusicDirectoryConverterTest.kt +++ b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APIMusicDirectoryConverterTest.kt @@ -7,7 +7,6 @@ import org.amshove.kluent.`should be equal to` import org.junit.Test import org.moire.ultrasonic.api.subsonic.models.MusicDirectory import org.moire.ultrasonic.api.subsonic.models.MusicDirectoryChild -import org.moire.ultrasonic.data.ActiveServerProvider /** * Unit test for extension functions in APIMusicDirectoryConverter.kt file. diff --git a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APISearchConverterTest.kt b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APISearchConverterTest.kt index fb23b47e..7da3d34b 100644 --- a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APISearchConverterTest.kt +++ b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APISearchConverterTest.kt @@ -11,7 +11,6 @@ import org.moire.ultrasonic.api.subsonic.models.MusicDirectoryChild import org.moire.ultrasonic.api.subsonic.models.SearchResult import org.moire.ultrasonic.api.subsonic.models.SearchThreeResult import org.moire.ultrasonic.api.subsonic.models.SearchTwoResult -import org.moire.ultrasonic.data.ActiveServerProvider /** * Unit test for extension function in APISearchConverter.kt file. From b11694d6a298717aa0a8df224d3594ab0cece5fc Mon Sep 17 00:00:00 2001 From: tzugen Date: Wed, 6 Jul 2022 08:21:25 +0200 Subject: [PATCH 12/17] Fix logic whether to showArtistPicture --- .../kotlin/org/moire/ultrasonic/adapters/ArtistRowBinder.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 d1cfbc10..bb18f9ae 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/ArtistRowBinder.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/ArtistRowBinder.kt @@ -110,9 +110,11 @@ class ArtistRowBinder( } private fun showArtistPicture(): Boolean { - val isOffline = ActiveServerProvider.isOffline() + val isOnline = !ActiveServerProvider.isOffline() val shouldShowArtistPicture = Settings.shouldShowArtistPicture - return (!isOffline && shouldShowArtistPicture) || Settings.useId3TagsOffline + + val id3Enabled = (isOnline && Settings.shouldUseId3Tags) || Settings.useId3TagsOffline + return id3Enabled && shouldShowArtistPicture } /** From b955d77152a7c4723652726d8ead0e2340d1c650 Mon Sep 17 00:00:00 2001 From: tzugen Date: Wed, 6 Jul 2022 11:14:46 +0200 Subject: [PATCH 13/17] Make Id3 offline dependent on Id3 --- .../ultrasonic/adapters/ArtistRowBinder.kt | 10 ++----- .../ultrasonic/data/ActiveServerProvider.kt | 7 +++++ .../fragment/TrackCollectionFragment.kt | 5 ++-- .../moire/ultrasonic/model/ArtistListModel.kt | 30 +++++-------------- .../org/moire/ultrasonic/util/Settings.kt | 5 +++- 5 files changed, 25 insertions(+), 32 deletions(-) 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 bb18f9ae..76543e82 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/ArtistRowBinder.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/ArtistRowBinder.kt @@ -1,6 +1,6 @@ /* - * ArtistRowAdapter.kt - * Copyright (C) 2009-2021 Ultrasonic developers + * ArtistRowBinder.kt + * Copyright (C) 2009-2022 Ultrasonic developers * * Distributed under terms of the GNU GPLv3 license. */ @@ -110,11 +110,7 @@ class ArtistRowBinder( } private fun showArtistPicture(): Boolean { - val isOnline = !ActiveServerProvider.isOffline() - val shouldShowArtistPicture = Settings.shouldShowArtistPicture - - val id3Enabled = (isOnline && Settings.shouldUseId3Tags) || Settings.useId3TagsOffline - return id3Enabled && shouldShowArtistPicture + return ActiveServerProvider.isID3Enabled() && Settings.shouldShowArtistPicture } /** 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 a63e9f0d..5e3cc2d0 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ActiveServerProvider.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ActiveServerProvider.kt @@ -249,6 +249,13 @@ class ActiveServerProvider( return preferences.getBoolean(Constants.PREFERENCES_KEY_SCROBBLE, false) } + /** + * Queries if ID3 tags should be used + */ + fun isID3Enabled(): Boolean { + return Settings.shouldUseId3Tags && (!isOffline() || Settings.useId3TagsOffline) + } + /** * Queries if Server Scaling is enabled */ diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt index b9b9f549..54587ee5 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt @@ -1,6 +1,6 @@ /* * TrackCollectionFragment.kt - * Copyright (C) 2009-2021 Ultrasonic developers + * Copyright (C) 2009-2022 Ultrasonic developers * * Distributed under terms of the GNU GPLv3 license. */ @@ -31,6 +31,7 @@ import org.moire.ultrasonic.adapters.AlbumHeader import org.moire.ultrasonic.adapters.AlbumRowBinder import org.moire.ultrasonic.adapters.HeaderViewBinder import org.moire.ultrasonic.adapters.TrackViewBinder +import org.moire.ultrasonic.data.ActiveServerProvider import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline import org.moire.ultrasonic.domain.Identifiable import org.moire.ultrasonic.domain.MusicDirectory @@ -604,7 +605,7 @@ open class TrackCollectionFragment : MultiListFragment() { listModel.getRandom(albumListSize) } else { setTitle(name) - if (!isOffline() && Settings.shouldUseId3Tags || Settings.useId3TagsOffline) { + if (ActiveServerProvider.isID3Enabled()) { if (isAlbum) { listModel.getAlbum(refresh2, id!!, name) } else { 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 76118894..e41c6bbc 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/model/ArtistListModel.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/model/ArtistListModel.kt @@ -1,20 +1,8 @@ /* - This file is part of Subsonic. - - Subsonic is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Subsonic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Subsonic. If not, see . - - Copyright 2020 (C) Jozsef Varga + * ArtistListModel.kt + * Copyright (C) 2009-2022 Ultrasonic developers + * + * Distributed under terms of the GNU GPLv3 license. */ package org.moire.ultrasonic.model @@ -24,9 +12,9 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import java.text.Collator +import org.moire.ultrasonic.data.ActiveServerProvider 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 @@ -57,12 +45,10 @@ class ArtistListModel(application: Application) : GenericListModel(application) val musicFolderId = activeServer.musicFolderId - val result: List - - if (!isOffline && useId3Tags || Settings.useId3TagsOffline) { - result = musicService.getArtists(refresh) + val result = if (ActiveServerProvider.isID3Enabled()) { + musicService.getArtists(refresh) } else { - result = musicService.getIndexes(musicFolderId, refresh) + musicService.getIndexes(musicFolderId, refresh) } artists.postValue(result.toMutableList().sortedWith(comparator)) 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 ce559913..a6a131ec 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Settings.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Settings.kt @@ -1,6 +1,6 @@ /* * Settings.kt - * Copyright (C) 2009-2021 Ultrasonic developers + * Copyright (C) 2009-2022 Ultrasonic developers * * Distributed under terms of the GNU GPLv3 license. */ @@ -160,9 +160,12 @@ object Settings { var showNowPlayingDetails by BooleanSetting(Constants.PREFERENCES_KEY_SHOW_NOW_PLAYING_DETAILS, false) + // Normally you don't need to use these Settings directly, + // use ActiveServerProvider.isID3Enabled() instead @JvmStatic var shouldUseId3Tags by BooleanSetting(Constants.PREFERENCES_KEY_ID3_TAGS, false) + // See comment above. @JvmStatic var useId3TagsOffline by BooleanSetting(Constants.PREFERENCES_KEY_ID3_TAGS_OFFLINE, false) From 78bfab3753a57ff8b0fcc26e3f4786878a1ded37 Mon Sep 17 00:00:00 2001 From: tzugen Date: Wed, 6 Jul 2022 12:26:48 +0200 Subject: [PATCH 14/17] Conditionally hide offline Id3 Setting --- .../org/moire/ultrasonic/fragment/SettingsFragment.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SettingsFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SettingsFragment.kt index 70bc45d0..3b383089 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SettingsFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SettingsFragment.kt @@ -76,6 +76,7 @@ class SettingsFragment : private var directoryCacheTime: ListPreference? = null private var mediaButtonsEnabled: CheckBoxPreference? = null private var showArtistPicture: CheckBoxPreference? = null + private var useId3TagsOffline: CheckBoxPreference? = null private var sharingDefaultDescription: EditTextPreference? = null private var sharingDefaultGreeting: EditTextPreference? = null private var sharingDefaultExpiration: TimeSpanPreference? = null @@ -121,6 +122,7 @@ class SettingsFragment : pauseOnBluetoothDevice = findPreference(Constants.PREFERENCES_KEY_PAUSE_ON_BLUETOOTH_DEVICE) debugLogToFile = findPreference(Constants.PREFERENCES_KEY_DEBUG_LOG_TO_FILE) showArtistPicture = findPreference(Constants.PREFERENCES_KEY_SHOW_ARTIST_PICTURE) + useId3TagsOffline = findPreference(Constants.PREFERENCES_KEY_ID3_TAGS_OFFLINE) customCacheLocation = findPreference(Constants.PREFERENCES_KEY_CUSTOM_CACHE_LOCATION) sharingDefaultGreeting?.text = shareGreeting @@ -196,7 +198,8 @@ class SettingsFragment : setDebugLogToFile(sharedPreferences.getBoolean(key, false)) } Constants.PREFERENCES_KEY_ID3_TAGS -> { - showArtistPicture!!.isEnabled = sharedPreferences.getBoolean(key, false) + showArtistPicture?.isEnabled = sharedPreferences.getBoolean(key, false) + useId3TagsOffline?.isEnabled = sharedPreferences.getBoolean(key, false) } Constants.PREFERENCES_KEY_THEME -> { RxBus.themeChangedEventPublisher.onNext(Unit) @@ -372,6 +375,7 @@ class SettingsFragment : debugLogToFile?.summary = "" } showArtistPicture?.isEnabled = shouldUseId3Tags + useId3TagsOffline?.isEnabled = shouldUseId3Tags } private fun setHideMedia(hide: Boolean) { From de0cb7713b8bbf0c7ebbf28655dda3faecfb76aa Mon Sep 17 00:00:00 2001 From: tzugen Date: Wed, 6 Jul 2022 14:55:19 +0200 Subject: [PATCH 15/17] Improve offline support for Compilations --- .../org/moire/ultrasonic/data/AlbumDao.kt | 6 +++ .../org/moire/ultrasonic/data/TrackDao.kt | 6 +++ .../moire/ultrasonic/service/Downloader.kt | 47 +++++++++++++------ .../ultrasonic/service/OfflineMusicService.kt | 18 +++++-- 4 files changed, 58 insertions(+), 19 deletions(-) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/AlbumDao.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/AlbumDao.kt index 8eccef40..e0b9a6cd 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/AlbumDao.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/AlbumDao.kt @@ -19,6 +19,12 @@ interface AlbumDao : GenericDao { @Query("SELECT * FROM albums") fun get(): List + /** + * Get album by id + */ + @Query("SELECT * FROM albums where id LIKE :albumId LIMIT 1") + fun get(albumId: String): Album + /** * Get albums by artist */ diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/TrackDao.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/TrackDao.kt index 7700d1a9..af2190bf 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/TrackDao.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/TrackDao.kt @@ -25,4 +25,10 @@ interface TrackDao : GenericDao { */ @Query("SELECT * FROM tracks WHERE albumId LIKE :id") fun byAlbum(id: String): List + + /** + * Get albums by artist + */ + @Query("SELECT * FROM tracks WHERE artistId LIKE :id") + fun byArtist(id: String): List } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/Downloader.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/Downloader.kt index d7cad0e7..b46cb151 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/Downloader.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/Downloader.kt @@ -14,6 +14,7 @@ import java.util.PriorityQueue 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.Artist import org.moire.ultrasonic.domain.Track import org.moire.ultrasonic.playback.LegacyPlaylistManager @@ -402,6 +403,14 @@ class Downloader( downloadFile.completeFile ) } + + // Hidden feature: If track is toggled between pinned/saved, refresh the metadata.. + try { + downloadFile.track.cacheMetadata() + } catch (ignore: Exception) { + Timber.w(ignore) + } + downloadFile.status.postValue(newStatus) return } @@ -518,7 +527,29 @@ class Downloader( val onlineDB = activeServerProvider.getActiveMetaDatabase() val offlineDB = activeServerProvider.offlineMetaDatabase - var artist: Artist? = onlineDB.artistDao().get(artistId!!) + cacheArtist(onlineDB, offlineDB, artistId!!) + + // Now cache the album + if (albumId?.isNotEmpty() == true) { + // This is a cached call + val albums = musicService.getAlbumsOfArtist(artistId!!, null, false) + val album = albums.find { it.id == albumId } + + if (album != null) { + offlineDB.albumDao().insert(album) + + // If the album is a Compilation, also cache the Album artist + if (album.artistId != null && album.artistId != artistId) + cacheArtist(onlineDB, offlineDB, album.artistId!!) + } + } + + // Now cache the track data + offlineDB.trackDao().insert(this) + } + + private fun cacheArtist(onlineDB: MetaDatabase, offlineDB: MetaDatabase, artistId: String) { + var artist: Artist? = onlineDB.artistDao().get(artistId) // If we are downloading a new album, and the user has not visited the Artists list // recently, then the artist won't be in the database. @@ -533,20 +564,6 @@ class Downloader( if (artist != null) { offlineDB.artistDao().insert(artist) } - - // Now cache the album - if (albumId?.isNotEmpty() == true) { - // This is a cached call - val albums = musicService.getAlbumsOfArtist(artistId!!, null, false) - val album = albums.find { it.id == albumId } - - if (album != null) { - offlineDB.albumDao().insert(album) - } - } - - // Now cache the track data - offlineDB.trackDao().insert(this) } private fun downloadAndSaveCoverArt() { 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 9af9f9ed..f4ddbbef 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt @@ -110,9 +110,6 @@ class OfflineMusicService : MusicService, KoinComponent { override fun getArtists(refresh: Boolean): List { val result = cachedArtists.get() - if (result.isEmpty()) { - // use indexes? - } return result } @@ -472,7 +469,20 @@ class OfflineMusicService : MusicService, KoinComponent { @Throws(Exception::class) override fun getAlbumsOfArtist(id: String, name: String?, refresh: Boolean): List { - return cachedAlbums.byArtist(id) + val directAlbums = cachedAlbums.byArtist(id) + + // The direct albums won't contain any compilations that the artist has participated in + // We need to fetch the tracks of the artist and then gather the compilation albums from that. + val tracks = cachedTracks.byArtist(id) + val albumIds = tracks.map { + it.albumId + }.distinct().filterNotNull() + + val compilationAlbums = albumIds.map { + cachedAlbums.get(it) + } + + return directAlbums.plus(compilationAlbums).distinct() } @Throws(OfflineException::class) From ecfce59e0fab4c7272a8f9279c5a5fd7b3f09785 Mon Sep 17 00:00:00 2001 From: tzugen Date: Thu, 7 Jul 2022 18:53:36 +0200 Subject: [PATCH 16/17] Add clearer warning to ID3 offline setting --- .../ultrasonic/activity/NavigationActivity.kt | 2 ++ .../ultrasonic/fragment/SettingsFragment.kt | 35 +++++++++++++++++-- .../org/moire/ultrasonic/util/Constants.kt | 1 + .../org/moire/ultrasonic/util/Settings.kt | 3 ++ ultrasonic/src/main/res/values/strings.xml | 3 +- 5 files changed, 41 insertions(+), 3 deletions(-) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/NavigationActivity.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/NavigationActivity.kt index 7c399d6c..5dac3838 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/NavigationActivity.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/NavigationActivity.kt @@ -392,6 +392,8 @@ class NavigationActivity : AppCompatActivity() { if (!infoDialogDisplayed) { infoDialogDisplayed = true + Settings.firstInstalledVersion = Util.getVersionCode(UApp.applicationContext()) + InfoDialog.Builder(this) .setTitle(R.string.main_welcome_title) .setMessage(R.string.main_welcome_text_demo) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SettingsFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SettingsFragment.kt index 3b383089..5e90715d 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SettingsFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SettingsFragment.kt @@ -6,11 +6,16 @@ import android.content.DialogInterface import android.content.Intent import android.content.SharedPreferences import android.content.SharedPreferences.OnSharedPreferenceChangeListener +import android.graphics.Color +import android.graphics.Typeface import android.net.Uri import android.os.Build import android.os.Bundle import android.provider.DocumentsContract import android.provider.SearchRecentSuggestions +import android.text.SpannableString +import android.text.style.ForegroundColorSpan +import android.text.style.StyleSpan import android.view.View import androidx.annotation.StringRes import androidx.fragment.app.DialogFragment @@ -126,11 +131,35 @@ class SettingsFragment : customCacheLocation = findPreference(Constants.PREFERENCES_KEY_CUSTOM_CACHE_LOCATION) sharingDefaultGreeting?.text = shareGreeting + + setupTextColors() setupClearSearchPreference() setupCacheLocationPreference() setupBluetoothDevicePreferences() } + private fun setupTextColors(enabled: Boolean = shouldUseId3Tags) { + val firstPart = getString(R.string.settings_use_id3_offline_warning) + var secondPart = getString(R.string.settings_use_id3_offline_summary) + + // Little hack to circumvent a bug in Android. If we just change the color, + // the text is not refreshed. If we also change the string, it is refreshed. + if (enabled) secondPart += " " + + val color = if (enabled) "#bd5164" else "#813b48" + + Timber.i(color) + + val warning = SpannableString(firstPart + "\n" + secondPart) + warning.setSpan( + ForegroundColorSpan(Color.parseColor(color)), 0, firstPart.length, 0 + ) + warning.setSpan( + StyleSpan(Typeface.BOLD), 0, firstPart.length, 0 + ) + useId3TagsOffline?.summary = warning + } + override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) update() @@ -198,8 +227,10 @@ class SettingsFragment : setDebugLogToFile(sharedPreferences.getBoolean(key, false)) } Constants.PREFERENCES_KEY_ID3_TAGS -> { - showArtistPicture?.isEnabled = sharedPreferences.getBoolean(key, false) - useId3TagsOffline?.isEnabled = sharedPreferences.getBoolean(key, false) + val enabled = sharedPreferences.getBoolean(key, false) + showArtistPicture?.isEnabled = enabled + useId3TagsOffline?.isEnabled = enabled + setupTextColors(enabled) } Constants.PREFERENCES_KEY_THEME -> { RxBus.themeChangedEventPublisher.onNext(Unit) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Constants.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Constants.kt index 9f44202b..0f12af49 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Constants.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Constants.kt @@ -105,6 +105,7 @@ object Constants { const val PREFERENCES_KEY_PAUSE_ON_BLUETOOTH_DEVICE = "pauseOnBluetoothDevice" const val PREFERENCES_KEY_DEBUG_LOG_TO_FILE = "debugLogToFile" const val PREFERENCES_KEY_OVERRIDE_LANGUAGE = "overrideLanguage" + const val PREFERENCES_FIRST_INSTALLED_VERSION = "firstInstalledVersion" const val PREFERENCE_VALUE_ALL = 0 const val PREFERENCE_VALUE_A2DP = 1 const val PREFERENCE_VALUE_DISABLED = 2 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 a6a131ec..018fa98c 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Settings.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Settings.kt @@ -251,6 +251,9 @@ object Settings { var useHwOffload by BooleanSetting(Constants.PREFERENCES_KEY_HARDWARE_OFFLOAD, false) + @JvmStatic + var firstInstalledVersion by IntSetting(Constants.PREFERENCES_FIRST_INSTALLED_VERSION, 0) + // TODO: Remove in December 2022 fun migrateFeatureStorage() { val sp = appContext.getSharedPreferences("feature_flags", Context.MODE_PRIVATE) diff --git a/ultrasonic/src/main/res/values/strings.xml b/ultrasonic/src/main/res/values/strings.xml index b4e81c76..551043dc 100644 --- a/ultrasonic/src/main/res/values/strings.xml +++ b/ultrasonic/src/main/res/values/strings.xml @@ -317,7 +317,8 @@ Browse Using ID3 Tags Use ID3 tag methods instead of file system based methods Use ID3 method also when offline - (Experimental) + Experimental: If you enable this Setting it will only show the music that you have downloaded with Ultrasonic 4.0 or later. + Earlier downloads don\'t have the necessary metadata downloaded. You can toggle between Pin and Save mode to trigger the download of the missing metadata. Show artist picture in artist list Displays the artist picture in the artist list if available Video From 798d795e81c44f9605186df57728b8dc3c403286 Mon Sep 17 00:00:00 2001 From: tzugen Date: Thu, 7 Jul 2022 19:20:40 +0200 Subject: [PATCH 17/17] Add Album list support in Offline --- .../org/moire/ultrasonic/data/AlbumDao.kt | 6 +++++ .../moire/ultrasonic/fragment/MainFragment.kt | 23 +++++++++++-------- .../ultrasonic/service/OfflineMusicService.kt | 3 ++- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/AlbumDao.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/AlbumDao.kt index e0b9a6cd..822f1a57 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/AlbumDao.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/AlbumDao.kt @@ -19,6 +19,12 @@ interface AlbumDao : GenericDao { @Query("SELECT * FROM albums") fun get(): List + /** + * Get all albums in a specific range + */ + @Query("SELECT * FROM albums LIMIT :offset,:size") + fun get(size: Int, offset: Int = 0): List + /** * Get album by id */ diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/MainFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/MainFragment.kt index 127e6a2b..7add706c 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/MainFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/MainFragment.kt @@ -66,17 +66,17 @@ class MainFragment : Fragment(), KoinComponent { override fun onResume() { super.onResume() - var shouldRestart = false + var shouldRelayout = false val currentId3Setting = Settings.shouldUseId3Tags // If setting has changed... - if (currentId3Setting != cachedId3Setting) { - cachedId3Setting = currentId3Setting - shouldRestart = true + if (currentId3Setting != useId3) { + useId3 = currentId3Setting + shouldRelayout = true } // then setup the list anew. - if (shouldRestart) { + if (shouldRelayout) { setupItemVisibility() } } @@ -109,17 +109,19 @@ class MainFragment : Fragment(), KoinComponent { private fun setupItemVisibility() { // Cache some values - cachedId3Setting = Settings.shouldUseId3Tags + useId3 = Settings.shouldUseId3Tags + useId3Offline = Settings.useId3TagsOffline + val isOnline = !isOffline() // Music musicTitle.isVisible = true artistsButton.isVisible = true - albumsButton.isVisible = isOnline + albumsButton.isVisible = isOnline || useId3Offline genresButton.isVisible = true // Songs - songsTitle.isVisible = isOnline + songsTitle.isVisible = true randomSongsButton.isVisible = true songsStarredButton.isVisible = isOnline @@ -128,7 +130,7 @@ class MainFragment : Fragment(), KoinComponent { albumsNewestButton.isVisible = isOnline albumsRecentButton.isVisible = isOnline albumsFrequentButton.isVisible = isOnline - albumsHighestButton.isVisible = isOnline && !cachedId3Setting + albumsHighestButton.isVisible = isOnline && !useId3 albumsRandomButton.isVisible = isOnline albumsStarredButton.isVisible = isOnline albumsAlphaByNameButton.isVisible = isOnline @@ -240,6 +242,7 @@ class MainFragment : Fragment(), KoinComponent { } companion object { - private var cachedId3Setting = false + private var useId3 = false + private var useId3Offline = false } } 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 f4ddbbef..6fc97d2d 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt @@ -328,7 +328,8 @@ class OfflineMusicService : MusicService, KoinComponent { offset: Int, musicFolderId: String? ): List { - throw OfflineException("getAlbumList2 isn't available in offline mode") + // TODO: Implement filtering by musicFolder? + return cachedAlbums.get(size, offset) } @Throws(Exception::class)