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.Cache
|
||||||
import org.oxycblt.musikr.cache.CacheDatabase
|
import org.oxycblt.musikr.cache.CacheDatabase
|
||||||
import org.oxycblt.musikr.cover.StoredCovers
|
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.Naming
|
||||||
import org.oxycblt.musikr.tag.interpret.Separators
|
import org.oxycblt.musikr.tag.interpret.Separators
|
||||||
import timber.log.Timber as L
|
import timber.log.Timber as L
|
||||||
|
@ -213,6 +215,7 @@ constructor(
|
||||||
private val musikr: Musikr,
|
private val musikr: Musikr,
|
||||||
@ApplicationContext private val context: Context,
|
@ApplicationContext private val context: Context,
|
||||||
private val cacheDatabase: CacheDatabase,
|
private val cacheDatabase: CacheDatabase,
|
||||||
|
private val playlistDatabase: PlaylistDatabase,
|
||||||
private val musicSettings: MusicSettings
|
private val musicSettings: MusicSettings
|
||||||
) : MusicRepository {
|
) : MusicRepository {
|
||||||
private val updateListeners = mutableListOf<MusicRepository.UpdateListener>()
|
private val updateListeners = mutableListOf<MusicRepository.UpdateListener>()
|
||||||
|
@ -361,10 +364,10 @@ constructor(
|
||||||
|
|
||||||
val storage =
|
val storage =
|
||||||
if (withCache) {
|
if (withCache) {
|
||||||
Storage(Cache.full(cacheDatabase), StoredCovers.from(context, "covers"))
|
Storage(Cache.full(cacheDatabase), StoredCovers.from(context, "covers"), StoredPlaylists.from(playlistDatabase))
|
||||||
} else {
|
} else {
|
||||||
// TODO: Revisioned covers
|
// TODO: Revisioned cache (as a stateful extension of musikr)
|
||||||
Storage(Cache.writeOnly(cacheDatabase), StoredCovers.from(context, "covers"))
|
Storage(Cache.writeOnly(cacheDatabase), StoredCovers.from(context, "covers"), StoredPlaylists.from(playlistDatabase))
|
||||||
}
|
}
|
||||||
val newLibrary =
|
val newLibrary =
|
||||||
musikr.run(
|
musikr.run(
|
||||||
|
|
|
@ -20,9 +20,10 @@ package org.oxycblt.musikr
|
||||||
|
|
||||||
import org.oxycblt.musikr.cache.Cache
|
import org.oxycblt.musikr.cache.Cache
|
||||||
import org.oxycblt.musikr.cover.StoredCovers
|
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.Naming
|
||||||
import org.oxycblt.musikr.tag.interpret.Separators
|
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)
|
data class Interpretation(val naming: Naming, val separators: Separators)
|
||||||
|
|
|
@ -70,7 +70,7 @@ private class MusikrImpl(
|
||||||
var extractedCount = 0
|
var extractedCount = 0
|
||||||
val explored =
|
val explored =
|
||||||
exploreStep
|
exploreStep
|
||||||
.explore(locations)
|
.explore(locations, storage)
|
||||||
.buffer(Channel.UNLIMITED)
|
.buffer(Channel.UNLIMITED)
|
||||||
.onStart { onProgress(IndexingProgress.Songs(0, 0)) }
|
.onStart { onProgress(IndexingProgress.Songs(0, 0)) }
|
||||||
.onEach { onProgress(IndexingProgress.Songs(extractedCount, ++exploredCount)) }
|
.onEach { onProgress(IndexingProgress.Songs(extractedCount, ++exploredCount)) }
|
||||||
|
|
|
@ -15,42 +15,64 @@
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* 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.musikr.pipeline
|
package org.oxycblt.musikr.pipeline
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.asFlow
|
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.flowOn
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.mapNotNull
|
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.MusicLocation
|
||||||
import org.oxycblt.musikr.fs.DeviceFile
|
import org.oxycblt.musikr.fs.DeviceFile
|
||||||
import org.oxycblt.musikr.fs.query.DeviceFiles
|
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
|
import org.oxycblt.musikr.playlist.m3u.M3U
|
||||||
|
|
||||||
internal interface ExploreStep {
|
internal interface ExploreStep {
|
||||||
fun explore(locations: List<MusicLocation>): Flow<ExploreNode>
|
fun explore(locations: List<MusicLocation>, storage: Storage): Flow<ExploreNode>
|
||||||
|
|
||||||
companion object {
|
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 {
|
private class ExploreStepImpl(
|
||||||
override fun explore(locations: List<MusicLocation>) =
|
private val deviceFiles: DeviceFiles
|
||||||
deviceFiles
|
) : ExploreStep {
|
||||||
.explore(locations.asFlow())
|
override fun explore(locations: List<MusicLocation>, storage: Storage): Flow<ExploreNode> {
|
||||||
.mapNotNull {
|
val audios =
|
||||||
when {
|
deviceFiles
|
||||||
it.mimeType == M3U.MIME_TYPE -> null
|
.explore(locations.asFlow())
|
||||||
it.mimeType.startsWith("audio/") -> ExploreNode.Audio(it)
|
.mapNotNull {
|
||||||
else -> null
|
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 {
|
internal sealed interface ExploreNode {
|
||||||
data class Audio(val file: DeviceFile) : 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(
|
data class PlaylistFile(
|
||||||
val name: String,
|
val name: String,
|
||||||
val songPointers: List<SongPointer>,
|
val songPointers: List<SongPointer>,
|
||||||
val editor: PlaylistHandle
|
val handle: PlaylistHandle
|
||||||
)
|
)
|
||||||
|
|
||||||
sealed interface SongPointer {
|
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
|
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.PlaylistFile
|
||||||
|
import org.oxycblt.musikr.playlist.SongPointer
|
||||||
|
|
||||||
interface StoredPlaylists {
|
interface StoredPlaylists {
|
||||||
fun read(): Flow<PlaylistFile>
|
suspend fun read(): List<PlaylistFile>
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun from(database: PlaylistDatabase): StoredPlaylists =
|
fun from(database: PlaylistDatabase): StoredPlaylists =
|
||||||
|
@ -35,5 +31,11 @@ interface StoredPlaylists {
|
||||||
}
|
}
|
||||||
|
|
||||||
private class StoredPlaylistsImpl(private val playlistDao: PlaylistDao) : 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