mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-11 07:29:14 +01:00
group download status per entity for the library tabs
The detail heroes ask for one entity's status at a time. The list surfaces need every status on screen at once, so add one grouped flow per tab that returns a lookup of entity id to counts, which the tiles and rows read the way song rows read the per-song map.
This commit is contained in:
@@ -4,6 +4,7 @@ import androidx.room3.Dao
|
|||||||
import androidx.room3.Query
|
import androidx.room3.Query
|
||||||
import androidx.room3.Upsert
|
import androidx.room3.Upsert
|
||||||
import com.subtracks.data.model.DownloadArtwork
|
import com.subtracks.data.model.DownloadArtwork
|
||||||
|
import com.subtracks.data.model.EntityDownloadStatus
|
||||||
import com.subtracks.data.model.ListDownloadStatus
|
import com.subtracks.data.model.ListDownloadStatus
|
||||||
import com.subtracks.data.model.SongDownload
|
import com.subtracks.data.model.SongDownload
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
@@ -57,6 +58,44 @@ interface DownloadDao {
|
|||||||
albumId: String,
|
albumId: String,
|
||||||
): Flow<ListDownloadStatus>
|
): Flow<ListDownloadStatus>
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"SELECT songs.albumId AS id, " +
|
||||||
|
"COUNT(songs.id) AS total, " +
|
||||||
|
"COALESCE(SUM(CASE WHEN song_downloads.status = 'Completed' THEN 1 ELSE 0 END), 0) AS downloaded, " +
|
||||||
|
"COALESCE(SUM(CASE WHEN song_downloads.status IN ('Queued', 'Running') THEN 1 ELSE 0 END), 0) AS downloading " +
|
||||||
|
"FROM songs " +
|
||||||
|
"LEFT JOIN song_downloads ON song_downloads.sourceId = songs.sourceId AND song_downloads.songId = songs.id " +
|
||||||
|
"WHERE songs.sourceId = :sourceId AND songs.albumId IS NOT NULL " +
|
||||||
|
"GROUP BY songs.albumId",
|
||||||
|
)
|
||||||
|
fun albumStatuses(sourceId: Long): Flow<List<EntityDownloadStatus>>
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"SELECT albums.artistId AS id, " +
|
||||||
|
"COUNT(songs.id) AS total, " +
|
||||||
|
"COALESCE(SUM(CASE WHEN song_downloads.status = 'Completed' THEN 1 ELSE 0 END), 0) AS downloaded, " +
|
||||||
|
"COALESCE(SUM(CASE WHEN song_downloads.status IN ('Queued', 'Running') THEN 1 ELSE 0 END), 0) AS downloading " +
|
||||||
|
"FROM songs " +
|
||||||
|
"JOIN albums ON albums.sourceId = songs.sourceId AND albums.id = songs.albumId " +
|
||||||
|
"LEFT JOIN song_downloads ON song_downloads.sourceId = songs.sourceId AND song_downloads.songId = songs.id " +
|
||||||
|
"WHERE songs.sourceId = :sourceId AND albums.artistId IS NOT NULL " +
|
||||||
|
"GROUP BY albums.artistId",
|
||||||
|
)
|
||||||
|
fun artistStatuses(sourceId: Long): Flow<List<EntityDownloadStatus>>
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"SELECT playlist_songs.playlistId AS id, " +
|
||||||
|
"COUNT(DISTINCT songs.id) AS total, " +
|
||||||
|
"COUNT(DISTINCT CASE WHEN song_downloads.status = 'Completed' THEN songs.id END) AS downloaded, " +
|
||||||
|
"COUNT(DISTINCT CASE WHEN song_downloads.status IN ('Queued', 'Running') THEN songs.id END) AS downloading " +
|
||||||
|
"FROM playlist_songs " +
|
||||||
|
"JOIN songs ON songs.sourceId = playlist_songs.sourceId AND songs.id = playlist_songs.songId " +
|
||||||
|
"LEFT JOIN song_downloads ON song_downloads.sourceId = songs.sourceId AND song_downloads.songId = songs.id " +
|
||||||
|
"WHERE playlist_songs.sourceId = :sourceId " +
|
||||||
|
"GROUP BY playlist_songs.playlistId",
|
||||||
|
)
|
||||||
|
fun playlistStatuses(sourceId: Long): Flow<List<EntityDownloadStatus>>
|
||||||
|
|
||||||
@Query(
|
@Query(
|
||||||
"SELECT d.sourceId AS sourceId, s.albumId AS albumId, s.artistId AS artistId, " +
|
"SELECT d.sourceId AS sourceId, s.albumId AS albumId, s.artistId AS artistId, " +
|
||||||
"al.id AS albumRow, al.coverArt AS albumCoverArt, " +
|
"al.id AS albumRow, al.coverArt AS albumCoverArt, " +
|
||||||
|
|||||||
@@ -52,6 +52,15 @@ data class ListDownloadStatus(
|
|||||||
val complete: Boolean get() = total > 0 && downloaded == total
|
val complete: Boolean get() = total > 0 && downloaded == total
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class EntityDownloadStatus(
|
||||||
|
val id: String,
|
||||||
|
val total: Long = 0,
|
||||||
|
val downloaded: Long = 0,
|
||||||
|
val downloading: Long = 0,
|
||||||
|
) {
|
||||||
|
fun toListStatus(): ListDownloadStatus = ListDownloadStatus(total, downloaded, downloading)
|
||||||
|
}
|
||||||
|
|
||||||
data class DownloadArtwork(
|
data class DownloadArtwork(
|
||||||
val sourceId: Long,
|
val sourceId: Long,
|
||||||
val albumId: String?,
|
val albumId: String?,
|
||||||
|
|||||||
@@ -185,6 +185,16 @@ class DownloadRepository(
|
|||||||
DownloadList.Artist -> db.downloadDao().artistStatus(sourceId, refId)
|
DownloadList.Artist -> db.downloadDao().artistStatus(sourceId, refId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun statuses(
|
||||||
|
sourceId: Long,
|
||||||
|
list: DownloadList,
|
||||||
|
): Flow<Map<String, ListDownloadStatus>> =
|
||||||
|
when (list) {
|
||||||
|
DownloadList.Album -> db.downloadDao().albumStatuses(sourceId)
|
||||||
|
DownloadList.Playlist -> db.downloadDao().playlistStatuses(sourceId)
|
||||||
|
DownloadList.Artist -> db.downloadDao().artistStatuses(sourceId)
|
||||||
|
}.map { rows -> rows.associate { it.id to it.toListStatus() } }
|
||||||
|
|
||||||
suspend fun downloadAll(
|
suspend fun downloadAll(
|
||||||
sourceId: Long,
|
sourceId: Long,
|
||||||
list: DownloadList,
|
list: DownloadList,
|
||||||
|
|||||||
@@ -731,6 +731,64 @@ class DownloadRepositoryTest {
|
|||||||
assertEquals(listOf("s1", "s2"), requestedSongIds())
|
assertEquals(listOf("s1", "s2"), requestedSongIds())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun groupedStatusesCountEachAlbumArtistAndPlaylistSeparately() =
|
||||||
|
runTest {
|
||||||
|
seedLibrary()
|
||||||
|
seedPlaylist(listOf("s1", "s2"))
|
||||||
|
runBlocking {
|
||||||
|
db.libraryDao().upsertSongs(
|
||||||
|
listOf(
|
||||||
|
Song(
|
||||||
|
sourceId = 1,
|
||||||
|
id = "s3",
|
||||||
|
albumId = "al2",
|
||||||
|
artistId = "ar2",
|
||||||
|
title = "Song 3",
|
||||||
|
album = "Other",
|
||||||
|
artist = "Other",
|
||||||
|
duration = 100,
|
||||||
|
track = 1,
|
||||||
|
disc = 1,
|
||||||
|
starred = null,
|
||||||
|
genre = null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
db.libraryDao().upsertAlbums(
|
||||||
|
listOf(
|
||||||
|
Album(
|
||||||
|
sourceId = 1,
|
||||||
|
id = "al2",
|
||||||
|
artistId = "ar2",
|
||||||
|
name = "Other",
|
||||||
|
albumArtist = "Other",
|
||||||
|
created = 0,
|
||||||
|
coverArt = null,
|
||||||
|
genre = null,
|
||||||
|
year = 2000,
|
||||||
|
starred = null,
|
||||||
|
songCount = 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
db.libraryDao().upsertArtists(listOf(Artist(sourceId = 1, id = "ar2", name = "Other", albumCount = 1, starred = null, coverArt = null)))
|
||||||
|
db.downloadDao().upsert(SongDownload(1, "s1", DownloadStatus.Completed))
|
||||||
|
db.downloadDao().upsert(SongDownload(1, "s3", DownloadStatus.Queued))
|
||||||
|
}
|
||||||
|
|
||||||
|
val albums = runBlocking { repository.statuses(1, DownloadList.Album).first() }
|
||||||
|
assertEquals(ListDownloadStatus(total = 2, downloaded = 1), albums["al1"])
|
||||||
|
assertEquals(ListDownloadStatus(total = 1, downloading = 1), albums["al2"])
|
||||||
|
|
||||||
|
val artists = runBlocking { repository.statuses(1, DownloadList.Artist).first() }
|
||||||
|
assertEquals(ListDownloadStatus(total = 2, downloaded = 1), artists["ar1"])
|
||||||
|
assertEquals(ListDownloadStatus(total = 1, downloading = 1), artists["ar2"])
|
||||||
|
|
||||||
|
val playlists = runBlocking { repository.statuses(1, DownloadList.Playlist).first() }
|
||||||
|
assertEquals(ListDownloadStatus(total = 2, downloaded = 1), playlists["pl1"])
|
||||||
|
}
|
||||||
|
|
||||||
private fun requestedSongIds(): List<String> = engine.requests.map { it.second.path.substringAfterLast('/') }
|
private fun requestedSongIds(): List<String> = engine.requests.map { it.second.path.substringAfterLast('/') }
|
||||||
|
|
||||||
private fun seedSongs(tracks: IntRange) {
|
private fun seedSongs(tracks: IntRange) {
|
||||||
|
|||||||
Reference in New Issue
Block a user