reconcile and notify downloads outside the active source

A source switched away from kept downloading on the platform while our poller
ignored it, so its rows froze as Running, the notification vanished and the only
Cancel went with it. The poll now reconciles every source's in-flight rows (the
engine reports status without a source) and the notification reads all of them,
which is also what cancelActive already acted on.

The grouped tab queries drop entities with nothing downloaded, since the UI
treats a missing entry and an all-zero one the same.

The indicator's two-value enum was used as a boolean, so it is one; and the
library screenshot now asserts the ring is drawn, rather than capturing an
image that would stay green if it disappeared.
This commit is contained in:
austinried
2026-10-04 00:30:03 +00:00
parent 47064e82f0
commit 7db6d1365f
9 changed files with 81 additions and 34 deletions
@@ -851,6 +851,40 @@ class DownloadRepositoryTest {
assertEquals(setOf("ar1" to "Artist"), songs.map { it.artistId to it.artistName }.toSet())
}
@Test
fun activeDownloadsCoverEverySourceNotJustTheActiveOne() =
runTest {
seedLibrary()
runBlocking {
db.sourcesDao().upsertSource(Source(2, "other", "http://other/", isActive = false, createdAt = 0))
db.libraryDao().upsertSongs(
listOf(
Song(
sourceId = 2,
id = "o1",
albumId = "al2",
artistId = "ar2",
title = "Other",
album = "Other",
artist = "Other",
duration = 100,
track = 1,
disc = 1,
starred = null,
genre = null,
),
),
)
db.downloadDao().upsert(SongDownload(1, "s1", DownloadStatus.Running, engineId = 1))
db.downloadDao().upsert(SongDownload(1, "s2", DownloadStatus.Completed))
db.downloadDao().upsert(SongDownload(2, "o1", DownloadStatus.Queued))
}
val rows = runBlocking { repository.activeDownloads().first() }
assertEquals(setOf(1L to "s1", 2L to "o1"), rows.map { it.sourceId to it.songId }.toSet())
}
private fun requestedSongIds(): List<String> = engine.requests.map { it.second.path.substringAfterLast('/') }
private fun seedSongs(tracks: IntRange) {
@@ -11,7 +11,9 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.test.assertCountEquals
import androidx.compose.ui.test.junit4.v2.createAndroidComposeRule
import androidx.compose.ui.test.onAllNodesWithContentDescription
import androidx.compose.ui.test.onAllNodesWithTag
import androidx.compose.ui.test.onAllNodesWithText
import androidx.compose.ui.test.onRoot
@@ -41,6 +43,7 @@ import com.subtracks.data.prefs.StreamQuality
import com.subtracks.playback.PlaybackState
import com.subtracks.playback.QueueItem
import com.subtracks.playback.RepeatMode
import com.subtracks.ui.components.DOWNLOADING_INDICATOR_TAG
import com.subtracks.ui.library.ALBUM_COVER_TAG
import com.subtracks.ui.library.ARTIST_NAME_SCRIM_TAG
import com.subtracks.ui.library.AlbumDetailScreen
@@ -154,6 +157,7 @@ class AppScreenshotTest {
}
}
awaitTag(ALBUM_COVER_TAG)
composeRule.onAllNodesWithContentDescription(DOWNLOADING_INDICATOR_TAG).assertCountEquals(1)
composeRule.onRoot().captureRoboImage("src/test/screenshots/library_albums_downloads.png")
}
@@ -1,37 +1,38 @@
package com.subtracks.ui.components
import com.subtracks.data.model.ListDownloadStatus
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
class DownloadIndicatorTest {
@Test
fun anUntouchedListShowsNothing() {
assertEquals(DownloadIndicator.Hidden, ListDownloadStatus().indicator())
fun anUntouchedListIsNotDownloading() {
assertFalse(ListDownloadStatus().isDownloading())
}
@Test
fun aPopulatedButUnstartedListShowsNothing() {
assertEquals(DownloadIndicator.Hidden, ListDownloadStatus(total = 4).indicator())
fun aPopulatedButUnstartedListIsNotDownloading() {
assertFalse(ListDownloadStatus(total = 4).isDownloading())
}
@Test
fun aQueuedButNotYetDownloadedListShowsProgress() {
assertEquals(DownloadIndicator.InProgress, ListDownloadStatus(total = 4, downloading = 4).indicator())
fun aQueuedButNotYetDownloadedListIsDownloading() {
assertTrue(ListDownloadStatus(total = 4, downloading = 4).isDownloading())
}
@Test
fun aPartlyDownloadedIdleListShowsNothing() {
assertEquals(DownloadIndicator.Hidden, ListDownloadStatus(total = 4, downloaded = 2).indicator())
fun aPartlyDownloadedIdleListIsNotDownloading() {
assertFalse(ListDownloadStatus(total = 4, downloaded = 2).isDownloading())
}
@Test
fun aPartlyDownloadedListWithSomethingInFlightShowsProgress() {
assertEquals(DownloadIndicator.InProgress, ListDownloadStatus(total = 4, downloaded = 2, downloading = 1).indicator())
fun aPartlyDownloadedListWithSomethingInFlightIsDownloading() {
assertTrue(ListDownloadStatus(total = 4, downloaded = 2, downloading = 1).isDownloading())
}
@Test
fun aFullyDownloadedListShowsNothing() {
assertEquals(DownloadIndicator.Hidden, ListDownloadStatus(total = 4, downloaded = 4).indicator())
fun aFullyDownloadedListIsNotDownloading() {
assertFalse(ListDownloadStatus(total = 4, downloaded = 4).isDownloading())
}
}