diff --git a/app/src/main/java/org/oxycblt/auxio/music/stack/explore/Files.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/Files.kt index 5008fb5c0..c418c4bb6 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/stack/explore/Files.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/Files.kt @@ -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 = listOf() ) -interface PlaylistFile { - val name: String -} \ No newline at end of file +data class PlaylistFile( + val name: String, + val songPointers: List, + val editor: PlaylistHandle +) + +interface PlaylistHandle { + val uid: Music.UID + suspend fun rename(name: String) + suspend fun add(songs: List) + suspend fun rewrite(songs: List) + suspend fun delete() +} + +sealed interface SongPointer { + data class UID(val uid: Music.UID) : SongPointer +// data class Path(val options: List) : SongPointer +} diff --git a/app/src/main/java/org/oxycblt/auxio/music/user/UserMusicDatabase.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/playlists/PlaylistDatabase.kt similarity index 97% rename from app/src/main/java/org/oxycblt/auxio/music/user/UserMusicDatabase.kt rename to app/src/main/java/org/oxycblt/auxio/music/stack/explore/playlists/PlaylistDatabase.kt index b6358ee3b..3f1fcefd6 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/user/UserMusicDatabase.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/playlists/PlaylistDatabase.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -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 } diff --git a/app/src/main/java/org/oxycblt/auxio/music/user/RawPlaylist.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/playlists/RawPlaylist.kt similarity index 94% rename from app/src/main/java/org/oxycblt/auxio/music/user/RawPlaylist.kt rename to app/src/main/java/org/oxycblt/auxio/music/stack/explore/playlists/RawPlaylist.kt index 27cf62554..ee79bb5fa 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/user/RawPlaylist.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/playlists/RawPlaylist.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -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) */ diff --git a/app/src/main/java/org/oxycblt/auxio/music/stack/explore/playlists/StoredPlaylists.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/playlists/StoredPlaylists.kt new file mode 100644 index 000000000..139a2b3de --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/playlists/StoredPlaylists.kt @@ -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 +} + +class StoredPlaylistsImpl @Inject constructor( + private val playlistDao: PlaylistDao +) : StoredPlaylists { + override fun read() = flow { + emitAll(playlistDao.readRawPlaylists() + .asFlow() + .map { + TODO() + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/oxycblt/auxio/music/user/UserModule.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/playlists/UserModule.kt similarity index 73% rename from app/src/main/java/org/oxycblt/auxio/music/user/UserModule.kt rename to app/src/main/java/org/oxycblt/auxio/music/stack/explore/playlists/UserModule.kt index 10a42edb1..0b8891922 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/user/UserModule.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/playlists/UserModule.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -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() } diff --git a/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/linker/LinkedMusic.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/linker/LinkedMusic.kt index cf1d38dbf..7d98ee929 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/linker/LinkedMusic.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/linker/LinkedMusic.kt @@ -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, AlbumImpl> } +interface LinkedPlaylist { + val prePlaylist: PrePlaylist + val songs: Linked, PlaylistImpl> +} + interface Linked { fun resolve(child: C): P } diff --git a/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/linker/PlaylistLinker.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/linker/PlaylistLinker.kt new file mode 100644 index 000000000..3c1e5d455 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/linker/PlaylistLinker.kt @@ -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, linkedSongs: Flow): Flow = emptyFlow() + fun resolve(): Collection = setOf() +} diff --git a/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/model/PlaylistImpl.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/model/PlaylistImpl.kt new file mode 100644 index 000000000..52919a4c4 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/model/PlaylistImpl.kt @@ -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 . + */ + +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)" +} diff --git a/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/prepare/PreMusic.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/prepare/PreMusic.kt index 44627ca79..a405ff2ef 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/prepare/PreMusic.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/prepare/PreMusic.kt @@ -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 +) \ No newline at end of file diff --git a/app/src/main/java/org/oxycblt/auxio/music/user/PlaylistImpl.kt b/app/src/main/java/org/oxycblt/auxio/music/user/PlaylistImpl.kt deleted file mode 100644 index 9f739a27e..000000000 --- a/app/src/main/java/org/oxycblt/auxio/music/user/PlaylistImpl.kt +++ /dev/null @@ -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 . - */ - -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 -) : 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) = 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.() -> 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, 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) }) - } -} diff --git a/app/src/main/java/org/oxycblt/auxio/music/user/UserLibrary.kt b/app/src/main/java/org/oxycblt/auxio/music/user/UserLibrary.kt deleted file mode 100644 index f166b630d..000000000 --- a/app/src/main/java/org/oxycblt/auxio/music/user/UserLibrary.kt +++ /dev/null @@ -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 . - */ - -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 - - /** - * 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 - - /** - * 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, - 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): 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): 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): 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, - deviceLibrary: DeviceLibrary, - nameFactory: Name.Known.Factory - ): MutableUserLibrary { - val playlistMap = mutableMapOf() - 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, - 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 - 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): 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): 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): 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 - } - } -}