diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt index 0d93d08c2..837d82e4e 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt @@ -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)) } } } diff --git a/app/src/main/java/org/oxycblt/auxio/music/Indexing.kt b/app/src/main/java/org/oxycblt/auxio/music/Indexing.kt index 33ab1315c..db12863ec 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/Indexing.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/Indexing.kt @@ -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" -} diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt index 328e03b27..4658855af 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt @@ -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. diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicSettings.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicSettings.kt index 06b711764..77b513973 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicSettings.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicSettings.kt @@ -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 { /** The configuration on how to handle particular directories in the music library. */ var musicDirs: MusicDirectories + var musicLocations: List /** 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 + 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) diff --git a/app/src/main/java/org/oxycblt/auxio/music/service/IndexingHolder.kt b/app/src/main/java/org/oxycblt/auxio/music/service/IndexingHolder.kt index 50483e7d8..30d36deb1 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/service/IndexingHolder.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/service/IndexingHolder.kt @@ -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 diff --git a/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/model/Library.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/model/Library.kt index e094b0ee3..849e25188 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/model/Library.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/model/Library.kt @@ -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): MutableLibrary diff --git a/app/src/main/res/values/settings.xml b/app/src/main/res/values/settings.xml index 49dbceebd..e1ea02bd1 100644 --- a/app/src/main/res/values/settings.xml +++ b/app/src/main/res/values/settings.xml @@ -18,6 +18,7 @@ auxio_square_covers auxio_include_dirs auxio_exclude_non_music + auxio_music_locations auxio_separators auxio_auto_sort_names