diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt index c03cc883d..96523ee59 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt @@ -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. diff --git a/app/src/main/java/org/oxycblt/auxio/music/stack/Indexer.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/Indexer.kt index c7d55e721..6e58afa8a 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/stack/Indexer.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/Indexer.kt @@ -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, interpretation: Interpretation): MutableLibrary + suspend fun run(uris: List, 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, interpretation: Interpretation) = coroutineScope { - val files = explorer.explore(uris) + override suspend fun run(uris: List, 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) } } diff --git a/app/src/main/java/org/oxycblt/auxio/music/stack/explore/Explorer.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/Explorer.kt index 22e7d37e6..305f9e74d 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/stack/explore/Explorer.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/Explorer.kt @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - + package org.oxycblt.auxio.music.stack.explore import android.net.Uri @@ -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): Files + fun explore(uris: List, eventHandler: suspend (Indexer.Event) -> Unit): Files } data class Files(val audios: Flow, val playlists: Flow) @@ -56,8 +58,15 @@ constructor( private val storedPlaylists: StoredPlaylists ) : Explorer { @OptIn(ExperimentalCoroutinesApi::class) - override fun explore(uris: List): Files { - val deviceFiles = deviceFiles.explore(uris.asFlow()).flowOn(Dispatchers.IO).buffer() + override fun explore(uris: List, 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.results(): Pair, Flow> { @@ -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 } } } } diff --git a/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/Interpreter.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/Interpreter.kt index dd953fcc8..f89eb9d2f 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/Interpreter.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/Interpreter.kt @@ -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, playlistFiles: Flow, - 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, playlistFiles: Flow, - 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) }