music: use saf fields in raw song

This commit is contained in:
Alexander Capehart 2024-11-19 10:16:09 -07:00
parent 5b447f7efb
commit cadd2d1231
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
3 changed files with 76 additions and 66 deletions

View file

@ -22,6 +22,7 @@ import java.util.UUID
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.fs.DeviceFile
import org.oxycblt.auxio.music.fs.Path
import org.oxycblt.auxio.music.info.Date
import org.oxycblt.auxio.music.info.ReleaseType
@ -32,23 +33,9 @@ import org.oxycblt.auxio.music.info.ReleaseType
* @author Alexander Capehart (OxygenCobalt)
*/
data class RawSong(
/**
* The ID of the [SongImpl]'s audio file, obtained from MediaStore. Note that this ID is highly
* unstable and should only be used for accessing the audio file.
*/
var mediaStoreId: Long? = null,
/** @see Song.dateAdded */
var dateAdded: Long? = null,
/** The latest date the [SongImpl]'s audio file was modified, as a unix epoch timestamp. */
var dateModified: Long? = null,
/** @see Song.path */
var path: Path? = null,
/** @see Song.size */
var size: Long? = null,
val file: DeviceFile,
/** @see Song.durationMs */
var durationMs: Long? = null,
/** @see Song.mimeType */
var extensionMimeType: String? = null,
/** @see Song.replayGainAdjustment */
var replayGainTrackAdjustment: Float? = null,
/** @see Song.replayGainAdjustment */

View file

@ -1,12 +1,29 @@
package org.oxycblt.auxio.music.fs
/*
* Copyright (c) 2024 Auxio Project
* DeviceFiles.kt is part of Auxio.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.music.fs
import android.content.ContentResolver
import android.content.Context
import android.net.Uri
import android.provider.DocumentsContract
import androidx.documentfile.provider.DocumentFile
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
@ -14,7 +31,6 @@ import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flatMapMerge
import kotlinx.coroutines.flow.flattenMerge
import kotlinx.coroutines.flow.flow
import javax.inject.Inject
interface DeviceFiles {
fun explore(uris: Flow<Uri>): Flow<DeviceFile>
@ -24,17 +40,15 @@ interface DeviceFiles {
class DeviceFilesImpl @Inject constructor(@ApplicationContext private val context: Context) :
DeviceFiles {
private val contentResolver = context.contentResolverSafe
override fun explore(uris: Flow<Uri>): Flow<DeviceFile> =
uris.flatMapMerge { rootUri ->
exploreImpl(contentResolver, rootUri, Components.nil())
}
uris.flatMapMerge { rootUri -> exploreImpl(contentResolver, rootUri, Components.nil()) }
private fun exploreImpl(
contentResolver: ContentResolver,
uri: Uri,
relativePath: Components
): Flow<DeviceFile> =
flow {
): Flow<DeviceFile> = flow {
contentResolver.useQuery(uri, PROJECTION) { cursor ->
val childUriIndex =
cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_DOCUMENT_ID)
@ -42,6 +56,9 @@ class DeviceFilesImpl @Inject constructor(@ApplicationContext private val contex
cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_DISPLAY_NAME)
val mimeTypeIndex =
cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_MIME_TYPE)
val sizeIndex = cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_SIZE)
val lastModifiedIndex =
cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_LAST_MODIFIED)
// Recurse into sub-directories as another flow
val recursions = mutableListOf<Flow<DeviceFile>>()
while (cursor.moveToNext()) {
@ -58,7 +75,9 @@ class DeviceFilesImpl @Inject constructor(@ApplicationContext private val contex
// 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, path))
val lastModified = cursor.getLong(lastModifiedIndex)
val size = cursor.getLong(sizeIndex)
emit(DeviceFile(childUri, mimeType, path, lastModified))
}
}
// Hypothetically, we could just emitAll as we recurse into a new directory,
@ -71,12 +90,15 @@ class DeviceFilesImpl @Inject constructor(@ApplicationContext private val contex
}
private companion object {
val PROJECTION = arrayOf(
val PROJECTION =
arrayOf(
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
DocumentsContract.Document.COLUMN_MIME_TYPE,
DocumentsContract.Document.COLUMN_SIZE,
DocumentsContract.Document.COLUMN_LAST_MODIFIED,
)
}
}
data class DeviceFile(val uri: Uri, val mimeType: String, val path: Components)
data class DeviceFile(val uri: Uri, val mimeType: String, val path: Components, val size: Long, val lastModified: Long)

View file

@ -88,13 +88,14 @@ inline fun <reified R> ContentResolver.mapQuery(
selector: String? = null,
args: Array<String>? = null,
crossinline transform: Cursor.() -> R
) = useQuery(uri, projection, selector, args) {
) =
useQuery(uri, projection, selector, args) {
sequence<R> {
while (it.moveToNext()) {
yield(it.transform())
}
}
}
}
/** Album art [MediaStore] database is not a built-in constant, have to define it ourselves. */
private val externalCoversUri = Uri.parse("content://media/external/audio/albumart")