cancel a list in one go, and round the hero's touch feedback

- Cancelling a big playlist did per-row work: a binder cancel, two queries and a
  delete each, thousands of times, under the lock. The platform takes a list of
  ids in one call, so the cancel is now one binder call, one delete per chunk of
  rows and one sweep, which is both the several seconds and the stale button.
- Partial files are covered by the same sweep, which already deletes any file in
  a source directory that no row names; a test now proves a stray .part goes with
  the cancel.
- The hero's control was a plain clickable Box after the long-press work, so its
  splash was square; it clips to a circle the way an IconButton does.
This commit is contained in:
austinried
2026-10-04 00:30:03 +00:00
parent 3b6fb1341b
commit a57ca92250
7 changed files with 71 additions and 20 deletions
@@ -6,6 +6,7 @@ class FakeDownloadEngine : DownloadEngine {
val requests = mutableListOf<Pair<Long, EngineRequest>>()
val cancelled = mutableListOf<Long>()
var cancelCalls = 0
var enqueueDelayMs = 0L
var failEnqueueFor: String? = null
@@ -21,8 +22,9 @@ class FakeDownloadEngine : DownloadEngine {
override fun download(id: Long): EngineDownload? = downloads[id]
override fun cancel(id: Long) {
cancelled += id
override fun cancel(ids: List<Long>) {
cancelCalls++
cancelled += ids
}
fun running(
@@ -670,6 +670,35 @@ class DownloadRepositoryTest {
assertEquals(5L, repository.downloadedBytes(1, DownloadList.Artist, "ar1"))
}
@Test
fun cancellingAListRemovesPartialFilesToo() =
runTest {
seedLibrary()
runBlocking { repository.downloadAll(1, DownloadList.Album, "al1") }
writeFile(1, "s1", "half")
writeFile(1, "s1.part", "half")
writeFile(1, "s2", "half")
runBlocking { repository.cancelAll(1, DownloadList.Album, "al1") }
assertFalse(file(1, "s1").exists())
assertFalse(File(file(1, "s1").parentFile, "s1.part").exists())
assertFalse(file(1, "s2").exists())
}
@Test
fun cancellingAListCancelsThePlatformOnce() =
runTest {
seedLibrary()
seedSongs(3..40)
runBlocking { repository.downloadAll(1, DownloadList.Album, "al1") }
runBlocking { repository.cancelAll(1, DownloadList.Album, "al1") }
assertEquals(1, engine.cancelCalls)
assertTrue(allRows().isEmpty())
}
private fun requestedSongIds(): List<String> = engine.requests.map { it.second.path.substringAfterLast('/') }
private fun seedSongs(tracks: IntRange) {