mirror of
https://github.com/ultrasonic/ultrasonic
synced 2025-02-12 01:30:46 +01:00
Add a Room database for Artists, Indexes and MusicFolders.
* There is one database for each Server * Index items are saved with a "musicFolderId" prop, which makes it possible to filter the database by musicFolder without necessarily having to query the server for it. * Databases for new Servers are created on the fly * If the user removes a server, the respective database is deleted.
This commit is contained in:
parent
fa94cd24da
commit
dbdb59bbff
@ -1,7 +1,14 @@
|
||||
apply from: bootstrap.kotlinModule
|
||||
apply from: bootstrap.androidModule
|
||||
apply plugin: 'kotlin-kapt'
|
||||
|
||||
ext {
|
||||
jacocoExclude = [
|
||||
'**/domain/**'
|
||||
]
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation androidSupport.roomRuntime
|
||||
implementation androidSupport.roomKtx
|
||||
kapt androidSupport.room
|
||||
}
|
||||
|
4
core/domain/src/main/AndroidManifest.xml
Normal file
4
core/domain/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.moire.ultrasonic.subsonic.domain">
|
||||
</manifest>
|
@ -1,18 +1,17 @@
|
||||
package org.moire.ultrasonic.domain
|
||||
|
||||
import java.io.Serializable
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(tableName = "artists")
|
||||
data class Artist(
|
||||
override var id: String? = null,
|
||||
@PrimaryKey override var id: String,
|
||||
override var name: String? = null,
|
||||
var index: String? = null,
|
||||
var coverArt: String? = null,
|
||||
var albumCount: Long? = null,
|
||||
var closeness: Int = 0
|
||||
) : Serializable, GenericEntry(), Comparable<Artist> {
|
||||
companion object {
|
||||
private const val serialVersionUID = -5790532593784846982L
|
||||
}
|
||||
override var index: String? = null,
|
||||
override var coverArt: String? = null,
|
||||
override var albumCount: Long? = null,
|
||||
override var closeness: Int = 0
|
||||
) : ArtistOrIndex(id), Comparable<Artist> {
|
||||
|
||||
override fun compareTo(other: Artist): Int {
|
||||
when {
|
||||
|
@ -0,0 +1,18 @@
|
||||
package org.moire.ultrasonic.domain
|
||||
|
||||
import androidx.room.Ignore
|
||||
|
||||
open class ArtistOrIndex(
|
||||
@Ignore
|
||||
override var id: String,
|
||||
@Ignore
|
||||
override var name: String? = null,
|
||||
@Ignore
|
||||
open var index: String? = null,
|
||||
@Ignore
|
||||
open var coverArt: String? = null,
|
||||
@Ignore
|
||||
open var albumCount: Long? = null,
|
||||
@Ignore
|
||||
open var closeness: Int = 0
|
||||
) : GenericEntry()
|
@ -1,8 +1,12 @@
|
||||
package org.moire.ultrasonic.domain
|
||||
|
||||
abstract class GenericEntry {
|
||||
// TODO: Should be non-null!
|
||||
abstract val id: String?
|
||||
import androidx.room.Ignore
|
||||
|
||||
open class GenericEntry {
|
||||
// TODO Should be non-null!
|
||||
@Ignore
|
||||
open val id: String? = null
|
||||
@Ignore
|
||||
open val name: String? = null
|
||||
|
||||
// These are just a formality and will never be called,
|
||||
|
@ -1,11 +1,14 @@
|
||||
package org.moire.ultrasonic.domain
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import java.io.Serializable
|
||||
|
||||
@Entity
|
||||
data class Genre(
|
||||
val name: String,
|
||||
val index: String
|
||||
) : Serializable {
|
||||
@PrimaryKey val index: String,
|
||||
override val name: String
|
||||
) : Serializable, GenericEntry() {
|
||||
companion object {
|
||||
private const val serialVersionUID = -3943025175219134028L
|
||||
}
|
||||
|
@ -0,0 +1,15 @@
|
||||
package org.moire.ultrasonic.domain
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(tableName = "indexes")
|
||||
data class Index(
|
||||
@PrimaryKey override var id: String,
|
||||
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)
|
@ -1,14 +0,0 @@
|
||||
package org.moire.ultrasonic.domain
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
data class Indexes(
|
||||
val lastModified: Long,
|
||||
val ignoredArticles: String,
|
||||
val shortcuts: MutableList<Artist> = mutableListOf(),
|
||||
val artists: MutableList<Artist> = mutableListOf()
|
||||
) : Serializable {
|
||||
companion object {
|
||||
private const val serialVersionUID = 8156117238598414701L
|
||||
}
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
package org.moire.ultrasonic.domain
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import java.io.Serializable
|
||||
import java.util.Date
|
||||
|
||||
@ -35,8 +37,9 @@ class MusicDirectory {
|
||||
return children.filter { it.isDirectory && includeDirs || !it.isDirectory && includeFiles }
|
||||
}
|
||||
|
||||
@Entity
|
||||
data class Entry(
|
||||
override var id: String,
|
||||
@PrimaryKey override var id: String,
|
||||
var parent: String? = null,
|
||||
var isDirectory: Boolean = false,
|
||||
var title: String? = null,
|
||||
|
@ -1,9 +1,13 @@
|
||||
package org.moire.ultrasonic.domain
|
||||
|
||||
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")
|
||||
data class MusicFolder(
|
||||
override val id: String,
|
||||
@PrimaryKey override val id: String,
|
||||
override val name: String
|
||||
) : GenericEntry()
|
||||
|
@ -1,9 +1,11 @@
|
||||
/**
|
||||
* This module provides a base for for submodules which depend on the Android runtime
|
||||
*/
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'jacoco'
|
||||
apply from: "${project.rootDir}/gradle_scripts/code_quality.gradle"
|
||||
|
||||
|
||||
android {
|
||||
compileSdkVersion versions.compileSdk
|
||||
|
||||
|
@ -1,5 +1,8 @@
|
||||
apply plugin: 'java-library'
|
||||
/**
|
||||
* This module provides a base for for pure kotlin modules
|
||||
*/
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'jacoco'
|
||||
apply from: "${project.rootDir}/gradle_scripts/code_quality.gradle"
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package org.moire.ultrasonic.data
|
||||
|
||||
import androidx.room.Room
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
@ -7,6 +8,7 @@ import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.moire.ultrasonic.R
|
||||
import org.moire.ultrasonic.app.UApp
|
||||
import org.moire.ultrasonic.di.DB_FILENAME
|
||||
import org.moire.ultrasonic.service.MusicServiceFactory.resetMusicService
|
||||
import org.moire.ultrasonic.util.Constants
|
||||
import org.moire.ultrasonic.util.Util
|
||||
@ -20,6 +22,8 @@ class ActiveServerProvider(
|
||||
private val repository: ServerSettingDao
|
||||
) {
|
||||
private var cachedServer: ServerSetting? = null
|
||||
private var cachedDatabase: MetaDatabase? = null
|
||||
private var cachedServerId: Int? = null
|
||||
|
||||
/**
|
||||
* Get the settings of the current Active Server
|
||||
@ -82,6 +86,33 @@ class ActiveServerProvider(
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun getActiveMetaDatabase(): MetaDatabase {
|
||||
val activeServer = getActiveServerId()
|
||||
|
||||
if (activeServer == cachedServerId && cachedDatabase != null) {
|
||||
return cachedDatabase!!
|
||||
}
|
||||
|
||||
Timber.i("Switching to new database, id:$activeServer")
|
||||
cachedServerId = activeServer
|
||||
val db = Room.databaseBuilder(
|
||||
UApp.applicationContext(),
|
||||
MetaDatabase::class.java,
|
||||
METADATA_DB + cachedServerId
|
||||
)
|
||||
.fallbackToDestructiveMigrationOnDowngrade()
|
||||
.build()
|
||||
return db
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun deleteMetaDatabase(id: Int) {
|
||||
cachedDatabase?.close()
|
||||
UApp.applicationContext().deleteDatabase(METADATA_DB + id)
|
||||
Timber.i("Deleted metadataBase, id:$id")
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum Subsonic API version of the current server.
|
||||
*/
|
||||
@ -130,6 +161,9 @@ class ActiveServerProvider(
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
const val METADATA_DB = "$DB_FILENAME-meta-"
|
||||
|
||||
/**
|
||||
* Queries if the Active Server is the "Offline" mode of Ultrasonic
|
||||
* @return True, if the "Offline" mode is selected
|
||||
|
@ -6,7 +6,8 @@ import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
|
||||
/**
|
||||
* Room Database to be used to store data for Ultrasonic
|
||||
* Room Database to be used to store global data for the whole app.
|
||||
* This could be settings or data that are not specific to any remote music database
|
||||
*/
|
||||
@Database(entities = [ServerSetting::class], version = 3)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
@ -0,0 +1,31 @@
|
||||
package org.moire.ultrasonic.data
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import org.moire.ultrasonic.domain.Artist
|
||||
|
||||
@Dao
|
||||
interface ArtistsDao {
|
||||
/**
|
||||
* Insert a list in the database. If the item already exists, replace it.
|
||||
*
|
||||
* @param objects the items to be inserted.
|
||||
*/
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
@JvmSuppressWildcards
|
||||
fun set(objects: List<Artist>)
|
||||
|
||||
/**
|
||||
* Clear the whole database
|
||||
*/
|
||||
@Query("DELETE FROM artists")
|
||||
fun clear()
|
||||
|
||||
/**
|
||||
* Get all artists
|
||||
*/
|
||||
@Query("SELECT * FROM artists")
|
||||
fun get(): List<Artist>
|
||||
}
|
@ -0,0 +1,146 @@
|
||||
package org.moire.ultrasonic.data
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import androidx.room.Update
|
||||
import org.moire.ultrasonic.domain.Index
|
||||
import org.moire.ultrasonic.domain.MusicFolder
|
||||
|
||||
@Dao
|
||||
interface MusicFoldersDao : GenericDao<MusicFolder> {
|
||||
/**
|
||||
* Clear the whole database
|
||||
*/
|
||||
@Query("DELETE FROM music_folders")
|
||||
fun clear()
|
||||
|
||||
/**
|
||||
* Get all folders
|
||||
*/
|
||||
@Query("SELECT * FROM music_folders")
|
||||
fun get(): List<MusicFolder>
|
||||
}
|
||||
|
||||
@Dao
|
||||
interface IndexDao : GenericDao<Index> {
|
||||
|
||||
/**
|
||||
* Clear the whole database
|
||||
*/
|
||||
@Query("DELETE FROM indexes")
|
||||
fun clear()
|
||||
|
||||
/**
|
||||
* Get all indexes
|
||||
*/
|
||||
@Query("SELECT * FROM indexes")
|
||||
fun get(): List<Index>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insertAll(vararg indexes: Index)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insertArray(arr: Array<Index>)
|
||||
|
||||
/**
|
||||
* Get all indexes for a specific folder id
|
||||
*/
|
||||
@Query("SELECT * FROM indexes where musicFolderId LIKE :musicFolderId")
|
||||
fun get(musicFolderId: String): List<Index>
|
||||
|
||||
/**
|
||||
* Upserts (insert or update) an object to the database
|
||||
*
|
||||
* @param obj the object to upsert
|
||||
*/
|
||||
@Transaction
|
||||
@JvmSuppressWildcards
|
||||
fun upsert(obj: Index) {
|
||||
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<Index>) {
|
||||
val insertResult = insertIgnoring(objList)
|
||||
val updateList: MutableList<Index> = ArrayList()
|
||||
for (i in insertResult.indices) {
|
||||
if (insertResult[i] == -1L) {
|
||||
updateList.add(objList[i])
|
||||
}
|
||||
}
|
||||
if (updateList.isNotEmpty()) {
|
||||
update(updateList)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface GenericDao<T> {
|
||||
/**
|
||||
* Replaces the list with a new collection
|
||||
*
|
||||
* @param objects the items to be inserted.
|
||||
*/
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
@JvmSuppressWildcards
|
||||
fun set(objects: List<T>)
|
||||
|
||||
/**
|
||||
* Insert an object in the database.
|
||||
*
|
||||
* @param obj the object to be inserted.
|
||||
* @return The SQLite row id
|
||||
*/
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
@JvmSuppressWildcards
|
||||
fun insertIgnoring(obj: T): Long
|
||||
|
||||
/**
|
||||
* Insert an array of objects in the database.
|
||||
*
|
||||
* @param obj the objects to be inserted.
|
||||
* @return The SQLite row ids
|
||||
*/
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
@JvmSuppressWildcards
|
||||
fun insertIgnoring(obj: List<T>?): List<Long>
|
||||
|
||||
/**
|
||||
* Update an object from the database.
|
||||
*
|
||||
* @param obj the object to be updated
|
||||
*/
|
||||
@Update
|
||||
@JvmSuppressWildcards
|
||||
fun update(obj: T)
|
||||
|
||||
/**
|
||||
* Update an array of objects from the database.
|
||||
*
|
||||
* @param obj the object to be updated
|
||||
*/
|
||||
@Update
|
||||
@JvmSuppressWildcards
|
||||
fun update(obj: List<T>?)
|
||||
|
||||
/**
|
||||
* Delete an object from the database
|
||||
*
|
||||
* @param obj the object to be deleted
|
||||
*/
|
||||
@Delete
|
||||
@JvmSuppressWildcards
|
||||
fun delete(obj: T)
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package org.moire.ultrasonic.data
|
||||
|
||||
import androidx.room.Database
|
||||
import androidx.room.RoomDatabase
|
||||
import org.moire.ultrasonic.domain.Artist
|
||||
import org.moire.ultrasonic.domain.Index
|
||||
import org.moire.ultrasonic.domain.MusicFolder
|
||||
|
||||
@Database(
|
||||
entities = [Artist::class, Index::class, MusicFolder::class],
|
||||
version = 1
|
||||
)
|
||||
abstract class MetaDatabase : RoomDatabase() {
|
||||
abstract fun artistsDao(): ArtistsDao
|
||||
|
||||
abstract fun musicFoldersDao(): MusicFoldersDao
|
||||
|
||||
abstract fun indexDao(): IndexDao
|
||||
}
|
@ -65,7 +65,7 @@ val musicServiceModule = module {
|
||||
single { SubsonicAPIClient(get(), get()) }
|
||||
|
||||
single<MusicService>(named(ONLINE_MUSIC_SERVICE)) {
|
||||
CachedMusicService(RESTMusicService(get(), get(), get()))
|
||||
CachedMusicService(RESTMusicService(get(), get()))
|
||||
}
|
||||
|
||||
single<MusicService>(named(OFFLINE_MUSIC_SERVICE)) {
|
||||
|
@ -5,12 +5,20 @@ 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(
|
||||
id = this@toDomainEntity.id,
|
||||
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(
|
||||
id = this@toIndexEntity.id,
|
||||
coverArt = this@toIndexEntity.coverArt,
|
||||
name = this@toIndexEntity.name
|
||||
)
|
||||
|
||||
fun APIArtist.toMusicDirectoryDomainEntity(): MusicDirectory = MusicDirectory().apply {
|
||||
name = this@toMusicDirectoryDomainEntity.name
|
||||
addAll(this@toMusicDirectoryDomainEntity.albumsList.map { it.toDomainEntity() })
|
||||
|
@ -3,15 +3,37 @@
|
||||
@file:JvmName("APIIndexesConverter")
|
||||
package org.moire.ultrasonic.domain
|
||||
|
||||
import org.moire.ultrasonic.api.subsonic.models.Index
|
||||
import org.moire.ultrasonic.api.subsonic.models.Index as APIIndex
|
||||
import org.moire.ultrasonic.api.subsonic.models.Indexes as APIIndexes
|
||||
|
||||
fun APIIndexes.toDomainEntity(): Indexes = Indexes(
|
||||
this.lastModified, this.ignoredArticles,
|
||||
this.shortcutList.map { it.toDomainEntity() }.toMutableList(),
|
||||
this.indexList.foldIndexToArtistList().toMutableList()
|
||||
fun APIIndexes.toArtistList(): List<Artist> {
|
||||
val list = this.shortcutList.map { it.toDomainEntity() }.toMutableList()
|
||||
list.addAll(this.indexList.foldIndexToArtistList())
|
||||
return list
|
||||
}
|
||||
|
||||
fun APIIndexes.toIndexList(musicFolderId: String?): List<Index> {
|
||||
val list = this.shortcutList.map { it.toIndexEntity() }.toMutableList()
|
||||
list.addAll(this.indexList.foldIndexToIndexList(musicFolderId))
|
||||
return list
|
||||
}
|
||||
|
||||
private fun List<APIIndex>.foldIndexToArtistList(): List<Artist> = this.fold(
|
||||
listOf(),
|
||||
{ acc, index ->
|
||||
acc + index.artists.map {
|
||||
it.toDomainEntity()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
private fun List<Index>.foldIndexToArtistList(): List<Artist> = this.fold(
|
||||
listOf(), { acc, index -> acc + index.artists.map { it.toDomainEntity() } }
|
||||
private fun List<APIIndex>.foldIndexToIndexList(musicFolderId: String?): List<Index> = this.fold(
|
||||
listOf(),
|
||||
{ acc, index ->
|
||||
acc + index.artists.map {
|
||||
val ret = it.toIndexEntity()
|
||||
ret.musicFolderId = musicFolderId
|
||||
ret
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -4,13 +4,13 @@ import android.os.Bundle
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.LiveData
|
||||
import org.moire.ultrasonic.R
|
||||
import org.moire.ultrasonic.domain.Artist
|
||||
import org.moire.ultrasonic.domain.ArtistOrIndex
|
||||
import org.moire.ultrasonic.util.Constants
|
||||
|
||||
/**
|
||||
* Displays the list of Artists from the media library
|
||||
*/
|
||||
class ArtistListFragment : GenericListFragment<Artist, ArtistRowAdapter>() {
|
||||
class ArtistListFragment : GenericListFragment<ArtistOrIndex, ArtistRowAdapter>() {
|
||||
|
||||
/**
|
||||
* The ViewModel to use to get the data
|
||||
@ -41,7 +41,7 @@ class ArtistListFragment : GenericListFragment<Artist, ArtistRowAdapter>() {
|
||||
/**
|
||||
* The central function to pass a query to the model and return a LiveData object
|
||||
*/
|
||||
override fun getLiveData(args: Bundle?): LiveData<List<Artist>> {
|
||||
override fun getLiveData(args: Bundle?): LiveData<List<ArtistOrIndex>> {
|
||||
val refresh = args?.getBoolean(Constants.INTENT_EXTRA_NAME_REFRESH) ?: false
|
||||
return listModel.getItems(refresh, refreshListView!!)
|
||||
}
|
||||
|
@ -23,19 +23,19 @@ import android.os.Bundle
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import org.moire.ultrasonic.domain.Artist
|
||||
import org.moire.ultrasonic.domain.ArtistOrIndex
|
||||
import org.moire.ultrasonic.service.MusicService
|
||||
|
||||
/**
|
||||
* Provides ViewModel which contains the list of available Artists
|
||||
*/
|
||||
class ArtistListModel(application: Application) : GenericListModel(application) {
|
||||
private val artists: MutableLiveData<List<Artist>> = MutableLiveData(listOf())
|
||||
private val artists: MutableLiveData<List<ArtistOrIndex>> = MutableLiveData(listOf())
|
||||
|
||||
/**
|
||||
* Retrieves all available Artists in a LiveData
|
||||
*/
|
||||
fun getItems(refresh: Boolean, swipe: SwipeRefreshLayout): LiveData<List<Artist>> {
|
||||
fun getItems(refresh: Boolean, swipe: SwipeRefreshLayout): LiveData<List<ArtistOrIndex>> {
|
||||
// Don't reload the data if navigating back to the view that was active before.
|
||||
// This way, we keep the scroll position
|
||||
if (artists.value!!.isEmpty() || refresh) {
|
||||
@ -55,14 +55,14 @@ class ArtistListModel(application: Application) : GenericListModel(application)
|
||||
|
||||
val musicFolderId = activeServer.musicFolderId
|
||||
|
||||
val result = if (!isOffline && useId3Tags)
|
||||
musicService.getArtists(refresh)
|
||||
else musicService.getIndexes(musicFolderId, refresh)
|
||||
val result: List<ArtistOrIndex>
|
||||
|
||||
val retrievedArtists: MutableList<Artist> =
|
||||
ArrayList(result.shortcuts.size + result.artists.size)
|
||||
retrievedArtists.addAll(result.shortcuts)
|
||||
retrievedArtists.addAll(result.artists)
|
||||
artists.postValue(retrievedArtists)
|
||||
if (!isOffline && useId3Tags) {
|
||||
result = musicService.getArtists(refresh)
|
||||
} else {
|
||||
result = musicService.getIndexes(musicFolderId, refresh)
|
||||
}
|
||||
|
||||
artists.postValue(result.toMutableList())
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import androidx.recyclerview.widget.RecyclerView
|
||||
import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView.SectionedAdapter
|
||||
import java.text.Collator
|
||||
import org.moire.ultrasonic.R
|
||||
import org.moire.ultrasonic.domain.Artist
|
||||
import org.moire.ultrasonic.domain.ArtistOrIndex
|
||||
import org.moire.ultrasonic.domain.MusicDirectory
|
||||
import org.moire.ultrasonic.imageloader.ImageLoader
|
||||
import org.moire.ultrasonic.util.Util
|
||||
@ -22,12 +22,12 @@ import org.moire.ultrasonic.util.Util
|
||||
* Creates a Row in a RecyclerView which contains the details of an Artist
|
||||
*/
|
||||
class ArtistRowAdapter(
|
||||
artistList: List<Artist>,
|
||||
onItemClick: (Artist) -> Unit,
|
||||
onContextMenuClick: (MenuItem, Artist) -> Boolean,
|
||||
artistList: List<ArtistOrIndex>,
|
||||
onItemClick: (ArtistOrIndex) -> Unit,
|
||||
onContextMenuClick: (MenuItem, ArtistOrIndex) -> Boolean,
|
||||
private val imageLoader: ImageLoader,
|
||||
onMusicFolderUpdate: (String?) -> Unit
|
||||
) : GenericRowAdapter<Artist>(
|
||||
) : GenericRowAdapter<ArtistOrIndex>(
|
||||
onItemClick,
|
||||
onContextMenuClick,
|
||||
onMusicFolderUpdate
|
||||
@ -43,7 +43,7 @@ class ArtistRowAdapter(
|
||||
/**
|
||||
* Sets the data to be displayed in the RecyclerView
|
||||
*/
|
||||
override fun setData(data: List<Artist>) {
|
||||
override fun setData(data: List<ArtistOrIndex>) {
|
||||
itemList = data.sortedWith(compareBy(Collator.getInstance()) { t -> t.name })
|
||||
super.notifyDataSetChanged()
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ open class GenericListModel(application: Application) :
|
||||
return true
|
||||
}
|
||||
|
||||
internal val musicFolders: MutableLiveData<List<MusicFolder>> = MutableLiveData()
|
||||
internal val musicFolders: MutableLiveData<List<MusicFolder>> = MutableLiveData(listOf())
|
||||
|
||||
/**
|
||||
* Helper function to check online status
|
||||
|
@ -8,7 +8,6 @@ import android.view.ViewGroup
|
||||
import android.widget.AdapterView
|
||||
import android.widget.ListView
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@ -104,7 +103,7 @@ class ServerSelectorFragment : Fragment() {
|
||||
val serverList = serverSettingsModel.getServerList()
|
||||
serverList.observe(
|
||||
this,
|
||||
Observer { t ->
|
||||
{ t ->
|
||||
serverRowAdapter!!.setData(t.toTypedArray())
|
||||
}
|
||||
)
|
||||
@ -141,10 +140,16 @@ class ServerSelectorFragment : Fragment() {
|
||||
dialog.dismiss()
|
||||
|
||||
val activeServerIndex = activeServerProvider.getActiveServer().index
|
||||
val id = ActiveServerProvider.getActiveServerId()
|
||||
|
||||
// If the currently active server is deleted, go offline
|
||||
if (index == activeServerIndex) setActiveServer(-1)
|
||||
|
||||
serverSettingsModel.deleteItem(index)
|
||||
|
||||
// Clear the metadata cache
|
||||
activeServerProvider.deleteMetaDatabase(id)
|
||||
|
||||
Timber.i("Server deleted: $index")
|
||||
}
|
||||
.setNegativeButton(R.string.common_cancel) { dialog, _ ->
|
||||
|
@ -11,10 +11,12 @@ import java.util.concurrent.TimeUnit
|
||||
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.Bookmark
|
||||
import org.moire.ultrasonic.domain.ChatMessage
|
||||
import org.moire.ultrasonic.domain.Genre
|
||||
import org.moire.ultrasonic.domain.Indexes
|
||||
import org.moire.ultrasonic.domain.Index
|
||||
import org.moire.ultrasonic.domain.JukeboxStatus
|
||||
import org.moire.ultrasonic.domain.Lyrics
|
||||
import org.moire.ultrasonic.domain.MusicDirectory
|
||||
@ -33,19 +35,24 @@ import org.moire.ultrasonic.util.Util
|
||||
@Suppress("TooManyFunctions")
|
||||
class CachedMusicService(private val musicService: MusicService) : MusicService, KoinComponent {
|
||||
private val activeServerProvider: ActiveServerProvider by inject()
|
||||
private var metaDatabase: MetaDatabase = activeServerProvider.getActiveMetaDatabase()
|
||||
|
||||
// Old style TimeLimitedCache
|
||||
private val cachedMusicDirectories: LRUCache<String, TimeLimitedCache<MusicDirectory?>>
|
||||
private val cachedArtist: LRUCache<String, TimeLimitedCache<MusicDirectory?>>
|
||||
private val cachedAlbum: LRUCache<String, TimeLimitedCache<MusicDirectory?>>
|
||||
private val cachedUserInfo: LRUCache<String, TimeLimitedCache<UserInfo?>>
|
||||
private val cachedLicenseValid = TimeLimitedCache<Boolean>(120, TimeUnit.SECONDS)
|
||||
private val cachedPlaylists = TimeLimitedCache<List<Playlist>?>(3600, TimeUnit.SECONDS)
|
||||
private val cachedPodcastsChannels =
|
||||
TimeLimitedCache<List<PodcastsChannel>?>(3600, TimeUnit.SECONDS)
|
||||
private val cachedGenres = TimeLimitedCache<List<Genre>>(10 * 3600, TimeUnit.SECONDS)
|
||||
|
||||
// New Room Database
|
||||
private var cachedArtists = metaDatabase.artistsDao()
|
||||
private var cachedIndexes = metaDatabase.indexDao()
|
||||
private val cachedMusicFolders = metaDatabase.musicFoldersDao()
|
||||
|
||||
private val cachedMusicDirectories: LRUCache<String?, TimeLimitedCache<MusicDirectory?>>
|
||||
private val cachedArtist: LRUCache<String?, TimeLimitedCache<MusicDirectory?>>
|
||||
private val cachedAlbum: LRUCache<String?, TimeLimitedCache<MusicDirectory?>>
|
||||
private val cachedUserInfo: LRUCache<String?, TimeLimitedCache<UserInfo?>>
|
||||
private val cachedLicenseValid = TimeLimitedCache<Boolean>(expiresAfter = 10, TimeUnit.MINUTES)
|
||||
private val cachedIndexes = TimeLimitedCache<Indexes?>()
|
||||
private val cachedArtists = TimeLimitedCache<Indexes?>()
|
||||
private val cachedPlaylists = TimeLimitedCache<List<Playlist>?>()
|
||||
private val cachedPodcastsChannels = TimeLimitedCache<List<PodcastsChannel>>()
|
||||
private val cachedMusicFolders =
|
||||
TimeLimitedCache<List<MusicFolder>?>(10, TimeUnit.HOURS)
|
||||
private val cachedGenres = TimeLimitedCache<List<Genre>?>(10, TimeUnit.HOURS)
|
||||
private var restUrl: String? = null
|
||||
private var cachedMusicFolderId: String? = null
|
||||
|
||||
@ -72,41 +79,51 @@ class CachedMusicService(private val musicService: MusicService) : MusicService,
|
||||
if (refresh) {
|
||||
cachedMusicFolders.clear()
|
||||
}
|
||||
var result = cachedMusicFolders.get()
|
||||
|
||||
val cache = cachedMusicFolders.get()
|
||||
if (cache != null) return cache
|
||||
|
||||
val result = musicService.getMusicFolders(refresh)
|
||||
cachedMusicFolders.set(result)
|
||||
|
||||
if (result.isEmpty()) {
|
||||
result = musicService.getMusicFolders(refresh)
|
||||
cachedMusicFolders.set(result)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun getIndexes(musicFolderId: String?, refresh: Boolean): Indexes {
|
||||
override fun getIndexes(musicFolderId: String?, refresh: Boolean): List<Index> {
|
||||
checkSettingsChanged()
|
||||
|
||||
if (refresh) {
|
||||
cachedIndexes.clear()
|
||||
cachedMusicFolders.clear()
|
||||
cachedMusicDirectories.clear()
|
||||
}
|
||||
var result = cachedIndexes.get()
|
||||
if (result == null) {
|
||||
result = musicService.getIndexes(musicFolderId, refresh)
|
||||
cachedIndexes.set(result)
|
||||
|
||||
var indexes: List<Index>
|
||||
|
||||
if (musicFolderId == null) {
|
||||
indexes = cachedIndexes.get()
|
||||
} else {
|
||||
indexes = cachedIndexes.get(musicFolderId)
|
||||
}
|
||||
return result
|
||||
|
||||
if (indexes.isEmpty()) {
|
||||
indexes = musicService.getIndexes(musicFolderId, refresh)
|
||||
cachedIndexes.upsert(indexes)
|
||||
}
|
||||
|
||||
return indexes
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun getArtists(refresh: Boolean): Indexes {
|
||||
override fun getArtists(refresh: Boolean): List<Artist> {
|
||||
checkSettingsChanged()
|
||||
if (refresh) {
|
||||
cachedArtists.clear()
|
||||
}
|
||||
var result = cachedArtists.get()
|
||||
if (result == null) {
|
||||
|
||||
if (result.isEmpty()) {
|
||||
result = musicService.getArtists(refresh)
|
||||
cachedArtist.clear()
|
||||
cachedArtists.set(result)
|
||||
}
|
||||
return result
|
||||
@ -296,19 +313,26 @@ class CachedMusicService(private val musicService: MusicService) : MusicService,
|
||||
return musicService.setJukeboxGain(gain)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun checkSettingsChanged() {
|
||||
val newUrl = activeServerProvider.getRestUrl(null)
|
||||
val newFolderId = activeServerProvider.getActiveServer().musicFolderId
|
||||
if (!Util.equals(newUrl, restUrl) || !Util.equals(cachedMusicFolderId, newFolderId)) {
|
||||
cachedMusicFolders.clear()
|
||||
// Switch database
|
||||
metaDatabase = activeServerProvider.getActiveMetaDatabase()
|
||||
cachedArtists = metaDatabase.artistsDao()
|
||||
cachedIndexes = metaDatabase.indexDao()
|
||||
|
||||
// Clear in memory caches
|
||||
cachedMusicDirectories.clear()
|
||||
cachedLicenseValid.clear()
|
||||
cachedIndexes.clear()
|
||||
cachedPlaylists.clear()
|
||||
cachedGenres.clear()
|
||||
cachedAlbum.clear()
|
||||
cachedArtist.clear()
|
||||
cachedUserInfo.clear()
|
||||
|
||||
// Set the cache keys
|
||||
restUrl = newUrl
|
||||
cachedMusicFolderId = newFolderId
|
||||
}
|
||||
@ -330,7 +354,7 @@ class CachedMusicService(private val musicService: MusicService) : MusicService,
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun getGenres(refresh: Boolean): List<Genre>? {
|
||||
override fun getGenres(refresh: Boolean): List<Genre> {
|
||||
checkSettingsChanged()
|
||||
if (refresh) {
|
||||
cachedGenres.clear()
|
||||
@ -338,11 +362,11 @@ class CachedMusicService(private val musicService: MusicService) : MusicService,
|
||||
var result = cachedGenres.get()
|
||||
if (result == null) {
|
||||
result = musicService.getGenres(refresh)
|
||||
cachedGenres.set(result)
|
||||
cachedGenres.set(result!!)
|
||||
}
|
||||
|
||||
val sorted = result?.toMutableList()
|
||||
sorted?.sortWith { genre, genre2 ->
|
||||
val sorted = result.toMutableList()
|
||||
sorted.sortWith { genre, genre2 ->
|
||||
genre.name.compareTo(
|
||||
genre2.name,
|
||||
ignoreCase = true
|
||||
|
@ -7,10 +7,11 @@
|
||||
package org.moire.ultrasonic.service
|
||||
|
||||
import java.io.InputStream
|
||||
import org.moire.ultrasonic.domain.Artist
|
||||
import org.moire.ultrasonic.domain.Bookmark
|
||||
import org.moire.ultrasonic.domain.ChatMessage
|
||||
import org.moire.ultrasonic.domain.Genre
|
||||
import org.moire.ultrasonic.domain.Indexes
|
||||
import org.moire.ultrasonic.domain.Index
|
||||
import org.moire.ultrasonic.domain.JukeboxStatus
|
||||
import org.moire.ultrasonic.domain.Lyrics
|
||||
import org.moire.ultrasonic.domain.MusicDirectory
|
||||
@ -46,10 +47,10 @@ interface MusicService {
|
||||
fun getMusicFolders(refresh: Boolean): List<MusicFolder>
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun getIndexes(musicFolderId: String?, refresh: Boolean): Indexes
|
||||
fun getIndexes(musicFolderId: String?, refresh: Boolean): List<Index>
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun getArtists(refresh: Boolean): Indexes
|
||||
fun getArtists(refresh: Boolean): List<Artist>
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun getMusicDirectory(id: String, name: String?, refresh: Boolean): MusicDirectory
|
||||
|
@ -28,7 +28,7 @@ import org.moire.ultrasonic.domain.Artist
|
||||
import org.moire.ultrasonic.domain.Bookmark
|
||||
import org.moire.ultrasonic.domain.ChatMessage
|
||||
import org.moire.ultrasonic.domain.Genre
|
||||
import org.moire.ultrasonic.domain.Indexes
|
||||
import org.moire.ultrasonic.domain.Index
|
||||
import org.moire.ultrasonic.domain.JukeboxStatus
|
||||
import org.moire.ultrasonic.domain.Lyrics
|
||||
import org.moire.ultrasonic.domain.MusicDirectory
|
||||
@ -50,21 +50,21 @@ import timber.log.Timber
|
||||
class OfflineMusicService : MusicService, KoinComponent {
|
||||
private val activeServerProvider: ActiveServerProvider by inject()
|
||||
|
||||
override fun getIndexes(musicFolderId: String?, refresh: Boolean): Indexes {
|
||||
val artists: MutableList<Artist> = ArrayList()
|
||||
override fun getIndexes(musicFolderId: String?, refresh: Boolean): List<Index> {
|
||||
val indexes: MutableList<Index> = ArrayList()
|
||||
val root = FileUtil.getMusicDirectory()
|
||||
for (file in FileUtil.listFiles(root)) {
|
||||
if (file.isDirectory) {
|
||||
val artist = Artist()
|
||||
artist.id = file.path
|
||||
artist.index = file.name.substring(0, 1)
|
||||
artist.name = file.name
|
||||
artists.add(artist)
|
||||
val index = Index(file.path)
|
||||
index.id = file.path
|
||||
index.index = file.name.substring(0, 1)
|
||||
index.name = file.name
|
||||
indexes.add(index)
|
||||
}
|
||||
}
|
||||
val ignoredArticlesString = "The El La Los Las Le Les"
|
||||
val ignoredArticles = COMPILE.split(ignoredArticlesString)
|
||||
artists.sortWith { lhsArtist, rhsArtist ->
|
||||
indexes.sortWith { lhsArtist, rhsArtist ->
|
||||
var lhs = lhsArtist.name!!.lowercase(Locale.ROOT)
|
||||
var rhs = rhsArtist.name!!.lowercase(Locale.ROOT)
|
||||
val lhs1 = lhs[0]
|
||||
@ -92,7 +92,7 @@ class OfflineMusicService : MusicService, KoinComponent {
|
||||
lhs.compareTo(rhs)
|
||||
}
|
||||
|
||||
return Indexes(0L, ignoredArticlesString, artists = artists)
|
||||
return indexes
|
||||
}
|
||||
|
||||
override fun getMusicDirectory(
|
||||
@ -127,8 +127,7 @@ class OfflineMusicService : MusicService, KoinComponent {
|
||||
val artistName = artistFile.name
|
||||
if (artistFile.isDirectory) {
|
||||
if (matchCriteria(criteria, artistName).also { closeness = it } > 0) {
|
||||
val artist = Artist()
|
||||
artist.id = artistFile.path
|
||||
val artist = Artist(artistFile.path)
|
||||
artist.index = artistFile.name.substring(0, 1)
|
||||
artist.name = artistName
|
||||
artist.closeness = closeness
|
||||
@ -442,7 +441,7 @@ class OfflineMusicService : MusicService, KoinComponent {
|
||||
override fun isLicenseValid(): Boolean = true
|
||||
|
||||
@Throws(OfflineException::class)
|
||||
override fun getArtists(refresh: Boolean): Indexes {
|
||||
override fun getArtists(refresh: Boolean): List<Artist> {
|
||||
throw OfflineException("getArtists isn't available in offline mode")
|
||||
}
|
||||
|
||||
|
@ -6,9 +6,6 @@
|
||||
*/
|
||||
package org.moire.ultrasonic.service
|
||||
|
||||
import java.io.BufferedWriter
|
||||
import java.io.File
|
||||
import java.io.FileWriter
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import okhttp3.Protocol
|
||||
@ -20,15 +17,13 @@ import org.moire.ultrasonic.api.subsonic.models.AlbumListType.Companion.fromName
|
||||
import org.moire.ultrasonic.api.subsonic.models.JukeboxAction
|
||||
import org.moire.ultrasonic.api.subsonic.throwOnFailure
|
||||
import org.moire.ultrasonic.api.subsonic.toStreamResponse
|
||||
import org.moire.ultrasonic.cache.PermanentFileStorage
|
||||
import org.moire.ultrasonic.cache.serializers.getIndexesSerializer
|
||||
import org.moire.ultrasonic.cache.serializers.getMusicFolderListSerializer
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
|
||||
import org.moire.ultrasonic.domain.Artist
|
||||
import org.moire.ultrasonic.domain.Bookmark
|
||||
import org.moire.ultrasonic.domain.ChatMessage
|
||||
import org.moire.ultrasonic.domain.Genre
|
||||
import org.moire.ultrasonic.domain.Indexes
|
||||
import org.moire.ultrasonic.domain.Index
|
||||
import org.moire.ultrasonic.domain.JukeboxStatus
|
||||
import org.moire.ultrasonic.domain.Lyrics
|
||||
import org.moire.ultrasonic.domain.MusicDirectory
|
||||
@ -39,11 +34,14 @@ import org.moire.ultrasonic.domain.SearchCriteria
|
||||
import org.moire.ultrasonic.domain.SearchResult
|
||||
import org.moire.ultrasonic.domain.Share
|
||||
import org.moire.ultrasonic.domain.UserInfo
|
||||
import org.moire.ultrasonic.domain.toArtistList
|
||||
import org.moire.ultrasonic.domain.toDomainEntitiesList
|
||||
import org.moire.ultrasonic.domain.toDomainEntity
|
||||
import org.moire.ultrasonic.domain.toDomainEntityList
|
||||
import org.moire.ultrasonic.domain.toIndexList
|
||||
import org.moire.ultrasonic.domain.toMusicDirectoryDomainEntity
|
||||
import org.moire.ultrasonic.util.FileUtil
|
||||
import org.moire.ultrasonic.util.FileUtilKt
|
||||
import org.moire.ultrasonic.util.Util
|
||||
import timber.log.Timber
|
||||
|
||||
@ -53,7 +51,6 @@ import timber.log.Timber
|
||||
@Suppress("LargeClass")
|
||||
open class RESTMusicService(
|
||||
val subsonicAPIClient: SubsonicAPIClient,
|
||||
private val fileStorage: PermanentFileStorage,
|
||||
private val activeServerProvider: ActiveServerProvider
|
||||
) : MusicService {
|
||||
|
||||
@ -77,49 +74,31 @@ open class RESTMusicService(
|
||||
override fun getMusicFolders(
|
||||
refresh: Boolean
|
||||
): List<MusicFolder> {
|
||||
val cachedMusicFolders = fileStorage.load(
|
||||
MUSIC_FOLDER_STORAGE_NAME, getMusicFolderListSerializer()
|
||||
)
|
||||
|
||||
if (cachedMusicFolders != null && !refresh) return cachedMusicFolders
|
||||
|
||||
val response = API.getMusicFolders().execute().throwOnFailure()
|
||||
|
||||
val musicFolders = response.body()!!.musicFolders.toDomainEntityList()
|
||||
fileStorage.store(MUSIC_FOLDER_STORAGE_NAME, musicFolders, getMusicFolderListSerializer())
|
||||
|
||||
return musicFolders
|
||||
return response.body()!!.musicFolders.toDomainEntityList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the artists for a given music folder *
|
||||
*/
|
||||
@Throws(Exception::class)
|
||||
override fun getIndexes(
|
||||
musicFolderId: String?,
|
||||
refresh: Boolean
|
||||
): Indexes {
|
||||
val indexName = INDEXES_STORAGE_NAME + (musicFolderId ?: "")
|
||||
|
||||
val cachedIndexes = fileStorage.load(indexName, getIndexesSerializer())
|
||||
if (cachedIndexes != null && !refresh) return cachedIndexes
|
||||
|
||||
): List<Index> {
|
||||
val response = API.getIndexes(musicFolderId, null).execute().throwOnFailure()
|
||||
|
||||
val indexes = response.body()!!.indexes.toDomainEntity()
|
||||
fileStorage.store(indexName, indexes, getIndexesSerializer())
|
||||
return indexes
|
||||
return response.body()!!.indexes.toIndexList(musicFolderId)
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun getArtists(
|
||||
refresh: Boolean
|
||||
): Indexes {
|
||||
val cachedArtists = fileStorage.load(ARTISTS_STORAGE_NAME, getIndexesSerializer())
|
||||
if (cachedArtists != null && !refresh) return cachedArtists
|
||||
|
||||
): List<Artist> {
|
||||
val response = API.getArtists(null).execute().throwOnFailure()
|
||||
|
||||
val indexes = response.body()!!.indexes.toDomainEntity()
|
||||
fileStorage.store(ARTISTS_STORAGE_NAME, indexes, getIndexesSerializer())
|
||||
return indexes
|
||||
return response.body()!!.indexes.toArtistList()
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
@ -186,11 +165,11 @@ open class RESTMusicService(
|
||||
criteria: SearchCriteria
|
||||
): SearchResult {
|
||||
return try {
|
||||
if (
|
||||
!isOffline() &&
|
||||
Util.getShouldUseId3Tags()
|
||||
) search3(criteria)
|
||||
else search2(criteria)
|
||||
if (!isOffline() && Util.getShouldUseId3Tags()) {
|
||||
search3(criteria)
|
||||
} else {
|
||||
search2(criteria)
|
||||
}
|
||||
} catch (ignored: ApiNotSupportedException) {
|
||||
// Ensure backward compatibility with REST 1.3.
|
||||
searchOld(criteria)
|
||||
@ -262,28 +241,7 @@ open class RESTMusicService(
|
||||
activeServerProvider.getActiveServer().name, name
|
||||
)
|
||||
|
||||
val fw = FileWriter(playlistFile)
|
||||
val bw = BufferedWriter(fw)
|
||||
|
||||
try {
|
||||
fw.write("#EXTM3U\n")
|
||||
for (e in playlist.getChildren()) {
|
||||
var filePath = FileUtil.getSongFile(e).absolutePath
|
||||
|
||||
if (!File(filePath).exists()) {
|
||||
val ext = FileUtil.getExtension(filePath)
|
||||
val base = FileUtil.getBaseName(filePath)
|
||||
filePath = "$base.complete.$ext"
|
||||
}
|
||||
fw.write(filePath + "\n")
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Timber.w("Failed to save playlist: %s", name)
|
||||
throw e
|
||||
} finally {
|
||||
bw.close()
|
||||
fw.close()
|
||||
}
|
||||
FileUtilKt.savePlaylist(playlistFile, playlist, name)
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
@ -711,10 +669,4 @@ open class RESTMusicService(
|
||||
activeServerProvider.setMinimumApiVersion(it.restApiVersion)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MUSIC_FOLDER_STORAGE_NAME = "music_folder"
|
||||
private const val INDEXES_STORAGE_NAME = "indexes"
|
||||
private const val ARTISTS_STORAGE_NAME = "artists"
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* FileUtil.kt
|
||||
* Copyright (C) 2009-2021 Ultrasonic developers
|
||||
*
|
||||
* Distributed under terms of the GNU GPLv3 license.
|
||||
*/
|
||||
|
||||
package org.moire.ultrasonic.util
|
||||
|
||||
import java.io.BufferedWriter
|
||||
import java.io.File
|
||||
import java.io.FileWriter
|
||||
import java.io.IOException
|
||||
import org.moire.ultrasonic.domain.MusicDirectory
|
||||
import timber.log.Timber
|
||||
|
||||
// TODO: Convert FileUtil.java and merge into here.
|
||||
object FileUtilKt {
|
||||
fun savePlaylist(
|
||||
playlistFile: File?,
|
||||
playlist: MusicDirectory,
|
||||
name: String
|
||||
) {
|
||||
val fw = FileWriter(playlistFile)
|
||||
val bw = BufferedWriter(fw)
|
||||
|
||||
try {
|
||||
fw.write("#EXTM3U\n")
|
||||
for (e in playlist.getChildren()) {
|
||||
var filePath = FileUtil.getSongFile(e).absolutePath
|
||||
|
||||
if (!File(filePath).exists()) {
|
||||
val ext = FileUtil.getExtension(filePath)
|
||||
val base = FileUtil.getBaseName(filePath)
|
||||
filePath = "$base.complete.$ext"
|
||||
}
|
||||
fw.write(filePath + "\n")
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Timber.w("Failed to save playlist: %s", name)
|
||||
throw e
|
||||
} finally {
|
||||
bw.close()
|
||||
fw.close()
|
||||
}
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@ import org.moire.ultrasonic.api.subsonic.models.Indexes
|
||||
/**
|
||||
* Unit tests for extension functions in [APIIndexesConverter.kt].
|
||||
*/
|
||||
class APIIndexesConverterTest {
|
||||
class APIIndexConverterTest {
|
||||
@Test
|
||||
fun `Should convert Indexes entity`() {
|
||||
val artistsA = listOf(
|
||||
@ -31,15 +31,12 @@ class APIIndexesConverterTest {
|
||||
shortcutList = artistsA
|
||||
)
|
||||
|
||||
val convertedEntity = entity.toDomainEntity()
|
||||
val convertedEntity = entity.toIndexList(null)
|
||||
|
||||
val expectedArtists = (artistsA + artistsT).map { it.toDomainEntity() }.toMutableList()
|
||||
with(convertedEntity) {
|
||||
lastModified `should be equal to` entity.lastModified
|
||||
ignoredArticles `should be equal to` entity.ignoredArticles
|
||||
artists.size `should be equal to` expectedArtists.size
|
||||
artists `should be equal to` expectedArtists
|
||||
shortcuts `should be equal to` artistsA.map { it.toDomainEntity() }.toMutableList()
|
||||
size `should be equal to` expectedArtists.size
|
||||
this `should be equal to` expectedArtists
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user