music: refactor saf loader into new module

This commit is contained in:
Alexander Capehart 2024-11-19 13:36:48 -07:00
parent 01a5e87a77
commit b651a3be03
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
14 changed files with 100 additions and 13 deletions

View file

@ -22,8 +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.stack.fs.DeviceFile
import org.oxycblt.auxio.music.info.Date
import org.oxycblt.auxio.music.info.ReleaseType

View file

@ -22,6 +22,10 @@ import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import org.oxycblt.auxio.music.stack.extractor.ExoPlayerTagExtractor
import org.oxycblt.auxio.music.stack.extractor.ExoPlayerTagExtractorImpl
import org.oxycblt.auxio.music.stack.extractor.TagInterpreter2
import org.oxycblt.auxio.music.stack.extractor.TagInterpreter2Impl
@Module
@InstallIn(SingletonComponent::class)

View file

@ -26,6 +26,7 @@ import kotlin.math.min
import org.oxycblt.auxio.image.extractor.CoverExtractor
import org.oxycblt.auxio.music.device.RawSong
import org.oxycblt.auxio.music.info.Date
import org.oxycblt.auxio.music.stack.extractor.TextTags
import org.oxycblt.auxio.util.nonZeroOrNull
import timber.log.Timber as L

View file

@ -0,0 +1,66 @@
package org.oxycblt.auxio.music.stack
import android.net.Uri
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.shareIn
import org.oxycblt.auxio.music.device.DeviceLibrary
import org.oxycblt.auxio.music.device.RawSong
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 javax.inject.Inject
interface Indexer {
suspend fun run(uris: List<Uri>): DeviceLibrary
}
class IndexerImpl @Inject constructor(
private val deviceFiles: DeviceFiles,
private val tagCache: TagCache,
private val tagExtractor: ExoPlayerTagExtractor,
private val interpreter: Interpreter
) : Indexer {
override suspend fun run(uris: List<Uri>) = coroutineScope {
val deviceFiles = deviceFiles.explore(uris.asFlow())
.flowOn(Dispatchers.IO)
.buffer()
val tagRead = tagCache.read(deviceFiles)
.flowOn(Dispatchers.IO)
.buffer()
val (cacheFiles, cacheSongs) = tagRead.split()
val tagExtractor =
tagExtractor.process(cacheFiles)
.flowOn(Dispatchers.IO)
.buffer()
val (_, extractorSongs) = tagExtractor.split()
val sharedExtractorSongs = extractorSongs.shareIn(
CoroutineScope(Dispatchers.Main),
started = SharingStarted.WhileSubscribed(),
replay = Int.MAX_VALUE
)
val tagWrite = async { tagCache.write(merge(cacheSongs, sharedExtractorSongs)) }
val deviceLibrary = async { interpreter.interpret(sharedExtractorSongs) }.await()
tagWrite.await()
deviceLibrary
}
private fun Flow<TagResult>.split(): Pair<Flow<DeviceFile>, Flow<RawSong>> {
val files = filterIsInstance<TagResult.Miss>().map { it.file }
val songs = filterIsInstance<TagResult.Hit>().map { it.rawSong }
return files to songs
}
}

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.music.cache
package org.oxycblt.auxio.music.stack.cache
import android.content.Context
import androidx.room.Room

View file

@ -1,11 +1,10 @@
package org.oxycblt.auxio.music.cache
package org.oxycblt.auxio.music.stack.cache
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.transform
import org.oxycblt.auxio.music.device.RawSong
import org.oxycblt.auxio.music.fs.DeviceFile
import org.oxycblt.auxio.music.metadata.TagResult
import org.oxycblt.auxio.music.stack.fs.DeviceFile
import org.oxycblt.auxio.music.stack.extractor.TagResult
import javax.inject.Inject
interface TagCache {

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.music.cache
package org.oxycblt.auxio.music.stack.cache
import androidx.room.Dao
import androidx.room.Database

View file

@ -1,4 +1,4 @@
package org.oxycblt.auxio.music.metadata
package org.oxycblt.auxio.music.stack.extractor
import android.os.HandlerThread
import androidx.media3.common.MediaItem
@ -9,7 +9,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.flow
import org.oxycblt.auxio.music.device.RawSong
import org.oxycblt.auxio.music.fs.DeviceFile
import org.oxycblt.auxio.music.stack.fs.DeviceFile
import java.util.concurrent.Future
import javax.inject.Inject
import timber.log.Timber as L

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.music.metadata
package org.oxycblt.auxio.music.stack.extractor
import androidx.core.text.isDigitsOnly
import androidx.media3.exoplayer.MetadataRetriever
@ -24,6 +24,8 @@ import javax.inject.Inject
import org.oxycblt.auxio.image.extractor.CoverExtractor
import org.oxycblt.auxio.music.device.RawSong
import org.oxycblt.auxio.music.info.Date
import org.oxycblt.auxio.music.metadata.parseId3v2PositionField
import org.oxycblt.auxio.music.metadata.parseVorbisPositionField
import org.oxycblt.auxio.util.nonZeroOrNull
/**

View file

@ -16,12 +16,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.music.metadata
package org.oxycblt.auxio.music.stack.extractor
import androidx.media3.common.Metadata
import androidx.media3.extractor.metadata.id3.InternalFrame
import androidx.media3.extractor.metadata.id3.TextInformationFrame
import androidx.media3.extractor.metadata.vorbis.VorbisComment
import org.oxycblt.auxio.music.metadata.correctWhitespace
/**
* Processing wrapper for [Metadata] that allows organized access to text-based audio tags.

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.music.fs
package org.oxycblt.auxio.music.stack.fs
import android.content.ContentResolver
import android.content.Context
@ -31,6 +31,9 @@ import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flatMapMerge
import kotlinx.coroutines.flow.flattenMerge
import kotlinx.coroutines.flow.flow
import org.oxycblt.auxio.music.fs.Components
import org.oxycblt.auxio.music.fs.contentResolverSafe
import org.oxycblt.auxio.music.fs.useQuery
interface DeviceFiles {
fun explore(uris: Flow<Uri>): Flow<DeviceFile>

View file

@ -0,0 +1,9 @@
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

@ -33,6 +33,8 @@ import org.junit.Assert.assertTrue
import org.junit.Test
import org.oxycblt.auxio.music.device.RawSong
import org.oxycblt.auxio.music.info.Date
import org.oxycblt.auxio.music.stack.cache.TagDao
import org.oxycblt.auxio.music.stack.cache.Tags
class CacheRepositoryTest {
@Test

View file

@ -27,6 +27,7 @@ import androidx.media3.extractor.metadata.vorbis.VorbisComment
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import org.oxycblt.auxio.music.stack.extractor.TextTags
class TextTagsTest {
@Test