music: re-add library find functionality

This commit is contained in:
Alexander Capehart 2024-11-26 10:08:07 -07:00
parent 9d9f810356
commit 0ba5ddce51
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
6 changed files with 83 additions and 89 deletions

View file

@ -34,7 +34,6 @@ import org.oxycblt.auxio.music.metadata.Separators
import org.oxycblt.auxio.music.stack.Indexer
import org.oxycblt.auxio.music.stack.interpret.Interpretation
import org.oxycblt.auxio.music.stack.interpret.model.MutableLibrary
import kotlin.math.exp
import timber.log.Timber as L
/**
@ -366,23 +365,24 @@ constructor(private val indexer: Indexer, private val musicSettings: MusicSettin
var explored = 0
var loaded = 0
val newLibrary = indexer.run(listOf(), Interpretation(nameFactory, separators)) {
when (it) {
is Indexer.Event.Discovered -> {
explored = it.amount
emitIndexingProgress(IndexingProgress.Songs(loaded, explored))
}
is Indexer.Event.Extracted -> {
loaded = it.amount
emitIndexingProgress(IndexingProgress.Songs(loaded, explored))
}
is Indexer.Event.Interpret -> {
if (explored == loaded) {
emitIndexingProgress(IndexingProgress.Indeterminate)
val newLibrary =
indexer.run(listOf(), Interpretation(nameFactory, separators)) {
when (it) {
is Indexer.Event.Discovered -> {
explored = it.amount
emitIndexingProgress(IndexingProgress.Songs(loaded, explored))
}
is Indexer.Event.Extracted -> {
loaded = it.amount
emitIndexingProgress(IndexingProgress.Songs(loaded, explored))
}
is Indexer.Event.Interpret -> {
if (explored == loaded) {
emitIndexingProgress(IndexingProgress.Indeterminate)
}
}
}
}
}
// We want to make sure that all reads and writes are synchronized due to the sheer
// amount of consumers of MusicRepository.

View file

@ -31,27 +31,31 @@ import org.oxycblt.auxio.music.stack.interpret.Interpreter
import org.oxycblt.auxio.music.stack.interpret.model.MutableLibrary
interface Indexer {
suspend fun run(uris: List<Uri>, interpretation: Interpretation, eventHandler: suspend (Event) -> Unit = {}): MutableLibrary
suspend fun run(
uris: List<Uri>,
interpretation: Interpretation,
eventHandler: suspend (Event) -> Unit = {}
): MutableLibrary
sealed interface Event {
data class Discovered(
val amount: Int,
) : Event
data class Extracted(
val amount: Int
) : Event
data class Extracted(val amount: Int) : Event
data class Interpret(
val amount: Int
) : Event
data class Interpret(val amount: Int) : Event
}
}
class IndexerImpl
@Inject
constructor(private val explorer: Explorer, private val interpreter: Interpreter) : Indexer {
override suspend fun run(uris: List<Uri>, interpretation: Interpretation, eventHandler: suspend (Event) -> Unit) = coroutineScope {
override suspend fun run(
uris: List<Uri>,
interpretation: Interpretation,
eventHandler: suspend (Event) -> Unit
) = coroutineScope {
val files = explorer.explore(uris, eventHandler)
val audioFiles = files.audios.flowOn(Dispatchers.IO).buffer()
val playlistFiles = files.playlists.flowOn(Dispatchers.IO).buffer()

View file

@ -60,13 +60,15 @@ constructor(
@OptIn(ExperimentalCoroutinesApi::class)
override fun explore(uris: List<Uri>, eventHandler: suspend (Indexer.Event) -> Unit): Files {
var discovered = 0
val deviceFiles = deviceFiles.explore(uris.asFlow())
.onEach {
discovered++
eventHandler(Indexer.Event.Discovered(discovered))
}
.flowOn(Dispatchers.IO)
.buffer()
val deviceFiles =
deviceFiles
.explore(uris.asFlow())
.onEach {
discovered++
eventHandler(Indexer.Event.Discovered(discovered))
}
.flowOn(Dispatchers.IO)
.buffer()
val tagRead = tagCache.read(deviceFiles).flowOn(Dispatchers.IO).buffer()
val (uncachedDeviceFiles, cachedAudioFiles) = tagRead.results()
val extractedAudioFiles =
@ -77,10 +79,11 @@ constructor(
.flattenMerge()
val writtenAudioFiles = tagCache.write(extractedAudioFiles).flowOn(Dispatchers.IO).buffer()
var loaded = 0
val audioFiles = merge(cachedAudioFiles, writtenAudioFiles).onEach {
loaded++
eventHandler(Indexer.Event.Extracted(loaded))
}
val audioFiles =
merge(cachedAudioFiles, writtenAudioFiles).onEach {
loaded++
eventHandler(Indexer.Event.Extracted(loaded))
}
val playlistFiles = storedPlaylists.read()
return Files(audioFiles, playlistFiles)
}
@ -97,8 +100,7 @@ constructor(
val indexed = withIndex()
val shared =
indexed.shareIn(
CoroutineScope(Dispatchers.Main), SharingStarted.WhileSubscribed(), replay = 0
)
CoroutineScope(Dispatchers.Main), SharingStarted.WhileSubscribed(), replay = 0)
return Array(n) { shared.filter { it.index % n == 0 }.map { it.value } }
}
}

View file

@ -83,7 +83,7 @@ class InterpreterImpl @Inject constructor(private val preparer: Preparer) : Inte
.register(artistLinkedSongs)
.flowOn(Dispatchers.Main)
.onEach {
interpreted++;
interpreted++
eventHandler(Indexer.Event.Interpret(interpreted))
}
.map { LinkedSongImpl(it) }
@ -91,16 +91,17 @@ class InterpreterImpl @Inject constructor(private val preparer: Preparer) : Inte
val albums = albumLinker.resolve()
val uidMap = mutableMapOf<Music.UID, SongImpl>()
val songs = albumLinkedSongs.mapNotNull {
val uid = it.preSong.computeUid()
val other = uidMap[uid]
if (other == null) {
SongImpl(it)
} else {
L.d("Song @ $uid already exists at ${other.path}, ignoring")
null
val songs =
albumLinkedSongs.mapNotNull {
val uid = it.preSong.computeUid()
val other = uidMap[uid]
if (other == null) {
SongImpl(it)
} else {
L.d("Song @ $uid already exists at ${other.path}, ignoring")
null
}
}
}
return LibraryImpl(
songs,
albums.onEach { it.finalize() },

View file

@ -18,9 +18,6 @@
package org.oxycblt.auxio.music.stack.interpret.model
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Library
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.Playlist
@ -47,53 +44,43 @@ class LibraryImpl(
) : MutableLibrary {
override val playlists = emptySet<Playlist>()
private val songUidMap = songs.associ { it.uid }
private val songUidMap = songs.associateBy { it.uid }
private val albumUidMap = albums.associateBy { it.uid }
private val artistUidMap = artists.associateBy { it.uid }
private val genreUidMap = genres.associateBy { it.uid }
private val playlistUidMap = playlists.associateBy { it.uid }
override fun findSong(uid: Music.UID): Song? {
TODO("Not yet implemented")
}
override fun findSong(uid: Music.UID) = songUidMap[uid]
override fun findSongByPath(path: Path): Song? {
TODO("Not yet implemented")
}
override fun findSongByPath(path: Path) = songs.find { it.path == path }
override fun findAlbum(uid: Music.UID): Album? {
TODO("Not yet implemented")
}
override fun findAlbum(uid: Music.UID) = albumUidMap[uid]
override fun findArtist(uid: Music.UID): Artist? {
TODO("Not yet implemented")
}
override fun findArtist(uid: Music.UID) = artistUidMap[uid]
override fun findGenre(uid: Music.UID): Genre? {
TODO("Not yet implemented")
}
override fun findGenre(uid: Music.UID) = genreUidMap[uid]
override fun findPlaylist(uid: Music.UID): Playlist? {
TODO("Not yet implemented")
}
override fun findPlaylist(uid: Music.UID) = playlistUidMap[uid]
override fun findPlaylistByName(name: String): Playlist? {
TODO("Not yet implemented")
}
override fun findPlaylistByName(name: String) = playlists.find { it.name.raw == name }
override suspend fun createPlaylist(name: String, songs: List<Song>): MutableLibrary {
TODO("Not yet implemented")
return this
}
override suspend fun renamePlaylist(playlist: Playlist, name: String): MutableLibrary {
TODO("Not yet implemented")
return this
}
override suspend fun addToPlaylist(playlist: Playlist, songs: List<Song>): MutableLibrary {
TODO("Not yet implemented")
return this
}
override suspend fun rewritePlaylist(playlist: Playlist, songs: List<Song>): MutableLibrary {
TODO("Not yet implemented")
return this
}
override suspend fun deletePlaylist(playlist: Playlist): MutableLibrary {
TODO("Not yet implemented")
return this
}
}

View file

@ -54,20 +54,20 @@ data class PreSong(
) {
fun computeUid() =
musicBrainzId?.let { Music.UID.musicBrainz(MusicType.SONGS, it) }
?: Music.UID.auxio(MusicType.SONGS) {
// Song UIDs are based on the raw data without parsing so that they remain
// consistent across music setting changes. Parents are not held up to the
// same standard since grouping is already inherently linked to settings.
update(rawName)
update(preAlbum.rawName)
update(date)
?: Music.UID.auxio(MusicType.SONGS) {
// Song UIDs are based on the raw data without parsing so that they remain
// consistent across music setting changes. Parents are not held up to the
// same standard since grouping is already inherently linked to settings.
update(rawName)
update(preAlbum.rawName)
update(date)
update(track)
update(disc?.number)
update(track)
update(disc?.number)
update(preArtists.map { artist -> artist.rawName })
update(preAlbum.preArtists.map { artist -> artist.rawName })
}
update(preArtists.map { artist -> artist.rawName })
update(preAlbum.preArtists.map { artist -> artist.rawName })
}
}
data class PreAlbum(