diff --git a/app/src/main/java/org/oxycblt/auxio/music/cache/TagCache.kt b/app/src/main/java/org/oxycblt/auxio/music/cache/TagCache.kt new file mode 100644 index 000000000..ec222b1aa --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/music/cache/TagCache.kt @@ -0,0 +1,2 @@ +package org.oxycblt.auxio.music.cache + diff --git a/app/src/main/java/org/oxycblt/auxio/music/fs/DeviceFiles.kt b/app/src/main/java/org/oxycblt/auxio/music/fs/DeviceFiles.kt index b73a13d48..3a61160ce 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/fs/DeviceFiles.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/fs/DeviceFiles.kt @@ -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): Flow = uris.flatMapMerge { rootUri -> - exploreImpl(contentResolver, rootUri) + exploreImpl(contentResolver, rootUri, Components.nil()) } private fun exploreImpl( contentResolver: ContentResolver, - uri: Uri + uri: Uri, + relativePath: Components ): Flow = 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>() 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) \ No newline at end of file +data class DeviceFile(val uri: Uri, val mimeType: String, val path: Components) \ No newline at end of file diff --git a/app/src/main/java/org/oxycblt/auxio/music/fs/Fs.kt b/app/src/main/java/org/oxycblt/auxio/music/fs/Fs.kt index 3338b202b..47f0a3de4 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/fs/Fs.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/fs/Fs.kt @@ -161,6 +161,8 @@ value class Components private constructor(val components: List) { 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 (/). *