music: re-add event handling

Kinda scuffed, will probably split into low-level events
and do the MusicRepository interpret step in Indexer.
This commit is contained in:
Alexander Capehart 2024-11-26 09:54:19 -07:00
parent ba29905aa6
commit 2f9ced2ac3
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
4 changed files with 77 additions and 13 deletions

View file

@ -34,6 +34,7 @@ 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
/**
@ -363,7 +364,27 @@ constructor(private val indexer: Indexer, private val musicSettings: MusicSettin
Name.Known.SimpleFactory
}
val newLibrary = indexer.run(listOf(), Interpretation(nameFactory, separators))
var explored = 0
var loaded = 0
var interpreted = 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 -> {
interpreted = it.amount
if (explored == loaded) {
emitIndexingProgress(IndexingProgress.Songs(loaded, explored))
}
}
}
}
// We want to make sure that all reads and writes are synchronized due to the sheer
// amount of consumers of MusicRepository.

View file

@ -24,22 +24,37 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.flowOn
import org.oxycblt.auxio.music.stack.Indexer.Event
import org.oxycblt.auxio.music.stack.explore.Explorer
import org.oxycblt.auxio.music.stack.interpret.Interpretation
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): 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 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) = coroutineScope {
val files = explorer.explore(uris)
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()
interpreter.interpret(audioFiles, playlistFiles, interpretation)
interpreter.interpret(audioFiles, playlistFiles, interpretation, eventHandler)
}
}

View file

@ -33,8 +33,10 @@ import kotlinx.coroutines.flow.flattenMerge
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.withIndex
import org.oxycblt.auxio.music.stack.Indexer
import org.oxycblt.auxio.music.stack.explore.cache.CacheResult
import org.oxycblt.auxio.music.stack.explore.cache.TagCache
import org.oxycblt.auxio.music.stack.explore.extractor.TagExtractor
@ -42,7 +44,7 @@ import org.oxycblt.auxio.music.stack.explore.fs.DeviceFiles
import org.oxycblt.auxio.music.stack.explore.playlists.StoredPlaylists
interface Explorer {
fun explore(uris: List<Uri>): Files
fun explore(uris: List<Uri>, eventHandler: suspend (Indexer.Event) -> Unit): Files
}
data class Files(val audios: Flow<AudioFile>, val playlists: Flow<PlaylistFile>)
@ -56,8 +58,15 @@ constructor(
private val storedPlaylists: StoredPlaylists
) : Explorer {
@OptIn(ExperimentalCoroutinesApi::class)
override fun explore(uris: List<Uri>): Files {
val deviceFiles = deviceFiles.explore(uris.asFlow()).flowOn(Dispatchers.IO).buffer()
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 tagRead = tagCache.read(deviceFiles).flowOn(Dispatchers.IO).buffer()
val (uncachedDeviceFiles, cachedAudioFiles) = tagRead.results()
val extractedAudioFiles =
@ -67,8 +76,13 @@ constructor(
.asFlow()
.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 playlistFiles = storedPlaylists.read()
return Files(merge(cachedAudioFiles, writtenAudioFiles), playlistFiles)
return Files(audioFiles, playlistFiles)
}
private fun Flow<CacheResult>.results(): Pair<Flow<DeviceFile>, Flow<AudioFile>> {
@ -83,7 +97,8 @@ 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

@ -24,7 +24,9 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.toList
import org.oxycblt.auxio.music.stack.Indexer
import org.oxycblt.auxio.music.stack.explore.AudioFile
import org.oxycblt.auxio.music.stack.explore.PlaylistFile
import org.oxycblt.auxio.music.stack.interpret.linker.AlbumLinker
@ -45,7 +47,8 @@ interface Interpreter {
suspend fun interpret(
audioFiles: Flow<AudioFile>,
playlistFiles: Flow<PlaylistFile>,
interpretation: Interpretation
interpretation: Interpretation,
eventHandler: suspend (Indexer.Event) -> Unit
): MutableLibrary
}
@ -53,12 +56,15 @@ class InterpreterImpl @Inject constructor(private val preparer: Preparer) : Inte
override suspend fun interpret(
audioFiles: Flow<AudioFile>,
playlistFiles: Flow<PlaylistFile>,
interpretation: Interpretation
interpretation: Interpretation,
eventHandler: suspend (Indexer.Event) -> Unit
): MutableLibrary {
val preSongs =
preparer.prepare(audioFiles, interpretation).flowOn(Dispatchers.Main).buffer()
val genreLinker = GenreLinker()
val genreLinkedSongs = genreLinker.register(preSongs).flowOn(Dispatchers.Main).buffer()
val artistLinker = ArtistLinker()
val artistLinkedSongs =
artistLinker.register(genreLinkedSongs).flowOn(Dispatchers.Main).buffer()
@ -67,14 +73,21 @@ class InterpreterImpl @Inject constructor(private val preparer: Preparer) : Inte
// before we go any further.
val genres = genreLinker.resolve()
val artists = artistLinker.resolve()
var interpreted = 0
val albumLinker = AlbumLinker()
val albumLinkedSongs =
albumLinker
.register(artistLinkedSongs)
.flowOn(Dispatchers.Main)
.onEach {
interpreted++;
eventHandler(Indexer.Event.Interpret(interpreted))
}
.map { LinkedSongImpl(it) }
.toList()
val albums = albumLinker.resolve()
val songs = albumLinkedSongs.map { SongImpl(it) }
return LibraryImpl(songs, albums, artists, genres)
}