music: move automatic reloading to musikr

This commit is contained in:
Alexander Capehart 2025-01-01 13:02:03 -07:00
parent ef751f1a11
commit 68098b97ed
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
5 changed files with 95 additions and 84 deletions

View file

@ -48,6 +48,8 @@ interface MusicSettings : Settings<MusicSettings.Listener> {
val intelligentSorting: Boolean
interface Listener {
/** Called when the current music locations changed. */
fun onMusicLocationsChanged() {}
/** Called when a setting controlling how music is loaded has changed. */
fun onIndexingSettingChanged() {}
/** Called when the [shouldBeObserving] configuration has changed. */
@ -109,7 +111,10 @@ class MusicSettingsImpl @Inject constructor(@ApplicationContext private val cont
// TODO: Differentiate "hard reloads" (Need the cache) and "Soft reloads"
// (just need to manipulate data)
when (key) {
getString(R.string.set_key_music_locations),
getString(R.string.set_key_music_locations) -> {
L.d("Dispatching music locations change")
listener.onMusicLocationsChanged()
}
getString(R.string.set_key_separators),
getString(R.string.set_key_auto_sort_names) -> {
L.d("Dispatching indexing setting change for $key")

View file

@ -35,6 +35,7 @@ import org.oxycblt.auxio.music.MusicSettings
import org.oxycblt.auxio.playback.state.PlaybackStateManager
import org.oxycblt.auxio.util.getSystemServiceCompat
import org.oxycblt.musikr.MusicParent
import org.oxycblt.musikr.track.UpdateTracker
import timber.log.Timber as L
class IndexingHolder
@ -45,7 +46,7 @@ private constructor(
private val musicRepository: MusicRepository,
private val musicSettings: MusicSettings,
private val imageLoader: ImageLoader,
private val contentObserver: SystemContentObserver
private val updateTracker: UpdateTracker
) :
MusicRepository.IndexingWorker,
MusicRepository.IndexingListener,
@ -58,7 +59,7 @@ private constructor(
private val musicRepository: MusicRepository,
private val musicSettings: MusicSettings,
private val imageLoader: ImageLoader,
private val contentObserver: SystemContentObserver
private val updateTracker: UpdateTracker
) {
fun create(context: Context, listener: ForegroundListener) =
IndexingHolder(
@ -68,7 +69,7 @@ private constructor(
musicRepository,
musicSettings,
imageLoader,
contentObserver)
updateTracker)
}
private val indexJob = Job()
@ -87,11 +88,11 @@ private constructor(
musicRepository.addUpdateListener(this)
musicRepository.addIndexingListener(this)
musicRepository.registerWorker(this)
contentObserver.attach()
updateTracker.track(musicSettings.musicLocations)
}
fun release() {
contentObserver.release()
updateTracker.release()
musicRepository.unregisterWorker(this)
musicRepository.removeIndexingListener(this)
musicRepository.removeUpdateListener(this)
@ -163,6 +164,12 @@ private constructor(
}
}
override fun onMusicLocationsChanged() {
super.onMusicLocationsChanged()
updateTracker.track(musicSettings.musicLocations)
musicRepository.requestIndex(true)
}
override fun onIndexingSettingChanged() {
super.onIndexingSettingChanged()
musicRepository.requestIndex(true)

View file

@ -1,78 +0,0 @@
/*
* Copyright (c) 2024 Auxio Project
* SystemContentObserver.kt is part of Auxio.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.music.service
import android.content.Context
import android.database.ContentObserver
import android.os.Handler
import android.os.Looper
import android.provider.MediaStore
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import org.oxycblt.auxio.music.MusicRepository
import org.oxycblt.auxio.music.MusicSettings
import timber.log.Timber as L
/**
* A [ContentObserver] that observes the [MediaStore] music database for changes, a behavior known
* to the user as automatic rescanning. The active (and not passive) nature of observing the
* database is what requires [MusicServiceFragment] to stay foreground when this is enabled.
*/
class SystemContentObserver
@Inject
constructor(
@ApplicationContext private val context: Context,
private val musicRepository: MusicRepository,
private val musicSettings: MusicSettings
) : ContentObserver(Handler(Looper.getMainLooper())), Runnable {
private val handler = Handler(Looper.getMainLooper())
fun attach() {
context.applicationContext.contentResolver.registerContentObserver(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, true, this)
}
/**
* Release this instance, preventing it from further observing the database and cancelling any
* pending update events.
*/
fun release() {
handler.removeCallbacks(this)
context.applicationContext.contentResolver.unregisterContentObserver(this)
}
override fun onChange(selfChange: Boolean) {
// Batch rapid-fire updates to the library into a single call to run after 500ms
handler.removeCallbacks(this)
handler.postDelayed(this, REINDEX_DELAY_MS)
}
override fun run() {
// Check here if we should even start a reindex. This is much less bug-prone than
// registering and de-registering this component as this setting changes.
if (musicSettings.shouldBeObserving) {
L.d("MediaStore changed, starting re-index")
musicRepository.requestIndex(true)
}
}
private companion object {
const val REINDEX_DELAY_MS = 500L
}
}

View file

@ -0,0 +1,42 @@
package org.oxycblt.musikr.track
import android.content.Context
import android.database.ContentObserver
import android.os.Handler
import android.os.Looper
import org.oxycblt.musikr.fs.MusicLocation
internal class LocationObserver(
private val context: Context,
private val location: MusicLocation,
private val listener: UpdateTracker.Callback
) : ContentObserver(Handler(Looper.getMainLooper())), Runnable {
private val handler = Handler(Looper.getMainLooper())
init {
context.applicationContext.contentResolver.registerContentObserver(
location.uri,
true,
this
)
}
fun release() {
handler.removeCallbacks(this)
context.applicationContext.contentResolver.unregisterContentObserver(this)
}
override fun onChange(selfChange: Boolean) {
// Batch rapid-fire updates into a single callback after delay
handler.removeCallbacks(this)
handler.postDelayed(this, REINDEX_DELAY_MS)
}
override fun run() {
listener.onUpdate(location)
}
private companion object {
const val REINDEX_DELAY_MS = 500L
}
}

View file

@ -0,0 +1,35 @@
package org.oxycblt.musikr.track
import android.content.Context
import org.oxycblt.musikr.fs.MusicLocation
interface UpdateTracker {
fun track(locations: List<MusicLocation>)
fun release()
interface Callback {
fun onUpdate(location: MusicLocation)
}
companion object {
fun from(context: Context, callback: Callback): UpdateTracker = UpdateTrackerImpl(context, callback)
}
}
private class UpdateTrackerImpl(
private val context: Context,
private val callback: UpdateTracker.Callback
) : UpdateTracker {
private val observers = mutableListOf<LocationObserver>()
override fun track(locations: List<MusicLocation>) {
release()
observers.addAll(locations.map { LocationObserver(context, it, callback) })
}
override fun release() {
observers.forEach { it.release() }
observers.clear()
}
}