mirror of
https://github.com/Boof2015/astra-mobile.git
synced 2026-08-17 19:24:22 +02:00
fix slider + list bugs
This commit is contained in:
+94
@@ -381,6 +381,95 @@ class RoomLibraryRepositoryTest {
|
||||
assertTrue(first.map { it.path }.intersect(second.map { it.path }.toSet()).isEmpty())
|
||||
}
|
||||
|
||||
/**
|
||||
* Walking backwards has to reproduce the forward ordering exactly — an off-by-one in
|
||||
* the reversed tie-break tuple would either duplicate a row or silently skip one when
|
||||
* the list refills upwards after an A-Z jump.
|
||||
*/
|
||||
@Test
|
||||
fun backwardTitlePagesMirrorForwardPages() = runBlocking {
|
||||
publish("g1", seedAlphabet())
|
||||
val dao = catalog.catalogDao()
|
||||
|
||||
val forward = mutableListOf<ActiveTrackView>()
|
||||
var afterKey: String? = null
|
||||
var afterPath = ""
|
||||
while (true) {
|
||||
val page = dao.getTitlePage(afterKey, afterPath, 40)
|
||||
if (page.isEmpty()) break
|
||||
forward += page
|
||||
afterKey = page.last().titleSortKey
|
||||
afterPath = page.last().path
|
||||
}
|
||||
assertEquals(ALPHABET_SEED_SIZE, forward.size)
|
||||
|
||||
val backward = mutableListOf<ActiveTrackView>()
|
||||
var beforeKey = forward.last().titleSortKey
|
||||
var beforePath = forward.last().path
|
||||
while (true) {
|
||||
val page = dao.getTitlePageBefore(beforeKey, beforePath, 40)
|
||||
if (page.isEmpty()) break
|
||||
backward += page
|
||||
beforeKey = page.last().titleSortKey
|
||||
beforePath = page.last().path
|
||||
}
|
||||
// Backward pages are exclusive of the anchor row and come back descending.
|
||||
assertEquals(
|
||||
forward.dropLast(1).map { it.path }.reversed(),
|
||||
backward.map { it.path },
|
||||
)
|
||||
// Nothing above the very first row.
|
||||
assertTrue(dao.getTitlePageBefore(forward.first().titleSortKey, forward.first().path, 40).isEmpty())
|
||||
}
|
||||
|
||||
/**
|
||||
* The exact shape a rail jump uses: a section anchor carries only the leading sort key
|
||||
* with an empty path tie-break, so the forward page starts at the letter and the
|
||||
* backward page must be the slice immediately above it — no overlap, no gap.
|
||||
*/
|
||||
@Test
|
||||
fun sectionAnchorSplitsCatalogWithoutOverlapOrGap() = runBlocking {
|
||||
publish("g1", seedAlphabet())
|
||||
val dao = catalog.catalogDao()
|
||||
|
||||
val all = mutableListOf<ActiveTrackView>()
|
||||
var afterKey: String? = null
|
||||
var afterPath = ""
|
||||
while (true) {
|
||||
val page = dao.getTitlePage(afterKey, afterPath, 100)
|
||||
if (page.isEmpty()) break
|
||||
all += page
|
||||
afterKey = page.last().titleSortKey
|
||||
afterPath = page.last().path
|
||||
}
|
||||
|
||||
val anchor = dao.getTitleSectionAnchors().first { it.sectionLabel == "F" }
|
||||
val at = dao.getTitlePage(anchor.sortKey, "", 40)
|
||||
val above = dao.getTitlePageBefore(anchor.sortKey, "", 40)
|
||||
|
||||
assertEquals("F", SortKeys.sectionLabel(at.first().title))
|
||||
assertTrue(above.all { SortKeys.sectionLabel(it.title) != "F" })
|
||||
|
||||
val anchorIndex = all.indexOfFirst { it.path == at.first().path }
|
||||
assertEquals(40, above.size)
|
||||
assertEquals(
|
||||
all.subList(anchorIndex - above.size, anchorIndex).map { it.path },
|
||||
above.reversed().map { it.path },
|
||||
)
|
||||
}
|
||||
|
||||
/** 10 tracks under each of A-Z, so every section has rows above and below it. */
|
||||
private fun seedAlphabet(): List<TrackEntity> =
|
||||
(0 until ALPHABET_SEED_SIZE).map { index ->
|
||||
val letter = 'A' + (index / 10)
|
||||
track(
|
||||
generation = "g1",
|
||||
index = index,
|
||||
title = "$letter${"%03d".format(index)} Song",
|
||||
path = "content://track/$index.flac",
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun publish(
|
||||
generation: String,
|
||||
tracks: List<TrackEntity>,
|
||||
@@ -442,4 +531,9 @@ class RoomLibraryRepositoryTest {
|
||||
trackSort = index,
|
||||
sectionLabel = SortKeys.sectionLabel(title),
|
||||
)
|
||||
|
||||
private companion object {
|
||||
/** 26 letters x 10 tracks. */
|
||||
const val ALPHABET_SEED_SIZE = 260
|
||||
}
|
||||
}
|
||||
|
||||
+39
@@ -90,6 +90,18 @@ class AstraLibraryDataModule : Module() {
|
||||
}
|
||||
}
|
||||
|
||||
AsyncFunction("getTrackPageBefore") Coroutine {
|
||||
sort: String,
|
||||
cursor: String?,
|
||||
limit: Int,
|
||||
->
|
||||
try {
|
||||
repository().getTrackPageBefore(sort, cursor, limit)
|
||||
} catch (_: StaleRevisionException) {
|
||||
mapOf("error" to "STALE_REVISION")
|
||||
}
|
||||
}
|
||||
|
||||
AsyncFunction("getTrack") Coroutine { path: String ->
|
||||
repository().getTrack(path)
|
||||
}
|
||||
@@ -367,6 +379,19 @@ class AstraLibraryDataModule : Module() {
|
||||
}
|
||||
}
|
||||
|
||||
AsyncFunction("getAlbumPageBefore") Coroutine {
|
||||
sort: String,
|
||||
includeSingles: Boolean,
|
||||
cursor: String?,
|
||||
limit: Int,
|
||||
->
|
||||
try {
|
||||
repository().getAlbumPageBefore(sort, includeSingles, cursor, limit)
|
||||
} catch (_: StaleRevisionException) {
|
||||
mapOf("error" to "STALE_REVISION")
|
||||
}
|
||||
}
|
||||
|
||||
AsyncFunction("getArtistPage") Coroutine {
|
||||
sort: String,
|
||||
groupingMode: String,
|
||||
@@ -381,6 +406,20 @@ class AstraLibraryDataModule : Module() {
|
||||
}
|
||||
}
|
||||
|
||||
AsyncFunction("getArtistPageBefore") Coroutine {
|
||||
sort: String,
|
||||
groupingMode: String,
|
||||
includeCollaborations: Boolean,
|
||||
cursor: String?,
|
||||
limit: Int,
|
||||
->
|
||||
try {
|
||||
repository().getArtistPageBefore(sort, groupingMode, includeCollaborations, cursor, limit)
|
||||
} catch (_: StaleRevisionException) {
|
||||
mapOf("error" to "STALE_REVISION")
|
||||
}
|
||||
}
|
||||
|
||||
AsyncFunction("getAlbumDetail") Coroutine { albumKey: String, cursor: String?, limit: Int ->
|
||||
try {
|
||||
repository().getAlbumDetail(albumKey, cursor, limit)
|
||||
|
||||
+222
-72
@@ -596,39 +596,7 @@ class AstraLibraryRepository private constructor(
|
||||
limit = limit,
|
||||
)
|
||||
}
|
||||
val next = rows.lastOrNull()?.let { row ->
|
||||
when (sort) {
|
||||
"artist" -> TrackPageCursor(
|
||||
revision = revision,
|
||||
kind = "tracks:$sort",
|
||||
text1 = row.artistSortKey,
|
||||
text2 = row.albumSortKey,
|
||||
text3 = "${row.titleSortKey}\u0000${row.path}",
|
||||
number1 = row.discSort.toLong(),
|
||||
number2 = row.trackSort.toLong(),
|
||||
// Artist cursor needs one extra string. Encode the path alongside the
|
||||
// title key with a NUL delimiter; SAF paths cannot contain NUL.
|
||||
)
|
||||
"recently_added" -> TrackPageCursor(
|
||||
revision = revision,
|
||||
kind = "tracks:$sort",
|
||||
text1 = row.path,
|
||||
number1 = row.addedAt,
|
||||
)
|
||||
"duration" -> TrackPageCursor(
|
||||
revision = revision,
|
||||
kind = "tracks:$sort",
|
||||
text1 = row.path,
|
||||
decimal1 = row.duration,
|
||||
)
|
||||
else -> TrackPageCursor(
|
||||
revision = revision,
|
||||
kind = "tracks:$sort",
|
||||
text1 = row.titleSortKey,
|
||||
text2 = row.path,
|
||||
)
|
||||
}.encode()
|
||||
}
|
||||
val next = rows.lastOrNull()?.let { row -> trackCursor(revision, sort, row).encode() }
|
||||
mapOf(
|
||||
"items" to rows.map(ActiveTrackView::toBridgeMap),
|
||||
"nextCursor" to next,
|
||||
@@ -638,6 +606,93 @@ class AstraLibraryRepository private constructor(
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* The page immediately *above* [cursorRaw] — the backward twin of [getTrackPage].
|
||||
* An A-Z rail jump drops the list into the middle of the catalog; this is what the
|
||||
* rows above it are fetched with when the user scrolls back up. Items come back
|
||||
* ascending, like every other page.
|
||||
*
|
||||
* `previousCursor` is non-null only on a full page: a short one means we walked off
|
||||
* the head of the catalog, so the caller knows to stop asking.
|
||||
*
|
||||
* Only the sorts the rail is offered for (`title`, `artist`) can be walked backwards;
|
||||
* anything else returns an empty page.
|
||||
*/
|
||||
suspend fun getTrackPageBefore(
|
||||
sort: String,
|
||||
cursorRaw: String?,
|
||||
requestedLimit: Int,
|
||||
): Map<String, Any?> = withCatalogRecovery { database ->
|
||||
initialize()
|
||||
val dao = database.catalogDao()
|
||||
val revision = dao.getRevision()
|
||||
val cursor = validateCursor(cursorRaw, revision, "tracks:$sort")
|
||||
val limit = requestedLimit.coerceIn(1, MAX_PAGE_SIZE)
|
||||
val descending = when {
|
||||
cursor == null -> emptyList()
|
||||
sort == "artist" -> dao.getArtistOrderPageBefore(
|
||||
beforeArtistKey = cursor.text1.orEmpty(),
|
||||
beforeAlbumKey = cursor.text2.orEmpty(),
|
||||
beforeDisc = cursor.number1?.toInt() ?: 0,
|
||||
beforeTrack = cursor.number2?.toInt() ?: 0,
|
||||
beforeTitleKey = cursorTitleKey(cursor),
|
||||
beforePath = cursorPath(cursor),
|
||||
limit = limit,
|
||||
)
|
||||
sort == "title" -> dao.getTitlePageBefore(
|
||||
beforeTitleKey = cursor.text1.orEmpty(),
|
||||
beforePath = cursor.text2.orEmpty(),
|
||||
limit = limit,
|
||||
)
|
||||
else -> emptyList()
|
||||
}
|
||||
// The DESC result's last row is the topmost one — the cursor for the page above this one.
|
||||
val previous = descending.takeIf { it.size == limit }
|
||||
?.lastOrNull()
|
||||
?.let { row -> trackCursor(revision, sort, row).encode() }
|
||||
mapOf(
|
||||
"items" to descending.reversed().map(ActiveTrackView::toBridgeMap),
|
||||
"nextCursor" to null,
|
||||
"previousCursor" to previous,
|
||||
"totalCount" to dao.countActiveTracks().toDouble(),
|
||||
"catalogRevision" to revision.toString(),
|
||||
)
|
||||
}
|
||||
|
||||
/** Shared by the forward and backward track pages so the two can never disagree. */
|
||||
private fun trackCursor(revision: Long, sort: String, row: ActiveTrackView): TrackPageCursor =
|
||||
when (sort) {
|
||||
"artist" -> TrackPageCursor(
|
||||
revision = revision,
|
||||
kind = "tracks:$sort",
|
||||
text1 = row.artistSortKey,
|
||||
text2 = row.albumSortKey,
|
||||
text3 = "${row.titleSortKey}\u0000${row.path}",
|
||||
number1 = row.discSort.toLong(),
|
||||
number2 = row.trackSort.toLong(),
|
||||
// Artist cursor needs one extra string. Encode the path alongside the
|
||||
// title key with a NUL delimiter; SAF paths cannot contain NUL.
|
||||
)
|
||||
"recently_added" -> TrackPageCursor(
|
||||
revision = revision,
|
||||
kind = "tracks:$sort",
|
||||
text1 = row.path,
|
||||
number1 = row.addedAt,
|
||||
)
|
||||
"duration" -> TrackPageCursor(
|
||||
revision = revision,
|
||||
kind = "tracks:$sort",
|
||||
text1 = row.path,
|
||||
decimal1 = row.duration,
|
||||
)
|
||||
else -> TrackPageCursor(
|
||||
revision = revision,
|
||||
kind = "tracks:$sort",
|
||||
text1 = row.titleSortKey,
|
||||
text2 = row.path,
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getTrack(path: String): Map<String, Any?>? =
|
||||
withCatalogRecovery { database -> database.catalogDao().getActiveTrack(path)?.toBridgeMap() }
|
||||
|
||||
@@ -1674,36 +1729,7 @@ class AstraLibraryRepository private constructor(
|
||||
limit,
|
||||
)
|
||||
}
|
||||
val next = rows.lastOrNull()?.let { row ->
|
||||
when (sort) {
|
||||
"artist" -> TrackPageCursor(
|
||||
revision,
|
||||
kind,
|
||||
text1 = row.artistSortKey,
|
||||
text2 = row.nameSortKey,
|
||||
text3 = row.identityKey,
|
||||
)
|
||||
"recently_added" -> TrackPageCursor(
|
||||
revision,
|
||||
kind,
|
||||
text1 = row.identityKey,
|
||||
number1 = row.latestAddedAt,
|
||||
)
|
||||
"year" -> TrackPageCursor(
|
||||
revision,
|
||||
kind,
|
||||
text1 = row.nameSortKey,
|
||||
text2 = row.identityKey,
|
||||
number1 = (row.year ?: 0).toLong(),
|
||||
)
|
||||
else -> TrackPageCursor(
|
||||
revision,
|
||||
kind,
|
||||
text1 = row.nameSortKey,
|
||||
text2 = row.identityKey,
|
||||
)
|
||||
}.encode()
|
||||
}
|
||||
val next = rows.lastOrNull()?.let { row -> albumCursor(revision, kind, sort, row).encode() }
|
||||
mapOf(
|
||||
"items" to rows.map(AlbumSummaryEntity::toBridgeMap),
|
||||
"nextCursor" to next,
|
||||
@@ -1713,6 +1739,85 @@ class AstraLibraryRepository private constructor(
|
||||
)
|
||||
}
|
||||
|
||||
/** Backward twin of [getAlbumPage]; see [getTrackPageBefore] for the contract. */
|
||||
suspend fun getAlbumPageBefore(
|
||||
sort: String,
|
||||
includeSingles: Boolean,
|
||||
cursorRaw: String?,
|
||||
requestedLimit: Int,
|
||||
): Map<String, Any?> = withCatalogRecovery { database ->
|
||||
val dao = database.catalogDao()
|
||||
val revision = dao.getRevision()
|
||||
val kind = "albums:$sort:${if (includeSingles) 1 else 0}"
|
||||
val cursor = validateCursor(cursorRaw, revision, kind)
|
||||
val limit = requestedLimit.coerceIn(1, MAX_PAGE_SIZE)
|
||||
val descending = when {
|
||||
cursor == null -> emptyList()
|
||||
sort == "artist" -> dao.getAlbumArtistPageBefore(
|
||||
revision,
|
||||
includeSingles,
|
||||
cursor.text1.orEmpty(),
|
||||
cursor.text2.orEmpty(),
|
||||
cursor.text3.orEmpty(),
|
||||
limit,
|
||||
)
|
||||
sort == "name" -> dao.getAlbumNamePageBefore(
|
||||
revision,
|
||||
includeSingles,
|
||||
cursor.text1.orEmpty(),
|
||||
cursor.text2.orEmpty(),
|
||||
limit,
|
||||
)
|
||||
else -> emptyList()
|
||||
}
|
||||
val previous = descending.takeIf { it.size == limit }
|
||||
?.lastOrNull()
|
||||
?.let { row -> albumCursor(revision, kind, sort, row).encode() }
|
||||
mapOf(
|
||||
"items" to descending.reversed().map(AlbumSummaryEntity::toBridgeMap),
|
||||
"nextCursor" to null,
|
||||
"previousCursor" to previous,
|
||||
"totalCount" to dao.countAlbums(revision, includeSingles).toDouble(),
|
||||
"catalogRevision" to revision.toString(),
|
||||
)
|
||||
}
|
||||
|
||||
/** Shared by the forward and backward album pages so the two can never disagree. */
|
||||
private fun albumCursor(
|
||||
revision: Long,
|
||||
kind: String,
|
||||
sort: String,
|
||||
row: AlbumSummaryEntity,
|
||||
): TrackPageCursor =
|
||||
when (sort) {
|
||||
"artist" -> TrackPageCursor(
|
||||
revision,
|
||||
kind,
|
||||
text1 = row.artistSortKey,
|
||||
text2 = row.nameSortKey,
|
||||
text3 = row.identityKey,
|
||||
)
|
||||
"recently_added" -> TrackPageCursor(
|
||||
revision,
|
||||
kind,
|
||||
text1 = row.identityKey,
|
||||
number1 = row.latestAddedAt,
|
||||
)
|
||||
"year" -> TrackPageCursor(
|
||||
revision,
|
||||
kind,
|
||||
text1 = row.nameSortKey,
|
||||
text2 = row.identityKey,
|
||||
number1 = (row.year ?: 0).toLong(),
|
||||
)
|
||||
else -> TrackPageCursor(
|
||||
revision,
|
||||
kind,
|
||||
text1 = row.nameSortKey,
|
||||
text2 = row.identityKey,
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getArtistPage(
|
||||
sort: String,
|
||||
groupingMode: String,
|
||||
@@ -1746,15 +1851,7 @@ class AstraLibraryRepository private constructor(
|
||||
limit,
|
||||
)
|
||||
}
|
||||
val next = rows.lastOrNull()?.let { row ->
|
||||
TrackPageCursor(
|
||||
revision,
|
||||
kind,
|
||||
text1 = row.nameSortKey,
|
||||
text2 = row.artistKey,
|
||||
number1 = if (sort == "track_count") row.trackCount else null,
|
||||
).encode()
|
||||
}
|
||||
val next = rows.lastOrNull()?.let { row -> artistCursor(revision, kind, sort, row).encode() }
|
||||
mapOf(
|
||||
"items" to rows.map(ArtistSummaryEntity::toBridgeMap),
|
||||
"nextCursor" to next,
|
||||
@@ -1764,6 +1861,59 @@ class AstraLibraryRepository private constructor(
|
||||
)
|
||||
}
|
||||
|
||||
/** Backward twin of [getArtistPage]; see [getTrackPageBefore] for the contract. */
|
||||
suspend fun getArtistPageBefore(
|
||||
sort: String,
|
||||
groupingMode: String,
|
||||
includeCollaborations: Boolean,
|
||||
cursorRaw: String?,
|
||||
requestedLimit: Int,
|
||||
): Map<String, Any?> = withCatalogRecovery { database ->
|
||||
val dao = database.catalogDao()
|
||||
val revision = dao.getRevision()
|
||||
val mode = if (groupingMode == "fileTags") "fileTags" else "astra"
|
||||
val kind = "artists:$sort:$mode:${if (includeCollaborations) 1 else 0}"
|
||||
val cursor = validateCursor(cursorRaw, revision, kind)
|
||||
val limit = requestedLimit.coerceIn(1, MAX_PAGE_SIZE)
|
||||
val descending = if (cursor == null || sort == "track_count") {
|
||||
emptyList()
|
||||
} else {
|
||||
dao.getArtistNamePageBefore(
|
||||
revision,
|
||||
mode,
|
||||
includeCollaborations,
|
||||
cursor.text1.orEmpty(),
|
||||
cursor.text2.orEmpty(),
|
||||
limit,
|
||||
)
|
||||
}
|
||||
val previous = descending.takeIf { it.size == limit }
|
||||
?.lastOrNull()
|
||||
?.let { row -> artistCursor(revision, kind, sort, row).encode() }
|
||||
mapOf(
|
||||
"items" to descending.reversed().map(ArtistSummaryEntity::toBridgeMap),
|
||||
"nextCursor" to null,
|
||||
"previousCursor" to previous,
|
||||
"totalCount" to dao.countArtists(revision, mode, includeCollaborations).toDouble(),
|
||||
"catalogRevision" to revision.toString(),
|
||||
)
|
||||
}
|
||||
|
||||
/** Shared by the forward and backward artist pages so the two can never disagree. */
|
||||
private fun artistCursor(
|
||||
revision: Long,
|
||||
kind: String,
|
||||
sort: String,
|
||||
row: ArtistSummaryEntity,
|
||||
): TrackPageCursor =
|
||||
TrackPageCursor(
|
||||
revision,
|
||||
kind,
|
||||
text1 = row.nameSortKey,
|
||||
text2 = row.artistKey,
|
||||
number1 = if (sort == "track_count") row.trackCount else null,
|
||||
)
|
||||
|
||||
suspend fun getAlbumDetail(
|
||||
albumKey: String,
|
||||
cursorRaw: String?,
|
||||
|
||||
+113
@@ -360,6 +360,26 @@ interface CatalogDao {
|
||||
limit: Int,
|
||||
): List<ActiveTrackView>
|
||||
|
||||
/**
|
||||
* Mirror of [getTitlePage] walking backwards. Rows come out DESC — the caller
|
||||
* reverses them so `items` is ascending like every other page. Backward paging
|
||||
* always has a cursor (there is nothing before the head), so no NULL branch.
|
||||
*/
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM active_tracks
|
||||
WHERE title_sort_key < :beforeTitleKey
|
||||
OR (title_sort_key = :beforeTitleKey AND path < :beforePath)
|
||||
ORDER BY title_sort_key DESC, path DESC
|
||||
LIMIT :limit
|
||||
""",
|
||||
)
|
||||
suspend fun getTitlePageBefore(
|
||||
beforeTitleKey: String,
|
||||
beforePath: String,
|
||||
limit: Int,
|
||||
): List<ActiveTrackView>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM active_tracks
|
||||
@@ -387,6 +407,34 @@ interface CatalogDao {
|
||||
limit: Int,
|
||||
): List<ActiveTrackView>
|
||||
|
||||
/** Mirror of [getArtistOrderPage] walking backwards; rows come out DESC. */
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM active_tracks
|
||||
WHERE artist_sort_key < :beforeArtistKey
|
||||
OR (artist_sort_key = :beforeArtistKey AND album_sort_key < :beforeAlbumKey)
|
||||
OR (artist_sort_key = :beforeArtistKey AND album_sort_key = :beforeAlbumKey AND disc_sort < :beforeDisc)
|
||||
OR (artist_sort_key = :beforeArtistKey AND album_sort_key = :beforeAlbumKey AND disc_sort = :beforeDisc
|
||||
AND track_sort < :beforeTrack)
|
||||
OR (artist_sort_key = :beforeArtistKey AND album_sort_key = :beforeAlbumKey AND disc_sort = :beforeDisc
|
||||
AND track_sort = :beforeTrack AND title_sort_key < :beforeTitleKey)
|
||||
OR (artist_sort_key = :beforeArtistKey AND album_sort_key = :beforeAlbumKey AND disc_sort = :beforeDisc
|
||||
AND track_sort = :beforeTrack AND title_sort_key = :beforeTitleKey AND path < :beforePath)
|
||||
ORDER BY artist_sort_key DESC, album_sort_key DESC, disc_sort DESC, track_sort DESC,
|
||||
title_sort_key DESC, path DESC
|
||||
LIMIT :limit
|
||||
""",
|
||||
)
|
||||
suspend fun getArtistOrderPageBefore(
|
||||
beforeArtistKey: String,
|
||||
beforeAlbumKey: String,
|
||||
beforeDisc: Int,
|
||||
beforeTrack: Int,
|
||||
beforeTitleKey: String,
|
||||
beforePath: String,
|
||||
limit: Int,
|
||||
): List<ActiveTrackView>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM active_tracks
|
||||
@@ -543,6 +591,26 @@ interface CatalogDao {
|
||||
limit: Int,
|
||||
): List<AlbumSummaryEntity>
|
||||
|
||||
/** Mirror of [getAlbumNamePage] walking backwards; rows come out DESC. */
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM album_summaries
|
||||
WHERE revision = :revision
|
||||
AND (:includeSingles OR is_single = 0)
|
||||
AND (name_sort_key < :beforeKey
|
||||
OR (name_sort_key = :beforeKey AND identity_key < :beforeId))
|
||||
ORDER BY name_sort_key DESC, identity_key DESC
|
||||
LIMIT :limit
|
||||
""",
|
||||
)
|
||||
suspend fun getAlbumNamePageBefore(
|
||||
revision: Long,
|
||||
includeSingles: Boolean,
|
||||
beforeKey: String,
|
||||
beforeId: String,
|
||||
limit: Int,
|
||||
): List<AlbumSummaryEntity>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM album_summaries
|
||||
@@ -566,6 +634,29 @@ interface CatalogDao {
|
||||
limit: Int,
|
||||
): List<AlbumSummaryEntity>
|
||||
|
||||
/** Mirror of [getAlbumArtistPage] walking backwards; rows come out DESC. */
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM album_summaries
|
||||
WHERE revision = :revision
|
||||
AND (:includeSingles OR is_single = 0)
|
||||
AND (artist_sort_key < :beforeArtistKey
|
||||
OR (artist_sort_key = :beforeArtistKey AND name_sort_key < :beforeNameKey)
|
||||
OR (artist_sort_key = :beforeArtistKey AND name_sort_key = :beforeNameKey
|
||||
AND identity_key < :beforeId))
|
||||
ORDER BY artist_sort_key DESC, name_sort_key DESC, identity_key DESC
|
||||
LIMIT :limit
|
||||
""",
|
||||
)
|
||||
suspend fun getAlbumArtistPageBefore(
|
||||
revision: Long,
|
||||
includeSingles: Boolean,
|
||||
beforeArtistKey: String,
|
||||
beforeNameKey: String,
|
||||
beforeId: String,
|
||||
limit: Int,
|
||||
): List<AlbumSummaryEntity>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM album_summaries
|
||||
@@ -652,6 +743,28 @@ interface CatalogDao {
|
||||
limit: Int,
|
||||
): List<ArtistSummaryEntity>
|
||||
|
||||
/** Mirror of [getArtistNamePage] walking backwards; rows come out DESC. */
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM artist_summaries
|
||||
WHERE revision = :revision
|
||||
AND grouping_mode = :groupingMode
|
||||
AND (:includeCollaborations OR is_collaboration = 0)
|
||||
AND (name_sort_key < :beforeKey
|
||||
OR (name_sort_key = :beforeKey AND artist_key < :beforeId))
|
||||
ORDER BY name_sort_key DESC, artist_key DESC
|
||||
LIMIT :limit
|
||||
""",
|
||||
)
|
||||
suspend fun getArtistNamePageBefore(
|
||||
revision: Long,
|
||||
groupingMode: String,
|
||||
includeCollaborations: Boolean,
|
||||
beforeKey: String,
|
||||
beforeId: String,
|
||||
limit: Int,
|
||||
): List<ArtistSummaryEntity>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM artist_summaries
|
||||
|
||||
Reference in New Issue
Block a user