music: connect saf indexer to libraries

Largely temporary, to be replaced with Interpreter
This commit is contained in:
Alexander Capehart 2024-11-19 15:17:05 -07:00
parent e51b2817e9
commit f76eafc9d4
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
8 changed files with 41 additions and 48 deletions

View file

@ -37,7 +37,7 @@ import org.oxycblt.auxio.music.cache.CacheRepository
import org.oxycblt.auxio.music.device.DeviceLibrary
import org.oxycblt.auxio.music.device.RawSong
import org.oxycblt.auxio.music.info.Name
import org.oxycblt.auxio.music.stack.interpreter.Separators
import org.oxycblt.auxio.music.metadata.Separators
import org.oxycblt.auxio.music.user.MutableUserLibrary
import org.oxycblt.auxio.music.user.UserLibrary
import org.oxycblt.auxio.util.DEFAULT_TIMEOUT

View file

@ -24,6 +24,7 @@ import android.provider.OpenableColumns
import java.util.UUID
import javax.inject.Inject
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
@ -35,7 +36,7 @@ import org.oxycblt.auxio.music.stack.fs.Path
import org.oxycblt.auxio.music.stack.fs.contentResolverSafe
import org.oxycblt.auxio.music.stack.fs.useQuery
import org.oxycblt.auxio.music.info.Name
import org.oxycblt.auxio.music.stack.interpreter.Separators
import org.oxycblt.auxio.music.metadata.Separators
import org.oxycblt.auxio.util.forEachWithTimeout
import org.oxycblt.auxio.util.sendWithTimeout
import org.oxycblt.auxio.util.unlikelyToBeNull
@ -123,8 +124,8 @@ interface DeviceLibrary {
* the instance.
*/
suspend fun create(
rawSongs: Channel<RawSong>,
processedSongs: Channel<RawSong>,
rawSongs: Flow<RawSong>,
onSongProcessed: () -> Unit,
separators: Separators,
nameFactory: Name.Known.Factory
): DeviceLibraryImpl
@ -133,8 +134,8 @@ interface DeviceLibrary {
class DeviceLibraryFactoryImpl @Inject constructor() : DeviceLibrary.Factory {
override suspend fun create(
rawSongs: Channel<RawSong>,
processedSongs: Channel<RawSong>,
rawSongs: Flow<RawSong>,
onSongProcessed: () -> Unit,
separators: Separators,
nameFactory: Name.Known.Factory
): DeviceLibraryImpl {
@ -144,7 +145,7 @@ class DeviceLibraryFactoryImpl @Inject constructor() : DeviceLibrary.Factory {
val genreGrouping = mutableMapOf<String?, Grouping<RawGenre, SongImpl>>()
// All music information is grouped as it is indexed by other components.
rawSongs.forEachWithTimeout { rawSong ->
rawSongs.collect { rawSong ->
val song = SongImpl(rawSong, nameFactory, separators)
// At times the indexer produces duplicate songs, try to filter these. Comparing by
// UID is sufficient for something like this, and also prevents collisions from
@ -153,11 +154,8 @@ class DeviceLibraryFactoryImpl @Inject constructor() : DeviceLibrary.Factory {
L.w(
"Duplicate song found: ${song.path} " +
"collides with ${unlikelyToBeNull(songGrouping[song.uid]).path}")
// We still want to say that we "processed" the song so that the user doesn't
// get confused at why the bar was only partly filled by the end of the loading
// process.
processedSongs.sendWithTimeout(rawSong)
return@forEachWithTimeout
onSongProcessed()
return@collect
}
songGrouping[song.uid] = song
@ -180,7 +178,7 @@ class DeviceLibraryFactoryImpl @Inject constructor() : DeviceLibrary.Factory {
appendToNameTree(song, rawGenre, genreGrouping) { old, new -> new.name < old.name }
}
processedSongs.sendWithTimeout(rawSong)
onSongProcessed()
}
// Now that all songs are processed, also process albums and group them into their
@ -343,7 +341,7 @@ class DeviceLibraryImpl(
) : DeviceLibrary {
// Use a mapping to make finding information based on it's UID much faster.
private val songUidMap = buildMap { songs.forEach { put(it.uid, it.finalize()) } }
private val songPathMap = buildMap { songs.forEach { put(it.path, it) } }
// private val songPathMap = buildMap { songs.forEach { put(it.path, it) } }
private val albumUidMap = buildMap { albums.forEach { put(it.uid, it.finalize()) } }
private val artistUidMap = buildMap { artists.forEach { put(it.uid, it.finalize()) } }
private val genreUidMap = buildMap { genres.forEach { put(it.uid, it.finalize()) } }
@ -365,17 +363,17 @@ class DeviceLibraryImpl(
override fun findGenre(uid: Music.UID): Genre? = genreUidMap[uid]
override fun findSongByPath(path: Path) = songPathMap[path]
override fun findSongByPath(path: Path) = null
override fun findSongForUri(context: Context, uri: Uri) =
context.contentResolverSafe.useQuery(
uri, arrayOf(OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE)) { cursor ->
cursor.moveToFirst()
// We are weirdly limited to DISPLAY_NAME and SIZE when trying to locate a
// song. Do what we can to hopefully find the song the user wanted to open.
val displayName =
cursor.getString(cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME))
val size = cursor.getLong(cursor.getColumnIndexOrThrow(OpenableColumns.SIZE))
songs.find { it.path.name == displayName && it.size == size }
}
override fun findSongForUri(context: Context, uri: Uri) = null
// context.contentResolverSafe.useQuery(
// uri, arrayOf(OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE)) { cursor ->
// cursor.moveToFirst()
// // We are weirdly limited to DISPLAY_NAME and SIZE when trying to locate a
// // song. Do what we can to hopefully find the song the user wanted to open.
// val displayName =
// cursor.getString(cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME))
// val size = cursor.getLong(cursor.getColumnIndexOrThrow(OpenableColumns.SIZE))
// songs.find { it.path.name == displayName && it.size == size }
// }
}

View file

@ -36,7 +36,7 @@ import org.oxycblt.auxio.music.info.Date
import org.oxycblt.auxio.music.info.Disc
import org.oxycblt.auxio.music.info.Name
import org.oxycblt.auxio.music.info.ReleaseType
import org.oxycblt.auxio.music.stack.interpreter.Separators
import org.oxycblt.auxio.music.metadata.Separators
import org.oxycblt.auxio.music.stack.extractor.parseId3GenreNames
import org.oxycblt.auxio.playback.replaygain.ReplayGainAdjustment
import org.oxycblt.auxio.util.positiveOrNull

View file

@ -1,7 +1,5 @@
package org.oxycblt.auxio.music.stack.interpreter
package org.oxycblt.auxio.music.metadata
import org.oxycblt.auxio.music.metadata.CharSeparators
import org.oxycblt.auxio.music.metadata.NoSeparators
import org.oxycblt.auxio.music.stack.extractor.correctWhitespace
import org.oxycblt.auxio.music.stack.extractor.splitEscaped

View file

@ -29,7 +29,6 @@ import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogSeparatorsBinding
import org.oxycblt.auxio.music.MusicSettings
import org.oxycblt.auxio.music.stack.interpreter.Separators
import org.oxycblt.auxio.ui.ViewBindingMaterialDialogFragment
import timber.log.Timber as L

View file

@ -14,27 +14,33 @@ import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.toList
import org.oxycblt.auxio.music.device.DeviceLibrary
import org.oxycblt.auxio.music.device.RawSong
import org.oxycblt.auxio.music.info.Name
import org.oxycblt.auxio.music.metadata.Separators
import org.oxycblt.auxio.music.stack.cache.TagCache
import org.oxycblt.auxio.music.stack.extractor.ExoPlayerTagExtractor
import org.oxycblt.auxio.music.stack.extractor.TagResult
import org.oxycblt.auxio.music.stack.fs.DeviceFile
import org.oxycblt.auxio.music.stack.fs.DeviceFiles
import org.oxycblt.auxio.music.stack.interpreter.Interpreter
import org.oxycblt.auxio.music.user.UserLibrary
import javax.inject.Inject
interface Indexer {
suspend fun run(uris: List<Uri>): DeviceLibrary
suspend fun run(uris: List<Uri>, separators: Separators, nameFactory: Name.Known.Factory): LibraryResult
}
data class LibraryResult(val deviceLibrary: DeviceLibrary, val userLibrary: UserLibrary)
class IndexerImpl @Inject constructor(
private val deviceFiles: DeviceFiles,
private val tagCache: TagCache,
private val tagExtractor: ExoPlayerTagExtractor,
private val interpreter: Interpreter
private val deviceLibraryFactory: DeviceLibrary.Factory,
private val userLibraryFactory: UserLibrary.Factory
) : Indexer {
override suspend fun run(uris: List<Uri>) = coroutineScope {
override suspend fun run(uris: List<Uri>, separators: Separators, nameFactory: Name.Known.Factory) = coroutineScope {
val deviceFiles = deviceFiles.explore(uris.asFlow())
.flowOn(Dispatchers.IO)
.buffer()
@ -52,10 +58,12 @@ class IndexerImpl @Inject constructor(
started = SharingStarted.WhileSubscribed(),
replay = Int.MAX_VALUE
)
val tagWrite = async { tagCache.write(merge(cacheSongs, sharedExtractorSongs)) }
val deviceLibrary = async { interpreter.interpret(sharedExtractorSongs) }.await()
val tagWrite = async(Dispatchers.IO) { tagCache.write(merge(cacheSongs, sharedExtractorSongs)) }
val rawPlaylists = async(Dispatchers.IO) { userLibraryFactory.query() }
val deviceLibrary = deviceLibraryFactory.create(merge(cacheSongs, sharedExtractorSongs), {}, separators, nameFactory)
val userLibrary = userLibraryFactory.create(rawPlaylists.await(), deviceLibrary, nameFactory)
tagWrite.await()
deviceLibrary
LibraryResult(deviceLibrary, userLibrary)
}
private fun Flow<TagResult>.split(): Pair<Flow<DeviceFile>, Flow<RawSong>> {

View file

@ -1,9 +0,0 @@
package org.oxycblt.auxio.music.stack.interpreter
import kotlinx.coroutines.flow.Flow
import org.oxycblt.auxio.music.device.DeviceLibrary
import org.oxycblt.auxio.music.device.RawSong
interface Interpreter {
suspend fun interpret(rawSong: Flow<RawSong>): DeviceLibrary
}

View file

@ -20,7 +20,6 @@ package org.oxycblt.auxio.music.metadata
import org.junit.Assert.assertEquals
import org.junit.Test
import org.oxycblt.auxio.music.stack.interpreter.Separators
class SeparatorsTest {
@Test