diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt index b41dd2a94..2f4fe0bee 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt @@ -360,13 +360,22 @@ class HomeFragment : binding.homeIndexingProgress.visibility = View.VISIBLE binding.homeIndexingActions.visibility = View.INVISIBLE - binding.homeIndexingStatus.setText(R.string.lng_indexing) - - // Actively loading songs, show the current progress. - binding.homeIndexingProgress.apply { - isIndeterminate = false - max = progress.explored - this.progress = progress.interpreted + when (progress) { + is IndexingProgress.Indeterminate -> { + // In a query/initialization state, show a generic loading status. + binding.homeIndexingStatus.text = getString(R.string.lng_indexing) + binding.homeIndexingProgress.isIndeterminate = true + } + is IndexingProgress.Songs -> { + // Actively loading songs, show the current progress. + binding.homeIndexingStatus.text = + getString(R.string.fmt_indexing, progress.loaded, progress.explored) + binding.homeIndexingProgress.apply { + isIndeterminate = false + max = progress.explored + this.progress = progress.loaded + } + } } } diff --git a/app/src/main/java/org/oxycblt/auxio/music/service/IndexerNotifications.kt b/app/src/main/java/org/oxycblt/auxio/music/service/IndexerNotifications.kt index 68fddfc14..cf1905119 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/service/IndexerNotifications.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/service/IndexerNotifications.kt @@ -61,19 +61,33 @@ class IndexingNotification(private val context: Context) : * @return true if the notification updated, false otherwise */ fun updateIndexingState(progress: IndexingProgress): Boolean { - // Determinate state, show an active progress meter. Since these updates arrive - // highly rapidly, only update every 1.5 seconds to prevent notification rate - // limiting. - val now = SystemClock.elapsedRealtime() - if (lastUpdateTime > -1 && (now - lastUpdateTime) < 1500) { - return false + when (progress) { + is IndexingProgress.Indeterminate -> { + // Indeterminate state, use a vaguer description and in-determinate progress. + // These events are not very frequent, and thus we don't need to safeguard + // against rate limiting. + L.d("Updating state to $progress") + lastUpdateTime = -1 + setContentText(context.getString(R.string.lng_indexing)) + setProgress(0, 0, true) + return true + } + is IndexingProgress.Songs -> { + // Determinate state, show an active progress meter. Since these updates arrive + // highly rapidly, only update every 1.5 seconds to prevent notification rate + // limiting. + val now = SystemClock.elapsedRealtime() + if (lastUpdateTime > -1 && (now - lastUpdateTime) < 1500) { + return false + } + lastUpdateTime = SystemClock.elapsedRealtime() + L.d("Updating state to $progress") + setContentText( + context.getString(R.string.fmt_indexing, progress.loaded, progress.explored)) + setProgress(progress.loaded, progress.explored, false) + return true + } } - lastUpdateTime = SystemClock.elapsedRealtime() - L.d("Updating state to $progress") - setContentText( - context.getString(R.string.fmt_indexing, progress.interpreted, progress.explored)) - setProgress(progress.explored, progress.interpreted, false) - return true } } 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 b06da8692..a4b043bd7 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 @@ -22,7 +22,9 @@ import android.net.Uri import javax.inject.Inject import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.buffer +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOn import org.oxycblt.auxio.music.stack.explore.Explorer import org.oxycblt.auxio.music.stack.interpret.Interpretation @@ -42,7 +44,11 @@ interface Indexer { * * @author Alexander Capehart (OxygenCobalt) */ -data class IndexingProgress(val interpreted: Int, val explored: Int) +sealed interface IndexingProgress { + data class Songs(val loaded: Int, val explored: Int) : IndexingProgress + + data object Indeterminate : IndexingProgress +} class IndexerImpl @Inject @@ -52,20 +58,25 @@ constructor(private val explorer: Explorer, private val interpreter: Interpreter interpretation: Interpretation, onProgress: suspend (IndexingProgress) -> Unit ) = coroutineScope { - var interpreted = 0 - var explored = 0 - onProgress(IndexingProgress(interpreted, explored)) - val files = - explorer.explore(uris) { - explored++ - onProgress(IndexingProgress(interpreted, explored)) - } - val audioFiles = files.audios.flowOn(Dispatchers.IO).buffer() + val files = explorer.explore(uris, onProgress) + val audioFiles = + files.audios + .cap( + start = { onProgress(IndexingProgress.Songs(0, 0)) }, + end = { onProgress(IndexingProgress.Indeterminate) }) + .flowOn(Dispatchers.IO) + .buffer() val playlistFiles = files.playlists.flowOn(Dispatchers.IO).buffer() - - interpreter.interpret(audioFiles, playlistFiles, interpretation) { - interpreted++ - onProgress(IndexingProgress(interpreted, explored)) - } + interpreter.interpret(audioFiles, playlistFiles, interpretation) } + + private fun Flow.cap(start: suspend () -> Unit, end: suspend () -> Unit): Flow = + flow { + start() + try { + collect { emit(it) } + } finally { + end() + } + } } 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 1726994aa..3a6d805df 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 @@ -34,13 +34,14 @@ import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.withIndex +import org.oxycblt.auxio.music.stack.IndexingProgress import org.oxycblt.auxio.music.stack.explore.cache.TagCache import org.oxycblt.auxio.music.stack.explore.extractor.TagExtractor import org.oxycblt.auxio.music.stack.explore.fs.DeviceFiles import org.oxycblt.auxio.music.stack.explore.playlists.StoredPlaylists interface Explorer { - fun explore(uris: List, onExplored: suspend () -> Unit): Files + fun explore(uris: List, onProgress: suspend (IndexingProgress.Songs) -> Unit): Files } data class Files(val audios: Flow, val playlists: Flow) @@ -54,14 +55,18 @@ constructor( private val storedPlaylists: StoredPlaylists ) : Explorer { @OptIn(ExperimentalCoroutinesApi::class) - override fun explore(uris: List, onExplored: suspend () -> Unit): Files { - var discovered = 0 + override fun explore( + uris: List, + onProgress: suspend (IndexingProgress.Songs) -> Unit + ): Files { + var loaded = 0 + var explored = 0 val deviceFiles = deviceFiles .explore(uris.asFlow()) .onEach { - discovered++ - onExplored() + explored++ + onProgress(IndexingProgress.Songs(loaded, explored)) } .flowOn(Dispatchers.IO) .buffer() @@ -73,8 +78,13 @@ constructor( // val writtenAudioFiles = // tagCache.write(extractedAudioFiles).flowOn(Dispatchers.IO).buffer() // val audioFiles = merge(cachedAudioFiles, writtenAudioFiles) + val audioFiles = + extractedAudioFiles.onEach { + loaded++ + onProgress(IndexingProgress.Songs(loaded, explored)) + } val playlistFiles = storedPlaylists.read() - return Files(extractedAudioFiles, playlistFiles) + return Files(audioFiles, playlistFiles) } /** Temporarily split a flow into 8 parallel threads and then */ diff --git a/app/src/main/java/org/oxycblt/auxio/music/stack/explore/fs/DeviceFiles.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/fs/DeviceFiles.kt index 0103aacc5..3b9f3307d 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/stack/explore/fs/DeviceFiles.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/fs/DeviceFiles.kt @@ -32,6 +32,7 @@ import kotlinx.coroutines.flow.flatMapMerge import kotlinx.coroutines.flow.flattenMerge import kotlinx.coroutines.flow.flow import org.oxycblt.auxio.music.stack.explore.DeviceFile +import timber.log.Timber interface DeviceFiles { fun explore(uris: Flow): Flow 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 c3b92c77b..45f00b0b9 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 @@ -25,7 +25,6 @@ import kotlinx.coroutines.flow.asFlow 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.Music import org.oxycblt.auxio.music.stack.explore.AudioFile @@ -49,8 +48,7 @@ interface Interpreter { suspend fun interpret( audioFiles: Flow, playlistFiles: Flow, - interpretation: Interpretation, - onInterpret: suspend () -> Unit + interpretation: Interpretation ): MutableLibrary } @@ -58,8 +56,7 @@ class InterpreterImpl @Inject constructor(private val preparer: Preparer) : Inte override suspend fun interpret( audioFiles: Flow, playlistFiles: Flow, - interpretation: Interpretation, - onInterpret: suspend () -> Unit + interpretation: Interpretation ): MutableLibrary { val preSongs = preparer.prepare(audioFiles, interpretation).flowOn(Dispatchers.Main).buffer() @@ -69,11 +66,7 @@ class InterpreterImpl @Inject constructor(private val preparer: Preparer) : Inte val artistLinker = ArtistLinker() val artistLinkedSongs = - artistLinker - .register(genreLinkedSongs) - .onEach { onInterpret() } - .flowOn(Dispatchers.Main) - .toList() + artistLinker.register(genreLinkedSongs).flowOn(Dispatchers.Main).toList() // This is intentional. Song and album instances are dependent on artist // data, so we need to ensure that all of the linked artist data is resolved // before we go any further.