diff --git a/app/src/main/kotlin/com/subtracks/data/db/LibraryDao.kt b/app/src/main/kotlin/com/subtracks/data/db/LibraryDao.kt index 03262f7b..e5da63a9 100644 --- a/app/src/main/kotlin/com/subtracks/data/db/LibraryDao.kt +++ b/app/src/main/kotlin/com/subtracks/data/db/LibraryDao.kt @@ -82,8 +82,8 @@ internal const val ARTISTS_SELECT = internal const val ARTIST_ORDER_BY_NAME = "name COLLATE NOCASE, id" internal const val ARTIST_ORDER_BY_NAME_REVERSED = "name COLLATE NOCASE DESC, id DESC" -internal const val ARTIST_ORDER_BY_ALBUM_COUNT = "albumCount DESC, name COLLATE NOCASE, id" -internal const val ARTIST_ORDER_BY_ALBUM_COUNT_REVERSED = "albumCount ASC, name COLLATE NOCASE DESC, id DESC" +internal const val ARTIST_ORDER_BY_ALBUM_COUNT = "artists.albumCount DESC, name COLLATE NOCASE, id" +internal const val ARTIST_ORDER_BY_ALBUM_COUNT_REVERSED = "artists.albumCount ASC, name COLLATE NOCASE DESC, id DESC" internal const val ARTIST_ORDER_BY_STARRED = "starred DESC NULLS LAST, name COLLATE NOCASE, id" internal const val ARTIST_ORDER_BY_STARRED_REVERSED = "starred ASC NULLS LAST, name COLLATE NOCASE DESC, id DESC" diff --git a/app/src/main/kotlin/com/subtracks/ui/playback/QueueScreen.kt b/app/src/main/kotlin/com/subtracks/ui/playback/QueueScreen.kt index 37a0c2bd..ef73ac1f 100644 --- a/app/src/main/kotlin/com/subtracks/ui/playback/QueueScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/playback/QueueScreen.kt @@ -74,6 +74,12 @@ private const val POSITION_TIMEOUT_MS = 1_000L internal const val QUEUE_WINDOW_ROWS = QUEUE_CHUNK * 3 +// Offline the view hides non-downloaded rows, so resolving a contiguous position range can span far +// more than it shows. Cap the positions resolved per load (and per reconcile) so a sparse large +// playlist cannot make the view resolve the whole queue; online every position is shown, so the cap +// is never reached. +private const val MAX_SCAN_POSITIONS = QUEUE_WINDOW_ROWS * 4 + data class QueueRow( val id: Long, val position: Long, @@ -158,7 +164,8 @@ class QueueViewModel( return } val start = (rows.minOfOrNull { it.position } ?: 0L).coerceIn(0, snapshot.size - 1) - val end = (rows.maxOfOrNull { it.position } ?: (start + QUEUE_CHUNK - 1)).coerceIn(start, snapshot.size - 1) + val rawEnd = (rows.maxOfOrNull { it.position } ?: (start + QUEUE_CHUNK - 1)).coerceIn(start, snapshot.size - 1) + val end = rawEnd.coerceAtMost(start + MAX_SCAN_POSITIONS - 1) val loaded = queueRepository.range(snapshot, start, end).filter(::visible) val reused = reusedIds() val updated = loaded.map { rowFor(it, reused) } @@ -180,9 +187,11 @@ class QueueViewModel( val snapshot = queueRepository.snapshot() val collected = mutableListOf() var end = first - 1 - while (end >= 0L && collected.size < QUEUE_CHUNK) { + var scanned = 0L + while (end >= 0L && collected.size < QUEUE_CHUNK && scanned < MAX_SCAN_POSITIONS) { val start = (end - QUEUE_CHUNK + 1).coerceAtLeast(0) collected.addAll(0, queueRepository.range(snapshot, start, end).filter(::visible)) + scanned += end - start + 1 end = start - 1 } if (collected.isEmpty()) return@withLock @@ -201,9 +210,11 @@ class QueueViewModel( if (last >= snapshot.size - 1) return@withLock val collected = mutableListOf() var start = last + 1 - while (start < snapshot.size && collected.size < QUEUE_CHUNK) { + var scanned = 0L + while (start < snapshot.size && collected.size < QUEUE_CHUNK && scanned < MAX_SCAN_POSITIONS) { val to = (start + QUEUE_CHUNK - 1).coerceAtMost(snapshot.size - 1) collected.addAll(queueRepository.range(snapshot, start, to).filter(::visible)) + scanned += to - start + 1 start = to + 1 } if (collected.isEmpty()) return@withLock diff --git a/app/src/test/kotlin/com/subtracks/data/db/LibraryDaoTest.kt b/app/src/test/kotlin/com/subtracks/data/db/LibraryDaoTest.kt index 7a46482a..253105f2 100644 --- a/app/src/test/kotlin/com/subtracks/data/db/LibraryDaoTest.kt +++ b/app/src/test/kotlin/com/subtracks/data/db/LibraryDaoTest.kt @@ -420,7 +420,7 @@ class LibraryDaoTest { } @Test - fun downloadedFilterSortsArtistsByTheirDownloadedAlbumCount() = + fun downloadedFilterShowsDownloadedAlbumCountsButSortsByTheSyncedCount() = runTest { val sourceId = source() val dao = db.libraryDao() @@ -448,15 +448,11 @@ class LibraryDaoTest { db.downloadDao().upsert(SongDownload(sourceId, it, DownloadStatus.Completed)) } - // ar-1 has two downloaded albums, ar-2 one; by synced count ar-2 (3) leads ar-1 (1). - assertEquals( - listOf("ar-1", "ar-2"), - dao.artistsByAlbumCount(sourceId, 0, "", downloadedFilter = 1).page().map { it.id }, - ) - assertEquals( - listOf("ar-2", "ar-1"), - dao.artistsByAlbumCount(sourceId, 0, "").page().map { it.id }, - ) + // ar-1 has two downloaded albums, ar-2 one. The list still orders by the synced count + // (so the album-count index stays usable) but reports the downloaded count. + val filtered = dao.artistsByAlbumCount(sourceId, 0, "", downloadedFilter = 1).page() + assertEquals(listOf("ar-2", "ar-1"), filtered.map { it.id }) + assertEquals(listOf(1L, 2L), filtered.map { it.albumCount }) } @Test