mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-11 07:29:14 +01:00
keep playback running when rebuilding the offline window, and order downloads newest-first
This commit is contained in:
+1584
File diff suppressed because it is too large
Load Diff
@@ -709,7 +709,7 @@ interface LibraryDao {
|
|||||||
"LEFT JOIN albums ON albums.sourceId = songs.sourceId AND albums.id = songs.albumId " +
|
"LEFT JOIN albums ON albums.sourceId = songs.sourceId AND albums.id = songs.albumId " +
|
||||||
"JOIN song_downloads d ON d.sourceId = songs.sourceId AND d.songId = songs.id " +
|
"JOIN song_downloads d ON d.sourceId = songs.sourceId AND d.songId = songs.id " +
|
||||||
"WHERE songs.sourceId = :sourceId AND d.status = 'Completed' " +
|
"WHERE songs.sourceId = :sourceId AND d.status = 'Completed' " +
|
||||||
"ORDER BY songs.title COLLATE NOCASE, songs.id",
|
"ORDER BY d.downloadedAt DESC, songs.title COLLATE NOCASE, songs.id",
|
||||||
)
|
)
|
||||||
fun downloadedSongs(sourceId: Long): Flow<List<AlbumSongItem>>
|
fun downloadedSongs(sourceId: Long): Flow<List<AlbumSongItem>>
|
||||||
|
|
||||||
@@ -717,7 +717,7 @@ interface LibraryDao {
|
|||||||
"SELECT songs.id FROM songs " +
|
"SELECT songs.id FROM songs " +
|
||||||
"JOIN song_downloads d ON d.sourceId = songs.sourceId AND d.songId = songs.id " +
|
"JOIN song_downloads d ON d.sourceId = songs.sourceId AND d.songId = songs.id " +
|
||||||
"WHERE songs.sourceId = :sourceId AND d.status = 'Completed' " +
|
"WHERE songs.sourceId = :sourceId AND d.status = 'Completed' " +
|
||||||
"ORDER BY songs.title COLLATE NOCASE, songs.id",
|
"ORDER BY d.downloadedAt DESC, songs.title COLLATE NOCASE, songs.id",
|
||||||
)
|
)
|
||||||
suspend fun downloadedSongIds(sourceId: Long): List<String>
|
suspend fun downloadedSongIds(sourceId: Long): List<String>
|
||||||
|
|
||||||
@@ -726,10 +726,31 @@ interface LibraryDao {
|
|||||||
"LEFT JOIN albums ON albums.sourceId = songs.sourceId AND albums.id = songs.albumId " +
|
"LEFT JOIN albums ON albums.sourceId = songs.sourceId AND albums.id = songs.albumId " +
|
||||||
"JOIN song_downloads d ON d.sourceId = songs.sourceId AND d.songId = songs.id " +
|
"JOIN song_downloads d ON d.sourceId = songs.sourceId AND d.songId = songs.id " +
|
||||||
"WHERE songs.sourceId = :sourceId AND d.status = 'Completed' " +
|
"WHERE songs.sourceId = :sourceId AND d.status = 'Completed' " +
|
||||||
"ORDER BY songs.title COLLATE NOCASE, songs.id",
|
"ORDER BY d.downloadedAt DESC, songs.title COLLATE NOCASE, songs.id",
|
||||||
)
|
)
|
||||||
fun homeDownloadedSongs(sourceId: Long): PagingSource<Int, AlbumSongItem>
|
fun homeDownloadedSongs(sourceId: Long): PagingSource<Int, AlbumSongItem>
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"SELECT albums.* FROM albums " +
|
||||||
|
"JOIN songs ON songs.sourceId = albums.sourceId AND songs.albumId = albums.id " +
|
||||||
|
"JOIN song_downloads d ON d.sourceId = songs.sourceId AND d.songId = songs.id AND d.status = 'Completed' " +
|
||||||
|
"WHERE albums.sourceId = :sourceId " +
|
||||||
|
"GROUP BY albums.sourceId, albums.id " +
|
||||||
|
"ORDER BY MAX(d.downloadedAt) DESC, albums.name COLLATE NOCASE, albums.id",
|
||||||
|
)
|
||||||
|
fun downloadedAlbums(sourceId: Long): PagingSource<Int, Album>
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"SELECT artists.* FROM artists " +
|
||||||
|
"JOIN albums ON albums.sourceId = artists.sourceId AND albums.artistId = artists.id " +
|
||||||
|
"JOIN songs ON songs.sourceId = albums.sourceId AND songs.albumId = albums.id " +
|
||||||
|
"JOIN song_downloads d ON d.sourceId = songs.sourceId AND d.songId = songs.id AND d.status = 'Completed' " +
|
||||||
|
"WHERE artists.sourceId = :sourceId " +
|
||||||
|
"GROUP BY artists.sourceId, artists.id " +
|
||||||
|
"ORDER BY MAX(d.downloadedAt) DESC, artists.name COLLATE NOCASE, artists.id",
|
||||||
|
)
|
||||||
|
fun downloadedArtists(sourceId: Long): PagingSource<Int, Artist>
|
||||||
|
|
||||||
@Query(
|
@Query(
|
||||||
"SELECT songs.*, albums.coverArt AS coverArt FROM song_genres " +
|
"SELECT songs.*, albums.coverArt AS coverArt FROM song_genres " +
|
||||||
"JOIN songs ON songs.sourceId = song_genres.sourceId AND songs.id = song_genres.songId " +
|
"JOIN songs ON songs.sourceId = song_genres.sourceId AND songs.id = song_genres.songId " +
|
||||||
@@ -768,13 +789,15 @@ interface LibraryDao {
|
|||||||
|
|
||||||
@Query(
|
@Query(
|
||||||
"SELECT COUNT(*) FROM song_downloads d " +
|
"SELECT COUNT(*) FROM song_downloads d " +
|
||||||
"JOIN songs ON songs.sourceId = d.sourceId AND songs.id = d.songId " +
|
"JOIN songs s ON s.sourceId = d.sourceId AND s.id = d.songId " +
|
||||||
"WHERE d.sourceId = :sourceId AND d.status = 'Completed' " +
|
"WHERE d.sourceId = :sourceId AND d.status = 'Completed' " +
|
||||||
"AND (songs.title COLLATE NOCASE < :title OR (songs.title COLLATE NOCASE = :title AND songs.id < :id))",
|
"AND (d.downloadedAt > (SELECT downloadedAt FROM song_downloads WHERE sourceId = :sourceId AND songId = :id) " +
|
||||||
|
"OR (d.downloadedAt = (SELECT downloadedAt FROM song_downloads WHERE sourceId = :sourceId AND songId = :id) " +
|
||||||
|
"AND (s.title COLLATE NOCASE < (SELECT title FROM songs WHERE sourceId = :sourceId AND id = :id) " +
|
||||||
|
"OR (s.title COLLATE NOCASE = (SELECT title FROM songs WHERE sourceId = :sourceId AND id = :id) AND s.id < :id))))",
|
||||||
)
|
)
|
||||||
suspend fun downloadedSongOrdinal(
|
suspend fun downloadedSongOrdinal(
|
||||||
sourceId: Long,
|
sourceId: Long,
|
||||||
title: String,
|
|
||||||
id: String,
|
id: String,
|
||||||
): Long
|
): Long
|
||||||
|
|
||||||
|
|||||||
@@ -352,6 +352,15 @@ val MIGRATION_23_24 =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val MIGRATION_24_25 =
|
||||||
|
object : Migration(24, 25) {
|
||||||
|
override suspend fun migrate(connection: SQLiteConnection) {
|
||||||
|
connection.execSQL(
|
||||||
|
"ALTER TABLE `song_downloads` ADD COLUMN `downloadedAt` INTEGER NOT NULL DEFAULT 0",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Mirrors the FTS5 table and content-sync triggers Room generates for the @Fts5 entities.
|
// Mirrors the FTS5 table and content-sync triggers Room generates for the @Fts5 entities.
|
||||||
private suspend fun createSearchIndex(
|
private suspend fun createSearchIndex(
|
||||||
connection: SQLiteConnection,
|
connection: SQLiteConnection,
|
||||||
@@ -409,4 +418,5 @@ val MIGRATIONS: Array<Migration> =
|
|||||||
MIGRATION_21_22,
|
MIGRATION_21_22,
|
||||||
MIGRATION_22_23,
|
MIGRATION_22_23,
|
||||||
MIGRATION_23_24,
|
MIGRATION_23_24,
|
||||||
|
MIGRATION_24_25,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ interface QueueDao {
|
|||||||
"JOIN songs ON songs.sourceId = d.sourceId AND songs.id = d.songId " +
|
"JOIN songs ON songs.sourceId = d.sourceId AND songs.id = d.songId " +
|
||||||
"LEFT JOIN albums ON albums.sourceId = songs.sourceId AND albums.id = songs.albumId " +
|
"LEFT JOIN albums ON albums.sourceId = songs.sourceId AND albums.id = songs.albumId " +
|
||||||
"WHERE d.sourceId = :sourceId AND d.status = 'Completed' " +
|
"WHERE d.sourceId = :sourceId AND d.status = 'Completed' " +
|
||||||
"ORDER BY songs.title COLLATE NOCASE, songs.id LIMIT :limit OFFSET :offset",
|
"ORDER BY d.downloadedAt DESC, songs.title COLLATE NOCASE, songs.id LIMIT :limit OFFSET :offset",
|
||||||
)
|
)
|
||||||
suspend fun downloadedSongs(
|
suspend fun downloadedSongs(
|
||||||
sourceId: Long,
|
sourceId: Long,
|
||||||
@@ -271,7 +271,7 @@ interface QueueDao {
|
|||||||
"SELECT songs.id FROM song_downloads d " +
|
"SELECT songs.id FROM song_downloads d " +
|
||||||
"JOIN songs ON songs.sourceId = d.sourceId AND songs.id = d.songId " +
|
"JOIN songs ON songs.sourceId = d.sourceId AND songs.id = d.songId " +
|
||||||
"WHERE d.sourceId = :sourceId AND d.status = 'Completed' " +
|
"WHERE d.sourceId = :sourceId AND d.status = 'Completed' " +
|
||||||
"ORDER BY songs.title COLLATE NOCASE, songs.id",
|
"ORDER BY d.downloadedAt DESC, songs.title COLLATE NOCASE, songs.id",
|
||||||
)
|
)
|
||||||
suspend fun downloadedSongIds(sourceId: Long): List<String>
|
suspend fun downloadedSongIds(sourceId: Long): List<String>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ import com.subtracks.data.model.UpNextEntry
|
|||||||
ArtworkSeed::class,
|
ArtworkSeed::class,
|
||||||
SongDownload::class,
|
SongDownload::class,
|
||||||
],
|
],
|
||||||
version = 24,
|
version = 25,
|
||||||
exportSchema = true,
|
exportSchema = true,
|
||||||
)
|
)
|
||||||
@ColumnTypeConverters(QueueKindConverter::class, DownloadStatusConverter::class)
|
@ColumnTypeConverters(QueueKindConverter::class, DownloadStatusConverter::class)
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ data class SongDownload(
|
|||||||
@ColumnInfo(defaultValue = "0") val bytes: Long = 0,
|
@ColumnInfo(defaultValue = "0") val bytes: Long = 0,
|
||||||
@ColumnInfo(defaultValue = "0") val total: Long = 0,
|
@ColumnInfo(defaultValue = "0") val total: Long = 0,
|
||||||
val error: String? = null,
|
val error: String? = null,
|
||||||
|
@ColumnInfo(defaultValue = "0") val downloadedAt: Long = 0,
|
||||||
) {
|
) {
|
||||||
val progress: Float?
|
val progress: Float?
|
||||||
get() = total.takeIf { it > 0 }?.let { (bytes.toFloat() / it).coerceIn(0f, 1f) }
|
get() = total.takeIf { it > 0 }?.let { (bytes.toFloat() / it).coerceIn(0f, 1f) }
|
||||||
|
|||||||
@@ -555,6 +555,12 @@ class DownloadRepository(
|
|||||||
status == DownloadStatus.Failed -> "Download failed"
|
status == DownloadStatus.Failed -> "Download failed"
|
||||||
else -> null
|
else -> null
|
||||||
},
|
},
|
||||||
|
downloadedAt =
|
||||||
|
if (status == DownloadStatus.Completed && row.downloadedAt == 0L) {
|
||||||
|
System.currentTimeMillis()
|
||||||
|
} else {
|
||||||
|
row.downloadedAt
|
||||||
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,6 +96,10 @@ class LibraryRepository(
|
|||||||
|
|
||||||
fun downloadedSongsPage(sourceId: Long): Flow<PagingData<AlbumSongItem>> = pager(60) { db.libraryDao().homeDownloadedSongs(sourceId) }
|
fun downloadedSongsPage(sourceId: Long): Flow<PagingData<AlbumSongItem>> = pager(60) { db.libraryDao().homeDownloadedSongs(sourceId) }
|
||||||
|
|
||||||
|
fun downloadedAlbumsPage(sourceId: Long): Flow<PagingData<Album>> = pager(40) { db.libraryDao().downloadedAlbums(sourceId) }
|
||||||
|
|
||||||
|
fun downloadedArtistsPage(sourceId: Long): Flow<PagingData<Artist>> = pager(60) { db.libraryDao().downloadedArtists(sourceId) }
|
||||||
|
|
||||||
fun songsByGenre(
|
fun songsByGenre(
|
||||||
sourceId: Long,
|
sourceId: Long,
|
||||||
genre: String,
|
genre: String,
|
||||||
@@ -115,9 +119,8 @@ class LibraryRepository(
|
|||||||
|
|
||||||
suspend fun downloadedSongOrdinal(
|
suspend fun downloadedSongOrdinal(
|
||||||
sourceId: Long,
|
sourceId: Long,
|
||||||
title: String,
|
|
||||||
id: String,
|
id: String,
|
||||||
): Long = db.libraryDao().downloadedSongOrdinal(sourceId, title, id)
|
): Long = db.libraryDao().downloadedSongOrdinal(sourceId, id)
|
||||||
|
|
||||||
fun albumsByDecade(
|
fun albumsByDecade(
|
||||||
sourceId: Long,
|
sourceId: Long,
|
||||||
|
|||||||
@@ -1241,25 +1241,29 @@ class PlaybackController(
|
|||||||
window: List<QueueWindowItem>,
|
window: List<QueueWindowItem>,
|
||||||
position: Long,
|
position: Long,
|
||||||
) {
|
) {
|
||||||
if (offline) {
|
val player = player ?: return
|
||||||
loadWindow(position, autoplay = player?.playWhenReady == true, startPositionMs = player?.currentPositionMs ?: 0)
|
downloads.awaitLoaded()
|
||||||
|
val ordered =
|
||||||
|
if (offline) window.filter { downloads.localUri(it.item.song.id) != null } else window
|
||||||
|
val index = ordered.indexOfFirst { it.position == position }
|
||||||
|
if (index < 0) {
|
||||||
|
loadWindow(position, autoplay = player.playWhenReady, startPositionMs = player.currentPositionMs)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val player = player ?: return
|
windowPositions = LongArray(ordered.size) { ordered[it].position }
|
||||||
val index = window.indexOfFirst { it.position == position }
|
windowFiltered = offline
|
||||||
if (index < 0) return
|
windowStart = ordered.first().position
|
||||||
|
windowEnd = ordered.last().position
|
||||||
|
lastPosition = position
|
||||||
updating = true
|
updating = true
|
||||||
try {
|
try {
|
||||||
repeat(player.currentIndex) { player.removeFirst() }
|
repeat(player.currentIndex) { player.removeFirst() }
|
||||||
while (player.itemCount > 1) player.removeLast()
|
while (player.itemCount > 1) player.removeLast()
|
||||||
for (i in index - 1 downTo 0) player.addFirst(window[i].item.toQueueItem())
|
for (i in index - 1 downTo 0) player.addFirst(ordered[i].item.toQueueItem())
|
||||||
for (i in index + 1 until window.size) player.addLast(window[i].item.toQueueItem())
|
for (i in index + 1 until ordered.size) player.addLast(ordered[i].item.toQueueItem())
|
||||||
} finally {
|
} finally {
|
||||||
updating = false
|
updating = false
|
||||||
}
|
}
|
||||||
windowStart = window.first().position
|
|
||||||
windowEnd = window.last().position
|
|
||||||
lastPosition = position
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun currentPosition(): Long? {
|
private fun currentPosition(): Long? {
|
||||||
|
|||||||
@@ -10,10 +10,7 @@ import com.subtracks.data.model.Artist
|
|||||||
import com.subtracks.data.model.CoverArtRef
|
import com.subtracks.data.model.CoverArtRef
|
||||||
import com.subtracks.data.model.Playlist
|
import com.subtracks.data.model.Playlist
|
||||||
import com.subtracks.data.model.Song
|
import com.subtracks.data.model.Song
|
||||||
import com.subtracks.data.prefs.AlbumSort
|
|
||||||
import com.subtracks.data.prefs.ArtistSort
|
|
||||||
import com.subtracks.data.prefs.PlaylistSort
|
import com.subtracks.data.prefs.PlaylistSort
|
||||||
import com.subtracks.data.prefs.StarredFilter
|
|
||||||
import com.subtracks.data.repo.LibraryRepository
|
import com.subtracks.data.repo.LibraryRepository
|
||||||
import com.subtracks.data.repo.SourceRepository
|
import com.subtracks.data.repo.SourceRepository
|
||||||
import com.subtracks.playback.PlaybackController
|
import com.subtracks.playback.PlaybackController
|
||||||
@@ -57,14 +54,7 @@ class HomeListViewModel(
|
|||||||
.flatMapLatest { id ->
|
.flatMapLatest { id ->
|
||||||
when {
|
when {
|
||||||
request.downloaded == OfflineListKind.Albums -> {
|
request.downloaded == OfflineListKind.Albums -> {
|
||||||
libraryRepository.albums(
|
libraryRepository.downloadedAlbumsPage(id)
|
||||||
sourceId = id,
|
|
||||||
sort = AlbumSort.Added,
|
|
||||||
descending = false,
|
|
||||||
starred = StarredFilter.Any,
|
|
||||||
search = "",
|
|
||||||
downloaded = true,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
request.decade != null -> {
|
request.decade != null -> {
|
||||||
@@ -107,14 +97,7 @@ class HomeListViewModel(
|
|||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
if (request.downloaded == OfflineListKind.Artists) {
|
if (request.downloaded == OfflineListKind.Artists) {
|
||||||
libraryRepository.artists(
|
libraryRepository.downloadedArtistsPage(id)
|
||||||
sourceId = id,
|
|
||||||
sort = ArtistSort.Name,
|
|
||||||
descending = false,
|
|
||||||
starred = StarredFilter.Any,
|
|
||||||
search = "",
|
|
||||||
downloaded = true,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
flowOf(PagingData.empty<Artist>())
|
flowOf(PagingData.empty<Artist>())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,10 +10,7 @@ import com.subtracks.data.model.Artist
|
|||||||
import com.subtracks.data.model.CoverArtRef
|
import com.subtracks.data.model.CoverArtRef
|
||||||
import com.subtracks.data.model.Playlist
|
import com.subtracks.data.model.Playlist
|
||||||
import com.subtracks.data.model.Song
|
import com.subtracks.data.model.Song
|
||||||
import com.subtracks.data.prefs.AlbumSort
|
|
||||||
import com.subtracks.data.prefs.ArtistSort
|
|
||||||
import com.subtracks.data.prefs.PlaylistSort
|
import com.subtracks.data.prefs.PlaylistSort
|
||||||
import com.subtracks.data.prefs.StarredFilter
|
|
||||||
import com.subtracks.data.repo.LibraryRepository
|
import com.subtracks.data.repo.LibraryRepository
|
||||||
import com.subtracks.data.repo.SourceRepository
|
import com.subtracks.data.repo.SourceRepository
|
||||||
import com.subtracks.data.repo.rediscoverCutoff
|
import com.subtracks.data.repo.rediscoverCutoff
|
||||||
@@ -66,30 +63,10 @@ class HomeViewModel(
|
|||||||
private val activeSource = libraryRepository.activeSourceId.filterNotNull()
|
private val activeSource = libraryRepository.activeSourceId.filterNotNull()
|
||||||
|
|
||||||
val downloadedAlbums: Flow<PagingData<Album>> =
|
val downloadedAlbums: Flow<PagingData<Album>> =
|
||||||
activeSource
|
activeSource.flatMapLatest { libraryRepository.downloadedAlbumsPage(it) }.cachedIn(viewModelScope)
|
||||||
.flatMapLatest { id ->
|
|
||||||
libraryRepository.albums(
|
|
||||||
sourceId = id,
|
|
||||||
sort = AlbumSort.Added,
|
|
||||||
descending = false,
|
|
||||||
starred = StarredFilter.Any,
|
|
||||||
search = "",
|
|
||||||
downloaded = true,
|
|
||||||
)
|
|
||||||
}.cachedIn(viewModelScope)
|
|
||||||
|
|
||||||
val downloadedArtists: Flow<PagingData<Artist>> =
|
val downloadedArtists: Flow<PagingData<Artist>> =
|
||||||
activeSource
|
activeSource.flatMapLatest { libraryRepository.downloadedArtistsPage(it) }.cachedIn(viewModelScope)
|
||||||
.flatMapLatest { id ->
|
|
||||||
libraryRepository.artists(
|
|
||||||
sourceId = id,
|
|
||||||
sort = ArtistSort.Name,
|
|
||||||
descending = false,
|
|
||||||
starred = StarredFilter.Any,
|
|
||||||
search = "",
|
|
||||||
downloaded = true,
|
|
||||||
)
|
|
||||||
}.cachedIn(viewModelScope)
|
|
||||||
|
|
||||||
val downloadedPlaylists: Flow<PagingData<Playlist>> =
|
val downloadedPlaylists: Flow<PagingData<Playlist>> =
|
||||||
activeSource
|
activeSource
|
||||||
@@ -252,7 +229,7 @@ internal fun CoroutineScope.playDownloadedList(
|
|||||||
song: Song,
|
song: Song,
|
||||||
) {
|
) {
|
||||||
launch {
|
launch {
|
||||||
val ordinal = libraryRepository.downloadedSongOrdinal(song.sourceId, song.title, song.id)
|
val ordinal = libraryRepository.downloadedSongOrdinal(song.sourceId, song.id)
|
||||||
playbackController.playDownloaded(song.sourceId, ordinal)
|
playbackController.playDownloaded(song.sourceId, ordinal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -217,6 +217,29 @@ class LibraryHomeDaoTest {
|
|||||||
assertEquals(page.map { it.song.id }, dao.songsByGenreIds(sourceId, "Rock"))
|
assertEquals(page.map { it.song.id }, dao.songsByGenreIds(sourceId, "Rock"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun downloadedSongsOrderByNewestDownload() =
|
||||||
|
runTest {
|
||||||
|
val sourceId = source()
|
||||||
|
val dao = db.libraryDao()
|
||||||
|
dao.upsertAlbums(listOf(album(sourceId, "al1", "Album", coverArt = "cover-1")))
|
||||||
|
dao.upsertSongs(
|
||||||
|
listOf(
|
||||||
|
song(sourceId, "s1", "One"),
|
||||||
|
song(sourceId, "s2", "Two"),
|
||||||
|
song(sourceId, "s3", "Three"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
val downloads = db.downloadDao()
|
||||||
|
downloads.upsert(SongDownload(sourceId, "s1", DownloadStatus.Completed, downloadedAt = 100))
|
||||||
|
downloads.upsert(SongDownload(sourceId, "s3", DownloadStatus.Completed, downloadedAt = 300))
|
||||||
|
downloads.upsert(SongDownload(sourceId, "s2", DownloadStatus.Completed, downloadedAt = 200))
|
||||||
|
|
||||||
|
assertEquals(listOf("s3", "s2", "s1"), dao.downloadedSongs(sourceId).first().map { it.song.id })
|
||||||
|
assertEquals(listOf("s3", "s2", "s1"), dao.downloadedSongIds(sourceId))
|
||||||
|
assertEquals(2L, dao.downloadedSongOrdinal(sourceId, "s1"))
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun insertGenre(
|
private suspend fun insertGenre(
|
||||||
sourceId: Long,
|
sourceId: Long,
|
||||||
songId: String,
|
songId: String,
|
||||||
|
|||||||
Reference in New Issue
Block a user