music: move automatic reloading to musikr
This commit is contained in:
parent
ef751f1a11
commit
68098b97ed
5 changed files with 95 additions and 84 deletions
|
@ -48,6 +48,8 @@ interface MusicSettings : Settings<MusicSettings.Listener> {
|
||||||
val intelligentSorting: Boolean
|
val intelligentSorting: Boolean
|
||||||
|
|
||||||
interface Listener {
|
interface Listener {
|
||||||
|
/** Called when the current music locations changed. */
|
||||||
|
fun onMusicLocationsChanged() {}
|
||||||
/** Called when a setting controlling how music is loaded has changed. */
|
/** Called when a setting controlling how music is loaded has changed. */
|
||||||
fun onIndexingSettingChanged() {}
|
fun onIndexingSettingChanged() {}
|
||||||
/** Called when the [shouldBeObserving] configuration has changed. */
|
/** 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"
|
// TODO: Differentiate "hard reloads" (Need the cache) and "Soft reloads"
|
||||||
// (just need to manipulate data)
|
// (just need to manipulate data)
|
||||||
when (key) {
|
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_separators),
|
||||||
getString(R.string.set_key_auto_sort_names) -> {
|
getString(R.string.set_key_auto_sort_names) -> {
|
||||||
L.d("Dispatching indexing setting change for $key")
|
L.d("Dispatching indexing setting change for $key")
|
||||||
|
|
|
@ -35,6 +35,7 @@ import org.oxycblt.auxio.music.MusicSettings
|
||||||
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
||||||
import org.oxycblt.auxio.util.getSystemServiceCompat
|
import org.oxycblt.auxio.util.getSystemServiceCompat
|
||||||
import org.oxycblt.musikr.MusicParent
|
import org.oxycblt.musikr.MusicParent
|
||||||
|
import org.oxycblt.musikr.track.UpdateTracker
|
||||||
import timber.log.Timber as L
|
import timber.log.Timber as L
|
||||||
|
|
||||||
class IndexingHolder
|
class IndexingHolder
|
||||||
|
@ -45,7 +46,7 @@ private constructor(
|
||||||
private val musicRepository: MusicRepository,
|
private val musicRepository: MusicRepository,
|
||||||
private val musicSettings: MusicSettings,
|
private val musicSettings: MusicSettings,
|
||||||
private val imageLoader: ImageLoader,
|
private val imageLoader: ImageLoader,
|
||||||
private val contentObserver: SystemContentObserver
|
private val updateTracker: UpdateTracker
|
||||||
) :
|
) :
|
||||||
MusicRepository.IndexingWorker,
|
MusicRepository.IndexingWorker,
|
||||||
MusicRepository.IndexingListener,
|
MusicRepository.IndexingListener,
|
||||||
|
@ -58,7 +59,7 @@ private constructor(
|
||||||
private val musicRepository: MusicRepository,
|
private val musicRepository: MusicRepository,
|
||||||
private val musicSettings: MusicSettings,
|
private val musicSettings: MusicSettings,
|
||||||
private val imageLoader: ImageLoader,
|
private val imageLoader: ImageLoader,
|
||||||
private val contentObserver: SystemContentObserver
|
private val updateTracker: UpdateTracker
|
||||||
) {
|
) {
|
||||||
fun create(context: Context, listener: ForegroundListener) =
|
fun create(context: Context, listener: ForegroundListener) =
|
||||||
IndexingHolder(
|
IndexingHolder(
|
||||||
|
@ -68,7 +69,7 @@ private constructor(
|
||||||
musicRepository,
|
musicRepository,
|
||||||
musicSettings,
|
musicSettings,
|
||||||
imageLoader,
|
imageLoader,
|
||||||
contentObserver)
|
updateTracker)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val indexJob = Job()
|
private val indexJob = Job()
|
||||||
|
@ -87,11 +88,11 @@ private constructor(
|
||||||
musicRepository.addUpdateListener(this)
|
musicRepository.addUpdateListener(this)
|
||||||
musicRepository.addIndexingListener(this)
|
musicRepository.addIndexingListener(this)
|
||||||
musicRepository.registerWorker(this)
|
musicRepository.registerWorker(this)
|
||||||
contentObserver.attach()
|
updateTracker.track(musicSettings.musicLocations)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun release() {
|
fun release() {
|
||||||
contentObserver.release()
|
updateTracker.release()
|
||||||
musicRepository.unregisterWorker(this)
|
musicRepository.unregisterWorker(this)
|
||||||
musicRepository.removeIndexingListener(this)
|
musicRepository.removeIndexingListener(this)
|
||||||
musicRepository.removeUpdateListener(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() {
|
override fun onIndexingSettingChanged() {
|
||||||
super.onIndexingSettingChanged()
|
super.onIndexingSettingChanged()
|
||||||
musicRepository.requestIndex(true)
|
musicRepository.requestIndex(true)
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue