music: discard songs w/o volumes

This commit is contained in:
Alexander Capehart 2024-01-01 13:36:53 -07:00
parent 673629dd26
commit 538533bf3f
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
2 changed files with 26 additions and 29 deletions

View file

@ -78,7 +78,7 @@ interface MediaStoreExtractor {
fun close() fun close()
fun populateFileInfo(rawSong: RawSong) fun populateFileInfo(rawSong: RawSong): Boolean
fun populateTags(rawSong: RawSong) fun populateTags(rawSong: RawSong)
} }
@ -101,7 +101,7 @@ interface MediaStoreExtractor {
// instead. // instead.
Build.MANUFACTURER.equals("huawei", ignoreCase = true) || Build.MANUFACTURER.equals("huawei", ignoreCase = true) ||
Build.VERSION.SDK_INT < Build.VERSION_CODES.Q -> Build.VERSION.SDK_INT < Build.VERSION_CODES.Q ->
DataPathInterpreter.Factory(volumeManager) Api24PathInterpreter.Factory(volumeManager)
else -> VolumePathInterpreter.Factory(volumeManager) else -> VolumePathInterpreter.Factory(volumeManager)
} }
@ -216,7 +216,9 @@ private class MediaStoreExtractorImpl(
) { ) {
while (query.moveToNext()) { while (query.moveToNext()) {
val rawSong = RawSong() val rawSong = RawSong()
query.populateFileInfo(rawSong) if (!query.populateFileInfo(rawSong)) {
continue
}
if (cache?.populate(rawSong) == true) { if (cache?.populate(rawSong) == true) {
completeSongs.sendWithTimeout(rawSong) completeSongs.sendWithTimeout(rawSong)
} else { } else {
@ -260,13 +262,13 @@ private class MediaStoreExtractorImpl(
override fun close() = cursor.close() override fun close() = cursor.close()
override fun populateFileInfo(rawSong: RawSong) { override fun populateFileInfo(rawSong: RawSong): Boolean {
rawSong.mediaStoreId = cursor.getLong(idIndex) rawSong.mediaStoreId = cursor.getLong(idIndex)
rawSong.dateAdded = cursor.getLong(dateAddedIndex) rawSong.dateAdded = cursor.getLong(dateAddedIndex)
rawSong.dateModified = cursor.getLong(dateModifiedIndex) rawSong.dateModified = cursor.getLong(dateModifiedIndex)
rawSong.extensionMimeType = cursor.getString(mimeTypeIndex) rawSong.extensionMimeType = cursor.getString(mimeTypeIndex)
rawSong.albumMediaStoreId = cursor.getLong(albumIdIndex) rawSong.albumMediaStoreId = cursor.getLong(albumIdIndex)
pathInterpreter.populate(rawSong) return pathInterpreter.populate(rawSong)
} }
override fun populateTags(rawSong: RawSong) { override fun populateTags(rawSong: RawSong) {
@ -343,19 +345,13 @@ private class MediaStoreExtractorImpl(
} }
} }
private interface Interpreter { private sealed interface PathInterpreter {
fun populate(rawSong: RawSong) fun populate(rawSong: RawSong): Boolean
interface Factory { interface Factory {
val projection: Array<String> val projection: Array<String>
fun wrap(cursor: Cursor): Interpreter fun wrap(cursor: Cursor): PathInterpreter
}
}
private sealed interface PathInterpreter : Interpreter {
interface Factory : Interpreter.Factory {
override fun wrap(cursor: Cursor): PathInterpreter
fun createSelector(paths: List<Path>): Selector? fun createSelector(paths: List<Path>): Selector?
@ -363,14 +359,14 @@ private sealed interface PathInterpreter : Interpreter {
} }
} }
private class DataPathInterpreter( private open class Api24PathInterpreter(
private val cursor: Cursor, private val cursor: Cursor,
private val volumeManager: VolumeManager private val volumeManager: VolumeManager
) : PathInterpreter { ) : PathInterpreter {
private val dataIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DATA) private val dataIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DATA)
private val volumes = volumeManager.getVolumes() private val volumes = volumeManager.getVolumes()
override fun populate(rawSong: RawSong) { override fun populate(rawSong: RawSong): Boolean {
val data = Components.parseUnix(cursor.getString(dataIndex)) val data = Components.parseUnix(cursor.getString(dataIndex))
// Find the volume that transforms the DATA column into a relative path. This is // Find the volume that transforms the DATA column into a relative path. This is
@ -379,11 +375,11 @@ private class DataPathInterpreter(
val volumePath = volume.components ?: continue val volumePath = volume.components ?: continue
if (volumePath.contains(data)) { if (volumePath.contains(data)) {
rawSong.path = Path(volume, data.depth(volumePath.components.size)) rawSong.path = Path(volume, data.depth(volumePath.components.size))
return return true
} }
} }
throw IllegalStateException("Could not find volume for path $data (tried: ${volumes.map { it.components }}})") return false
} }
class Factory(private val volumeManager: VolumeManager) : PathInterpreter.Factory { class Factory(private val volumeManager: VolumeManager) : PathInterpreter.Factory {
@ -415,7 +411,7 @@ private class DataPathInterpreter(
} }
override fun wrap(cursor: Cursor): PathInterpreter = override fun wrap(cursor: Cursor): PathInterpreter =
DataPathInterpreter(cursor, volumeManager) Api24PathInterpreter(cursor, volumeManager)
} }
} }
@ -429,20 +425,17 @@ private class VolumePathInterpreter(private val cursor: Cursor, volumeManager: V
cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.RELATIVE_PATH) cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.RELATIVE_PATH)
private val volumes = volumeManager.getVolumes() private val volumes = volumeManager.getVolumes()
override fun populate(rawSong: RawSong) { override fun populate(rawSong: RawSong): Boolean {
// Find the StorageVolume whose MediaStore name corresponds to it. // Find the StorageVolume whose MediaStore name corresponds to it.
val volumeName = cursor.getString(volumeIndex) val volumeName = cursor.getString(volumeIndex)
val volume = volumes.find { it.mediaStoreName == volumeName } val volume = volumes.find { it.mediaStoreName == volumeName } ?: return false
if (volume == null) {
throw IllegalStateException("Could not find volume for name $volumeName")
}
// Relative path does not include file name, must use DISPLAY_NAME and add it // Relative path does not include file name, must use DISPLAY_NAME and add it
// in manually. // in manually.
val relativePath = cursor.getString(relativePathIndex) val relativePath = cursor.getString(relativePathIndex)
val displayName = cursor.getString(displayNameIndex) val displayName = cursor.getString(displayNameIndex)
val components = Components.parseUnix(relativePath).child(displayName) val components = Components.parseUnix(relativePath).child(displayName)
rawSong.path = Path(volume, components) rawSong.path = Path(volume, components)
return true
} }
class Factory(private val volumeManager: VolumeManager) : PathInterpreter.Factory { class Factory(private val volumeManager: VolumeManager) : PathInterpreter.Factory {
@ -493,9 +486,13 @@ private class VolumePathInterpreter(private val cursor: Cursor, volumeManager: V
} }
} }
private sealed interface TagInterpreter : Interpreter { private sealed interface TagInterpreter {
interface Factory : Interpreter.Factory { fun populate(rawSong: RawSong)
override fun wrap(cursor: Cursor): TagInterpreter
interface Factory {
val projection: Array<String>
fun wrap(cursor: Cursor): TagInterpreter
} }
} }

View file

@ -163,7 +163,7 @@ private fun Fragment.launch(
suspend fun <E> SendChannel<E>.sendWithTimeout(element: E, timeout: Long = 10000) { suspend fun <E> SendChannel<E>.sendWithTimeout(element: E, timeout: Long = 10000) {
try { try {
withTimeout(timeout) { send(element) } withTimeout(timeout) { send(element) }
} catch (e: Exception) { } catch (e: TimeoutCancellationException) {
throw TimeoutException("Timed out sending element $element to channel: $e") throw TimeoutException("Timed out sending element $element to channel: $e")
} }
} }