music: include path with loaded saf files

This commit is contained in:
Alexander Capehart 2024-11-15 12:11:57 -07:00
parent 300f26739d
commit 5b447f7efb
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
3 changed files with 21 additions and 12 deletions

View file

@ -0,0 +1,2 @@
package org.oxycblt.auxio.music.cache

View file

@ -3,12 +3,10 @@ package org.oxycblt.auxio.music.fs
import android.content.ContentResolver
import android.content.Context
import android.media.MediaFormat
import android.net.Uri
import android.os.storage.StorageManager
import android.os.storage.StorageVolume
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.flow.Flow
import kotlinx.coroutines.flow.asFlow
@ -23,38 +21,44 @@ interface DeviceFiles {
}
@OptIn(ExperimentalCoroutinesApi::class)
class DeviceFilesImpl @Inject constructor(private val contentResolver: ContentResolver) :
class DeviceFilesImpl @Inject constructor(@ApplicationContext private val context: Context) :
DeviceFiles {
private val contentResolver = context.contentResolverSafe
override fun explore(uris: Flow<Uri>): Flow<DeviceFile> =
uris.flatMapMerge { rootUri ->
exploreImpl(contentResolver, rootUri)
exploreImpl(contentResolver, rootUri, Components.nil())
}
private fun exploreImpl(
contentResolver: ContentResolver,
uri: Uri
uri: Uri,
relativePath: Components
): Flow<DeviceFile> =
flow {
contentResolver.useQuery(uri, PROJECTION) { cursor ->
val childUriIndex =
cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_DOCUMENT_ID)
val displayNameIndex =
cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_DISPLAY_NAME)
val mimeTypeIndex =
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>>()
while (cursor.moveToNext()) {
val childId = cursor.getString(childUriIndex)
val childUri = DocumentsContract.buildDocumentUriUsingTree(uri, childId)
val displayName = cursor.getString(displayNameIndex)
val path = relativePath.child(displayName)
val mimeType = cursor.getString(mimeTypeIndex)
if (mimeType == DocumentsContract.Document.MIME_TYPE_DIR) {
// This does NOT block the current coroutine. Instead, we will
// evaluate this flow in parallel later to maximize throughput.
recursions.add(exploreImpl(contentResolver, childUri))
recursions.add(exploreImpl(contentResolver, childUri, path))
} else {
// 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
// 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,
@ -69,9 +73,10 @@ class DeviceFilesImpl @Inject constructor(private val contentResolver: ContentRe
private companion object {
val PROJECTION = arrayOf(
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)

View file

@ -161,6 +161,8 @@ value class Components private constructor(val components: List<String>) {
fun containing(other: Components) = Components(other.components.drop(components.size))
companion object {
fun nil() = Components(listOf())
/**
* Parses a path string into a [Components] instance by the unix path separator (/).
*