music: split off music location into musikr

This commit is contained in:
Alexander Capehart 2024-12-07 11:46:03 -07:00
parent abeabcb8df
commit 3eaa96ffda
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
6 changed files with 57 additions and 32 deletions

View file

@ -27,6 +27,7 @@ import org.oxycblt.auxio.list.recycler.DialogRecyclerView
import org.oxycblt.musikr.fs.Path import org.oxycblt.musikr.fs.Path
import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.inflater import org.oxycblt.auxio.util.inflater
import org.oxycblt.musikr.fs.MusicLocation
import timber.log.Timber as L import timber.log.Timber as L
/** /**
@ -93,8 +94,6 @@ class LocationAdapter(private val listener: Listener) : RecyclerView.Adapter<Mus
} }
} }
data class MusicLocation(val uri: Uri, val path: Path)
/** /**
* A [RecyclerView.Recycler] that displays a [MusicLocation]. Use [from] to create an instance. * A [RecyclerView.Recycler] that displays a [MusicLocation]. Use [from] to create an instance.
* *

View file

@ -36,9 +36,9 @@ import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogMusicLocationsBinding import org.oxycblt.auxio.databinding.DialogMusicLocationsBinding
import org.oxycblt.auxio.music.MusicSettings import org.oxycblt.auxio.music.MusicSettings
import org.oxycblt.musikr.fs.path.DocumentPathFactory
import org.oxycblt.auxio.ui.ViewBindingMaterialDialogFragment import org.oxycblt.auxio.ui.ViewBindingMaterialDialogFragment
import org.oxycblt.auxio.util.showToast import org.oxycblt.auxio.util.showToast
import org.oxycblt.musikr.fs.MusicLocation
import timber.log.Timber as L import timber.log.Timber as L
/** /**
@ -51,7 +51,7 @@ class MusicSourcesDialog :
ViewBindingMaterialDialogFragment<DialogMusicLocationsBinding>(), LocationAdapter.Listener { ViewBindingMaterialDialogFragment<DialogMusicLocationsBinding>(), LocationAdapter.Listener {
private val locationAdapter = LocationAdapter(this) private val locationAdapter = LocationAdapter(this)
private var openDocumentTreeLauncher: ActivityResultLauncher<Uri?>? = null private var openDocumentTreeLauncher: ActivityResultLauncher<Uri?>? = null
@Inject lateinit var documentPathFactory: DocumentPathFactory @Inject lateinit var musicLocationFactory: MusicLocation.Factory
@Inject lateinit var musicSettings: MusicSettings @Inject lateinit var musicSettings: MusicSettings
override fun onCreateBinding(inflater: LayoutInflater) = override fun onCreateBinding(inflater: LayoutInflater) =
@ -76,17 +76,7 @@ class MusicSourcesDialog :
) { ) {
openDocumentTreeLauncher = openDocumentTreeLauncher =
registerForActivityResult( registerForActivityResult(
object : ActivityResultContracts.OpenDocumentTree() { ActivityResultContracts.OpenDocumentTree(),
override fun createIntent(context: Context, input: Uri?): Intent {
return super.createIntent(context, input).apply {
flags =
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or
Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or
Intent.FLAG_GRANT_PREFIX_URI_PERMISSION
}
}
},
::addDocumentTreeUriToDirs) ::addDocumentTreeUriToDirs)
binding.locationsAdd.apply { binding.locationsAdd.apply {
@ -117,8 +107,7 @@ class MusicSourcesDialog :
?: musicSettings.musicLocations ?: musicSettings.musicLocations
val locations = val locations =
locationUris.mapNotNull { locationUris.mapNotNull {
MusicLocation( musicLocationFactory.create(it)
it, documentPathFactory.unpackDocumentTreeUri(it) ?: return@mapNotNull null)
} }
locationAdapter.addAll(locations) locationAdapter.addAll(locations)
@ -160,10 +149,10 @@ class MusicSourcesDialog :
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
contentResolver.takePersistableUriPermission(uri, takeFlags) contentResolver.takePersistableUriPermission(uri, takeFlags)
val path = documentPathFactory.unpackDocumentTreeUri(uri) val location = musicLocationFactory.create(uri)
if (path != null) { if (location != null) {
locationAdapter.add(MusicLocation(uri, path)) locationAdapter.add(location)
requireBinding().locationsEmpty.isVisible = false requireBinding().locationsEmpty.isVisible = false
} else { } else {
requireContext().showToast(R.string.err_bad_location) requireContext().showToast(R.string.err_bad_location)

View file

@ -26,6 +26,7 @@ import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.onStart
import org.oxycblt.musikr.fs.MusicLocation
import org.oxycblt.musikr.model.MutableLibrary import org.oxycblt.musikr.model.MutableLibrary
import org.oxycblt.musikr.pipeline.EvaluateStep import org.oxycblt.musikr.pipeline.EvaluateStep
import org.oxycblt.musikr.pipeline.ExploreStep import org.oxycblt.musikr.pipeline.ExploreStep
@ -34,7 +35,7 @@ import org.oxycblt.musikr.tag.Interpretation
interface Indexer { interface Indexer {
suspend fun run( suspend fun run(
uris: List<Uri>, locations: MusicLocation,
interpretation: Interpretation, interpretation: Interpretation,
onProgress: suspend (IndexingProgress) -> Unit = {} onProgress: suspend (IndexingProgress) -> Unit = {}
): MutableLibrary ): MutableLibrary
@ -59,7 +60,7 @@ constructor(
private val evaluateStep: EvaluateStep private val evaluateStep: EvaluateStep
) : Indexer { ) : Indexer {
override suspend fun run( override suspend fun run(
uris: List<Uri>, locations: MusicLocation,
interpretation: Interpretation, interpretation: Interpretation,
onProgress: suspend (IndexingProgress) -> Unit onProgress: suspend (IndexingProgress) -> Unit
) = coroutineScope { ) = coroutineScope {

View file

@ -35,7 +35,7 @@ import org.oxycblt.musikr.fs.path.DocumentPathFactory
import timber.log.Timber import timber.log.Timber
interface DeviceFiles { interface DeviceFiles {
fun explore(uris: Flow<Uri>): Flow<DeviceFile> fun explore(locations: Flow<MusicLocation>): Flow<DeviceFile>
} }
data class DeviceFile( data class DeviceFile(
@ -50,19 +50,17 @@ data class DeviceFile(
class DeviceFilesImpl class DeviceFilesImpl
@Inject @Inject
constructor( constructor(
@ApplicationContext private val context: Context, @ApplicationContext private val context: Context
private val documentPathFactory: DocumentPathFactory
) : DeviceFiles { ) : DeviceFiles {
private val contentResolver = context.contentResolverSafe private val contentResolver = context.contentResolverSafe
override fun explore(uris: Flow<Uri>): Flow<DeviceFile> = override fun explore(locations: Flow<MusicLocation>): Flow<DeviceFile> =
uris.flatMapMerge { rootUri -> locations.flatMapMerge { location ->
Timber.d("$rootUri")
exploreImpl( exploreImpl(
contentResolver, contentResolver,
rootUri, location.uri,
DocumentsContract.getTreeDocumentId(rootUri), DocumentsContract.getTreeDocumentId(location.uri),
requireNotNull(documentPathFactory.unpackDocumentTreeUri(rootUri))) location.path)
} }
private fun exploreImpl( private fun exploreImpl(

View file

@ -56,4 +56,6 @@ interface FsBindsModule {
fun documentPathFactory(documentTreePathFactory: DocumentPathFactoryImpl): DocumentPathFactory fun documentPathFactory(documentTreePathFactory: DocumentPathFactoryImpl): DocumentPathFactory
@Binds fun deviceFiles(deviceFilesImpl: DeviceFilesImpl): DeviceFiles @Binds fun deviceFiles(deviceFilesImpl: DeviceFilesImpl): DeviceFiles
@Binds fun musicLocationFactory(musicLocationFactoryImpl: MusicLocationFactoryImpl): MusicLocation.Factory
} }

View file

@ -0,0 +1,36 @@
package org.oxycblt.musikr.fs
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.provider.DocumentsContract
import dagger.hilt.android.qualifiers.ApplicationContext
import org.oxycblt.musikr.fs.path.DocumentPathFactory
import javax.inject.Inject
class MusicLocation internal constructor(val uri: Uri, val path: Path) {
override fun equals(other: Any?) =
other is MusicLocation && uri == other.uri && path == other.path
override fun hashCode() = 31 * uri.hashCode() + path.hashCode()
override fun toString() = "src:$uri=$path"
interface Factory {
fun create(uri: Uri): MusicLocation?
}
}
class MusicLocationFactoryImpl @Inject constructor(
@ApplicationContext private val context: Context,
private val documentPathFactory: DocumentPathFactory
) : MusicLocation.Factory {
override fun create(uri: Uri): MusicLocation? {
check(DocumentsContract.isTreeUri(uri)) { "URI $uri is not a document tree URI" }
val path = documentPathFactory.unpackDocumentTreeUri(uri) ?: return null
context.contentResolverSafe.takePersistableUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
return MusicLocation(uri, path)
}
}