music: build saf loader playlist boilerplate
This commit is contained in:
parent
ba9ab5a445
commit
c4f4797028
11 changed files with 140 additions and 399 deletions
|
@ -19,6 +19,8 @@
|
|||
package org.oxycblt.auxio.music.stack.explore
|
||||
|
||||
import android.net.Uri
|
||||
import org.oxycblt.auxio.music.Music
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.music.stack.interpret.model.SongImpl
|
||||
import org.oxycblt.auxio.music.info.Date
|
||||
import org.oxycblt.auxio.music.stack.explore.fs.Path
|
||||
|
@ -61,6 +63,21 @@ data class AudioFile(
|
|||
var genreNames: List<String> = listOf()
|
||||
)
|
||||
|
||||
interface PlaylistFile {
|
||||
val name: String
|
||||
}
|
||||
data class PlaylistFile(
|
||||
val name: String,
|
||||
val songPointers: List<SongPointer>,
|
||||
val editor: PlaylistHandle
|
||||
)
|
||||
|
||||
interface PlaylistHandle {
|
||||
val uid: Music.UID
|
||||
suspend fun rename(name: String)
|
||||
suspend fun add(songs: List<Song>)
|
||||
suspend fun rewrite(songs: List<Song>)
|
||||
suspend fun delete()
|
||||
}
|
||||
|
||||
sealed interface SongPointer {
|
||||
data class UID(val uid: Music.UID) : SongPointer
|
||||
// data class Path(val options: List<Path>) : SongPointer
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.oxycblt.auxio.music.user
|
||||
package org.oxycblt.auxio.music.stack.explore.playlists
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Database
|
||||
|
@ -38,7 +38,7 @@ import org.oxycblt.auxio.music.Music
|
|||
version = 30,
|
||||
exportSchema = false)
|
||||
@TypeConverters(Music.UID.TypeConverters::class)
|
||||
abstract class UserMusicDatabase : RoomDatabase() {
|
||||
abstract class PlaylistDatabase : RoomDatabase() {
|
||||
abstract fun playlistDao(): PlaylistDao
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.oxycblt.auxio.music.user
|
||||
package org.oxycblt.auxio.music.stack.explore.playlists
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Embedded
|
||||
|
@ -27,7 +27,7 @@ import androidx.room.Relation
|
|||
import org.oxycblt.auxio.music.Music
|
||||
|
||||
/**
|
||||
* Raw playlist information persisted to [UserMusicDatabase].
|
||||
* Raw playlist information persisted to [PlaylistDatabase].
|
||||
*
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
|
@ -0,0 +1,26 @@
|
|||
package org.oxycblt.auxio.music.stack.explore.playlists
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.asFlow
|
||||
import kotlinx.coroutines.flow.emitAll
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import org.oxycblt.auxio.music.stack.explore.PlaylistFile
|
||||
import org.oxycblt.auxio.music.stack.explore.SongPointer
|
||||
import javax.inject.Inject
|
||||
|
||||
interface StoredPlaylists {
|
||||
fun read(): Flow<PlaylistFile>
|
||||
}
|
||||
|
||||
class StoredPlaylistsImpl @Inject constructor(
|
||||
private val playlistDao: PlaylistDao
|
||||
) : StoredPlaylists {
|
||||
override fun read() = flow {
|
||||
emitAll(playlistDao.readRawPlaylists()
|
||||
.asFlow()
|
||||
.map {
|
||||
TODO()
|
||||
})
|
||||
}
|
||||
}
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.oxycblt.auxio.music.user
|
||||
package org.oxycblt.auxio.music.stack.explore.playlists
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Room
|
||||
|
@ -29,19 +29,19 @@ import dagger.hilt.components.SingletonComponent
|
|||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface UserModule {
|
||||
@Binds fun userLibraryFactory(factory: UserLibraryFactoryImpl): UserLibrary.Factory
|
||||
interface PlaylistModule {
|
||||
@Binds fun storedPlaylists(impl: StoredPlaylistsImpl): StoredPlaylists
|
||||
}
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class UserRoomModule {
|
||||
@Provides fun playlistDao(database: UserMusicDatabase) = database.playlistDao()
|
||||
class PlaylistRoomModule {
|
||||
@Provides fun playlistDao(database: PlaylistDatabase) = database.playlistDao()
|
||||
|
||||
@Provides
|
||||
fun userMusicDatabase(@ApplicationContext context: Context) =
|
||||
fun playlistDatabase(@ApplicationContext context: Context) =
|
||||
Room.databaseBuilder(
|
||||
context.applicationContext, UserMusicDatabase::class.java, "user_music.db")
|
||||
context.applicationContext, PlaylistDatabase::class.java, "user_music.db")
|
||||
.fallbackToDestructiveMigration()
|
||||
.build()
|
||||
}
|
|
@ -1,10 +1,14 @@
|
|||
package org.oxycblt.auxio.music.stack.interpret.linker
|
||||
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.music.stack.explore.PlaylistFile
|
||||
import org.oxycblt.auxio.music.stack.interpret.model.AlbumImpl
|
||||
import org.oxycblt.auxio.music.stack.interpret.model.ArtistImpl
|
||||
import org.oxycblt.auxio.music.stack.interpret.model.GenreImpl
|
||||
import org.oxycblt.auxio.music.stack.interpret.model.PlaylistImpl
|
||||
import org.oxycblt.auxio.music.stack.interpret.model.SongImpl
|
||||
import org.oxycblt.auxio.music.stack.interpret.prepare.PreAlbum
|
||||
import org.oxycblt.auxio.music.stack.interpret.prepare.PrePlaylist
|
||||
import org.oxycblt.auxio.music.stack.interpret.prepare.PreSong
|
||||
|
||||
interface LinkedSong {
|
||||
|
@ -19,6 +23,11 @@ interface LinkedAlbum {
|
|||
val artists: Linked<List<ArtistImpl>, AlbumImpl>
|
||||
}
|
||||
|
||||
interface LinkedPlaylist {
|
||||
val prePlaylist: PrePlaylist
|
||||
val songs: Linked<List<SongImpl>, PlaylistImpl>
|
||||
}
|
||||
|
||||
interface Linked<P, C> {
|
||||
fun resolve(child: C): P
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package org.oxycblt.auxio.music.stack.interpret.linker
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import org.oxycblt.auxio.music.stack.explore.PlaylistFile
|
||||
import org.oxycblt.auxio.music.stack.interpret.model.GenreImpl
|
||||
import org.oxycblt.auxio.music.stack.interpret.model.PlaylistImpl
|
||||
import org.oxycblt.auxio.music.stack.interpret.model.SongImpl
|
||||
import org.oxycblt.auxio.music.stack.interpret.prepare.PreSong
|
||||
|
||||
|
||||
class PlaylistLinker {
|
||||
fun register(playlists: Flow<PlaylistFile>, linkedSongs: Flow<AlbumLinker.LinkedSong>): Flow<LinkedPlaylist> = emptyFlow()
|
||||
fun resolve(): Collection<PlaylistImpl> = setOf()
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Auxio Project
|
||||
* PlaylistImpl.kt is part of Auxio.
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.oxycblt.auxio.music.stack.interpret.model
|
||||
|
||||
import org.oxycblt.auxio.image.extractor.ParentCover
|
||||
import org.oxycblt.auxio.music.Music
|
||||
import org.oxycblt.auxio.music.MusicType
|
||||
import org.oxycblt.auxio.music.Playlist
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.music.info.Name
|
||||
import org.oxycblt.auxio.music.stack.explore.PlaylistFile
|
||||
import org.oxycblt.auxio.music.stack.explore.playlists.RawPlaylist
|
||||
import org.oxycblt.auxio.music.stack.interpret.linker.LinkedPlaylist
|
||||
|
||||
class PlaylistImpl(linkedPlaylist: LinkedPlaylist) : Playlist {
|
||||
private val prePlaylist = linkedPlaylist.prePlaylist
|
||||
override val uid = prePlaylist.handle.uid
|
||||
override val name: Name.Known = prePlaylist.name
|
||||
override val songs = linkedPlaylist.songs.resolve(this)
|
||||
override val durationMs = songs.sumOf { it.durationMs }
|
||||
override val cover = songs.takeIf { it.isNotEmpty() }?.let { ParentCover.from(it.first(), it) }
|
||||
private var hashCode = uid.hashCode()
|
||||
|
||||
init {
|
||||
hashCode = 31 * hashCode + prePlaylist.hashCode()
|
||||
hashCode = 31 * hashCode + songs.hashCode()
|
||||
}
|
||||
|
||||
override fun equals(other: Any?) =
|
||||
other is PlaylistImpl && prePlaylist == other.prePlaylist && songs == other.songs
|
||||
|
||||
override fun hashCode() = hashCode
|
||||
|
||||
override fun toString() = "Playlist(uid=$uid, name=$name)"
|
||||
}
|
|
@ -6,13 +6,13 @@ import org.oxycblt.auxio.music.info.Date
|
|||
import org.oxycblt.auxio.music.info.Disc
|
||||
import org.oxycblt.auxio.music.info.Name
|
||||
import org.oxycblt.auxio.music.info.ReleaseType
|
||||
import org.oxycblt.auxio.music.stack.explore.PlaylistFile
|
||||
import org.oxycblt.auxio.music.stack.explore.PlaylistHandle
|
||||
import org.oxycblt.auxio.music.stack.explore.fs.MimeType
|
||||
import org.oxycblt.auxio.music.stack.explore.fs.Path
|
||||
import org.oxycblt.auxio.playback.replaygain.ReplayGainAdjustment
|
||||
import java.util.UUID
|
||||
|
||||
interface PrePlaylist
|
||||
|
||||
data class PreSong(
|
||||
val musicBrainzId: UUID?,
|
||||
val name: Name,
|
||||
|
@ -51,3 +51,9 @@ data class PreGenre(
|
|||
val name: Name,
|
||||
val rawName: String?,
|
||||
)
|
||||
|
||||
data class PrePlaylist(
|
||||
val name: Name.Known,
|
||||
val rawName: String?,
|
||||
val handle: PlaylistHandle
|
||||
)
|
|
@ -1,103 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Auxio Project
|
||||
* PlaylistImpl.kt is part of Auxio.
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.oxycblt.auxio.music.user
|
||||
|
||||
import org.oxycblt.auxio.image.extractor.ParentCover
|
||||
import org.oxycblt.auxio.music.Music
|
||||
import org.oxycblt.auxio.music.MusicType
|
||||
import org.oxycblt.auxio.music.Playlist
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.music.model.DeviceLibrary
|
||||
import org.oxycblt.auxio.music.info.Name
|
||||
|
||||
class PlaylistImpl
|
||||
private constructor(
|
||||
override val uid: Music.UID,
|
||||
override val name: Name.Known,
|
||||
override val songs: List<Song>
|
||||
) : Playlist {
|
||||
override val durationMs = songs.sumOf { it.durationMs }
|
||||
private var hashCode = uid.hashCode()
|
||||
|
||||
init {
|
||||
hashCode = 31 * hashCode + name.hashCode()
|
||||
hashCode = 31 * hashCode + songs.hashCode()
|
||||
}
|
||||
|
||||
override fun equals(other: Any?) =
|
||||
other is PlaylistImpl && uid == other.uid && name == other.name && songs == other.songs
|
||||
|
||||
override fun hashCode() = hashCode
|
||||
|
||||
override fun toString() = "Playlist(uid=$uid, name=$name)"
|
||||
|
||||
override val cover = songs.takeIf { it.isNotEmpty() }?.let { ParentCover.from(it.first(), it) }
|
||||
|
||||
/**
|
||||
* Clone the data in this instance to a new [PlaylistImpl] with the given [name].
|
||||
*
|
||||
* @param name The new name to use.
|
||||
* @param nameFactory The [Name.Known.Factory] to interpret name information with.
|
||||
*/
|
||||
fun edit(name: String, nameFactory: Name.Known.Factory) =
|
||||
PlaylistImpl(uid, nameFactory.parse(name, null), songs)
|
||||
|
||||
/**
|
||||
* Clone the data in this instance to a new [PlaylistImpl] with the given [Song]s.
|
||||
*
|
||||
* @param songs The new [Song]s to use.
|
||||
*/
|
||||
fun edit(songs: List<Song>) = PlaylistImpl(uid, name, songs)
|
||||
|
||||
/**
|
||||
* Clone the data in this instance to a new [PlaylistImpl] with the given [edits].
|
||||
*
|
||||
* @param edits The edits to make to the [Song]s of the playlist.
|
||||
*/
|
||||
inline fun edit(edits: MutableList<Song>.() -> Unit) = edit(songs.toMutableList().apply(edits))
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Create a new instance with a novel UID.
|
||||
*
|
||||
* @param name The name of the playlist.
|
||||
* @param songs The songs to initially populate the playlist with.
|
||||
* @param nameFactory The [Name.Known.Factory] to interpret name information with.
|
||||
*/
|
||||
fun from(name: String, songs: List<Song>, nameFactory: Name.Known.Factory) =
|
||||
PlaylistImpl(Music.UID.auxio(MusicType.PLAYLISTS), nameFactory.parse(name, null), songs)
|
||||
|
||||
/**
|
||||
* Populate a new instance from a read [RawPlaylist].
|
||||
*
|
||||
* @param rawPlaylist The [RawPlaylist] to read from.
|
||||
* @param deviceLibrary The [DeviceLibrary] to initialize from.
|
||||
* @param nameFactory The [Name.Known.Factory] to interpret name information with.
|
||||
*/
|
||||
fun fromRaw(
|
||||
rawPlaylist: RawPlaylist,
|
||||
deviceLibrary: DeviceLibrary,
|
||||
nameFactory: Name.Known.Factory
|
||||
) =
|
||||
PlaylistImpl(
|
||||
rawPlaylist.playlistInfo.playlistUid,
|
||||
nameFactory.parse(rawPlaylist.playlistInfo.name, null),
|
||||
rawPlaylist.songs.mapNotNull { deviceLibrary.findSong(it.songUid) })
|
||||
}
|
||||
}
|
|
@ -1,280 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Auxio Project
|
||||
* UserLibrary.kt is part of Auxio.
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.oxycblt.auxio.music.user
|
||||
|
||||
import java.lang.Exception
|
||||
import javax.inject.Inject
|
||||
import org.oxycblt.auxio.music.Music
|
||||
import org.oxycblt.auxio.music.MusicRepository
|
||||
import org.oxycblt.auxio.music.Playlist
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.music.model.DeviceLibrary
|
||||
import org.oxycblt.auxio.music.info.Name
|
||||
import timber.log.Timber as L
|
||||
|
||||
/**
|
||||
* Organized library information controlled by the user.
|
||||
*
|
||||
* Unlike [DeviceLibrary], [UserLibrary]s can be mutated without needing to clone the instance. It
|
||||
* is also not backed by library information, rather an app database with in-memory caching. It is
|
||||
* generally not expected to create this yourself, and instead rely on MusicRepository.
|
||||
*
|
||||
* @author Alexander Capehart
|
||||
*
|
||||
* TODO: Communicate errors
|
||||
* TODO: How to handle empty playlists that appear because all of their songs have disappeared?
|
||||
*/
|
||||
interface UserLibrary {
|
||||
/** The current user-defined playlists. */
|
||||
val playlists: Collection<Playlist>
|
||||
|
||||
/**
|
||||
* Find a [Playlist] instance corresponding to the given [Music.UID].
|
||||
*
|
||||
* @param uid The [Music.UID] to search for.
|
||||
* @return The corresponding [Song], or null if one was not found.
|
||||
*/
|
||||
fun findPlaylist(uid: Music.UID): Playlist?
|
||||
|
||||
/**
|
||||
* Finds a playlist by it's [name]. Since all [Playlist] names must be unique, this will always
|
||||
* return at most 1 value.
|
||||
*
|
||||
* @param name The name [String] to search for.
|
||||
*/
|
||||
fun findPlaylist(name: String): Playlist?
|
||||
|
||||
/** Constructs a [UserLibrary] implementation in an asynchronous manner. */
|
||||
interface Factory {
|
||||
/**
|
||||
* Read all [RawPlaylist] information from the database, which can be transformed into a
|
||||
* [UserLibrary] later.
|
||||
*
|
||||
* @return A list of [RawPlaylist]s.
|
||||
*/
|
||||
suspend fun query(): List<RawPlaylist>
|
||||
|
||||
/**
|
||||
* Create a new [UserLibrary] from read [RawPlaylist] instances and a precursor
|
||||
* [DeviceLibrary].
|
||||
*
|
||||
* @param rawPlaylists The [RawPlaylist]s to use.
|
||||
* @param deviceLibrary The [DeviceLibrary] to use.
|
||||
* @return The new [UserLibrary] instance.
|
||||
*/
|
||||
suspend fun create(
|
||||
rawPlaylists: List<RawPlaylist>,
|
||||
deviceLibrary: DeviceLibrary,
|
||||
nameFactory: Name.Known.Factory
|
||||
): MutableUserLibrary
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A mutable instance of [UserLibrary]. Not meant for use outside of the music module. Use
|
||||
* [MusicRepository] instead.
|
||||
*
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
interface MutableUserLibrary : UserLibrary {
|
||||
/**
|
||||
* Make a new [Playlist].
|
||||
*
|
||||
* @param name The name of the [Playlist].
|
||||
* @param songs The songs to place in the [Playlist].
|
||||
* @return The new [Playlist] instance, or null if one could not be created.
|
||||
*/
|
||||
suspend fun createPlaylist(name: String, songs: List<Song>): Playlist?
|
||||
|
||||
/**
|
||||
* Rename a [Playlist].
|
||||
*
|
||||
* @param playlist The [Playlist] to rename.
|
||||
* @param name The name of the new [Playlist].
|
||||
* @return True if the [Playlist] was successfully renamed, false otherwise.
|
||||
*/
|
||||
suspend fun renamePlaylist(playlist: Playlist, name: String): Boolean
|
||||
|
||||
/**
|
||||
* Delete a [Playlist].
|
||||
*
|
||||
* @param playlist The playlist to delete.
|
||||
* @return True if the [Playlist] was successfully deleted, false otherwise.
|
||||
*/
|
||||
suspend fun deletePlaylist(playlist: Playlist): Boolean
|
||||
|
||||
/**
|
||||
* Add [Song]s to a [Playlist].
|
||||
*
|
||||
* @param playlist The [Playlist] to add to. Must currently exist.
|
||||
* @param songs The [Song]s to add to the [Playlist].
|
||||
* @return True if the [Song]s were successfully added, false otherwise.
|
||||
*/
|
||||
suspend fun addToPlaylist(playlist: Playlist, songs: List<Song>): Boolean
|
||||
|
||||
/**
|
||||
* Update the [Song]s of a [Playlist].
|
||||
*
|
||||
* @param playlist The [Playlist] to update.
|
||||
* @param songs The new [Song]s to be contained in the [Playlist].
|
||||
* @return True if the [Playlist] was successfully updated, false otherwise.
|
||||
*/
|
||||
suspend fun rewritePlaylist(playlist: Playlist, songs: List<Song>): Boolean
|
||||
}
|
||||
|
||||
class UserLibraryFactoryImpl @Inject constructor(private val playlistDao: PlaylistDao) :
|
||||
UserLibrary.Factory {
|
||||
override suspend fun query() =
|
||||
try {
|
||||
val rawPlaylists = playlistDao.readRawPlaylists()
|
||||
L.d("Successfully read ${rawPlaylists.size} playlists")
|
||||
rawPlaylists
|
||||
} catch (e: Exception) {
|
||||
L.e("Unable to read playlists: $e")
|
||||
listOf()
|
||||
}
|
||||
|
||||
override suspend fun create(
|
||||
rawPlaylists: List<RawPlaylist>,
|
||||
deviceLibrary: DeviceLibrary,
|
||||
nameFactory: Name.Known.Factory
|
||||
): MutableUserLibrary {
|
||||
val playlistMap = mutableMapOf<Music.UID, PlaylistImpl>()
|
||||
for (rawPlaylist in rawPlaylists) {
|
||||
val playlistImpl = PlaylistImpl.fromRaw(rawPlaylist, deviceLibrary, nameFactory)
|
||||
playlistMap[playlistImpl.uid] = playlistImpl
|
||||
}
|
||||
return UserLibraryImpl(playlistDao, playlistMap, nameFactory)
|
||||
}
|
||||
}
|
||||
|
||||
private class UserLibraryImpl(
|
||||
private val playlistDao: PlaylistDao,
|
||||
private val playlistMap: MutableMap<Music.UID, PlaylistImpl>,
|
||||
private val nameFactory: Name.Known.Factory
|
||||
) : MutableUserLibrary {
|
||||
override fun hashCode() = playlistMap.hashCode()
|
||||
|
||||
override fun equals(other: Any?) = other is UserLibraryImpl && other.playlistMap == playlistMap
|
||||
|
||||
override fun toString() = "UserLibrary(playlists=${playlists.size})"
|
||||
|
||||
override val playlists: Collection<Playlist>
|
||||
get() = playlistMap.values.toSet()
|
||||
|
||||
override fun findPlaylist(uid: Music.UID) = playlistMap[uid]
|
||||
|
||||
override fun findPlaylist(name: String) = playlistMap.values.find { it.name.raw == name }
|
||||
|
||||
override suspend fun createPlaylist(name: String, songs: List<Song>): Playlist? {
|
||||
val playlistImpl = PlaylistImpl.from(name, songs, nameFactory)
|
||||
synchronized(this) { playlistMap[playlistImpl.uid] = playlistImpl }
|
||||
val rawPlaylist =
|
||||
RawPlaylist(
|
||||
PlaylistInfo(playlistImpl.uid, playlistImpl.name.raw),
|
||||
playlistImpl.songs.map { PlaylistSong(it.uid) })
|
||||
|
||||
return try {
|
||||
playlistDao.insertPlaylist(rawPlaylist)
|
||||
L.d("Successfully created playlist $name with ${songs.size} songs")
|
||||
playlistImpl
|
||||
} catch (e: Exception) {
|
||||
L.e("Unable to create playlist $name with ${songs.size} songs")
|
||||
L.e(e.stackTraceToString())
|
||||
synchronized(this) { playlistMap.remove(playlistImpl.uid) }
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun renamePlaylist(playlist: Playlist, name: String): Boolean {
|
||||
val playlistImpl =
|
||||
synchronized(this) {
|
||||
requireNotNull(playlistMap[playlist.uid]) { "Cannot rename invalid playlist" }
|
||||
.also { playlistMap[it.uid] = it.edit(name, nameFactory) }
|
||||
}
|
||||
|
||||
return try {
|
||||
playlistDao.replacePlaylistInfo(PlaylistInfo(playlist.uid, name))
|
||||
L.d("Successfully renamed $playlist to $name")
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
L.e("Unable to rename $playlist to $name: $e")
|
||||
L.e(e.stackTraceToString())
|
||||
synchronized(this) { playlistMap[playlistImpl.uid] = playlistImpl }
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun deletePlaylist(playlist: Playlist): Boolean {
|
||||
val playlistImpl =
|
||||
synchronized(this) {
|
||||
requireNotNull(playlistMap[playlist.uid]) { "Cannot remove invalid playlist" }
|
||||
.also { playlistMap.remove(it.uid) }
|
||||
}
|
||||
|
||||
return try {
|
||||
playlistDao.deletePlaylist(playlist.uid)
|
||||
L.d("Successfully deleted $playlist")
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
L.e("Unable to delete $playlist: $e")
|
||||
L.e(e.stackTraceToString())
|
||||
synchronized(this) { playlistMap[playlistImpl.uid] = playlistImpl }
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun addToPlaylist(playlist: Playlist, songs: List<Song>): Boolean {
|
||||
val playlistImpl =
|
||||
synchronized(this) {
|
||||
requireNotNull(playlistMap[playlist.uid]) { "Cannot add to invalid playlist" }
|
||||
.also { playlistMap[it.uid] = it.edit { addAll(songs) } }
|
||||
}
|
||||
|
||||
return try {
|
||||
playlistDao.insertPlaylistSongs(playlist.uid, songs.map { PlaylistSong(it.uid) })
|
||||
L.d("Successfully added ${songs.size} songs to $playlist")
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
L.e("Unable to add ${songs.size} songs to $playlist: $e")
|
||||
L.e(e.stackTraceToString())
|
||||
synchronized(this) { playlistMap[playlistImpl.uid] = playlistImpl }
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun rewritePlaylist(playlist: Playlist, songs: List<Song>): Boolean {
|
||||
val playlistImpl =
|
||||
synchronized(this) {
|
||||
requireNotNull(playlistMap[playlist.uid]) { "Cannot rewrite invalid playlist" }
|
||||
.also { playlistMap[it.uid] = it.edit(songs) }
|
||||
}
|
||||
|
||||
return try {
|
||||
playlistDao.replacePlaylistSongs(playlist.uid, songs.map { PlaylistSong(it.uid) })
|
||||
L.d("Successfully rewrote $playlist with ${songs.size} songs")
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
L.e("Unable to rewrite $playlist with ${songs.size} songs: $e")
|
||||
L.e(e.stackTraceToString())
|
||||
synchronized(this) { playlistMap[playlistImpl.uid] = playlistImpl }
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue