musikr: re-implement playlist loading

This commit is contained in:
Alexander Capehart 2024-12-16 18:45:51 -05:00
parent 50bfe9926b
commit 6e3b03d4c6
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
7 changed files with 91 additions and 27 deletions

View file

@ -39,6 +39,8 @@ import org.oxycblt.musikr.Storage
import org.oxycblt.musikr.cache.Cache
import org.oxycblt.musikr.cache.CacheDatabase
import org.oxycblt.musikr.cover.StoredCovers
import org.oxycblt.musikr.playlist.db.PlaylistDatabase
import org.oxycblt.musikr.playlist.db.StoredPlaylists
import org.oxycblt.musikr.tag.interpret.Naming
import org.oxycblt.musikr.tag.interpret.Separators
import timber.log.Timber as L
@ -213,6 +215,7 @@ constructor(
private val musikr: Musikr,
@ApplicationContext private val context: Context,
private val cacheDatabase: CacheDatabase,
private val playlistDatabase: PlaylistDatabase,
private val musicSettings: MusicSettings
) : MusicRepository {
private val updateListeners = mutableListOf<MusicRepository.UpdateListener>()
@ -361,10 +364,10 @@ constructor(
val storage =
if (withCache) {
Storage(Cache.full(cacheDatabase), StoredCovers.from(context, "covers"))
Storage(Cache.full(cacheDatabase), StoredCovers.from(context, "covers"), StoredPlaylists.from(playlistDatabase))
} else {
// TODO: Revisioned covers
Storage(Cache.writeOnly(cacheDatabase), StoredCovers.from(context, "covers"))
// TODO: Revisioned cache (as a stateful extension of musikr)
Storage(Cache.writeOnly(cacheDatabase), StoredCovers.from(context, "covers"), StoredPlaylists.from(playlistDatabase))
}
val newLibrary =
musikr.run(

View file

@ -20,9 +20,10 @@ package org.oxycblt.musikr
import org.oxycblt.musikr.cache.Cache
import org.oxycblt.musikr.cover.StoredCovers
import org.oxycblt.musikr.playlist.db.StoredPlaylists
import org.oxycblt.musikr.tag.interpret.Naming
import org.oxycblt.musikr.tag.interpret.Separators
data class Storage(val cache: Cache, val storedCovers: StoredCovers)
data class Storage(val cache: Cache, val storedCovers: StoredCovers, val storedPlaylists: StoredPlaylists)
data class Interpretation(val naming: Naming, val separators: Separators)

View file

@ -70,7 +70,7 @@ private class MusikrImpl(
var extractedCount = 0
val explored =
exploreStep
.explore(locations)
.explore(locations, storage)
.buffer(Channel.UNLIMITED)
.onStart { onProgress(IndexingProgress.Songs(0, 0)) }
.onEach { onProgress(IndexingProgress.Songs(extractedCount, ++exploredCount)) }

View file

@ -22,23 +22,35 @@ import android.content.Context
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.merge
import org.oxycblt.musikr.Storage
import org.oxycblt.musikr.fs.MusicLocation
import org.oxycblt.musikr.fs.DeviceFile
import org.oxycblt.musikr.fs.query.DeviceFiles
import org.oxycblt.musikr.playlist.PlaylistFile
import org.oxycblt.musikr.playlist.db.StoredPlaylists
import org.oxycblt.musikr.playlist.m3u.M3U
internal interface ExploreStep {
fun explore(locations: List<MusicLocation>): Flow<ExploreNode>
fun explore(locations: List<MusicLocation>, storage: Storage): Flow<ExploreNode>
companion object {
fun from(context: Context): ExploreStep = ExploreStepImpl(DeviceFiles.from(context))
fun from(context: Context): ExploreStep =
ExploreStepImpl(DeviceFiles.from(context))
}
}
private class ExploreStepImpl(private val deviceFiles: DeviceFiles) : ExploreStep {
override fun explore(locations: List<MusicLocation>) =
private class ExploreStepImpl(
private val deviceFiles: DeviceFiles
) : ExploreStep {
override fun explore(locations: List<MusicLocation>, storage: Storage): Flow<ExploreNode> {
val audios =
deviceFiles
.explore(locations.asFlow())
.mapNotNull {
@ -49,8 +61,18 @@ private class ExploreStepImpl(private val deviceFiles: DeviceFiles) : ExploreSte
}
}
.flowOn(Dispatchers.IO)
.buffer()
val playlists =
flow { emitAll(storage.storedPlaylists.read().asFlow()) }
.map { ExploreNode.Playlist(it) }
.flowOn(Dispatchers.IO)
.buffer()
return merge(audios, playlists)
}
}
internal sealed interface ExploreNode {
data class Audio(val file: DeviceFile) : ExploreNode
data class Playlist(val file: PlaylistFile) : ExploreNode
}

View file

@ -24,7 +24,7 @@ import org.oxycblt.musikr.Song
data class PlaylistFile(
val name: String,
val songPointers: List<SongPointer>,
val editor: PlaylistHandle
val handle: PlaylistHandle
)
sealed interface SongPointer {

View file

@ -0,0 +1,36 @@
package org.oxycblt.musikr.playlist.db
import org.oxycblt.musikr.Music
import org.oxycblt.musikr.Song
import org.oxycblt.musikr.playlist.PlaylistHandle
import java.util.UUID
internal class StoredPlaylistHandle(
private val playlistInfo: PlaylistInfo,
private val playlistDao: PlaylistDao
) : PlaylistHandle {
override val uid = playlistInfo.playlistUid
override suspend fun rename(name: String) {
playlistDao.replacePlaylistInfo(playlistInfo.copy(name = name))
}
override suspend fun rewrite(songs: List<Song>) {
playlistDao.replacePlaylistSongs(
uid,
songs.map { PlaylistSong(it.uid) }
)
}
override suspend fun add(songs: List<Song>) {
playlistDao.insertPlaylistSongs(
uid,
songs.map { PlaylistSong(it.uid) }
)
}
override suspend fun delete() {
playlistDao.deletePlaylist(uid)
}
}

View file

@ -18,15 +18,11 @@
package org.oxycblt.musikr.playlist.db
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import org.oxycblt.musikr.playlist.PlaylistFile
import org.oxycblt.musikr.playlist.SongPointer
interface StoredPlaylists {
fun read(): Flow<PlaylistFile>
suspend fun read(): List<PlaylistFile>
companion object {
fun from(database: PlaylistDatabase): StoredPlaylists =
@ -35,5 +31,11 @@ interface StoredPlaylists {
}
private class StoredPlaylistsImpl(private val playlistDao: PlaylistDao) : StoredPlaylists {
override fun read() = flow { emitAll(playlistDao.readRawPlaylists().asFlow().map { TODO() }) }
override suspend fun read() = playlistDao.readRawPlaylists().map {
PlaylistFile(
it.playlistInfo.name,
it.songs.map { song -> SongPointer.UID(song.songUid) },
StoredPlaylistHandle(it.playlistInfo, playlistDao)
)
}
}