music: avoid hanging on discovery errors
Do not hang when an error halts the discovery process. This was an oversight with the previous band-aid fix regarding handling errors in music loading. If something failed, the channels would not close, resulting in the main loop consuming the channel hanging. There's probably a deeper issue causing this in 3.1.2, but with this fix I can actually start digging for it.
This commit is contained in:
parent
8bcc86c972
commit
036d952085
1 changed files with 44 additions and 30 deletions
|
@ -372,7 +372,14 @@ 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 MediaStore query")
|
logD("Starting MediaStore query")
|
||||||
val mediaStoreQueryJob = worker.scope.tryAsync { mediaStoreExtractor.query() }
|
val mediaStoreQueryJob = worker.scope.async {
|
||||||
|
val query = try {
|
||||||
|
mediaStoreExtractor.query()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
return@async Result.failure(e)
|
||||||
|
}
|
||||||
|
Result.success(query)
|
||||||
|
}
|
||||||
val cache =
|
val cache =
|
||||||
if (withCache) {
|
if (withCache) {
|
||||||
logD("Reading cache")
|
logD("Reading cache")
|
||||||
|
@ -392,22 +399,39 @@ constructor(
|
||||||
val processedSongs = Channel<RawSong>(Channel.UNLIMITED)
|
val processedSongs = Channel<RawSong>(Channel.UNLIMITED)
|
||||||
logD("Started MediaStore discovery")
|
logD("Started MediaStore discovery")
|
||||||
val mediaStoreJob =
|
val mediaStoreJob =
|
||||||
worker.scope.tryAsync {
|
worker.scope.async {
|
||||||
mediaStoreExtractor.consume(query, cache, incompleteSongs, completeSongs)
|
try {
|
||||||
|
mediaStoreExtractor.consume(query, cache, incompleteSongs, completeSongs)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
incompleteSongs.close(e)
|
||||||
|
return@async
|
||||||
|
}
|
||||||
incompleteSongs.close()
|
incompleteSongs.close()
|
||||||
}
|
}
|
||||||
logD("Started ExoPlayer discovery")
|
|
||||||
|
logD("Started ExoPlayer tag extraction")
|
||||||
val metadataJob =
|
val metadataJob =
|
||||||
worker.scope.tryAsync {
|
worker.scope.async {
|
||||||
tagExtractor.consume(incompleteSongs, completeSongs)
|
try {
|
||||||
|
tagExtractor.consume(incompleteSongs, completeSongs)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
completeSongs.close(e)
|
||||||
|
return@async
|
||||||
|
}
|
||||||
completeSongs.close()
|
completeSongs.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
logD("Starting DeviceLibrary creation")
|
logD("Starting DeviceLibrary creation")
|
||||||
val deviceLibraryJob =
|
val deviceLibraryJob =
|
||||||
worker.scope.tryAsync(Dispatchers.Default) {
|
worker.scope.async(Dispatchers.Default) {
|
||||||
deviceLibraryFactory.create(completeSongs, processedSongs).also {
|
val deviceLibrary = try {
|
||||||
processedSongs.close()
|
deviceLibraryFactory.create(completeSongs, processedSongs)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
processedSongs.close(e)
|
||||||
|
return@async Result.failure(e)
|
||||||
}
|
}
|
||||||
|
processedSongs.close()
|
||||||
|
Result.success(deviceLibrary)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Await completed raw songs as they are processed.
|
// Await completed raw songs as they are processed.
|
||||||
|
@ -418,8 +442,8 @@ constructor(
|
||||||
}
|
}
|
||||||
logD("Awaiting discovery completion")
|
logD("Awaiting discovery completion")
|
||||||
// These should be no-ops, but we need the error state to see if we should keep going.
|
// These should be no-ops, but we need the error state to see if we should keep going.
|
||||||
mediaStoreJob.await().getOrThrow()
|
mediaStoreJob.await()
|
||||||
metadataJob.await().getOrThrow()
|
metadataJob.await()
|
||||||
|
|
||||||
if (rawSongs.isEmpty()) {
|
if (rawSongs.isEmpty()) {
|
||||||
logE("Music library was empty")
|
logE("Music library was empty")
|
||||||
|
@ -431,7 +455,14 @@ constructor(
|
||||||
logD("Discovered ${rawSongs.size} songs, starting finalization")
|
logD("Discovered ${rawSongs.size} songs, starting finalization")
|
||||||
emitIndexingProgress(IndexingProgress.Indeterminate)
|
emitIndexingProgress(IndexingProgress.Indeterminate)
|
||||||
logD("Starting UserLibrary query")
|
logD("Starting UserLibrary query")
|
||||||
val userLibraryQueryJob = worker.scope.tryAsync { userLibraryFactory.query() }
|
val userLibraryQueryJob = worker.scope.async {
|
||||||
|
val rawPlaylists = try {
|
||||||
|
userLibraryFactory.query()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
return@async Result.failure(e)
|
||||||
|
}
|
||||||
|
Result.success(rawPlaylists)
|
||||||
|
}
|
||||||
if (cache == null || cache.invalidated) {
|
if (cache == null || cache.invalidated) {
|
||||||
logD("Writing cache [why=${cache?.invalidated}]")
|
logD("Writing cache [why=${cache?.invalidated}]")
|
||||||
cacheRepository.writeCache(rawSongs)
|
cacheRepository.writeCache(rawSongs)
|
||||||
|
@ -446,8 +477,6 @@ constructor(
|
||||||
logD("Successfully indexed music library [device=$deviceLibrary user=$userLibrary]")
|
logD("Successfully indexed music library [device=$deviceLibrary user=$userLibrary]")
|
||||||
emitIndexingCompletion(null)
|
emitIndexingCompletion(null)
|
||||||
|
|
||||||
// Comparing the library instances is obscenely expensive, do it within the library
|
|
||||||
|
|
||||||
val deviceLibraryChanged: Boolean
|
val deviceLibraryChanged: Boolean
|
||||||
val userLibraryChanged: Boolean
|
val userLibraryChanged: Boolean
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
|
@ -462,27 +491,12 @@ constructor(
|
||||||
this.userLibrary = userLibrary
|
this.userLibrary = userLibrary
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Listeners are expecting a callback in the main thread, switch
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
dispatchLibraryChange(deviceLibraryChanged, userLibraryChanged)
|
dispatchLibraryChange(deviceLibraryChanged, userLibraryChanged)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* An extension of [async] that forces the outcome to a [Result] to allow exceptions to bubble
|
|
||||||
* upwards instead of crashing the entire app.
|
|
||||||
*/
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun emitIndexingProgress(progress: IndexingProgress) {
|
private suspend fun emitIndexingProgress(progress: IndexingProgress) {
|
||||||
yield()
|
yield()
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
|
|
Loading…
Reference in a new issue