diff --git a/musikr/src/main/java/org/oxycblt/musikr/Config.kt b/musikr/src/main/java/org/oxycblt/musikr/Config.kt index a793680db..e2b23350d 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/Config.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/Config.kt @@ -53,5 +53,8 @@ data class Interpretation( val naming: Naming, /** 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 ) diff --git a/musikr/src/main/java/org/oxycblt/musikr/fs/device/DeviceFiles.kt b/musikr/src/main/java/org/oxycblt/musikr/fs/device/DeviceFiles.kt index 2f737995c..1aa776b45 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/fs/device/DeviceFiles.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/fs/device/DeviceFiles.kt @@ -34,7 +34,7 @@ import org.oxycblt.musikr.fs.MusicLocation import org.oxycblt.musikr.fs.Path internal interface DeviceFiles { - fun explore(locations: Flow): Flow + fun explore(locations: Flow, ignoreHidden: Boolean = true): Flow companion object { fun from(context: Context): DeviceFiles = DeviceFilesImpl(context.contentResolverSafe) @@ -43,20 +43,22 @@ internal interface DeviceFiles { @OptIn(ExperimentalCoroutinesApi::class) private class DeviceFilesImpl(private val contentResolver: ContentResolver) : DeviceFiles { - override fun explore(locations: Flow): Flow = + override fun explore(locations: Flow, ignoreHidden: Boolean): Flow = locations.flatMapMerge { location -> exploreImpl( contentResolver, location.uri, DocumentsContract.getTreeDocumentId(location.uri), - location.path) + location.path, + ignoreHidden) } private fun exploreImpl( contentResolver: ContentResolver, rootUri: Uri, treeDocumentId: String, - relativePath: Path + relativePath: Path, + ignoreHidden: Boolean ): Flow = flow { contentResolver.useQuery( DocumentsContract.buildChildDocumentsUriUsingTree(rootUri, treeDocumentId), @@ -74,12 +76,18 @@ private class DeviceFilesImpl(private val contentResolver: ContentResolver) : De while (cursor.moveToNext()) { val childId = cursor.getString(childUriIndex) val displayName = cursor.getString(displayNameIndex) + + // Skip hidden files/directories if ignoreHidden is true + if (ignoreHidden && displayName.startsWith(".")) { + continue + } + val newPath = relativePath.file(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. - recursive.add(exploreImpl(contentResolver, rootUri, childId, newPath)) + recursive.add(exploreImpl(contentResolver, rootUri, childId, newPath, ignoreHidden)) } else if (mimeType.startsWith("audio/") && mimeType != "audio/x-mpegurl") { // 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