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.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
|
||||
|
|
|
@ -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 }
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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>> {
|
||||
|
|
|
@ -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.Test
|
||||
import org.oxycblt.auxio.music.stack.interpreter.Separators
|
||||
|
||||
class SeparatorsTest {
|
||||
@Test
|
||||
|
|
Loading…
Reference in a new issue