musikr: move storage/interpretation dependence to construction
This makes some testing and certain code more ergonomic.
This commit is contained in:
parent
f3913b148a
commit
bdfd9d6e23
11 changed files with 87 additions and 81 deletions
|
@ -26,7 +26,6 @@ import dagger.hilt.InstallIn
|
|||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
import org.oxycblt.musikr.Musikr
|
||||
import org.oxycblt.musikr.cache.CacheDatabase
|
||||
import org.oxycblt.musikr.playlist.db.PlaylistDatabase
|
||||
|
||||
|
@ -48,6 +47,4 @@ class MusikrShimModule {
|
|||
@Singleton
|
||||
@Provides
|
||||
fun playlistDatabase(@ApplicationContext context: Context) = PlaylistDatabase.from(context)
|
||||
|
||||
@Provides fun musikr(@ApplicationContext context: Context) = Musikr.new(context)
|
||||
}
|
||||
|
|
|
@ -58,6 +58,7 @@ import timber.log.Timber as L
|
|||
*/
|
||||
interface MusicRepository {
|
||||
val library: Library?
|
||||
|
||||
/** The current state of music loading. Null if no load has occurred yet. */
|
||||
val indexingState: IndexingState?
|
||||
|
||||
|
@ -212,7 +213,6 @@ interface MusicRepository {
|
|||
class MusicRepositoryImpl
|
||||
@Inject
|
||||
constructor(
|
||||
private val musikr: Musikr,
|
||||
@ApplicationContext private val context: Context,
|
||||
private val cacheDatabase: CacheDatabase,
|
||||
private val playlistDatabase: PlaylistDatabase,
|
||||
|
@ -375,9 +375,11 @@ constructor(
|
|||
StoredCovers.from(context, "covers"),
|
||||
StoredPlaylists.from(playlistDatabase))
|
||||
}
|
||||
|
||||
val interpretation = Interpretation(nameFactory, separators)
|
||||
|
||||
val newLibrary =
|
||||
musikr.run(
|
||||
locations, storage, Interpretation(nameFactory, separators), ::emitIndexingProgress)
|
||||
Musikr.new(context, storage, interpretation).run(locations, ::emitIndexingProgress)
|
||||
|
||||
emitIndexingCompletion(null)
|
||||
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
|
||||
package org.oxycblt.musikr
|
||||
|
||||
import org.oxycblt.musikr.cover.StoredCovers
|
||||
import org.oxycblt.musikr.fs.Path
|
||||
|
||||
interface Library {
|
||||
|
@ -28,8 +27,6 @@ interface Library {
|
|||
val genres: Collection<Genre>
|
||||
val playlists: Collection<Playlist>
|
||||
|
||||
val storedCovers: StoredCovers
|
||||
|
||||
fun findSong(uid: Music.UID): Song?
|
||||
|
||||
fun findSongByPath(path: Path): Song?
|
||||
|
|
|
@ -33,14 +33,15 @@ import org.oxycblt.musikr.pipeline.ExtractStep
|
|||
interface Musikr {
|
||||
suspend fun run(
|
||||
locations: List<MusicLocation>,
|
||||
storage: Storage,
|
||||
interpretation: Interpretation,
|
||||
onProgress: suspend (IndexingProgress) -> Unit = {}
|
||||
): MutableLibrary
|
||||
|
||||
companion object {
|
||||
fun new(context: Context): Musikr =
|
||||
MusikrImpl(ExploreStep.from(context), ExtractStep.from(context), EvaluateStep.new())
|
||||
fun new(context: Context, storage: Storage, interpretation: Interpretation): Musikr =
|
||||
MusikrImpl(
|
||||
ExploreStep.from(context, storage),
|
||||
ExtractStep.from(context, storage),
|
||||
EvaluateStep.new(storage, interpretation))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,24 +63,22 @@ private class MusikrImpl(
|
|||
) : Musikr {
|
||||
override suspend fun run(
|
||||
locations: List<MusicLocation>,
|
||||
storage: Storage,
|
||||
interpretation: Interpretation,
|
||||
onProgress: suspend (IndexingProgress) -> Unit
|
||||
) = coroutineScope {
|
||||
var exploredCount = 0
|
||||
var extractedCount = 0
|
||||
val explored =
|
||||
exploreStep
|
||||
.explore(locations, storage)
|
||||
.explore(locations)
|
||||
.buffer(Channel.UNLIMITED)
|
||||
.onStart { onProgress(IndexingProgress.Songs(0, 0)) }
|
||||
.onEach { onProgress(IndexingProgress.Songs(extractedCount, ++exploredCount)) }
|
||||
val extracted =
|
||||
extractStep
|
||||
.extract(storage, explored)
|
||||
.extract(explored)
|
||||
.buffer(Channel.UNLIMITED)
|
||||
.onEach { onProgress(IndexingProgress.Songs(++extractedCount, exploredCount)) }
|
||||
.onCompletion { onProgress(IndexingProgress.Indeterminate) }
|
||||
evaluateStep.evaluate(storage, interpretation, extracted)
|
||||
evaluateStep.evaluate(extracted)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,19 +21,23 @@ package org.oxycblt.musikr.model
|
|||
import org.oxycblt.musikr.Album
|
||||
import org.oxycblt.musikr.Artist
|
||||
import org.oxycblt.musikr.Genre
|
||||
import org.oxycblt.musikr.Interpretation
|
||||
import org.oxycblt.musikr.MutableLibrary
|
||||
import org.oxycblt.musikr.Song
|
||||
import org.oxycblt.musikr.Storage
|
||||
import org.oxycblt.musikr.graph.AlbumVertex
|
||||
import org.oxycblt.musikr.graph.ArtistVertex
|
||||
import org.oxycblt.musikr.graph.GenreVertex
|
||||
import org.oxycblt.musikr.graph.MusicGraph
|
||||
import org.oxycblt.musikr.graph.PlaylistVertex
|
||||
import org.oxycblt.musikr.graph.SongVertex
|
||||
import org.oxycblt.musikr.playlist.db.StoredPlaylists
|
||||
import org.oxycblt.musikr.playlist.interpret.PlaylistInterpreter
|
||||
|
||||
internal interface LibraryFactory {
|
||||
fun create(graph: MusicGraph, storage: Storage, interpretation: Interpretation): MutableLibrary
|
||||
fun create(
|
||||
graph: MusicGraph,
|
||||
storedPlaylists: StoredPlaylists,
|
||||
playlistInterpreter: PlaylistInterpreter
|
||||
): MutableLibrary
|
||||
|
||||
companion object {
|
||||
fun new(): LibraryFactory = LibraryFactoryImpl()
|
||||
|
@ -43,8 +47,8 @@ internal interface LibraryFactory {
|
|||
private class LibraryFactoryImpl() : LibraryFactory {
|
||||
override fun create(
|
||||
graph: MusicGraph,
|
||||
storage: Storage,
|
||||
interpretation: Interpretation
|
||||
storedPlaylists: StoredPlaylists,
|
||||
playlistInterpreter: PlaylistInterpreter
|
||||
): MutableLibrary {
|
||||
val songs =
|
||||
graph.songVertex.mapTo(mutableSetOf()) { vertex ->
|
||||
|
@ -66,7 +70,8 @@ private class LibraryFactoryImpl() : LibraryFactory {
|
|||
graph.playlistVertex.mapTo(mutableSetOf()) { vertex ->
|
||||
PlaylistImpl(PlaylistVertexCore(vertex))
|
||||
}
|
||||
return LibraryImpl(songs, albums, artists, genres, playlists, storage, interpretation)
|
||||
return LibraryImpl(
|
||||
songs, albums, artists, genres, playlists, storedPlaylists, playlistInterpreter)
|
||||
}
|
||||
|
||||
private class SongVertexCore(private val vertex: SongVertex) : SongCore {
|
||||
|
|
|
@ -18,13 +18,14 @@
|
|||
|
||||
package org.oxycblt.musikr.model
|
||||
|
||||
import org.oxycblt.musikr.Interpretation
|
||||
import org.oxycblt.musikr.Music
|
||||
import org.oxycblt.musikr.MutableLibrary
|
||||
import org.oxycblt.musikr.Playlist
|
||||
import org.oxycblt.musikr.Song
|
||||
import org.oxycblt.musikr.Storage
|
||||
import org.oxycblt.musikr.fs.Path
|
||||
import org.oxycblt.musikr.playlist.db.StoredPlaylists
|
||||
import org.oxycblt.musikr.playlist.interpret.PlaylistInterpreter
|
||||
import org.oxycblt.musikr.playlist.interpret.PostPlaylist
|
||||
|
||||
internal data class LibraryImpl(
|
||||
override val songs: Collection<SongImpl>,
|
||||
|
@ -32,8 +33,8 @@ internal data class LibraryImpl(
|
|||
override val artists: Collection<ArtistImpl>,
|
||||
override val genres: Collection<GenreImpl>,
|
||||
override val playlists: Collection<Playlist>,
|
||||
private val storage: Storage,
|
||||
private val interpretation: Interpretation
|
||||
private val storedPlaylists: StoredPlaylists,
|
||||
private val playlistInterpreter: PlaylistInterpreter
|
||||
) : MutableLibrary {
|
||||
private val songUidMap = songs.associateBy { it.uid }
|
||||
private val albumUidMap = albums.associateBy { it.uid }
|
||||
|
@ -41,8 +42,6 @@ internal data class LibraryImpl(
|
|||
private val genreUidMap = genres.associateBy { it.uid }
|
||||
private val playlistUidMap = playlists.associateBy { it.uid }
|
||||
|
||||
override val storedCovers = storage.storedCovers
|
||||
|
||||
override fun findSong(uid: Music.UID) = songUidMap[uid]
|
||||
|
||||
override fun findSongByPath(path: Path) = songs.find { it.path == path }
|
||||
|
@ -76,4 +75,9 @@ internal data class LibraryImpl(
|
|||
override suspend fun deletePlaylist(playlist: Playlist): MutableLibrary {
|
||||
return this
|
||||
}
|
||||
|
||||
private class NewPlaylistCore(
|
||||
override val prePlaylist: PostPlaylist,
|
||||
override val songs: List<Song>
|
||||
) : PlaylistCore
|
||||
}
|
||||
|
|
|
@ -32,32 +32,30 @@ import org.oxycblt.musikr.MutableLibrary
|
|||
import org.oxycblt.musikr.Storage
|
||||
import org.oxycblt.musikr.graph.MusicGraph
|
||||
import org.oxycblt.musikr.model.LibraryFactory
|
||||
import org.oxycblt.musikr.playlist.db.StoredPlaylists
|
||||
import org.oxycblt.musikr.playlist.interpret.PlaylistInterpreter
|
||||
import org.oxycblt.musikr.tag.interpret.TagInterpreter
|
||||
|
||||
internal interface EvaluateStep {
|
||||
suspend fun evaluate(
|
||||
storage: Storage,
|
||||
interpretation: Interpretation,
|
||||
extractedMusic: Flow<ExtractedMusic>
|
||||
): MutableLibrary
|
||||
suspend fun evaluate(extractedMusic: Flow<ExtractedMusic>): MutableLibrary
|
||||
|
||||
companion object {
|
||||
fun new(): EvaluateStep =
|
||||
EvaluateStepImpl(TagInterpreter.new(), PlaylistInterpreter.new(), LibraryFactory.new())
|
||||
fun new(storage: Storage, interpretation: Interpretation): EvaluateStep =
|
||||
EvaluateStepImpl(
|
||||
TagInterpreter.new(interpretation),
|
||||
PlaylistInterpreter.new(interpretation),
|
||||
storage.storedPlaylists,
|
||||
LibraryFactory.new())
|
||||
}
|
||||
}
|
||||
|
||||
private class EvaluateStepImpl(
|
||||
private val tagInterpreter: TagInterpreter,
|
||||
private val playlistInterpreter: PlaylistInterpreter,
|
||||
private val storedPlaylists: StoredPlaylists,
|
||||
private val libraryFactory: LibraryFactory
|
||||
) : EvaluateStep {
|
||||
override suspend fun evaluate(
|
||||
storage: Storage,
|
||||
interpretation: Interpretation,
|
||||
extractedMusic: Flow<ExtractedMusic>
|
||||
): MutableLibrary {
|
||||
override suspend fun evaluate(extractedMusic: Flow<ExtractedMusic>): MutableLibrary {
|
||||
val filterFlow =
|
||||
extractedMusic.divert {
|
||||
when (it) {
|
||||
|
@ -68,12 +66,12 @@ private class EvaluateStepImpl(
|
|||
val rawSongs = filterFlow.right
|
||||
val preSongs =
|
||||
rawSongs
|
||||
.map { tagInterpreter.interpret(it, interpretation) }
|
||||
.map { tagInterpreter.interpret(it) }
|
||||
.flowOn(Dispatchers.Default)
|
||||
.buffer(Channel.UNLIMITED)
|
||||
val prePlaylists =
|
||||
filterFlow.left
|
||||
.map { playlistInterpreter.interpret(it, interpretation) }
|
||||
.map { playlistInterpreter.interpret(it) }
|
||||
.flowOn(Dispatchers.Default)
|
||||
.buffer(Channel.UNLIMITED)
|
||||
val graphBuilder = MusicGraph.builder()
|
||||
|
@ -84,6 +82,6 @@ private class EvaluateStepImpl(
|
|||
prePlaylists.onEach { graphBuilder.add(it) })
|
||||
graphBuild.collect()
|
||||
val graph = graphBuilder.build()
|
||||
return libraryFactory.create(graph, storage, interpretation)
|
||||
return libraryFactory.create(graph, storedPlaylists, playlistInterpreter)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,18 +34,23 @@ import org.oxycblt.musikr.fs.DeviceFile
|
|||
import org.oxycblt.musikr.fs.MusicLocation
|
||||
import org.oxycblt.musikr.fs.query.DeviceFiles
|
||||
import org.oxycblt.musikr.playlist.PlaylistFile
|
||||
import org.oxycblt.musikr.playlist.db.StoredPlaylists
|
||||
import org.oxycblt.musikr.playlist.m3u.M3U
|
||||
|
||||
internal interface ExploreStep {
|
||||
fun explore(locations: List<MusicLocation>, storage: Storage): Flow<ExploreNode>
|
||||
fun explore(locations: List<MusicLocation>): Flow<ExploreNode>
|
||||
|
||||
companion object {
|
||||
fun from(context: Context): ExploreStep = ExploreStepImpl(DeviceFiles.from(context))
|
||||
fun from(context: Context, storage: Storage): ExploreStep =
|
||||
ExploreStepImpl(DeviceFiles.from(context), storage.storedPlaylists)
|
||||
}
|
||||
}
|
||||
|
||||
private class ExploreStepImpl(private val deviceFiles: DeviceFiles) : ExploreStep {
|
||||
override fun explore(locations: List<MusicLocation>, storage: Storage): Flow<ExploreNode> {
|
||||
private class ExploreStepImpl(
|
||||
private val deviceFiles: DeviceFiles,
|
||||
private val storedPlaylists: StoredPlaylists
|
||||
) : ExploreStep {
|
||||
override fun explore(locations: List<MusicLocation>): Flow<ExploreNode> {
|
||||
val audios =
|
||||
deviceFiles
|
||||
.explore(locations.asFlow())
|
||||
|
@ -59,7 +64,7 @@ private class ExploreStepImpl(private val deviceFiles: DeviceFiles) : ExploreSte
|
|||
.flowOn(Dispatchers.IO)
|
||||
.buffer()
|
||||
val playlists =
|
||||
flow { emitAll(storage.storedPlaylists.read().asFlow()) }
|
||||
flow { emitAll(storedPlaylists.read().asFlow()) }
|
||||
.map { ExploreNode.Playlist(it) }
|
||||
.flowOn(Dispatchers.IO)
|
||||
.buffer()
|
||||
|
|
|
@ -28,8 +28,10 @@ import kotlinx.coroutines.flow.map
|
|||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import kotlinx.coroutines.flow.merge
|
||||
import org.oxycblt.musikr.Storage
|
||||
import org.oxycblt.musikr.cache.Cache
|
||||
import org.oxycblt.musikr.cache.CacheResult
|
||||
import org.oxycblt.musikr.cover.Cover
|
||||
import org.oxycblt.musikr.cover.StoredCovers
|
||||
import org.oxycblt.musikr.fs.DeviceFile
|
||||
import org.oxycblt.musikr.metadata.MetadataExtractor
|
||||
import org.oxycblt.musikr.metadata.Properties
|
||||
|
@ -38,19 +40,25 @@ import org.oxycblt.musikr.tag.parse.ParsedTags
|
|||
import org.oxycblt.musikr.tag.parse.TagParser
|
||||
|
||||
internal interface ExtractStep {
|
||||
fun extract(storage: Storage, nodes: Flow<ExploreNode>): Flow<ExtractedMusic>
|
||||
fun extract(nodes: Flow<ExploreNode>): Flow<ExtractedMusic>
|
||||
|
||||
companion object {
|
||||
fun from(context: Context): ExtractStep =
|
||||
ExtractStepImpl(MetadataExtractor.from(context), TagParser.new())
|
||||
fun from(context: Context, storage: Storage): ExtractStep =
|
||||
ExtractStepImpl(
|
||||
MetadataExtractor.from(context),
|
||||
TagParser.new(),
|
||||
storage.cache,
|
||||
storage.storedCovers)
|
||||
}
|
||||
}
|
||||
|
||||
private class ExtractStepImpl(
|
||||
private val metadataExtractor: MetadataExtractor,
|
||||
private val tagParser: TagParser
|
||||
private val tagParser: TagParser,
|
||||
private val cache: Cache,
|
||||
private val storedCovers: StoredCovers
|
||||
) : ExtractStep {
|
||||
override fun extract(storage: Storage, nodes: Flow<ExploreNode>): Flow<ExtractedMusic> {
|
||||
override fun extract(nodes: Flow<ExploreNode>): Flow<ExtractedMusic> {
|
||||
val filterFlow =
|
||||
nodes.divert {
|
||||
when (it) {
|
||||
|
@ -62,10 +70,7 @@ private class ExtractStepImpl(
|
|||
val playlistNodes = filterFlow.left.map { ExtractedMusic.Playlist(it) }
|
||||
|
||||
val cacheResults =
|
||||
audioNodes
|
||||
.map { storage.cache.read(it) }
|
||||
.flowOn(Dispatchers.IO)
|
||||
.buffer(Channel.UNLIMITED)
|
||||
audioNodes.map { cache.read(it) }.flowOn(Dispatchers.IO).buffer(Channel.UNLIMITED)
|
||||
val cacheFlow =
|
||||
cacheResults.divert {
|
||||
when (it) {
|
||||
|
@ -82,7 +87,7 @@ private class ExtractStepImpl(
|
|||
.mapNotNull { file ->
|
||||
val metadata = metadataExtractor.extract(file) ?: return@mapNotNull null
|
||||
val tags = tagParser.parse(file, metadata)
|
||||
val cover = metadata.cover?.let { storage.storedCovers.write(it) }
|
||||
val cover = metadata.cover?.let { storedCovers.write(it) }
|
||||
RawSong(file, metadata.properties, tags, cover)
|
||||
}
|
||||
.flowOn(Dispatchers.IO)
|
||||
|
@ -91,7 +96,7 @@ private class ExtractStepImpl(
|
|||
val writtenSongs =
|
||||
merge(*extractedSongs)
|
||||
.map {
|
||||
storage.cache.write(it)
|
||||
cache.write(it)
|
||||
ExtractedMusic.Song(it)
|
||||
}
|
||||
.flowOn(Dispatchers.IO)
|
||||
|
|
|
@ -21,33 +21,27 @@ package org.oxycblt.musikr.playlist.interpret
|
|||
import org.oxycblt.musikr.Interpretation
|
||||
import org.oxycblt.musikr.playlist.PlaylistFile
|
||||
import org.oxycblt.musikr.playlist.PlaylistHandle
|
||||
import org.oxycblt.musikr.tag.interpret.Naming
|
||||
|
||||
internal interface PlaylistInterpreter {
|
||||
fun interpret(file: PlaylistFile, interpretation: Interpretation): PrePlaylist
|
||||
fun interpret(file: PlaylistFile): PrePlaylist
|
||||
|
||||
fun interpret(
|
||||
name: String,
|
||||
handle: PlaylistHandle,
|
||||
interpretation: Interpretation
|
||||
): PostPlaylist
|
||||
fun interpret(name: String, handle: PlaylistHandle): PostPlaylist
|
||||
|
||||
companion object {
|
||||
fun new(): PlaylistInterpreter = PlaylistInterpreterImpl
|
||||
fun new(interpretation: Interpretation): PlaylistInterpreter =
|
||||
PlaylistInterpreterImpl(interpretation.naming)
|
||||
}
|
||||
}
|
||||
|
||||
private data object PlaylistInterpreterImpl : PlaylistInterpreter {
|
||||
override fun interpret(file: PlaylistFile, interpretation: Interpretation) =
|
||||
private class PlaylistInterpreterImpl(private val naming: Naming) : PlaylistInterpreter {
|
||||
override fun interpret(file: PlaylistFile) =
|
||||
PrePlaylist(
|
||||
name = interpretation.naming.name(file.name, null),
|
||||
name = naming.name(file.name, null),
|
||||
rawName = file.name,
|
||||
handle = file.handle,
|
||||
songPointers = file.songPointers)
|
||||
|
||||
override fun interpret(
|
||||
name: String,
|
||||
handle: PlaylistHandle,
|
||||
interpretation: Interpretation
|
||||
): PostPlaylist =
|
||||
PostPlaylist(name = interpretation.naming.name(name, null), rawName = name, handle = handle)
|
||||
override fun interpret(name: String, handle: PlaylistHandle): PostPlaylist =
|
||||
PostPlaylist(name = naming.name(name, null), rawName = name, handle = handle)
|
||||
}
|
||||
|
|
|
@ -31,15 +31,15 @@ import org.oxycblt.musikr.tag.parse.ParsedTags
|
|||
import org.oxycblt.musikr.util.toUuidOrNull
|
||||
|
||||
internal interface TagInterpreter {
|
||||
fun interpret(song: RawSong, interpretation: Interpretation): PreSong
|
||||
fun interpret(song: RawSong): PreSong
|
||||
|
||||
companion object {
|
||||
fun new(): TagInterpreter = TagInterpreterImpl
|
||||
fun new(interpretation: Interpretation): TagInterpreter = TagInterpreterImpl(interpretation)
|
||||
}
|
||||
}
|
||||
|
||||
private data object TagInterpreterImpl : TagInterpreter {
|
||||
override fun interpret(song: RawSong, interpretation: Interpretation): PreSong {
|
||||
private class TagInterpreterImpl(private val interpretation: Interpretation) : TagInterpreter {
|
||||
override fun interpret(song: RawSong): PreSong {
|
||||
val individualPreArtists =
|
||||
makePreArtists(
|
||||
song.tags.artistMusicBrainzIds,
|
||||
|
|
Loading…
Reference in a new issue