musikr: re-implement playlist loading
This commit is contained in:
parent
50bfe9926b
commit
6e3b03d4c6
7 changed files with 91 additions and 27 deletions
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)) }
|
||||
|
|
|
@ -22,35 +22,57 @@ 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>) =
|
||||
deviceFiles
|
||||
.explore(locations.asFlow())
|
||||
.mapNotNull {
|
||||
when {
|
||||
it.mimeType == M3U.MIME_TYPE -> null
|
||||
it.mimeType.startsWith("audio/") -> ExploreNode.Audio(it)
|
||||
else -> null
|
||||
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 {
|
||||
when {
|
||||
it.mimeType == M3U.MIME_TYPE -> null
|
||||
it.mimeType.startsWith("audio/") -> ExploreNode.Audio(it)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
.flowOn(Dispatchers.IO)
|
||||
.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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue