Skip to content

Commit

Permalink
musikr: initial root documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
OxygenCobalt committed Jan 14, 2025
1 parent b6d8018 commit c9d4b01
Show file tree
Hide file tree
Showing 3 changed files with 181 additions and 7 deletions.
26 changes: 25 additions & 1 deletion musikr/src/main/java/org/oxycblt/musikr/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,34 @@ import org.oxycblt.musikr.playlist.db.StoredPlaylists
import org.oxycblt.musikr.tag.interpret.Naming
import org.oxycblt.musikr.tag.interpret.Separators

/** Side-effect laden [Storage] for use during music loading and [MutableLibrary] operation. */
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,

/**
* 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,

/**
* 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
)

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
)
105 changes: 105 additions & 0 deletions musikr/src/main/java/org/oxycblt/musikr/Library.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,38 +20,143 @@ package org.oxycblt.musikr

import org.oxycblt.musikr.fs.Path

/**
* An immutable music library.
*
* No operations here will create side effects.
*/
interface Library {
val songs: Collection<Song>
val albums: Collection<Album>
val artists: Collection<Artist>
val genres: Collection<Genre>
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

/**
* 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?

/**
* 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?

/**
* 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?

/**
* 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?

/**
* 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?

/**
* 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?

/**
* 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?
}

/**
* 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 {
/**
* 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

/**
* 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

/**
* 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

/**
* 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

/**
* 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
}
57 changes: 51 additions & 6 deletions musikr/src/main/java/org/oxycblt/musikr/Musikr.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,44 @@ import org.oxycblt.musikr.pipeline.EvaluateStep
import org.oxycblt.musikr.pipeline.ExploreStep
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 {
/**
* 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(
locations: List<MusicLocation>,
onProgress: suspend (IndexingProgress) -> Unit = {}
): LibraryResult

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 =
MusikrImpl(
storage,
Expand All @@ -46,20 +77,35 @@ interface Musikr {
}
}

/** Simple library handle returned by [Musikr.run]. */
interface LibraryResult {
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()
}

/**
* Represents the current progress of music loading.
*
* @author Alexander Capehart (OxygenCobalt)
*/
/** Music loading progress as reported by the music pipeline. */
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

/**
* Currently creating the music graph alongside I/O finalization.
*
* There is no way to measure progress on these events.
*/
data object Indeterminate : IndexingProgress
}

Expand Down Expand Up @@ -96,7 +142,6 @@ private class LibraryResultImpl(
private val storage: Storage,
override val library: MutableLibrary
) : LibraryResult {

override suspend fun cleanup() {
storage.storedCovers.cleanup(library.songs.mapNotNull { it.cover })
}
Expand Down

0 comments on commit c9d4b01

Please sign in to comment.