download a song's playlist artwork too

This commit is contained in:
austinried
2026-10-04 00:30:04 +00:00
parent 410b13c920
commit 3e042cb380
4 changed files with 61 additions and 7 deletions
@@ -8,6 +8,7 @@ import com.subtracks.data.model.DownloadedSong
import com.subtracks.data.model.EntityDownloadStatus
import com.subtracks.data.model.ListDownloadStatus
import com.subtracks.data.model.SongDownload
import com.subtracks.data.model.SourceCoverArt
import kotlinx.coroutines.flow.Flow
@Dao
@@ -125,6 +126,25 @@ interface DownloadDao {
)
suspend fun artwork(): List<DownloadArtwork>
@Query(
"SELECT DISTINCT p.coverArt AS coverArt FROM playlist_songs ps " +
"JOIN playlists p ON p.sourceId = ps.sourceId AND p.id = ps.playlistId " +
"WHERE ps.sourceId = :sourceId AND ps.songId = :songId AND p.coverArt IS NOT NULL",
)
suspend fun playlistCoversForSong(
sourceId: Long,
songId: String,
): List<String>
@Query(
"SELECT DISTINCT ps.sourceId AS sourceId, p.coverArt AS coverArt " +
"FROM playlist_songs ps " +
"JOIN playlists p ON p.sourceId = ps.sourceId AND p.id = ps.playlistId " +
"JOIN song_downloads d ON d.sourceId = ps.sourceId AND d.songId = ps.songId " +
"WHERE p.coverArt IS NOT NULL",
)
suspend fun downloadedPlaylistCovers(): List<SourceCoverArt>
@Query("SELECT * FROM song_downloads WHERE sourceId = :sourceId AND songId = :songId")
suspend fun find(
sourceId: Long,
@@ -86,6 +86,11 @@ data class DownloadArtwork(
get() = (albumId != null && albumRow == null) || (artistId != null && artistRow == null)
}
data class SourceCoverArt(
val sourceId: Long,
val coverArt: String,
)
class DownloadStatusConverter {
@ColumnTypeConverter
fun fromDownloadStatus(status: DownloadStatus): String = status.name
@@ -388,9 +388,11 @@ class DownloadRepository(
) {
val song = db.libraryDao().songOnce(sourceId, songId)
val coverArt =
listOfNotNull(
song?.albumId?.let { db.libraryDao().albumOnce(sourceId, it)?.coverArt },
song?.artistId?.let { db.libraryDao().artistOnce(sourceId, it)?.coverArt },
(
listOfNotNull(
song?.albumId?.let { db.libraryDao().albumOnce(sourceId, it)?.coverArt },
song?.artistId?.let { db.libraryDao().artistOnce(sourceId, it)?.coverArt },
) + db.downloadDao().playlistCoversForSong(sourceId, songId)
).distinct()
coverArt.forEach { cover ->
storeArtwork(sourceId, cover, thumbnail = false)
@@ -423,18 +425,22 @@ class DownloadRepository(
* rows could not all be resolved against the library, in which case nothing is swept for it:
* pruning an album or artist would otherwise delete artwork a download still holds.
*/
private suspend fun artworkToKeep(): Map<Long, Set<String>?> =
db
private suspend fun artworkToKeep(): Map<Long, Set<String>?> {
val playlistCovers = db.downloadDao().downloadedPlaylistCovers().groupBy({ it.sourceId }, { it.coverArt })
return db
.downloadDao()
.artwork()
.groupBy { it.sourceId }
.mapValues { (sourceId, rows) ->
if (rows.any { it.hasMissingLibraryRow }) return@mapValues null
rows
.flatMap { row -> listOfNotNull(row.albumCoverArt, row.artistCoverArt) }
val covers =
rows.flatMap { row -> listOfNotNull(row.albumCoverArt, row.artistCoverArt) } +
playlistCovers[sourceId].orEmpty()
covers
.flatMap { cover -> listOf(false, true).map { thumbnail -> coverArtKey(sourceId, cover, thumbnail) } }
.toSet()
}
}
private suspend fun recover(row: SongDownload) {
when (row.status) {
@@ -323,6 +323,28 @@ class DownloadRepositoryTest {
}
}
@Test
fun downloadingASongAlsoStoresTheArtworkOfPlaylistsThatContainIt() =
runTest {
seedLibrary()
runBlocking {
db.libraryDao().upsertPlaylists(
listOf(Playlist(1, "pl1", "Playlist", comment = null, coverArt = PLAYLIST_ART, songCount = 2, created = 0)),
)
db.libraryDao().upsertPlaylistSongs(listOf(PlaylistSong(1, "pl1", "s1", 0)))
}
runBlocking { repository.download(1, "s1") }
listOf(false, true).forEach { thumbnail ->
val key = coverArtKey(1, PLAYLIST_ART, thumbnail)
assertNotNull("expected stored playlist art for $key", artwork.uri(1, key))
}
runBlocking { repository.reconcile() }
assertNotNull(artwork.uri(1, coverArtKey(1, PLAYLIST_ART, false)))
}
@Test
fun theDownloadStartsBeforeItsArtworkIsFetched() =
runTest {
@@ -1105,5 +1127,6 @@ class DownloadRepositoryTest {
private companion object {
const val ALBUM_ART = "art-al1"
const val ARTIST_ART = "art-ar1"
const val PLAYLIST_ART = "art-pl1"
}
}