music: include path with loaded saf files
This commit is contained in:
parent
300f26739d
commit
5b447f7efb
3 changed files with 21 additions and 12 deletions
2
app/src/main/java/org/oxycblt/auxio/music/cache/TagCache.kt
vendored
Normal file
2
app/src/main/java/org/oxycblt/auxio/music/cache/TagCache.kt
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
package org.oxycblt.auxio.music.cache
|
||||||
|
|
|
@ -3,12 +3,10 @@ package org.oxycblt.auxio.music.fs
|
||||||
|
|
||||||
import android.content.ContentResolver
|
import android.content.ContentResolver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.media.MediaFormat
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.storage.StorageManager
|
|
||||||
import android.os.storage.StorageVolume
|
|
||||||
import android.provider.DocumentsContract
|
import android.provider.DocumentsContract
|
||||||
import android.webkit.MimeTypeMap
|
import androidx.documentfile.provider.DocumentFile
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.asFlow
|
import kotlinx.coroutines.flow.asFlow
|
||||||
|
@ -23,38 +21,44 @@ interface DeviceFiles {
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
class DeviceFilesImpl @Inject constructor(private val contentResolver: ContentResolver) :
|
class DeviceFilesImpl @Inject constructor(@ApplicationContext private val context: Context) :
|
||||||
DeviceFiles {
|
DeviceFiles {
|
||||||
|
private val contentResolver = context.contentResolverSafe
|
||||||
override fun explore(uris: Flow<Uri>): Flow<DeviceFile> =
|
override fun explore(uris: Flow<Uri>): Flow<DeviceFile> =
|
||||||
uris.flatMapMerge { rootUri ->
|
uris.flatMapMerge { rootUri ->
|
||||||
exploreImpl(contentResolver, rootUri)
|
exploreImpl(contentResolver, rootUri, Components.nil())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun exploreImpl(
|
private fun exploreImpl(
|
||||||
contentResolver: ContentResolver,
|
contentResolver: ContentResolver,
|
||||||
uri: Uri
|
uri: Uri,
|
||||||
|
relativePath: Components
|
||||||
): Flow<DeviceFile> =
|
): Flow<DeviceFile> =
|
||||||
flow {
|
flow {
|
||||||
contentResolver.useQuery(uri, PROJECTION) { cursor ->
|
contentResolver.useQuery(uri, PROJECTION) { cursor ->
|
||||||
val childUriIndex =
|
val childUriIndex =
|
||||||
cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_DOCUMENT_ID)
|
cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_DOCUMENT_ID)
|
||||||
|
val displayNameIndex =
|
||||||
|
cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_DISPLAY_NAME)
|
||||||
val mimeTypeIndex =
|
val mimeTypeIndex =
|
||||||
cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_MIME_TYPE)
|
cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_MIME_TYPE)
|
||||||
// Recurse into sub-directories as another flowO
|
// Recurse into sub-directories as another flow
|
||||||
val recursions = mutableListOf<Flow<DeviceFile>>()
|
val recursions = mutableListOf<Flow<DeviceFile>>()
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
val childId = cursor.getString(childUriIndex)
|
val childId = cursor.getString(childUriIndex)
|
||||||
val childUri = DocumentsContract.buildDocumentUriUsingTree(uri, childId)
|
val childUri = DocumentsContract.buildDocumentUriUsingTree(uri, childId)
|
||||||
|
val displayName = cursor.getString(displayNameIndex)
|
||||||
|
val path = relativePath.child(displayName)
|
||||||
val mimeType = cursor.getString(mimeTypeIndex)
|
val mimeType = cursor.getString(mimeTypeIndex)
|
||||||
if (mimeType == DocumentsContract.Document.MIME_TYPE_DIR) {
|
if (mimeType == DocumentsContract.Document.MIME_TYPE_DIR) {
|
||||||
// This does NOT block the current coroutine. Instead, we will
|
// This does NOT block the current coroutine. Instead, we will
|
||||||
// evaluate this flow in parallel later to maximize throughput.
|
// evaluate this flow in parallel later to maximize throughput.
|
||||||
recursions.add(exploreImpl(contentResolver, childUri))
|
recursions.add(exploreImpl(contentResolver, childUri, path))
|
||||||
} else {
|
} else {
|
||||||
// Immediately emit all files given that it's just an O(1) op.
|
// Immediately emit all files given that it's just an O(1) op.
|
||||||
// This also just makes sure the outer flow has a reason to exist
|
// This also just makes sure the outer flow has a reason to exist
|
||||||
// rather than just being a glorified async.
|
// rather than just being a glorified async.
|
||||||
emit(DeviceFile(childUri, mimeType))
|
emit(DeviceFile(childUri, mimeType, path))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Hypothetically, we could just emitAll as we recurse into a new directory,
|
// Hypothetically, we could just emitAll as we recurse into a new directory,
|
||||||
|
@ -69,9 +73,10 @@ class DeviceFilesImpl @Inject constructor(private val contentResolver: ContentRe
|
||||||
private companion object {
|
private companion object {
|
||||||
val PROJECTION = arrayOf(
|
val PROJECTION = arrayOf(
|
||||||
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
|
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
|
||||||
DocumentsContract.Document.COLUMN_MIME_TYPE
|
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
|
||||||
|
DocumentsContract.Document.COLUMN_MIME_TYPE,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DeviceFile(val uri: Uri, val mimeType: String)
|
data class DeviceFile(val uri: Uri, val mimeType: String, val path: Components)
|
|
@ -161,6 +161,8 @@ value class Components private constructor(val components: List<String>) {
|
||||||
fun containing(other: Components) = Components(other.components.drop(components.size))
|
fun containing(other: Components) = Components(other.components.drop(components.size))
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
fun nil() = Components(listOf())
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a path string into a [Components] instance by the unix path separator (/).
|
* Parses a path string into a [Components] instance by the unix path separator (/).
|
||||||
*
|
*
|
||||||
|
|
Loading…
Reference in a new issue