Skip to content

Commit

Permalink
refactor: mapping table queries
Browse files Browse the repository at this point in the history
  • Loading branch information
zyrouge committed Feb 10, 2025
1 parent a0fd898 commit 6c6f73c
Show file tree
Hide file tree
Showing 24 changed files with 424 additions and 53 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.github.zyrouge.symphony.services.database

import io.github.zyrouge.symphony.Symphony
import io.github.zyrouge.symphony.services.database.store.ArtworkStore
import io.github.zyrouge.symphony.services.database.store.SongArtworkStore
import io.github.zyrouge.symphony.utils.KeyGenerator

class Database(symphony: Symphony) {
Expand All @@ -24,8 +24,6 @@ class Database(symphony: Symphony) {
val albumSongMapping get() = persistent.albumSongMapping()
val artists get() = persistent.artists()
val artistSongMapping get() = persistent.artistSongMapping()
val artworks = ArtworkStore(symphony)
val artworkIndices get() = persistent.artworkIndices()
val composers get() = persistent.composers()
val composerSongMapping get() = persistent.composerSongMapping()
val genres get() = persistent.genre()
Expand All @@ -35,6 +33,10 @@ class Database(symphony: Symphony) {
val mediaTreeSongFiles get() = persistent.mediaTreeSongFiles()
val playlists get() = persistent.playlists()
val playlistSongMapping get() = persistent.playlistSongMapping()
val songArtworks = SongArtworkStore(symphony)
val songArtworkIndices get() = persistent.songArtworkIndices()
val songLyrics get() = persistent.songLyrics()
val songQueueSongMapping get() = persistent.songQueueSongMapping()
val songQueue get() = persistent.songQueue()
val songs get() = persistent.songs()
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import io.github.zyrouge.symphony.services.database.store.AlbumSongMappingStore
import io.github.zyrouge.symphony.services.database.store.AlbumStore
import io.github.zyrouge.symphony.services.database.store.ArtistSongMappingStore
import io.github.zyrouge.symphony.services.database.store.ArtistStore
import io.github.zyrouge.symphony.services.database.store.ArtworkIndexStore
import io.github.zyrouge.symphony.services.database.store.ComposerSongMappingStore
import io.github.zyrouge.symphony.services.database.store.ComposerStore
import io.github.zyrouge.symphony.services.database.store.GenreSongMappingStore
Expand All @@ -20,7 +19,10 @@ import io.github.zyrouge.symphony.services.database.store.MediaTreeLyricFileStor
import io.github.zyrouge.symphony.services.database.store.MediaTreeSongFileStore
import io.github.zyrouge.symphony.services.database.store.PlaylistSongMappingStore
import io.github.zyrouge.symphony.services.database.store.PlaylistStore
import io.github.zyrouge.symphony.services.database.store.SongArtworkIndexStore
import io.github.zyrouge.symphony.services.database.store.SongLyricStore
import io.github.zyrouge.symphony.services.database.store.SongQueueSongMappingStore
import io.github.zyrouge.symphony.services.database.store.SongQueueStore
import io.github.zyrouge.symphony.services.database.store.SongStore
import io.github.zyrouge.symphony.services.groove.entities.Album
import io.github.zyrouge.symphony.services.groove.entities.AlbumArtistMapping
Expand All @@ -40,29 +42,33 @@ import io.github.zyrouge.symphony.services.groove.entities.PlaylistSongMapping
import io.github.zyrouge.symphony.services.groove.entities.Song
import io.github.zyrouge.symphony.services.groove.entities.SongArtworkIndex
import io.github.zyrouge.symphony.services.groove.entities.SongLyric
import io.github.zyrouge.symphony.services.groove.entities.SongQueue
import io.github.zyrouge.symphony.services.groove.entities.SongQueueSongMapping
import io.github.zyrouge.symphony.utils.RoomConvertors

@Database(
version = 1,
entities = [
Album::class,
AlbumArtistMapping::class,
AlbumComposerMapping::class,
AlbumSongMapping::class,
Artist::class,
Album::class,
ArtistSongMapping::class,
SongArtworkIndex::class,
Composer::class,
Artist::class,
ComposerSongMapping::class,
Genre::class,
Composer::class,
GenreSongMapping::class,
Genre::class,
MediaTreeFolder::class,
MediaTreeLyricFile::class,
MediaTreeSongFile::class,
Playlist::class,
PlaylistSongMapping::class,
Song::class,
Playlist::class,
SongArtworkIndex::class,
SongLyric::class,
SongQueueSongMapping::class,
SongQueue::class,
Song::class,
],
)
@TypeConverters(RoomConvertors::class)
Expand All @@ -73,7 +79,6 @@ abstract class PersistentDatabase : RoomDatabase() {
abstract fun albums(): AlbumStore
abstract fun artistSongMapping(): ArtistSongMappingStore
abstract fun artists(): ArtistStore
abstract fun artworkIndices(): ArtworkIndexStore
abstract fun composerSongMapping(): ComposerSongMappingStore
abstract fun composers(): ComposerStore
abstract fun genreSongMapping(): GenreSongMappingStore
Expand All @@ -83,7 +88,10 @@ abstract class PersistentDatabase : RoomDatabase() {
abstract fun mediaTreeSongFiles(): MediaTreeSongFileStore
abstract fun playlistSongMapping(): PlaylistSongMappingStore
abstract fun playlists(): PlaylistStore
abstract fun songArtworkIndices(): SongArtworkIndexStore
abstract fun songLyrics(): SongLyricStore
abstract fun songQueueSongMapping(): SongQueueSongMappingStore
abstract fun songQueue(): SongQueueStore
abstract fun songs(): SongStore

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,22 @@ import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import io.github.zyrouge.symphony.services.groove.entities.AlbumComposerMapping
import io.github.zyrouge.symphony.services.groove.repositories.SongRepository

@Dao
interface AlbumComposerMappingStore {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsert(vararg entities: AlbumComposerMapping)
}

fun AlbumComposerMappingStore.valuesMappedAsFlow(
songStore: SongStore,
id: String,
sortBy: SongRepository.SortBy,
sortReverse: Boolean,
) = songStore.valuesAsFlow(
sortBy,
sortReverse,
additionalClauseBeforeJoins = "JOIN ${AlbumComposerMapping.TABLE}.${AlbumComposerMapping.COLUMN_COMPOSER_ID} = ? ",
additionalArgsBeforeJoins = arrayOf(id),
)
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,22 @@ import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import io.github.zyrouge.symphony.services.groove.entities.ArtistSongMapping
import io.github.zyrouge.symphony.services.groove.repositories.SongRepository

@Dao
interface ArtistSongMappingStore {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsert(vararg entities: ArtistSongMapping)
}

fun ArtistSongMappingStore.valuesMappedAsFlow(
songStore: SongStore,
id: String,
sortBy: SongRepository.SortBy,
sortReverse: Boolean,
) = songStore.valuesAsFlow(
sortBy,
sortReverse,
additionalClauseBeforeJoins = "JOIN ${ArtistSongMapping.TABLE}.${ArtistSongMapping.COLUMN_ARTIST_ID} = ? ",
additionalArgsBeforeJoins = arrayOf(id),
)
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,22 @@ import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import io.github.zyrouge.symphony.services.groove.entities.ComposerSongMapping
import io.github.zyrouge.symphony.services.groove.repositories.SongRepository

@Dao
interface ComposerSongMappingStore {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsert(vararg entities: ComposerSongMapping)
}

fun ComposerSongMappingStore.valuesMappedAsFlow(
songStore: SongStore,
id: String,
sortBy: SongRepository.SortBy,
sortReverse: Boolean,
) = songStore.valuesAsFlow(
sortBy,
sortReverse,
additionalClauseBeforeJoins = "JOIN ${ComposerSongMapping.TABLE}.${ComposerSongMapping.COLUMN_COMPOSER_ID} = ? ",
additionalArgsBeforeJoins = arrayOf(id),
)
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,22 @@ import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import io.github.zyrouge.symphony.services.groove.entities.GenreSongMapping
import io.github.zyrouge.symphony.services.groove.repositories.SongRepository

@Dao
interface GenreSongMappingStore {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsert(vararg entities: GenreSongMapping)
}

fun GenreSongMappingStore.valuesMappedAsFlow(
songStore: SongStore,
id: String,
sortBy: SongRepository.SortBy,
sortReverse: Boolean,
) = songStore.valuesAsFlow(
sortBy,
sortReverse,
additionalClauseBeforeJoins = "JOIN ${GenreSongMapping.TABLE}.${GenreSongMapping.COLUMN_GENRE_ID} = ? ",
additionalArgsBeforeJoins = arrayOf(id),
)
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import io.github.zyrouge.symphony.services.groove.entities.PlaylistSongMapping
import io.github.zyrouge.symphony.services.groove.entities.Song
import io.github.zyrouge.symphony.services.groove.repositories.SongRepository
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.mapLatest

@Dao
interface PlaylistSongMappingStore {
Expand All @@ -13,3 +18,33 @@ interface PlaylistSongMappingStore {
@Query("DELETE FROM ${PlaylistSongMapping.TABLE} WHERE ${PlaylistSongMapping.COLUMN_PLAYLIST_ID} IN (:ids)")
suspend fun deletePlaylistIds(ids: Collection<String>)
}

@OptIn(ExperimentalCoroutinesApi::class)
fun PlaylistSongMappingStore.valuesMappedAsFlow(
songStore: SongStore,
id: String,
sortBy: SongRepository.SortBy,
sortReverse: Boolean,
): Flow<List<Song>> {
val query = songStore.valuesAsFlowQuery(
sortBy,
sortReverse,
additionalClauseBeforeJoins = "JOIN ${PlaylistSongMapping.TABLE} ON ${PlaylistSongMapping.TABLE}.${PlaylistSongMapping.COLUMN_PLAYLIST_ID} = ? AND ${PlaylistSongMapping.TABLE}.${PlaylistSongMapping.COLUMN_SONG_ID} = ${Song.COLUMN_ID} ",
additionalArgsBeforeJoins = arrayOf(id),
)
val entries = songStore.entriesAsPlaylistSongMappedFlowRaw(query)
return entries.mapLatest {
val list = mutableListOf<Song>()
var head = it.firstNotNullOfOrNull {
when {
it.value.mapping.isStart -> it.value
else -> null
}
}
while (head != null) {
list.add(head.song)
head = it[head.mapping.nextId]
}
list.toList()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ import androidx.room.Query
import io.github.zyrouge.symphony.services.groove.entities.SongArtworkIndex

@Dao
interface ArtworkIndexStore {
interface SongArtworkIndexStore {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsert(vararg entities: SongArtworkIndex): List<String>

@Query("SELECT * FROM ${SongArtworkIndex.TABLE}")
@Query("SELECT * FROM ${SongArtworkIndex.TABLE} WHERE ${SongArtworkIndex.COLUMN_SONG_ID} = :songId LIMIT 1")
fun findBySongId(songId: String): SongArtworkIndex?

@Query("SELECT * FROM ${SongArtworkIndex.TABLE} WHERE ${SongArtworkIndex.COLUMN_SONG_ID} != null")
fun entriesSongIdMapped(): Map<@MapColumn(SongArtworkIndex.COLUMN_SONG_ID) String, SongArtworkIndex>
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import io.github.zyrouge.symphony.Symphony
import io.github.zyrouge.symphony.services.database.adapters.FileTreeDatabaseAdapter
import java.nio.file.Paths

class ArtworkStore(val symphony: Symphony) {
private val path = Paths.get(symphony.applicationContext.dataDir.absolutePath, "covers")
class SongArtworkStore(val symphony: Symphony) {
private val path = Paths.get(symphony.applicationContext.dataDir.absolutePath, "song_artworks")
private val adapter = FileTreeDatabaseAdapter(path.toFile())

fun get(key: String) = adapter.get(key)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package io.github.zyrouge.symphony.services.database.store

import androidx.room.Dao
import androidx.room.Insert
import androidx.room.MapColumn
import androidx.room.Query
import androidx.room.RawQuery
import androidx.sqlite.db.SimpleSQLiteQuery
import androidx.sqlite.db.SupportSQLiteQuery
import io.github.zyrouge.symphony.services.groove.entities.Song
import io.github.zyrouge.symphony.services.groove.entities.SongQueueSongMapping
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.mapLatest

@Dao
interface SongQueueSongMappingStore {
@Insert
suspend fun insert(vararg entities: SongQueueSongMapping)

@Query("DELETE FROM ${SongQueueSongMapping.TABLE} WHERE ${SongQueueSongMapping.COLUMN_QUEUE_ID} IN (:ids)")
suspend fun deleteSongQueueIds(ids: Collection<String>)

@RawQuery(observedEntities = [Song::class, SongQueueSongMapping::class])
fun entriesAsFlowRaw(query: SupportSQLiteQuery): Flow<Map<@MapColumn(SongQueueSongMapping.COLUMN_SONG_ID) String, Song.AlongSongQueueMapping>>
}

fun SongQueueSongMappingStore.entriesAsFlow(queueId: String): Flow<Map<String, Song.AlongSongQueueMapping>> {
val query = "SELECT ${Song.TABLE}.*, " +
"${SongQueueSongMapping.TABLE}.* " +
"FROM ${SongQueueSongMapping.TABLE} " +
"WHERE ${SongQueueSongMapping.TABLE}.${SongQueueSongMapping.COLUMN_QUEUE_ID} = ? " +
"LEFT JOIN ${Song.TABLE} ON ${Song.TABLE}.${Song.COLUMN_ID} = ${SongQueueSongMapping.COLUMN_SONG_ID} " +
"ORDER BY ${SongQueueSongMapping.TABLE}.${SongQueueSongMapping.COLUMN_IS_HEAD} DESC"
val args = arrayOf(queueId)
return entriesAsFlowRaw(SimpleSQLiteQuery(query, args))
}

@OptIn(ExperimentalCoroutinesApi::class)
fun SongQueueSongMappingStore.transformEntriesAsValuesFlow(entries: Flow<Map<String, Song.AlongSongQueueMapping>>): Flow<List<Song>> {
return entries.mapLatest {
val list = mutableListOf<Song>()
var head = it.firstNotNullOfOrNull {
when {
it.value.mapping.isStart -> it.value
else -> null
}
}
while (head != null) {
list.add(head.song)
head = it[head.mapping.nextId]
}
list.toList()
}
}

fun SongQueueSongMappingStore.valuesAsFlow(queueId: String): Flow<List<Song>> {
val entries = entriesAsFlow(queueId)
return transformEntriesAsValuesFlow(entries)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.github.zyrouge.symphony.services.database.store

import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import androidx.room.RawQuery
import androidx.sqlite.db.SimpleSQLiteQuery
import androidx.sqlite.db.SupportSQLiteQuery
import io.github.zyrouge.symphony.services.groove.entities.SongQueue
import io.github.zyrouge.symphony.services.groove.entities.SongQueueSongMapping
import kotlinx.coroutines.flow.Flow

@Dao
interface SongQueueStore {
@Insert
suspend fun insert(vararg entities: SongQueue): List<String>

@Query("DELETE FROM ${SongQueue.TABLE} WHERE ${SongQueue.COLUMN_ID} = :id")
suspend fun delete(id: String): Int

@RawQuery(observedEntities = [SongQueue::class, SongQueueSongMapping::class])
fun valuesAsFlowRaw(query: SupportSQLiteQuery): Flow<List<SongQueue.AlongAttributes>>
}

fun SongQueueStore.valuesAsFlow(): Flow<List<SongQueue.AlongAttributes>> {
val query = "SELECT ${SongQueue.TABLE}.*, " +
"COUNT(${SongQueueSongMapping.TABLE}.${SongQueueSongMapping.COLUMN_SONG_ID}) as ${SongQueue.AlongAttributes.EMBEDDED_TRACKS_COUNT} " +
"FROM ${SongQueue.TABLE} " +
"LEFT JOIN ${SongQueueSongMapping.TABLE} ON ${SongQueueSongMapping.TABLE}.${SongQueueSongMapping.COLUMN_QUEUE_ID} = ${SongQueue.TABLE}.${SongQueue.COLUMN_ID}"
return valuesAsFlowRaw(SimpleSQLiteQuery(query))
}
Loading

0 comments on commit 6c6f73c

Please sign in to comment.