music: correctly bubble exceptions

Correctly bubble failures in the music loading process.

Do it the easy way and simply map to a result, then backl to an
exception. I need to actually just make it fully bubble event
This commit is contained in:
Alexander Capehart 2023-04-15 17:59:29 -06:00
parent 89d599ae4e
commit b031adabeb
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47

View file

@ -23,17 +23,10 @@ import android.content.Context
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Build import android.os.Build
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import kotlinx.coroutines.*
import java.util.LinkedList import java.util.LinkedList
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.yield
import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.music.* import org.oxycblt.auxio.music.*
import org.oxycblt.auxio.music.cache.CacheRepository import org.oxycblt.auxio.music.cache.CacheRepository
@ -44,6 +37,8 @@ import org.oxycblt.auxio.music.storage.MediaStoreExtractor
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logE import org.oxycblt.auxio.util.logE
import org.oxycblt.auxio.util.logW import org.oxycblt.auxio.util.logW
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
/** /**
* Core music loading state class. * Core music loading state class.
@ -351,14 +346,15 @@ constructor(
// Do the initial query of the cache and media databases in parallel. // Do the initial query of the cache and media databases in parallel.
logD("Starting queries") logD("Starting queries")
val mediaStoreQueryJob = scope.async { mediaStoreExtractor.query() } val mediaStoreQueryJob = scope.tryAsync { mediaStoreExtractor.query() }
val cache = val cache =
if (withCache) { if (withCache) {
cacheRepository.readCache() cacheRepository.readCache()
} else { } else {
null null
} }
val query = mediaStoreQueryJob.await() // TODO: Stupid, actually bubble results properly
val query = mediaStoreQueryJob.await().getOrThrow()
// Now start processing the queried song information in parallel. Songs that can't be // Now start processing the queried song information in parallel. Songs that can't be
// received from the cache are consisted incomplete and pushed to a separate channel // received from the cache are consisted incomplete and pushed to a separate channel
@ -367,10 +363,10 @@ constructor(
val completeSongs = Channel<RawSong>(Channel.UNLIMITED) val completeSongs = Channel<RawSong>(Channel.UNLIMITED)
val incompleteSongs = Channel<RawSong>(Channel.UNLIMITED) val incompleteSongs = Channel<RawSong>(Channel.UNLIMITED)
val mediaStoreJob = val mediaStoreJob =
scope.async { scope.tryAsync {
mediaStoreExtractor.consume(query, cache, incompleteSongs, completeSongs) mediaStoreExtractor.consume(query, cache, incompleteSongs, completeSongs)
} }
val metadataJob = scope.async { tagExtractor.consume(incompleteSongs, completeSongs) } val metadataJob = scope.tryAsync { tagExtractor.consume(incompleteSongs, completeSongs) }
// Await completed raw songs as they are processed. // Await completed raw songs as they are processed.
val rawSongs = LinkedList<RawSong>() val rawSongs = LinkedList<RawSong>()
@ -379,8 +375,8 @@ constructor(
emitIndexing(Indexer.Indexing.Songs(rawSongs.size, query.projectedTotal)) emitIndexing(Indexer.Indexing.Songs(rawSongs.size, query.projectedTotal))
} }
// These should be no-ops // These should be no-ops
mediaStoreJob.await() mediaStoreJob.await().getOrThrow()
metadataJob.await() metadataJob.await().getOrThrow()
if (rawSongs.isEmpty()) { if (rawSongs.isEmpty()) {
logE("Music library was empty") logE("Music library was empty")
@ -391,13 +387,21 @@ constructor(
// parallel. // parallel.
logD("Discovered ${rawSongs.size} songs, starting finalization") logD("Discovered ${rawSongs.size} songs, starting finalization")
emitIndexing(Indexer.Indexing.Indeterminate) emitIndexing(Indexer.Indexing.Indeterminate)
val libraryJob = scope.async(Dispatchers.Main) { Library.from(rawSongs, musicSettings) } val libraryJob = scope.tryAsync(Dispatchers.Main) { Library.from(rawSongs, musicSettings) }
if (cache == null || cache.invalidated) { if (cache == null || cache.invalidated) {
cacheRepository.writeCache(rawSongs) cacheRepository.writeCache(rawSongs)
} }
return libraryJob.await() return libraryJob.await().getOrThrow()
} }
private inline fun <R> CoroutineScope.tryAsync(context: CoroutineContext = EmptyCoroutineContext, crossinline block: suspend () -> R) = async(context) {
try {
Result.success(block())
} catch (e: Exception) {
Result.failure(e)
}
}
/** /**
* Emit a new [Indexer.State.Indexing] state. This can be used to signal the current state of * Emit a new [Indexer.State.Indexing] state. This can be used to signal the current state of
* the music loading process to external code. Assumes that the callee has already checked if * the music loading process to external code. Assumes that the callee has already checked if