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.Album
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.fs.DeviceFile import org.oxycblt.auxio.music.stack.fs.DeviceFile
import org.oxycblt.auxio.music.fs.Path
import org.oxycblt.auxio.music.info.Date import org.oxycblt.auxio.music.info.Date
import org.oxycblt.auxio.music.info.ReleaseType import org.oxycblt.auxio.music.info.ReleaseType

View file

@ -22,6 +22,10 @@ import dagger.Binds
import dagger.Module import dagger.Module
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent 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 @Module
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)

View file

@ -26,6 +26,7 @@ import kotlin.math.min
import org.oxycblt.auxio.image.extractor.CoverExtractor import org.oxycblt.auxio.image.extractor.CoverExtractor
import org.oxycblt.auxio.music.device.RawSong import org.oxycblt.auxio.music.device.RawSong
import org.oxycblt.auxio.music.info.Date import org.oxycblt.auxio.music.info.Date
import org.oxycblt.auxio.music.stack.extractor.TextTags
import org.oxycblt.auxio.util.nonZeroOrNull import org.oxycblt.auxio.util.nonZeroOrNull
import timber.log.Timber as L 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/>. * 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 android.content.Context
import androidx.room.Room 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.flow
import kotlinx.coroutines.flow.transform import kotlinx.coroutines.flow.transform
import org.oxycblt.auxio.music.device.RawSong import org.oxycblt.auxio.music.device.RawSong
import org.oxycblt.auxio.music.fs.DeviceFile import org.oxycblt.auxio.music.stack.fs.DeviceFile
import org.oxycblt.auxio.music.metadata.TagResult import org.oxycblt.auxio.music.stack.extractor.TagResult
import javax.inject.Inject import javax.inject.Inject
interface TagCache { interface TagCache {

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * 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.Dao
import androidx.room.Database 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 android.os.HandlerThread
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
@ -9,7 +9,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import org.oxycblt.auxio.music.device.RawSong 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 java.util.concurrent.Future
import javax.inject.Inject import javax.inject.Inject
import timber.log.Timber as L import timber.log.Timber as L

View file

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

View file

@ -16,12 +16,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * 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.common.Metadata
import androidx.media3.extractor.metadata.id3.InternalFrame import androidx.media3.extractor.metadata.id3.InternalFrame
import androidx.media3.extractor.metadata.id3.TextInformationFrame import androidx.media3.extractor.metadata.id3.TextInformationFrame
import androidx.media3.extractor.metadata.vorbis.VorbisComment 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. * 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/>. * 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.ContentResolver
import android.content.Context import android.content.Context
@ -31,6 +31,9 @@ import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flatMapMerge import kotlinx.coroutines.flow.flatMapMerge
import kotlinx.coroutines.flow.flattenMerge import kotlinx.coroutines.flow.flattenMerge
import kotlinx.coroutines.flow.flow 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 { interface DeviceFiles {
fun explore(uris: Flow<Uri>): Flow<DeviceFile> 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.junit.Test
import org.oxycblt.auxio.music.device.RawSong import org.oxycblt.auxio.music.device.RawSong
import org.oxycblt.auxio.music.info.Date 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 { class CacheRepositoryTest {
@Test @Test

View file

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