music: integrate new loader into services
This commit is contained in:
parent
b0c6dd2b74
commit
4618996fc5
7 changed files with 43 additions and 95 deletions
|
@ -62,9 +62,6 @@ import org.oxycblt.auxio.music.IndexingState
|
|||
import org.oxycblt.auxio.music.Music
|
||||
import org.oxycblt.auxio.music.MusicType
|
||||
import org.oxycblt.auxio.music.MusicViewModel
|
||||
import org.oxycblt.auxio.music.NoAudioPermissionException
|
||||
import org.oxycblt.auxio.music.NoMusicException
|
||||
import org.oxycblt.auxio.music.PERMISSION_READ_AUDIO
|
||||
import org.oxycblt.auxio.music.Playlist
|
||||
import org.oxycblt.auxio.music.PlaylistDecision
|
||||
import org.oxycblt.auxio.music.PlaylistMessage
|
||||
|
@ -331,48 +328,19 @@ class HomeFragment :
|
|||
binding.homeIndexingContainer.visibility = View.VISIBLE
|
||||
binding.homeIndexingProgress.visibility = View.INVISIBLE
|
||||
binding.homeIndexingActions.visibility = View.VISIBLE
|
||||
when (error) {
|
||||
is NoAudioPermissionException -> {
|
||||
L.d("Showing permission prompt")
|
||||
binding.homeIndexingStatus.setText(R.string.err_no_perms)
|
||||
// Configure the action to act as a permission launcher.
|
||||
binding.homeIndexingTry.apply {
|
||||
text = context.getString(R.string.lbl_grant)
|
||||
setOnClickListener {
|
||||
requireNotNull(storagePermissionLauncher) {
|
||||
"Permission launcher was not available"
|
||||
}
|
||||
.launch(PERMISSION_READ_AUDIO)
|
||||
}
|
||||
}
|
||||
binding.homeIndexingMore.visibility = View.GONE
|
||||
}
|
||||
is NoMusicException -> {
|
||||
L.d("Showing no music error")
|
||||
binding.homeIndexingStatus.setText(R.string.err_no_music)
|
||||
// Configure the action to act as a reload trigger.
|
||||
binding.homeIndexingTry.apply {
|
||||
visibility = View.VISIBLE
|
||||
text = context.getString(R.string.lbl_retry)
|
||||
setOnClickListener { musicModel.refresh() }
|
||||
}
|
||||
binding.homeIndexingMore.visibility = View.GONE
|
||||
}
|
||||
else -> {
|
||||
L.d("Showing generic error")
|
||||
binding.homeIndexingStatus.setText(R.string.err_index_failed)
|
||||
// Configure the action to act as a reload trigger.
|
||||
binding.homeIndexingTry.apply {
|
||||
visibility = View.VISIBLE
|
||||
text = context.getString(R.string.lbl_retry)
|
||||
setOnClickListener { musicModel.rescan() }
|
||||
}
|
||||
binding.homeIndexingMore.apply {
|
||||
visibility = View.VISIBLE
|
||||
setOnClickListener {
|
||||
findNavController().navigateSafe(HomeFragmentDirections.reportError(error))
|
||||
}
|
||||
}
|
||||
|
||||
L.d("Showing generic error")
|
||||
binding.homeIndexingStatus.setText(R.string.err_index_failed)
|
||||
// Configure the action to act as a reload trigger.
|
||||
binding.homeIndexingTry.apply {
|
||||
visibility = View.VISIBLE
|
||||
text = context.getString(R.string.lbl_retry)
|
||||
setOnClickListener { musicModel.rescan() }
|
||||
}
|
||||
binding.homeIndexingMore.apply {
|
||||
visibility = View.VISIBLE
|
||||
setOnClickListener {
|
||||
findNavController().navigateSafe(HomeFragmentDirections.reportError(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,21 +50,3 @@ sealed interface IndexingState {
|
|||
*/
|
||||
data class Completed(val error: Exception?) : IndexingState
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown by the music loader when [PERMISSION_READ_AUDIO] was not granted.
|
||||
*
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class NoAudioPermissionException : Exception() {
|
||||
override val message = "Storage permissions are required to load music"
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown when no music was found.
|
||||
*
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class NoMusicException : Exception() {
|
||||
override val message = "No music was found on the device"
|
||||
}
|
||||
|
|
|
@ -18,17 +18,13 @@
|
|||
|
||||
package org.oxycblt.auxio.music
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import androidx.core.content.ContextCompat
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.yield
|
||||
import org.oxycblt.auxio.music.MusicRepository.IndexingWorker
|
||||
import org.oxycblt.auxio.music.info.Name
|
||||
import org.oxycblt.auxio.music.metadata.Separators
|
||||
import org.oxycblt.auxio.music.stack.Indexer
|
||||
|
@ -163,7 +159,7 @@ interface MusicRepository {
|
|||
* @param withCache Whether to load with the music cache or not.
|
||||
* @return The top-level music loading [Job] started.
|
||||
*/
|
||||
fun index(worker: IndexingWorker, withCache: Boolean): Job
|
||||
suspend fun index(worker: IndexingWorker, withCache: Boolean)
|
||||
|
||||
/** A listener for changes to the stored music information. */
|
||||
interface UpdateListener {
|
||||
|
@ -191,12 +187,6 @@ interface MusicRepository {
|
|||
|
||||
/** A persistent worker that can load music in the background. */
|
||||
interface IndexingWorker {
|
||||
/** A [Context] required to read device storage */
|
||||
val workerContext: Context
|
||||
|
||||
/** The [CoroutineScope] to perform coroutine music loading work on. */
|
||||
val scope: CoroutineScope
|
||||
|
||||
/**
|
||||
* Request that the music loading process ([index]) should be started. Any prior loads
|
||||
* should be canceled.
|
||||
|
@ -327,12 +317,9 @@ constructor(private val indexer: Indexer, private val musicSettings: MusicSettin
|
|||
indexingWorker?.requestIndex(withCache)
|
||||
}
|
||||
|
||||
override fun index(worker: MusicRepository.IndexingWorker, withCache: Boolean) =
|
||||
worker.scope.launch { indexWrapper(worker.workerContext, this, withCache) }
|
||||
|
||||
private suspend fun indexWrapper(context: Context, scope: CoroutineScope, withCache: Boolean) {
|
||||
override suspend fun index(worker: IndexingWorker, withCache: Boolean) {
|
||||
try {
|
||||
indexImpl(context, scope, withCache)
|
||||
indexImpl(withCache)
|
||||
} catch (e: CancellationException) {
|
||||
// Got cancelled, propagate upwards to top-level co-routine.
|
||||
L.d("Loading routine was cancelled")
|
||||
|
@ -346,15 +333,7 @@ constructor(private val indexer: Indexer, private val musicSettings: MusicSettin
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun indexImpl(context: Context, scope: CoroutineScope, withCache: Boolean) {
|
||||
// Make sure we have permissions before going forward. Theoretically this would be better
|
||||
// done at the UI level, but that intertwines logic and display too much.
|
||||
if (ContextCompat.checkSelfPermission(context, PERMISSION_READ_AUDIO) ==
|
||||
PackageManager.PERMISSION_DENIED) {
|
||||
L.e("Permissions were not granted")
|
||||
throw NoAudioPermissionException()
|
||||
}
|
||||
|
||||
private suspend fun indexImpl(withCache: Boolean) {
|
||||
// Obtain configuration information
|
||||
val separators = Separators.from(musicSettings.separators)
|
||||
val nameFactory =
|
||||
|
@ -363,9 +342,10 @@ constructor(private val indexer: Indexer, private val musicSettings: MusicSettin
|
|||
} else {
|
||||
Name.Known.SimpleFactory
|
||||
}
|
||||
val uris = musicSettings.musicLocations
|
||||
|
||||
val newLibrary =
|
||||
indexer.run(listOf(), Interpretation(nameFactory, separators), ::emitIndexingProgress)
|
||||
indexer.run(uris, Interpretation(nameFactory, separators), ::emitIndexingProgress)
|
||||
|
||||
// We want to make sure that all reads and writes are synchronized due to the sheer
|
||||
// amount of consumers of MusicRepository.
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
package org.oxycblt.auxio.music
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.core.content.edit
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import javax.inject.Inject
|
||||
|
@ -36,6 +37,7 @@ import timber.log.Timber as L
|
|||
interface MusicSettings : Settings<MusicSettings.Listener> {
|
||||
/** The configuration on how to handle particular directories in the music library. */
|
||||
var musicDirs: MusicDirectories
|
||||
var musicLocations: List<Uri>
|
||||
/** Whether to exclude non-music audio files from the music library. */
|
||||
val excludeNonMusic: Boolean
|
||||
/** Whether to be actively watching for changes in the music library. */
|
||||
|
@ -79,6 +81,21 @@ constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override var musicLocations: List<Uri>
|
||||
get() {
|
||||
val dirs =
|
||||
sharedPreferences.getStringSet(getString(R.string.set_key_music_locations), null)
|
||||
?: emptySet()
|
||||
return dirs.map { Uri.parse(it) }
|
||||
}
|
||||
set(value) {
|
||||
sharedPreferences.edit {
|
||||
putStringSet(
|
||||
getString(R.string.set_key_music_locations), value.map(Uri::toString).toSet())
|
||||
apply()
|
||||
}
|
||||
}
|
||||
|
||||
override val excludeNonMusic: Boolean
|
||||
get() = sharedPreferences.getBoolean(getString(R.string.set_key_exclude_non_music), true)
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import javax.inject.Inject
|
|||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import org.oxycblt.auxio.BuildConfig
|
||||
import org.oxycblt.auxio.ForegroundListener
|
||||
import org.oxycblt.auxio.ForegroundServiceNotification
|
||||
|
@ -38,7 +39,7 @@ import timber.log.Timber as L
|
|||
|
||||
class IndexingHolder
|
||||
private constructor(
|
||||
override val workerContext: Context,
|
||||
private val workerContext: Context,
|
||||
private val foregroundListener: ForegroundListener,
|
||||
private val playbackManager: PlaybackStateManager,
|
||||
private val musicRepository: MusicRepository,
|
||||
|
@ -130,11 +131,10 @@ private constructor(
|
|||
// Cancel the previous music loading job.
|
||||
currentIndexJob?.cancel()
|
||||
// Start a new music loading job on a co-routine.
|
||||
currentIndexJob = musicRepository.index(this, withCache)
|
||||
currentIndexJob =
|
||||
indexScope.launch { musicRepository.index(this@IndexingHolder, withCache) }
|
||||
}
|
||||
|
||||
override val scope = indexScope
|
||||
|
||||
override fun onIndexingStateChanged() {
|
||||
foregroundListener.updateForeground(ForegroundListener.Change.INDEXER)
|
||||
val state = musicRepository.indexingState
|
||||
|
|
|
@ -22,7 +22,7 @@ import org.oxycblt.auxio.music.Library
|
|||
import org.oxycblt.auxio.music.Music
|
||||
import org.oxycblt.auxio.music.Playlist
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.music.stack.explore.fs.Path
|
||||
import org.oxycblt.auxio.music.stack.explore.fs.Pathi
|
||||
|
||||
interface MutableLibrary : Library {
|
||||
suspend fun createPlaylist(name: String, songs: List<Song>): MutableLibrary
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
<string name="set_key_square_covers" translatable="false">auxio_square_covers</string>
|
||||
<string name="set_key_music_dirs_include" translatable="false">auxio_include_dirs</string>
|
||||
<string name="set_key_exclude_non_music" translatable="false">auxio_exclude_non_music</string>
|
||||
<string name="set_key_music_locations" translatable="false">auxio_music_locations</string>
|
||||
<string name="set_key_separators" translatable="false">auxio_separators</string>
|
||||
<string name="set_key_auto_sort_names" translatable="false">auxio_auto_sort_names</string>
|
||||
|
||||
|
|
Loading…
Reference in a new issue