music: split off music location into musikr
This commit is contained in:
parent
abeabcb8df
commit
3eaa96ffda
6 changed files with 57 additions and 32 deletions
|
@ -15,7 +15,7 @@
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.oxycblt.auxio.music.locations
|
package org.oxycblt.auxio.music.locations
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
@ -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.
|
||||||
*
|
*
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
36
app/src/main/java/org/oxycblt/musikr/fs/MusicLocation.kt
Normal file
36
app/src/main/java/org/oxycblt/musikr/fs/MusicLocation.kt
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue