musikr: add option to ignore hidden files/directories

This commit is contained in:
Alexander Capehart (aider) 2025-02-25 15:44:11 -07:00 committed by Alexander Capehart
parent ddeba2c496
commit e2b0601d4c
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
2 changed files with 17 additions and 6 deletions

View file

@ -53,5 +53,8 @@ data class Interpretation(
val naming: Naming, val naming: Naming,
/** What separators delimit multi-value audio tags. */ /** What separators delimit multi-value audio tags. */
val separators: Separators val separators: Separators,
/** Whether to ignore hidden files and directories (those starting with a dot). */
val ignoreHidden: Boolean = true
) )

View file

@ -34,7 +34,7 @@ import org.oxycblt.musikr.fs.MusicLocation
import org.oxycblt.musikr.fs.Path import org.oxycblt.musikr.fs.Path
internal interface DeviceFiles { internal interface DeviceFiles {
fun explore(locations: Flow<MusicLocation>): Flow<DeviceFile> fun explore(locations: Flow<MusicLocation>, ignoreHidden: Boolean = true): Flow<DeviceFile>
companion object { companion object {
fun from(context: Context): DeviceFiles = DeviceFilesImpl(context.contentResolverSafe) fun from(context: Context): DeviceFiles = DeviceFilesImpl(context.contentResolverSafe)
@ -43,20 +43,22 @@ internal interface DeviceFiles {
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
private class DeviceFilesImpl(private val contentResolver: ContentResolver) : DeviceFiles { private class DeviceFilesImpl(private val contentResolver: ContentResolver) : DeviceFiles {
override fun explore(locations: Flow<MusicLocation>): Flow<DeviceFile> = override fun explore(locations: Flow<MusicLocation>, ignoreHidden: Boolean): Flow<DeviceFile> =
locations.flatMapMerge { location -> locations.flatMapMerge { location ->
exploreImpl( exploreImpl(
contentResolver, contentResolver,
location.uri, location.uri,
DocumentsContract.getTreeDocumentId(location.uri), DocumentsContract.getTreeDocumentId(location.uri),
location.path) location.path,
ignoreHidden)
} }
private fun exploreImpl( private fun exploreImpl(
contentResolver: ContentResolver, contentResolver: ContentResolver,
rootUri: Uri, rootUri: Uri,
treeDocumentId: String, treeDocumentId: String,
relativePath: Path relativePath: Path,
ignoreHidden: Boolean
): Flow<DeviceFile> = flow { ): Flow<DeviceFile> = flow {
contentResolver.useQuery( contentResolver.useQuery(
DocumentsContract.buildChildDocumentsUriUsingTree(rootUri, treeDocumentId), DocumentsContract.buildChildDocumentsUriUsingTree(rootUri, treeDocumentId),
@ -74,12 +76,18 @@ private class DeviceFilesImpl(private val contentResolver: ContentResolver) : De
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
val childId = cursor.getString(childUriIndex) val childId = cursor.getString(childUriIndex)
val displayName = cursor.getString(displayNameIndex) val displayName = cursor.getString(displayNameIndex)
// Skip hidden files/directories if ignoreHidden is true
if (ignoreHidden && displayName.startsWith(".")) {
continue
}
val newPath = relativePath.file(displayName) val newPath = relativePath.file(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.
recursive.add(exploreImpl(contentResolver, rootUri, childId, newPath)) recursive.add(exploreImpl(contentResolver, rootUri, childId, newPath, ignoreHidden))
} else if (mimeType.startsWith("audio/") && mimeType != "audio/x-mpegurl") { } else if (mimeType.startsWith("audio/") && mimeType != "audio/x-mpegurl") {
// 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