diff --git a/CHANGELOG.md b/CHANGELOG.md index f27c5b614..f484b7284 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,11 @@ ## dev [v2.3.2, v2.4.0, or v3.0.0] +#### What's Improved +- Genre parsing now handles multiple integer values and cover/remix indicators + #### Dev/Meta +- New translations [Fjuro -> Czech] - Moved music loading to a foreground service ## v2.3.1 diff --git a/app/src/main/java/org/oxycblt/auxio/image/BitmapProvider.kt b/app/src/main/java/org/oxycblt/auxio/image/BitmapProvider.kt index ad8be8224..66ad95c5b 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/BitmapProvider.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/BitmapProvider.kt @@ -25,6 +25,7 @@ import coil.request.Disposable import coil.request.ImageRequest import coil.size.Size import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.util.logD /** * A utility to provide bitmaps in a manner less prone to race conditions. @@ -33,15 +34,15 @@ import org.oxycblt.auxio.music.Song * request with some target callbacks could result in overlapping requests causing unrelated * updates. This class (to an extent) resolves this by keeping track of the current request and * disposing of it every time a new request is created. This greatly reduces the surface for race - * conditions save the case of instruction-by-instruction data races, which are effectively - * impossible to solve. + * conditions. * * @author OxygenCobalt */ class BitmapProvider(private val context: Context) { private var currentRequest: Request? = null + private var currentGeneration = 0L - /* If this provider is currently attempting to load something. */ + /** If this provider is currently attempting to load something. */ val isBusy: Boolean get() = currentRequest?.run { !disposable.isDisposed } ?: false @@ -50,6 +51,10 @@ class BitmapProvider(private val context: Context) { * callback. */ fun load(song: Song, target: Target) { + // Increment the generation value so that previous requests are invalidated. + // This is a second safeguard to mitigate instruction-by-instruction race conditions. + val generation = synchronized(this) { ++currentGeneration } + currentRequest?.run { disposable.dispose() } currentRequest = null @@ -59,8 +64,16 @@ class BitmapProvider(private val context: Context) { .data(song) .size(Size.ORIGINAL) .target( - onSuccess = { target.onCompleted(it.toBitmap()) }, - onError = { target.onCompleted(null) }) + onSuccess = { + if (generation == currentGeneration) { + target.onCompleted(it.toBitmap()) + } + }, + onError = { + if (generation == currentGeneration) { + target.onCompleted(null) + } + }) .transformations(SquareFrameTransform.INSTANCE)) currentRequest = Request(context.imageLoader.enqueue(request.build()), target) diff --git a/app/src/main/java/org/oxycblt/auxio/music/Indexer.kt b/app/src/main/java/org/oxycblt/auxio/music/Indexer.kt index 4cf8d6119..7ead34966 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/Indexer.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/Indexer.kt @@ -57,7 +57,7 @@ class Indexer { private var lastResponse: Response? = null private var loadingState: Loading? = null - private var currentGeneration: Long = 0 + private var currentGeneration = 0L private val callbacks = mutableListOf() fun addCallback(callback: Callback) { diff --git a/app/src/main/java/org/oxycblt/auxio/music/IndexerService.kt b/app/src/main/java/org/oxycblt/auxio/music/IndexerService.kt index b4545bd7e..e83b0ef06 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/IndexerService.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/IndexerService.kt @@ -88,10 +88,10 @@ class IndexerService : Service(), Indexer.Callback { override fun onIndexerStateChanged(state: Indexer.State?) { when (state) { is Indexer.State.Complete -> { - if (state.response is Indexer.Response.Ok && musicStore.library == null) { + if (state.response is Indexer.Response.Ok && + state.response.library != musicStore.library) { // Load was completed successfully, so apply the new library if we // have not already. - // TODO: Change null check for equality check [automatic rescanning] musicStore.library = state.response.library } diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicUtil.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicUtil.kt index 2c9083929..03430b34a 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicUtil.kt @@ -108,6 +108,8 @@ private fun String.parseId3v1Genre(): String? = // CR and RX are not technically ID3v1, but are formatted similarly to a plain number. this == "CR" -> "Cover" this == "RX" -> "Remix" + + // Current name is fine. else -> null } @@ -119,7 +121,7 @@ private fun String.parseId3v2Genre(): String? { // You can read the spec for it here: https://id3.org/id3v2.3.0#TCON // This implementation in particular is based off Mutagen's genre parser. - // Case 1: Genre IDs in the format (DIGITS|RX|CR). If these exist, parse them as + // Case 1: Genre IDs in the format (INT|RX|CR). If these exist, parse them as // ID3v1 tags. val genreIds = groups[1] if (genreIds != null && genreIds.value.isNotEmpty()) { @@ -143,6 +145,7 @@ private fun String.parseId3v2Genre(): String? { return genres.distinctBy { it }.joinToString(separator = ", ").ifEmpty { null } } +/** Regex that implements matching for ID3v2's genre format. */ private val GENRE_RE = Regex("((?:\\(([0-9]+|RX|CR)\\))*)(.+)?") /**