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,
/** 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
internal interface DeviceFiles {
fun explore(locations: Flow<MusicLocation>): Flow<DeviceFile>
fun explore(locations: Flow<MusicLocation>, ignoreHidden: Boolean = true): Flow<DeviceFile>
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<MusicLocation>): Flow<DeviceFile> =
override fun explore(locations: Flow<MusicLocation>, ignoreHidden: Boolean): Flow<DeviceFile> =
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<DeviceFile> = 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