musikr: initial root documentation
This commit is contained in:
parent
b6d80189ca
commit
c9d4b01f9f
3 changed files with 181 additions and 7 deletions
|
@ -24,10 +24,34 @@ import org.oxycblt.musikr.playlist.db.StoredPlaylists
|
||||||
import org.oxycblt.musikr.tag.interpret.Naming
|
import org.oxycblt.musikr.tag.interpret.Naming
|
||||||
import org.oxycblt.musikr.tag.interpret.Separators
|
import org.oxycblt.musikr.tag.interpret.Separators
|
||||||
|
|
||||||
|
/** Side-effect laden [Storage] for use during music loading and [MutableLibrary] operation. */
|
||||||
data class Storage(
|
data class Storage(
|
||||||
|
/**
|
||||||
|
* A factory producing a repository of cached metadata to read and write from over the course of
|
||||||
|
* music loading. This will only be used during music loading.
|
||||||
|
*/
|
||||||
val cache: Cache.Factory,
|
val cache: Cache.Factory,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A repository of cover images to for re-use during music loading. Should be kept in lock-step
|
||||||
|
* with the cache for best performance. This will be used during music loading and when
|
||||||
|
* retrieving cover information from the library.
|
||||||
|
*/
|
||||||
val storedCovers: MutableCovers,
|
val storedCovers: MutableCovers,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A repository of user-created playlists that should also be loaded into the library. This will
|
||||||
|
* be used during music loading and mutated when creating, renaming, or deleting playlists in
|
||||||
|
* the library.
|
||||||
|
*/
|
||||||
val storedPlaylists: StoredPlaylists
|
val storedPlaylists: StoredPlaylists
|
||||||
)
|
)
|
||||||
|
|
||||||
data class Interpretation(val naming: Naming, val separators: Separators)
|
/** Configuration for how to interpret and extrapolate certain audio tags. */
|
||||||
|
data class Interpretation(
|
||||||
|
/** How to construct names from audio tags. */
|
||||||
|
val naming: Naming,
|
||||||
|
|
||||||
|
/** What separators delimit multi-value audio tags. */
|
||||||
|
val separators: Separators
|
||||||
|
)
|
||||||
|
|
|
@ -20,6 +20,11 @@ package org.oxycblt.musikr
|
||||||
|
|
||||||
import org.oxycblt.musikr.fs.Path
|
import org.oxycblt.musikr.fs.Path
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An immutable music library.
|
||||||
|
*
|
||||||
|
* No operations here will create side effects.
|
||||||
|
*/
|
||||||
interface Library {
|
interface Library {
|
||||||
val songs: Collection<Song>
|
val songs: Collection<Song>
|
||||||
val albums: Collection<Album>
|
val albums: Collection<Album>
|
||||||
|
@ -27,31 +32,131 @@ interface Library {
|
||||||
val genres: Collection<Genre>
|
val genres: Collection<Genre>
|
||||||
val playlists: Collection<Playlist>
|
val playlists: Collection<Playlist>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this library is empty (i.e no songs, which means no other music item)
|
||||||
|
*
|
||||||
|
* @return true if this library is empty, false otherwise
|
||||||
|
*/
|
||||||
fun empty(): Boolean
|
fun empty(): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a [Song] by it's [Music.UID]
|
||||||
|
*
|
||||||
|
* @param uid the [Music.UID] of the song
|
||||||
|
* @return the song if found, null otherwise
|
||||||
|
*/
|
||||||
fun findSong(uid: Music.UID): Song?
|
fun findSong(uid: Music.UID): Song?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a [Song] by it's [Path]
|
||||||
|
*
|
||||||
|
* @param path the [Path] of the song
|
||||||
|
* @return the song if found, null otherwise
|
||||||
|
*/
|
||||||
fun findSongByPath(path: Path): Song?
|
fun findSongByPath(path: Path): Song?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find an [Album] by it's [Music.UID]
|
||||||
|
*
|
||||||
|
* @param uid the [Music.UID] of the album
|
||||||
|
* @return the album if found, null otherwise
|
||||||
|
*/
|
||||||
fun findAlbum(uid: Music.UID): Album?
|
fun findAlbum(uid: Music.UID): Album?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find an [Artist] by it's [Music.UID]
|
||||||
|
*
|
||||||
|
* @param uid the [Music.UID] of the artist
|
||||||
|
* @return the artist if found, null otherwise
|
||||||
|
*/
|
||||||
fun findArtist(uid: Music.UID): Artist?
|
fun findArtist(uid: Music.UID): Artist?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a [Genre] by it's [Music.UID]
|
||||||
|
*
|
||||||
|
* @param uid the [Music.UID] of the genre
|
||||||
|
* @return the genre if found, null otherwise
|
||||||
|
*/
|
||||||
fun findGenre(uid: Music.UID): Genre?
|
fun findGenre(uid: Music.UID): Genre?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a [Playlist] by it's [Music.UID]
|
||||||
|
*
|
||||||
|
* @param uid the [Music.UID] of the playlist
|
||||||
|
* @return the playlist if found, null otherwise
|
||||||
|
*/
|
||||||
fun findPlaylist(uid: Music.UID): Playlist?
|
fun findPlaylist(uid: Music.UID): Playlist?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a [Playlist] by it's name
|
||||||
|
*
|
||||||
|
* @param name the name of the playlist
|
||||||
|
* @return the playlist if found, null otherwise
|
||||||
|
*/
|
||||||
fun findPlaylistByName(name: String): Playlist?
|
fun findPlaylistByName(name: String): Playlist?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A mutable extension of [Library].
|
||||||
|
*
|
||||||
|
* Operations here will cause side-effects within the [Storage] used when this library was loaded.
|
||||||
|
* However, it won't actually mutate the [Library] itself, rather return a cloned instance with the
|
||||||
|
* changes applied. It is up to the client to update their reference to the library within their
|
||||||
|
* state handling.
|
||||||
|
*/
|
||||||
interface MutableLibrary : Library {
|
interface MutableLibrary : Library {
|
||||||
|
/**
|
||||||
|
* Create a new [Playlist] with the given name and songs.
|
||||||
|
*
|
||||||
|
* This will commit the new playlist to the stored playlists in the [Storage] used to load the
|
||||||
|
* library.
|
||||||
|
*
|
||||||
|
* @param name the name of the playlist
|
||||||
|
* @param songs the songs to add to the playlist
|
||||||
|
* @return a new [MutableLibrary] with the new playlist
|
||||||
|
*/
|
||||||
suspend fun createPlaylist(name: String, songs: List<Song>): MutableLibrary
|
suspend fun createPlaylist(name: String, songs: List<Song>): MutableLibrary
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rename a [Playlist].
|
||||||
|
*
|
||||||
|
* This will commit to whatever playlist source the given [Playlist] was loaded from.
|
||||||
|
*
|
||||||
|
* @param playlist the playlist to rename
|
||||||
|
* @param name the new name of the playlist
|
||||||
|
* @return a new [MutableLibrary] with the renamed playlist
|
||||||
|
*/
|
||||||
suspend fun renamePlaylist(playlist: Playlist, name: String): MutableLibrary
|
suspend fun renamePlaylist(playlist: Playlist, name: String): MutableLibrary
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add songs to a [Playlist].
|
||||||
|
*
|
||||||
|
* This will commit to whatever playlist source the given [Playlist] was loaded from.
|
||||||
|
*
|
||||||
|
* @param playlist the playlist to add songs to
|
||||||
|
* @param songs the songs to add to the playlist
|
||||||
|
* @return a new [MutableLibrary] with the edited playlist
|
||||||
|
*/
|
||||||
suspend fun addToPlaylist(playlist: Playlist, songs: List<Song>): MutableLibrary
|
suspend fun addToPlaylist(playlist: Playlist, songs: List<Song>): MutableLibrary
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove songs from a [Playlist].
|
||||||
|
*
|
||||||
|
* This will commit to whatever playlist source the given [Playlist] was loaded from.
|
||||||
|
*
|
||||||
|
* @param playlist the playlist to remove songs from
|
||||||
|
* @param songs the songs to remove from the playlist
|
||||||
|
* @return a new [MutableLibrary] with the edited playlist
|
||||||
|
*/
|
||||||
suspend fun rewritePlaylist(playlist: Playlist, songs: List<Song>): MutableLibrary
|
suspend fun rewritePlaylist(playlist: Playlist, songs: List<Song>): MutableLibrary
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a [Playlist].
|
||||||
|
*
|
||||||
|
* This will commit to whatever playlist source the given [Playlist] was loaded from.
|
||||||
|
*
|
||||||
|
* @param playlist the playlist to delete
|
||||||
|
* @return a new [MutableLibrary] with the edited playlist
|
||||||
|
*/
|
||||||
suspend fun deletePlaylist(playlist: Playlist): MutableLibrary
|
suspend fun deletePlaylist(playlist: Playlist): MutableLibrary
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,13 +30,44 @@ import org.oxycblt.musikr.pipeline.EvaluateStep
|
||||||
import org.oxycblt.musikr.pipeline.ExploreStep
|
import org.oxycblt.musikr.pipeline.ExploreStep
|
||||||
import org.oxycblt.musikr.pipeline.ExtractStep
|
import org.oxycblt.musikr.pipeline.ExtractStep
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A highly opinionated, multi-threaded device music library.
|
||||||
|
*
|
||||||
|
* Use this to load music with [run].
|
||||||
|
*
|
||||||
|
* Note the following:
|
||||||
|
* 1. Musikr's API surface is intended to be primarily "stateless", with side-effects mostly
|
||||||
|
* contained within [Storage]. It's your job to manage long-term state.
|
||||||
|
* 2. There are no "defaults" in Musikr. You should think carefully about the parameters you are
|
||||||
|
* specifying and know consider they are desirable or not.
|
||||||
|
* 3. Musikr is currently not extendable, so if you're embedding this elsewhere you should be ready
|
||||||
|
* to fork and modify the source code.
|
||||||
|
*/
|
||||||
interface Musikr {
|
interface Musikr {
|
||||||
|
/**
|
||||||
|
* Start loading music from the given [locations] and the configuration provided earlier.
|
||||||
|
*
|
||||||
|
* @param locations The [MusicLocation]s to search for music in.
|
||||||
|
* @param onProgress Optional callback to receive progress on the current status of the music
|
||||||
|
* pipeline. Warning: These events will be rapid-fire.
|
||||||
|
* @return A handle to the newly created library alongside further cleanup.
|
||||||
|
*/
|
||||||
suspend fun run(
|
suspend fun run(
|
||||||
locations: List<MusicLocation>,
|
locations: List<MusicLocation>,
|
||||||
onProgress: suspend (IndexingProgress) -> Unit = {}
|
onProgress: suspend (IndexingProgress) -> Unit = {}
|
||||||
): LibraryResult
|
): LibraryResult
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
/**
|
||||||
|
* Create a new instance from the given configuration.
|
||||||
|
*
|
||||||
|
* @param context The context to use for loading resources.
|
||||||
|
* @param storage Side-effect laden storage for use within the music loader **and** when
|
||||||
|
* mutating [MutableLibrary]. You should take responsibility for managing their long-term
|
||||||
|
* state.
|
||||||
|
* @param interpretation The configuration to use for interpreting certain vague tags. This
|
||||||
|
* should be configured by the user, if possible.
|
||||||
|
*/
|
||||||
fun new(context: Context, storage: Storage, interpretation: Interpretation): Musikr =
|
fun new(context: Context, storage: Storage, interpretation: Interpretation): Musikr =
|
||||||
MusikrImpl(
|
MusikrImpl(
|
||||||
storage,
|
storage,
|
||||||
|
@ -46,20 +77,35 @@ interface Musikr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Simple library handle returned by [Musikr.run]. */
|
||||||
interface LibraryResult {
|
interface LibraryResult {
|
||||||
val library: MutableLibrary
|
val library: MutableLibrary
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up expired resources. This should be done as soon as possible after music loading to
|
||||||
|
* reduce storage use.
|
||||||
|
*
|
||||||
|
* This may have unexpected results if previous [Library]s are in circulation across your app,
|
||||||
|
* so use it once you've fully updated your state.
|
||||||
|
*/
|
||||||
suspend fun cleanup()
|
suspend fun cleanup()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Music loading progress as reported by the music pipeline. */
|
||||||
* Represents the current progress of music loading.
|
|
||||||
*
|
|
||||||
* @author Alexander Capehart (OxygenCobalt)
|
|
||||||
*/
|
|
||||||
sealed interface IndexingProgress {
|
sealed interface IndexingProgress {
|
||||||
|
/**
|
||||||
|
* Currently indexing and extracting tags from device music.
|
||||||
|
*
|
||||||
|
* @param explored The amount of music currently found from the given [MusicLocation]s.
|
||||||
|
* @param loaded The amount of music that has had metadata extracted and parsed.
|
||||||
|
*/
|
||||||
data class Songs(val loaded: Int, val explored: Int) : IndexingProgress
|
data class Songs(val loaded: Int, val explored: Int) : IndexingProgress
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Currently creating the music graph alongside I/O finalization.
|
||||||
|
*
|
||||||
|
* There is no way to measure progress on these events.
|
||||||
|
*/
|
||||||
data object Indeterminate : IndexingProgress
|
data object Indeterminate : IndexingProgress
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,7 +142,6 @@ private class LibraryResultImpl(
|
||||||
private val storage: Storage,
|
private val storage: Storage,
|
||||||
override val library: MutableLibrary
|
override val library: MutableLibrary
|
||||||
) : LibraryResult {
|
) : LibraryResult {
|
||||||
|
|
||||||
override suspend fun cleanup() {
|
override suspend fun cleanup() {
|
||||||
storage.storedCovers.cleanup(library.songs.mapNotNull { it.cover })
|
storage.storedCovers.cleanup(library.songs.mapNotNull { it.cover })
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue