music: connect saf indexer to libraries
Largely temporary, to be replaced with Interpreter
This commit is contained in:
parent
e51b2817e9
commit
f76eafc9d4
8 changed files with 41 additions and 48 deletions
|
@ -37,7 +37,7 @@ import org.oxycblt.auxio.music.cache.CacheRepository
|
||||||
import org.oxycblt.auxio.music.device.DeviceLibrary
|
import org.oxycblt.auxio.music.device.DeviceLibrary
|
||||||
import org.oxycblt.auxio.music.device.RawSong
|
import org.oxycblt.auxio.music.device.RawSong
|
||||||
import org.oxycblt.auxio.music.info.Name
|
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.MutableUserLibrary
|
||||||
import org.oxycblt.auxio.music.user.UserLibrary
|
import org.oxycblt.auxio.music.user.UserLibrary
|
||||||
import org.oxycblt.auxio.util.DEFAULT_TIMEOUT
|
import org.oxycblt.auxio.util.DEFAULT_TIMEOUT
|
||||||
|
|
|
@ -24,6 +24,7 @@ import android.provider.OpenableColumns
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import org.oxycblt.auxio.music.Album
|
import org.oxycblt.auxio.music.Album
|
||||||
import org.oxycblt.auxio.music.Artist
|
import org.oxycblt.auxio.music.Artist
|
||||||
import org.oxycblt.auxio.music.Genre
|
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.contentResolverSafe
|
||||||
import org.oxycblt.auxio.music.stack.fs.useQuery
|
import org.oxycblt.auxio.music.stack.fs.useQuery
|
||||||
import org.oxycblt.auxio.music.info.Name
|
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.forEachWithTimeout
|
||||||
import org.oxycblt.auxio.util.sendWithTimeout
|
import org.oxycblt.auxio.util.sendWithTimeout
|
||||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||||
|
@ -123,8 +124,8 @@ interface DeviceLibrary {
|
||||||
* the instance.
|
* the instance.
|
||||||
*/
|
*/
|
||||||
suspend fun create(
|
suspend fun create(
|
||||||
rawSongs: Channel<RawSong>,
|
rawSongs: Flow<RawSong>,
|
||||||
processedSongs: Channel<RawSong>,
|
onSongProcessed: () -> Unit,
|
||||||
separators: Separators,
|
separators: Separators,
|
||||||
nameFactory: Name.Known.Factory
|
nameFactory: Name.Known.Factory
|
||||||
): DeviceLibraryImpl
|
): DeviceLibraryImpl
|
||||||
|
@ -133,8 +134,8 @@ interface DeviceLibrary {
|
||||||
|
|
||||||
class DeviceLibraryFactoryImpl @Inject constructor() : DeviceLibrary.Factory {
|
class DeviceLibraryFactoryImpl @Inject constructor() : DeviceLibrary.Factory {
|
||||||
override suspend fun create(
|
override suspend fun create(
|
||||||
rawSongs: Channel<RawSong>,
|
rawSongs: Flow<RawSong>,
|
||||||
processedSongs: Channel<RawSong>,
|
onSongProcessed: () -> Unit,
|
||||||
separators: Separators,
|
separators: Separators,
|
||||||
nameFactory: Name.Known.Factory
|
nameFactory: Name.Known.Factory
|
||||||
): DeviceLibraryImpl {
|
): DeviceLibraryImpl {
|
||||||
|
@ -144,7 +145,7 @@ class DeviceLibraryFactoryImpl @Inject constructor() : DeviceLibrary.Factory {
|
||||||
val genreGrouping = mutableMapOf<String?, Grouping<RawGenre, SongImpl>>()
|
val genreGrouping = mutableMapOf<String?, Grouping<RawGenre, SongImpl>>()
|
||||||
|
|
||||||
// All music information is grouped as it is indexed by other components.
|
// All music information is grouped as it is indexed by other components.
|
||||||
rawSongs.forEachWithTimeout { rawSong ->
|
rawSongs.collect { rawSong ->
|
||||||
val song = SongImpl(rawSong, nameFactory, separators)
|
val song = SongImpl(rawSong, nameFactory, separators)
|
||||||
// At times the indexer produces duplicate songs, try to filter these. Comparing by
|
// 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
|
// UID is sufficient for something like this, and also prevents collisions from
|
||||||
|
@ -153,11 +154,8 @@ class DeviceLibraryFactoryImpl @Inject constructor() : DeviceLibrary.Factory {
|
||||||
L.w(
|
L.w(
|
||||||
"Duplicate song found: ${song.path} " +
|
"Duplicate song found: ${song.path} " +
|
||||||
"collides with ${unlikelyToBeNull(songGrouping[song.uid]).path}")
|
"collides with ${unlikelyToBeNull(songGrouping[song.uid]).path}")
|
||||||
// We still want to say that we "processed" the song so that the user doesn't
|
onSongProcessed()
|
||||||
// get confused at why the bar was only partly filled by the end of the loading
|
return@collect
|
||||||
// process.
|
|
||||||
processedSongs.sendWithTimeout(rawSong)
|
|
||||||
return@forEachWithTimeout
|
|
||||||
}
|
}
|
||||||
songGrouping[song.uid] = song
|
songGrouping[song.uid] = song
|
||||||
|
|
||||||
|
@ -180,7 +178,7 @@ class DeviceLibraryFactoryImpl @Inject constructor() : DeviceLibrary.Factory {
|
||||||
appendToNameTree(song, rawGenre, genreGrouping) { old, new -> new.name < old.name }
|
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
|
// Now that all songs are processed, also process albums and group them into their
|
||||||
|
@ -343,7 +341,7 @@ class DeviceLibraryImpl(
|
||||||
) : DeviceLibrary {
|
) : DeviceLibrary {
|
||||||
// Use a mapping to make finding information based on it's UID much faster.
|
// 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 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 albumUidMap = buildMap { albums.forEach { put(it.uid, it.finalize()) } }
|
||||||
private val artistUidMap = buildMap { artists.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()) } }
|
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 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) =
|
override fun findSongForUri(context: Context, uri: Uri) = null
|
||||||
context.contentResolverSafe.useQuery(
|
// context.contentResolverSafe.useQuery(
|
||||||
uri, arrayOf(OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE)) { cursor ->
|
// uri, arrayOf(OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE)) { cursor ->
|
||||||
cursor.moveToFirst()
|
// cursor.moveToFirst()
|
||||||
// We are weirdly limited to DISPLAY_NAME and SIZE when trying to locate a
|
// // 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.
|
// // song. Do what we can to hopefully find the song the user wanted to open.
|
||||||
val displayName =
|
// val displayName =
|
||||||
cursor.getString(cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME))
|
// cursor.getString(cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME))
|
||||||
val size = cursor.getLong(cursor.getColumnIndexOrThrow(OpenableColumns.SIZE))
|
// val size = cursor.getLong(cursor.getColumnIndexOrThrow(OpenableColumns.SIZE))
|
||||||
songs.find { it.path.name == displayName && it.size == size }
|
// songs.find { it.path.name == displayName && it.size == size }
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ import org.oxycblt.auxio.music.info.Date
|
||||||
import org.oxycblt.auxio.music.info.Disc
|
import org.oxycblt.auxio.music.info.Disc
|
||||||
import org.oxycblt.auxio.music.info.Name
|
import org.oxycblt.auxio.music.info.Name
|
||||||
import org.oxycblt.auxio.music.info.ReleaseType
|
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.music.stack.extractor.parseId3GenreNames
|
||||||
import org.oxycblt.auxio.playback.replaygain.ReplayGainAdjustment
|
import org.oxycblt.auxio.playback.replaygain.ReplayGainAdjustment
|
||||||
import org.oxycblt.auxio.util.positiveOrNull
|
import org.oxycblt.auxio.util.positiveOrNull
|
||||||
|
|
|
@ -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.correctWhitespace
|
||||||
import org.oxycblt.auxio.music.stack.extractor.splitEscaped
|
import org.oxycblt.auxio.music.stack.extractor.splitEscaped
|
||||||
|
|
|
@ -29,7 +29,6 @@ import org.oxycblt.auxio.BuildConfig
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.databinding.DialogSeparatorsBinding
|
import org.oxycblt.auxio.databinding.DialogSeparatorsBinding
|
||||||
import org.oxycblt.auxio.music.MusicSettings
|
import org.oxycblt.auxio.music.MusicSettings
|
||||||
import org.oxycblt.auxio.music.stack.interpreter.Separators
|
|
||||||
import org.oxycblt.auxio.ui.ViewBindingMaterialDialogFragment
|
import org.oxycblt.auxio.ui.ViewBindingMaterialDialogFragment
|
||||||
import timber.log.Timber as L
|
import timber.log.Timber as L
|
||||||
|
|
||||||
|
|
|
@ -14,27 +14,33 @@ import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.merge
|
import kotlinx.coroutines.flow.merge
|
||||||
import kotlinx.coroutines.flow.shareIn
|
import kotlinx.coroutines.flow.shareIn
|
||||||
|
import kotlinx.coroutines.flow.toList
|
||||||
import org.oxycblt.auxio.music.device.DeviceLibrary
|
import org.oxycblt.auxio.music.device.DeviceLibrary
|
||||||
import org.oxycblt.auxio.music.device.RawSong
|
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.cache.TagCache
|
||||||
import org.oxycblt.auxio.music.stack.extractor.ExoPlayerTagExtractor
|
import org.oxycblt.auxio.music.stack.extractor.ExoPlayerTagExtractor
|
||||||
import org.oxycblt.auxio.music.stack.extractor.TagResult
|
import org.oxycblt.auxio.music.stack.extractor.TagResult
|
||||||
import org.oxycblt.auxio.music.stack.fs.DeviceFile
|
import org.oxycblt.auxio.music.stack.fs.DeviceFile
|
||||||
import org.oxycblt.auxio.music.stack.fs.DeviceFiles
|
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
|
import javax.inject.Inject
|
||||||
|
|
||||||
interface Indexer {
|
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(
|
class IndexerImpl @Inject constructor(
|
||||||
private val deviceFiles: DeviceFiles,
|
private val deviceFiles: DeviceFiles,
|
||||||
private val tagCache: TagCache,
|
private val tagCache: TagCache,
|
||||||
private val tagExtractor: ExoPlayerTagExtractor,
|
private val tagExtractor: ExoPlayerTagExtractor,
|
||||||
private val interpreter: Interpreter
|
private val deviceLibraryFactory: DeviceLibrary.Factory,
|
||||||
|
private val userLibraryFactory: UserLibrary.Factory
|
||||||
) : Indexer {
|
) : 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())
|
val deviceFiles = deviceFiles.explore(uris.asFlow())
|
||||||
.flowOn(Dispatchers.IO)
|
.flowOn(Dispatchers.IO)
|
||||||
.buffer()
|
.buffer()
|
||||||
|
@ -52,10 +58,12 @@ class IndexerImpl @Inject constructor(
|
||||||
started = SharingStarted.WhileSubscribed(),
|
started = SharingStarted.WhileSubscribed(),
|
||||||
replay = Int.MAX_VALUE
|
replay = Int.MAX_VALUE
|
||||||
)
|
)
|
||||||
val tagWrite = async { tagCache.write(merge(cacheSongs, sharedExtractorSongs)) }
|
val tagWrite = async(Dispatchers.IO) { tagCache.write(merge(cacheSongs, sharedExtractorSongs)) }
|
||||||
val deviceLibrary = async { interpreter.interpret(sharedExtractorSongs) }.await()
|
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()
|
tagWrite.await()
|
||||||
deviceLibrary
|
LibraryResult(deviceLibrary, userLibrary)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Flow<TagResult>.split(): Pair<Flow<DeviceFile>, Flow<RawSong>> {
|
private fun Flow<TagResult>.split(): Pair<Flow<DeviceFile>, Flow<RawSong>> {
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -20,7 +20,6 @@ package org.oxycblt.auxio.music.metadata
|
||||||
|
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.oxycblt.auxio.music.stack.interpreter.Separators
|
|
||||||
|
|
||||||
class SeparatorsTest {
|
class SeparatorsTest {
|
||||||
@Test
|
@Test
|
||||||
|
|
Loading…
Reference in a new issue