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.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)
|
|
@ -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 (/).
|
||||
*
|
||||
|
|
Loading…
Reference in a new issue