mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-11 15:29:15 +01:00
library: stop download writes from invalidating the browse lists
The album/artist/playlist paging SQL named song_downloads, so Room invalidated the pages on every download progress tick, janking the list and resetting scroll when a download started. Route the default (filter off) path through a raw query that only observes its own table; the downloaded/offline path keeps the download-aware queries.
This commit is contained in:
@@ -4,6 +4,8 @@ import androidx.paging.PagingSource
|
|||||||
import androidx.room3.Dao
|
import androidx.room3.Dao
|
||||||
import androidx.room3.DaoReturnTypeConverters
|
import androidx.room3.DaoReturnTypeConverters
|
||||||
import androidx.room3.Query
|
import androidx.room3.Query
|
||||||
|
import androidx.room3.RawQuery
|
||||||
|
import androidx.room3.RoomRawQuery
|
||||||
import androidx.room3.Transaction
|
import androidx.room3.Transaction
|
||||||
import androidx.room3.Upsert
|
import androidx.room3.Upsert
|
||||||
import androidx.room3.paging.PagingSourceDaoReturnTypeConverter
|
import androidx.room3.paging.PagingSourceDaoReturnTypeConverter
|
||||||
@@ -432,6 +434,15 @@ interface LibraryDao {
|
|||||||
)
|
)
|
||||||
suspend fun deleteOrphanPlaylistSongs(sourceId: Long)
|
suspend fun deleteOrphanPlaylistSongs(sourceId: Long)
|
||||||
|
|
||||||
|
@RawQuery(observedEntities = [Album::class])
|
||||||
|
fun albumsPaging(query: RoomRawQuery): PagingSource<Int, Album>
|
||||||
|
|
||||||
|
@RawQuery(observedEntities = [Artist::class])
|
||||||
|
fun artistsPaging(query: RoomRawQuery): PagingSource<Int, Artist>
|
||||||
|
|
||||||
|
@RawQuery(observedEntities = [Playlist::class])
|
||||||
|
fun playlistsPaging(query: RoomRawQuery): PagingSource<Int, Playlist>
|
||||||
|
|
||||||
@Query("SELECT * $ALBUMS_FILTER ORDER BY $ALBUM_ORDER_BY_NAME")
|
@Query("SELECT * $ALBUMS_FILTER ORDER BY $ALBUM_ORDER_BY_NAME")
|
||||||
fun albumsByName(
|
fun albumsByName(
|
||||||
sourceId: Long,
|
sourceId: Long,
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
package com.subtracks.data.db
|
||||||
|
|
||||||
|
import androidx.room3.RoomRawQuery
|
||||||
|
import com.subtracks.data.prefs.AlbumSort
|
||||||
|
import com.subtracks.data.prefs.ArtistSort
|
||||||
|
import com.subtracks.data.prefs.PlaylistSort
|
||||||
|
|
||||||
|
// These filters mirror the download-aware ones in LibraryDao but never mention song_downloads, so
|
||||||
|
// the resulting PagingSource is not invalidated by download writes (including progress updates).
|
||||||
|
internal const val ALBUMS_FILTER_BASE =
|
||||||
|
"FROM albums WHERE sourceId = ? " +
|
||||||
|
"AND (? = 0 OR (? = 1 AND starred IS NOT NULL) OR (? = 2 AND starred IS NULL)) " +
|
||||||
|
"AND (? = '' " +
|
||||||
|
"OR (length(?) >= 3 AND rowid IN (SELECT rowid FROM album_search " +
|
||||||
|
"WHERE album_search MATCH '\"' || replace(?, '\"', '\"\"') || '\"')) " +
|
||||||
|
"OR (length(?) < 3 AND (instr(lower(name), lower(?)) > 0 " +
|
||||||
|
"OR instr(lower(albumArtist), lower(?)) > 0))) "
|
||||||
|
|
||||||
|
internal const val ARTISTS_FILTER_BASE =
|
||||||
|
"FROM artists WHERE sourceId = ? " +
|
||||||
|
"AND (? = 0 OR (? = 1 AND starred IS NOT NULL) OR (? = 2 AND starred IS NULL)) " +
|
||||||
|
"AND (? = '' " +
|
||||||
|
"OR (length(?) >= 3 AND rowid IN (SELECT rowid FROM artist_search " +
|
||||||
|
"WHERE artist_search MATCH '\"' || replace(?, '\"', '\"\"') || '\"')) " +
|
||||||
|
"OR (length(?) < 3 AND instr(lower(name), lower(?)) > 0)) "
|
||||||
|
|
||||||
|
internal const val PLAYLISTS_FILTER_BASE =
|
||||||
|
"FROM playlists WHERE sourceId = ? " +
|
||||||
|
"AND (? = '' " +
|
||||||
|
"OR (length(?) >= 3 AND rowid IN (SELECT rowid FROM playlist_search " +
|
||||||
|
"WHERE playlist_search MATCH '\"' || replace(?, '\"', '\"\"') || '\"')) " +
|
||||||
|
"OR (length(?) < 3 AND instr(lower(name), lower(?)) > 0)) "
|
||||||
|
|
||||||
|
internal fun albumsDefaultQuery(
|
||||||
|
sourceId: Long,
|
||||||
|
starredFilter: Int,
|
||||||
|
search: String,
|
||||||
|
sort: AlbumSort,
|
||||||
|
descending: Boolean,
|
||||||
|
): RoomRawQuery {
|
||||||
|
val order = albumOrder(sort, descending)
|
||||||
|
return RoomRawQuery("SELECT * $ALBUMS_FILTER_BASE ORDER BY $order") { stmt ->
|
||||||
|
stmt.bindLong(1, sourceId)
|
||||||
|
stmt.bindInt(2, starredFilter)
|
||||||
|
stmt.bindInt(3, starredFilter)
|
||||||
|
stmt.bindInt(4, starredFilter)
|
||||||
|
stmt.bindText(5, search)
|
||||||
|
stmt.bindText(6, search)
|
||||||
|
stmt.bindText(7, search)
|
||||||
|
stmt.bindText(8, search)
|
||||||
|
stmt.bindText(9, search)
|
||||||
|
stmt.bindText(10, search)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun artistsDefaultQuery(
|
||||||
|
sourceId: Long,
|
||||||
|
starredFilter: Int,
|
||||||
|
search: String,
|
||||||
|
sort: ArtistSort,
|
||||||
|
descending: Boolean,
|
||||||
|
): RoomRawQuery {
|
||||||
|
val order = artistOrder(sort, descending)
|
||||||
|
return RoomRawQuery("SELECT * $ARTISTS_FILTER_BASE ORDER BY $order") { stmt ->
|
||||||
|
stmt.bindLong(1, sourceId)
|
||||||
|
stmt.bindInt(2, starredFilter)
|
||||||
|
stmt.bindInt(3, starredFilter)
|
||||||
|
stmt.bindInt(4, starredFilter)
|
||||||
|
stmt.bindText(5, search)
|
||||||
|
stmt.bindText(6, search)
|
||||||
|
stmt.bindText(7, search)
|
||||||
|
stmt.bindText(8, search)
|
||||||
|
stmt.bindText(9, search)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun playlistsDefaultQuery(
|
||||||
|
sourceId: Long,
|
||||||
|
search: String,
|
||||||
|
sort: PlaylistSort,
|
||||||
|
descending: Boolean,
|
||||||
|
): RoomRawQuery {
|
||||||
|
val order = playlistOrder(sort, descending)
|
||||||
|
return RoomRawQuery("SELECT * $PLAYLISTS_FILTER_BASE ORDER BY $order") { stmt ->
|
||||||
|
stmt.bindLong(1, sourceId)
|
||||||
|
stmt.bindText(2, search)
|
||||||
|
stmt.bindText(3, search)
|
||||||
|
stmt.bindText(4, search)
|
||||||
|
stmt.bindText(5, search)
|
||||||
|
stmt.bindText(6, search)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun albumOrder(
|
||||||
|
sort: AlbumSort,
|
||||||
|
descending: Boolean,
|
||||||
|
): String =
|
||||||
|
when (sort) {
|
||||||
|
AlbumSort.Name -> if (descending) ALBUM_ORDER_BY_NAME_REVERSED else ALBUM_ORDER_BY_NAME
|
||||||
|
AlbumSort.Artist -> if (descending) ALBUM_ORDER_BY_ARTIST_REVERSED else ALBUM_ORDER_BY_ARTIST
|
||||||
|
AlbumSort.Year -> if (descending) ALBUM_ORDER_BY_YEAR_REVERSED else ALBUM_ORDER_BY_YEAR
|
||||||
|
AlbumSort.Added -> if (descending) ALBUM_ORDER_BY_ADDED_REVERSED else ALBUM_ORDER_BY_ADDED
|
||||||
|
AlbumSort.Starred -> if (descending) ALBUM_ORDER_BY_STARRED_REVERSED else ALBUM_ORDER_BY_STARRED
|
||||||
|
AlbumSort.Frequent -> if (descending) ALBUM_ORDER_BY_FREQUENT_REVERSED else ALBUM_ORDER_BY_FREQUENT
|
||||||
|
AlbumSort.Recent -> if (descending) ALBUM_ORDER_BY_RECENT_REVERSED else ALBUM_ORDER_BY_RECENT
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun artistOrder(
|
||||||
|
sort: ArtistSort,
|
||||||
|
descending: Boolean,
|
||||||
|
): String =
|
||||||
|
when (sort) {
|
||||||
|
ArtistSort.Name -> {
|
||||||
|
if (descending) ARTIST_ORDER_BY_NAME_REVERSED else ARTIST_ORDER_BY_NAME
|
||||||
|
}
|
||||||
|
|
||||||
|
ArtistSort.AlbumCount -> {
|
||||||
|
if (descending) ARTIST_ORDER_BY_ALBUM_COUNT_REVERSED else ARTIST_ORDER_BY_ALBUM_COUNT
|
||||||
|
}
|
||||||
|
|
||||||
|
ArtistSort.Starred -> {
|
||||||
|
if (descending) ARTIST_ORDER_BY_STARRED_REVERSED else ARTIST_ORDER_BY_STARRED
|
||||||
|
}
|
||||||
|
|
||||||
|
ArtistSort.Frequent -> {
|
||||||
|
if (descending) ARTIST_ORDER_BY_FREQUENT_REVERSED else ARTIST_ORDER_BY_FREQUENT
|
||||||
|
}
|
||||||
|
|
||||||
|
ArtistSort.Recent -> {
|
||||||
|
if (descending) ARTIST_ORDER_BY_RECENT_REVERSED else ARTIST_ORDER_BY_RECENT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun playlistOrder(
|
||||||
|
sort: PlaylistSort,
|
||||||
|
descending: Boolean,
|
||||||
|
): String =
|
||||||
|
when (sort) {
|
||||||
|
PlaylistSort.Name -> if (descending) PLAYLIST_ORDER_BY_NAME_REVERSED else PLAYLIST_ORDER_BY_NAME
|
||||||
|
PlaylistSort.Added -> if (descending) PLAYLIST_ORDER_BY_ADDED_REVERSED else PLAYLIST_ORDER_BY_ADDED
|
||||||
|
PlaylistSort.Updated -> if (descending) PLAYLIST_ORDER_BY_UPDATED_REVERSED else PLAYLIST_ORDER_BY_UPDATED
|
||||||
|
}
|
||||||
@@ -5,6 +5,9 @@ import androidx.paging.PagingConfig
|
|||||||
import androidx.paging.PagingData
|
import androidx.paging.PagingData
|
||||||
import androidx.paging.PagingSource
|
import androidx.paging.PagingSource
|
||||||
import com.subtracks.data.db.SubtracksDatabase
|
import com.subtracks.data.db.SubtracksDatabase
|
||||||
|
import com.subtracks.data.db.albumsDefaultQuery
|
||||||
|
import com.subtracks.data.db.artistsDefaultQuery
|
||||||
|
import com.subtracks.data.db.playlistsDefaultQuery
|
||||||
import com.subtracks.data.model.Album
|
import com.subtracks.data.model.Album
|
||||||
import com.subtracks.data.model.AlbumSongItem
|
import com.subtracks.data.model.AlbumSongItem
|
||||||
import com.subtracks.data.model.Artist
|
import com.subtracks.data.model.Artist
|
||||||
@@ -148,6 +151,9 @@ class LibraryRepository(
|
|||||||
pager(40) {
|
pager(40) {
|
||||||
val dao = db.libraryDao()
|
val dao = db.libraryDao()
|
||||||
val downloadedFilter = if (downloaded) 1 else 0
|
val downloadedFilter = if (downloaded) 1 else 0
|
||||||
|
if (!downloaded) {
|
||||||
|
dao.albumsPaging(albumsDefaultQuery(sourceId, starred.ordinal, search, sort, descending))
|
||||||
|
} else {
|
||||||
when (sort) {
|
when (sort) {
|
||||||
AlbumSort.Name -> {
|
AlbumSort.Name -> {
|
||||||
if (descending) {
|
if (descending) {
|
||||||
@@ -206,6 +212,7 @@ class LibraryRepository(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun artists(
|
fun artists(
|
||||||
sourceId: Long,
|
sourceId: Long,
|
||||||
@@ -218,6 +225,9 @@ class LibraryRepository(
|
|||||||
pager(60) {
|
pager(60) {
|
||||||
val dao = db.libraryDao()
|
val dao = db.libraryDao()
|
||||||
val downloadedFilter = if (downloaded) 1 else 0
|
val downloadedFilter = if (downloaded) 1 else 0
|
||||||
|
if (!downloaded) {
|
||||||
|
dao.artistsPaging(artistsDefaultQuery(sourceId, starred.ordinal, search, sort, descending))
|
||||||
|
} else {
|
||||||
when (sort) {
|
when (sort) {
|
||||||
ArtistSort.Name -> {
|
ArtistSort.Name -> {
|
||||||
if (descending) {
|
if (descending) {
|
||||||
@@ -260,6 +270,7 @@ class LibraryRepository(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun playlists(
|
fun playlists(
|
||||||
sourceId: Long,
|
sourceId: Long,
|
||||||
@@ -271,6 +282,9 @@ class LibraryRepository(
|
|||||||
pager(40) {
|
pager(40) {
|
||||||
val dao = db.libraryDao()
|
val dao = db.libraryDao()
|
||||||
val downloadedFilter = if (downloaded) 1 else 0
|
val downloadedFilter = if (downloaded) 1 else 0
|
||||||
|
if (!downloaded) {
|
||||||
|
dao.playlistsPaging(playlistsDefaultQuery(sourceId, search, sort, descending))
|
||||||
|
} else {
|
||||||
when (sort) {
|
when (sort) {
|
||||||
PlaylistSort.Name -> {
|
PlaylistSort.Name -> {
|
||||||
if (descending) {
|
if (descending) {
|
||||||
@@ -297,6 +311,7 @@ class LibraryRepository(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun album(
|
fun album(
|
||||||
sourceId: Long,
|
sourceId: Long,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import androidx.room3.useReaderConnection
|
|||||||
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
|
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
|
||||||
import androidx.test.core.app.ApplicationProvider
|
import androidx.test.core.app.ApplicationProvider
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import com.subtracks.awaitUntil
|
||||||
import com.subtracks.data.model.Album
|
import com.subtracks.data.model.Album
|
||||||
import com.subtracks.data.model.Artist
|
import com.subtracks.data.model.Artist
|
||||||
import com.subtracks.data.model.DownloadStatus
|
import com.subtracks.data.model.DownloadStatus
|
||||||
@@ -15,6 +16,9 @@ import com.subtracks.data.model.PlaylistSong
|
|||||||
import com.subtracks.data.model.Song
|
import com.subtracks.data.model.Song
|
||||||
import com.subtracks.data.model.SongDownload
|
import com.subtracks.data.model.SongDownload
|
||||||
import com.subtracks.data.model.Source
|
import com.subtracks.data.model.Source
|
||||||
|
import com.subtracks.data.prefs.AlbumSort
|
||||||
|
import com.subtracks.data.prefs.ArtistSort
|
||||||
|
import com.subtracks.data.prefs.PlaylistSort
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
@@ -24,6 +28,7 @@ import org.junit.Assert.assertTrue
|
|||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class LibraryDaoTest {
|
class LibraryDaoTest {
|
||||||
@@ -703,6 +708,117 @@ class LibraryDaoTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun defaultPagingQueriesMatchTheDownloadFreeFilters() =
|
||||||
|
runTest {
|
||||||
|
val sourceId = source()
|
||||||
|
val dao = db.libraryDao()
|
||||||
|
dao.upsertAlbums(
|
||||||
|
listOf(
|
||||||
|
album(sourceId, "a", "Beta", year = 2000, starred = null).copy(albumArtist = "Zed"),
|
||||||
|
album(sourceId, "b", "Alpha", year = 2010, starred = 5).copy(albumArtist = "Zed"),
|
||||||
|
album(sourceId, "c", "Gamma", year = 1990, starred = null).copy(albumArtist = "Ann"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
listOf("Alpha", "Beta", "Gamma"),
|
||||||
|
dao.albumsPaging(albumsDefaultQuery(sourceId, 0, "", AlbumSort.Name, false)).page().map { it.name },
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
listOf("Gamma", "Beta", "Alpha"),
|
||||||
|
dao.albumsPaging(albumsDefaultQuery(sourceId, 0, "", AlbumSort.Name, true)).page().map { it.name },
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
listOf("Alpha", "Beta", "Gamma"),
|
||||||
|
dao.albumsPaging(albumsDefaultQuery(sourceId, 0, "", AlbumSort.Year, false)).page().map { it.name },
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
listOf("Gamma", "Beta", "Alpha"),
|
||||||
|
dao.albumsPaging(albumsDefaultQuery(sourceId, 0, "", AlbumSort.Artist, false)).page().map { it.name },
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
listOf("Alpha"),
|
||||||
|
dao.albumsPaging(albumsDefaultQuery(sourceId, 0, "alp", AlbumSort.Name, false)).page().map { it.name },
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
listOf("Gamma"),
|
||||||
|
dao.albumsPaging(albumsDefaultQuery(sourceId, 0, "am", AlbumSort.Name, false)).page().map { it.name },
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
listOf("Alpha"),
|
||||||
|
dao.albumsPaging(albumsDefaultQuery(sourceId, 1, "", AlbumSort.Name, false)).page().map { it.name },
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
listOf("Beta", "Gamma"),
|
||||||
|
dao.albumsPaging(albumsDefaultQuery(sourceId, 2, "", AlbumSort.Name, false)).page().map { it.name },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun defaultArtistAndPlaylistPagingQueriesMatchTheDownloadFreeFilters() =
|
||||||
|
runTest {
|
||||||
|
val sourceId = source()
|
||||||
|
val dao = db.libraryDao()
|
||||||
|
dao.upsertArtists(
|
||||||
|
listOf(
|
||||||
|
artist(sourceId, "ar-1", "Beta", albumCount = 3),
|
||||||
|
artist(sourceId, "ar-2", "Alpha", albumCount = 1),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
dao.upsertPlaylists(
|
||||||
|
listOf(
|
||||||
|
playlist(sourceId, "p1", "Beta", created = 0, changed = 5),
|
||||||
|
playlist(sourceId, "p2", "Alpha", created = 1, changed = 0),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
listOf("Alpha", "Beta"),
|
||||||
|
dao.artistsPaging(artistsDefaultQuery(sourceId, 0, "a", ArtistSort.Name, false)).page().map { it.name },
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
listOf("Beta", "Alpha"),
|
||||||
|
dao.artistsPaging(artistsDefaultQuery(sourceId, 0, "", ArtistSort.AlbumCount, false)).page().map { it.name },
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
listOf("Alpha", "Beta"),
|
||||||
|
dao.playlistsPaging(playlistsDefaultQuery(sourceId, "a", PlaylistSort.Name, false)).page().map { it.name },
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
listOf("Alpha", "Beta"),
|
||||||
|
dao.playlistsPaging(playlistsDefaultQuery(sourceId, "", PlaylistSort.Added, false)).page().map { it.name },
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
listOf("Beta", "Alpha"),
|
||||||
|
dao.playlistsPaging(playlistsDefaultQuery(sourceId, "", PlaylistSort.Updated, false)).page().map { it.name },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun downloadWritesDoNotInvalidateTheDefaultPagingSource() =
|
||||||
|
runTest {
|
||||||
|
val sourceId = source()
|
||||||
|
val dao = db.libraryDao()
|
||||||
|
dao.upsertAlbums(listOf(album(sourceId, "a", "Alpha", year = null, starred = null)))
|
||||||
|
dao.upsertSongs(listOf(song(sourceId, "s1", "One", starred = null).copy(albumId = "a")))
|
||||||
|
|
||||||
|
val defaultInvalidated = AtomicBoolean(false)
|
||||||
|
val default = dao.albumsPaging(albumsDefaultQuery(sourceId, 0, "", AlbumSort.Name, false))
|
||||||
|
default.registerInvalidatedCallback { defaultInvalidated.set(true) }
|
||||||
|
default.page()
|
||||||
|
|
||||||
|
val downloadedInvalidated = AtomicBoolean(false)
|
||||||
|
val downloaded = dao.albumsByName(sourceId, 0, "", downloadedFilter = 1)
|
||||||
|
downloaded.registerInvalidatedCallback { downloadedInvalidated.set(true) }
|
||||||
|
downloaded.page()
|
||||||
|
|
||||||
|
db.downloadDao().upsert(SongDownload(sourceId, "s1", DownloadStatus.Completed))
|
||||||
|
|
||||||
|
awaitUntil("the downloaded list observes download writes") { downloadedInvalidated.get() }
|
||||||
|
assertFalse("the default list must not observe download writes", defaultInvalidated.get())
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun source(): Long =
|
private suspend fun source(): Long =
|
||||||
db.sourcesDao().insertSource(Source(name = "Navidrome", address = "http://localhost", isActive = true, createdAt = 0))
|
db.sourcesDao().insertSource(Source(name = "Navidrome", address = "http://localhost", isActive = true, createdAt = 0))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user