mirror of
https://github.com/Boof2015/astra-mobile.git
synced 2026-08-16 16:21:21 +02:00
add library sort ordering
This commit is contained in:
+225
@@ -645,6 +645,212 @@ class RoomLibraryRepositoryTest {
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun descendingTitlePagesAndAnchorsRemainGapFree() = runBlocking {
|
||||
publish("g1", seedAlphabet())
|
||||
val dao = catalog.catalogDao()
|
||||
val expected = dao.getTitlePage(null, "", ALPHABET_SEED_SIZE)
|
||||
.sortedWith(compareByDescending<ActiveTrackView> { it.titleSortKey }.thenBy { it.path })
|
||||
|
||||
val paged = mutableListOf<ActiveTrackView>()
|
||||
var afterKey: String? = null
|
||||
var afterPath = ""
|
||||
while (true) {
|
||||
val page = dao.getTitlePageDescending(afterKey, afterPath, 37)
|
||||
if (page.isEmpty()) break
|
||||
paged += page
|
||||
afterKey = page.last().titleSortKey
|
||||
afterPath = page.last().path
|
||||
}
|
||||
assertEquals(expected.map { it.path }, paged.map { it.path })
|
||||
|
||||
val anchor = dao.getTitleSectionAnchorsDescending().first { it.sectionLabel == "F" }
|
||||
val at = dao.getTitlePageDescending(anchor.sortKey, "", 40)
|
||||
val above = dao.getTitlePageBeforeDescending(anchor.sortKey, "", 40)
|
||||
assertEquals("F", SortKeys.sectionLabel(at.first().title))
|
||||
assertTrue(above.all { SortKeys.sectionLabel(it.title) != "F" })
|
||||
val anchorIndex = expected.indexOfFirst { it.path == at.first().path }
|
||||
assertEquals(
|
||||
expected.subList(anchorIndex - above.size, anchorIndex).map { it.path },
|
||||
above.reversed().map { it.path },
|
||||
)
|
||||
assertTrue(
|
||||
dao.getTitlePageBeforeDescending(expected.first().titleSortKey, expected.first().path, 40)
|
||||
.isEmpty(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun trackDirectionsReverseOnlyThePrimaryField() = runBlocking {
|
||||
val rows = listOf(
|
||||
track("g1", 1, "Zulu Beta").copy(
|
||||
artist = "Zulu",
|
||||
artistSortKey = SortKeys.forText("Zulu"),
|
||||
album = "Beta",
|
||||
albumSortKey = SortKeys.forText("Beta"),
|
||||
addedAt = 30,
|
||||
duration = 300.0,
|
||||
),
|
||||
track("g1", 2, "Zulu Alpha").copy(
|
||||
artist = "Zulu",
|
||||
artistSortKey = SortKeys.forText("Zulu"),
|
||||
album = "Alpha",
|
||||
albumSortKey = SortKeys.forText("Alpha"),
|
||||
addedAt = 10,
|
||||
duration = 100.0,
|
||||
),
|
||||
track("g1", 3, "Alpha Gamma").copy(
|
||||
artist = "Alpha",
|
||||
artistSortKey = SortKeys.forText("Alpha"),
|
||||
album = "Gamma",
|
||||
albumSortKey = SortKeys.forText("Gamma"),
|
||||
addedAt = 20,
|
||||
duration = 200.0,
|
||||
),
|
||||
)
|
||||
publish("g1", rows)
|
||||
val dao = catalog.catalogDao()
|
||||
assertEquals(
|
||||
listOf(rows[1].path, rows[0].path, rows[2].path),
|
||||
dao.getArtistOrderPageDescending(null, "", 0, 0, "", "", 10).map { it.path },
|
||||
)
|
||||
assertEquals(
|
||||
listOf(rows[1].path, rows[2].path, rows[0].path),
|
||||
dao.getRecentlyAddedPageAscending(null, "", 10).map { it.path },
|
||||
)
|
||||
assertEquals(
|
||||
listOf(rows[1].path, rows[2].path, rows[0].path),
|
||||
dao.getDurationPageAscending(null, "", 10).map { it.path },
|
||||
)
|
||||
assertEquals(
|
||||
dao.getArtistOrderPageDescending(null, "", 0, 0, "", "", 10).map { it.path },
|
||||
dao.getAllPathsByArtistDescending(),
|
||||
)
|
||||
assertEquals(
|
||||
dao.getTitlePageDescending(null, "", 10).map { it.path },
|
||||
dao.getAllPathsByTitleDescending(),
|
||||
)
|
||||
assertEquals(
|
||||
dao.getRecentlyAddedPageAscending(null, "", 10).map { it.path },
|
||||
dao.getAllPathsByRecentlyAddedAscending(),
|
||||
)
|
||||
assertEquals(
|
||||
dao.getDurationPageAscending(null, "", 10).map { it.path },
|
||||
dao.getAllPathsByDurationAscending(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun albumAndArtistSummaryQueriesSupportBothDirections() = runBlocking {
|
||||
val dao = catalog.catalogDao()
|
||||
val revision = 7L
|
||||
dao.putAlbumSummaries(
|
||||
listOf(
|
||||
AlbumSummaryEntity(
|
||||
revision = revision,
|
||||
identityKey = "z-beta",
|
||||
album = "Beta",
|
||||
artist = "Zulu",
|
||||
year = 2020,
|
||||
trackCount = 1,
|
||||
totalDuration = 1.0,
|
||||
latestAddedAt = 30,
|
||||
nameSortKey = SortKeys.forText("Beta"),
|
||||
artistSortKey = SortKeys.forText("Zulu"),
|
||||
sectionLabel = "B",
|
||||
isSingle = false,
|
||||
),
|
||||
AlbumSummaryEntity(
|
||||
revision = revision,
|
||||
identityKey = "z-alpha",
|
||||
album = "Alpha",
|
||||
artist = "Zulu",
|
||||
year = null,
|
||||
trackCount = 1,
|
||||
totalDuration = 1.0,
|
||||
latestAddedAt = 10,
|
||||
nameSortKey = SortKeys.forText("Alpha"),
|
||||
artistSortKey = SortKeys.forText("Zulu"),
|
||||
sectionLabel = "A",
|
||||
isSingle = false,
|
||||
),
|
||||
AlbumSummaryEntity(
|
||||
revision = revision,
|
||||
identityKey = "a-gamma",
|
||||
album = "Gamma",
|
||||
artist = "Alpha",
|
||||
year = 1990,
|
||||
trackCount = 1,
|
||||
totalDuration = 1.0,
|
||||
latestAddedAt = 20,
|
||||
nameSortKey = SortKeys.forText("Gamma"),
|
||||
artistSortKey = SortKeys.forText("Alpha"),
|
||||
sectionLabel = "G",
|
||||
isSingle = false,
|
||||
),
|
||||
),
|
||||
)
|
||||
assertEquals(
|
||||
listOf("z-alpha", "z-beta", "a-gamma"),
|
||||
dao.getAlbumArtistPageDescending(revision, true, null, "", "", 10)
|
||||
.map { it.identityKey },
|
||||
)
|
||||
assertEquals(
|
||||
listOf("a-gamma", "z-beta", "z-alpha"),
|
||||
dao.getAlbumNamePageDescending(revision, true, null, "", 10)
|
||||
.map { it.identityKey },
|
||||
)
|
||||
assertEquals(
|
||||
listOf("a-gamma"),
|
||||
dao.getAlbumNamePageBeforeDescending(
|
||||
revision,
|
||||
true,
|
||||
SortKeys.forText("Beta"),
|
||||
"z-beta",
|
||||
10,
|
||||
).map { it.identityKey },
|
||||
)
|
||||
assertEquals(
|
||||
listOf("a-gamma", "z-beta", "z-alpha"),
|
||||
dao.getAlbumYearPageAscending(revision, true, 0, null, "", "", 10)
|
||||
.map { it.identityKey },
|
||||
)
|
||||
assertEquals(
|
||||
listOf("z-alpha", "a-gamma", "z-beta"),
|
||||
dao.getAlbumRecentPageAscending(revision, true, null, "", 10)
|
||||
.map { it.identityKey },
|
||||
)
|
||||
|
||||
dao.putArtistSummaries(
|
||||
listOf(
|
||||
artistSummary(revision, "zulu", "Zulu", 2),
|
||||
artistSummary(revision, "alpha", "Alpha", 2),
|
||||
artistSummary(revision, "beta", "Beta", 1),
|
||||
),
|
||||
)
|
||||
assertEquals(
|
||||
listOf("Zulu", "Beta", "Alpha"),
|
||||
dao.getArtistNamePageDescending(revision, "astra", true, null, "", 10)
|
||||
.map { it.artist },
|
||||
)
|
||||
assertEquals(
|
||||
listOf("Zulu"),
|
||||
dao.getArtistNamePageBeforeDescending(
|
||||
revision,
|
||||
"astra",
|
||||
true,
|
||||
SortKeys.forText("Beta"),
|
||||
"beta",
|
||||
10,
|
||||
).map { it.artist },
|
||||
)
|
||||
assertEquals(
|
||||
listOf("Beta", "Alpha", "Zulu"),
|
||||
dao.getArtistCountPageAscending(revision, "astra", true, null, "", "", 10)
|
||||
.map { it.artist },
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun structuredArtistCreditsPreserveNamesContainingPunctuation() = runBlocking {
|
||||
val artistNames = listOf("Earth, Wind & Fire", "The Emotions")
|
||||
@@ -965,6 +1171,25 @@ class RoomLibraryRepositoryTest {
|
||||
sectionLabel = SortKeys.sectionLabel(title),
|
||||
)
|
||||
|
||||
private fun artistSummary(
|
||||
revision: Long,
|
||||
key: String,
|
||||
name: String,
|
||||
count: Long,
|
||||
): ArtistSummaryEntity = ArtistSummaryEntity(
|
||||
revision = revision,
|
||||
artistKey = key,
|
||||
artist = name,
|
||||
groupingMode = "astra",
|
||||
trackCount = count,
|
||||
primaryTrackCount = count,
|
||||
albumCount = 1,
|
||||
nameSortKey = SortKeys.forText(name),
|
||||
sectionLabel = SortKeys.sectionLabel(name),
|
||||
isCollaboration = false,
|
||||
artworkHashesJson = "[]",
|
||||
)
|
||||
|
||||
private companion object {
|
||||
/** 26 letters x 10 tracks. */
|
||||
const val ALPHABET_SEED_SIZE = 260
|
||||
|
||||
+21
-6
@@ -81,11 +81,12 @@ class AstraLibraryDataModule : Module() {
|
||||
|
||||
AsyncFunction("getTrackPage") Coroutine {
|
||||
sort: String,
|
||||
direction: String,
|
||||
cursor: String?,
|
||||
limit: Int,
|
||||
->
|
||||
try {
|
||||
repository().getTrackPage(sort, cursor, limit)
|
||||
repository().getTrackPage(sort, direction, cursor, limit)
|
||||
} catch (_: StaleRevisionException) {
|
||||
mapOf("error" to "STALE_REVISION")
|
||||
}
|
||||
@@ -93,11 +94,12 @@ class AstraLibraryDataModule : Module() {
|
||||
|
||||
AsyncFunction("getTrackPageBefore") Coroutine {
|
||||
sort: String,
|
||||
direction: String,
|
||||
cursor: String?,
|
||||
limit: Int,
|
||||
->
|
||||
try {
|
||||
repository().getTrackPageBefore(sort, cursor, limit)
|
||||
repository().getTrackPageBefore(sort, direction, cursor, limit)
|
||||
} catch (_: StaleRevisionException) {
|
||||
mapOf("error" to "STALE_REVISION")
|
||||
}
|
||||
@@ -385,12 +387,13 @@ class AstraLibraryDataModule : Module() {
|
||||
|
||||
AsyncFunction("getAlbumPage") Coroutine {
|
||||
sort: String,
|
||||
direction: String,
|
||||
includeSingles: Boolean,
|
||||
cursor: String?,
|
||||
limit: Int,
|
||||
->
|
||||
try {
|
||||
repository().getAlbumPage(sort, includeSingles, cursor, limit)
|
||||
repository().getAlbumPage(sort, direction, includeSingles, cursor, limit)
|
||||
} catch (_: StaleRevisionException) {
|
||||
mapOf("error" to "STALE_REVISION")
|
||||
}
|
||||
@@ -398,12 +401,13 @@ class AstraLibraryDataModule : Module() {
|
||||
|
||||
AsyncFunction("getAlbumPageBefore") Coroutine {
|
||||
sort: String,
|
||||
direction: String,
|
||||
includeSingles: Boolean,
|
||||
cursor: String?,
|
||||
limit: Int,
|
||||
->
|
||||
try {
|
||||
repository().getAlbumPageBefore(sort, includeSingles, cursor, limit)
|
||||
repository().getAlbumPageBefore(sort, direction, includeSingles, cursor, limit)
|
||||
} catch (_: StaleRevisionException) {
|
||||
mapOf("error" to "STALE_REVISION")
|
||||
}
|
||||
@@ -411,13 +415,14 @@ class AstraLibraryDataModule : Module() {
|
||||
|
||||
AsyncFunction("getArtistPage") Coroutine {
|
||||
sort: String,
|
||||
direction: String,
|
||||
groupingMode: String,
|
||||
includeCollaborations: Boolean,
|
||||
cursor: String?,
|
||||
limit: Int,
|
||||
->
|
||||
try {
|
||||
repository().getArtistPage(sort, groupingMode, includeCollaborations, cursor, limit)
|
||||
repository().getArtistPage(sort, direction, groupingMode, includeCollaborations, cursor, limit)
|
||||
} catch (_: StaleRevisionException) {
|
||||
mapOf("error" to "STALE_REVISION")
|
||||
}
|
||||
@@ -425,13 +430,21 @@ class AstraLibraryDataModule : Module() {
|
||||
|
||||
AsyncFunction("getArtistPageBefore") Coroutine {
|
||||
sort: String,
|
||||
direction: String,
|
||||
groupingMode: String,
|
||||
includeCollaborations: Boolean,
|
||||
cursor: String?,
|
||||
limit: Int,
|
||||
->
|
||||
try {
|
||||
repository().getArtistPageBefore(sort, groupingMode, includeCollaborations, cursor, limit)
|
||||
repository().getArtistPageBefore(
|
||||
sort,
|
||||
direction,
|
||||
groupingMode,
|
||||
includeCollaborations,
|
||||
cursor,
|
||||
limit,
|
||||
)
|
||||
} catch (_: StaleRevisionException) {
|
||||
mapOf("error" to "STALE_REVISION")
|
||||
}
|
||||
@@ -560,6 +573,7 @@ class AstraLibraryDataModule : Module() {
|
||||
AsyncFunction("getSectionAnchors") Coroutine {
|
||||
kind: String,
|
||||
sort: String,
|
||||
direction: String,
|
||||
includeSingles: Boolean,
|
||||
groupingMode: String,
|
||||
includeCollaborations: Boolean,
|
||||
@@ -567,6 +581,7 @@ class AstraLibraryDataModule : Module() {
|
||||
repository().getSectionAnchors(
|
||||
kind,
|
||||
sort,
|
||||
direction,
|
||||
includeSingles,
|
||||
groupingMode,
|
||||
includeCollaborations,
|
||||
|
||||
+178
-71
@@ -635,41 +635,43 @@ class AstraLibraryRepository private constructor(
|
||||
|
||||
suspend fun getTrackPage(
|
||||
sort: String,
|
||||
directionRaw: 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 direction = normalizeSortDirection(directionRaw)
|
||||
val cursor = validateCursor(cursorRaw, revision, "tracks:$sort:$direction")
|
||||
val limit = requestedLimit.coerceIn(1, MAX_PAGE_SIZE)
|
||||
val rows = when (sort) {
|
||||
"artist" -> dao.getArtistOrderPage(
|
||||
afterArtistKey = cursor?.text1,
|
||||
afterAlbumKey = cursor?.text2.orEmpty(),
|
||||
afterDisc = cursor?.number1?.toInt() ?: 0,
|
||||
afterTrack = cursor?.number2?.toInt() ?: 0,
|
||||
afterTitleKey = cursor?.let(::cursorTitleKey).orEmpty(),
|
||||
afterPath = cursor?.let { cursorPath(it) }.orEmpty(),
|
||||
limit = limit,
|
||||
"artist" -> (if (direction == "desc") dao::getArtistOrderPageDescending else dao::getArtistOrderPage)(
|
||||
cursor?.text1,
|
||||
cursor?.text2.orEmpty(),
|
||||
cursor?.number1?.toInt() ?: 0,
|
||||
cursor?.number2?.toInt() ?: 0,
|
||||
cursor?.let(::cursorTitleKey).orEmpty(),
|
||||
cursor?.let { cursorPath(it) }.orEmpty(),
|
||||
limit,
|
||||
)
|
||||
"recently_added" -> dao.getRecentlyAddedPage(
|
||||
afterAddedAt = cursor?.number1,
|
||||
afterPath = cursor?.text1.orEmpty(),
|
||||
limit = limit,
|
||||
"recently_added" -> (if (direction == "asc") dao::getRecentlyAddedPageAscending else dao::getRecentlyAddedPage)(
|
||||
cursor?.number1,
|
||||
cursor?.text1.orEmpty(),
|
||||
limit,
|
||||
)
|
||||
"duration" -> dao.getDurationPage(
|
||||
afterDuration = cursor?.decimal1,
|
||||
afterPath = cursor?.text1.orEmpty(),
|
||||
limit = limit,
|
||||
"duration" -> (if (direction == "asc") dao::getDurationPageAscending else dao::getDurationPage)(
|
||||
cursor?.decimal1,
|
||||
cursor?.text1.orEmpty(),
|
||||
limit,
|
||||
)
|
||||
else -> dao.getTitlePage(
|
||||
afterTitleKey = cursor?.text1,
|
||||
afterPath = cursor?.text2.orEmpty(),
|
||||
limit = limit,
|
||||
else -> (if (direction == "desc") dao::getTitlePageDescending else dao::getTitlePage)(
|
||||
cursor?.text1,
|
||||
cursor?.text2.orEmpty(),
|
||||
limit,
|
||||
)
|
||||
}
|
||||
val next = rows.lastOrNull()?.let { row -> trackCursor(revision, sort, row).encode() }
|
||||
val next = rows.lastOrNull()?.let { row -> trackCursor(revision, sort, direction, row).encode() }
|
||||
mapOf(
|
||||
"items" to rows.map(ActiveTrackView::toBridgeMap),
|
||||
"nextCursor" to next,
|
||||
@@ -693,36 +695,46 @@ class AstraLibraryRepository private constructor(
|
||||
*/
|
||||
suspend fun getTrackPageBefore(
|
||||
sort: String,
|
||||
directionRaw: 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 direction = normalizeSortDirection(directionRaw)
|
||||
val cursor = validateCursor(cursorRaw, revision, "tracks:$sort:$direction")
|
||||
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 == "artist" -> (if (direction == "desc") {
|
||||
dao::getArtistOrderPageBeforeDescending
|
||||
} else {
|
||||
dao::getArtistOrderPageBefore
|
||||
})(
|
||||
cursor.text1.orEmpty(),
|
||||
cursor.text2.orEmpty(),
|
||||
cursor.number1?.toInt() ?: 0,
|
||||
cursor.number2?.toInt() ?: 0,
|
||||
cursorTitleKey(cursor),
|
||||
cursorPath(cursor),
|
||||
limit,
|
||||
)
|
||||
sort == "title" -> dao.getTitlePageBefore(
|
||||
beforeTitleKey = cursor.text1.orEmpty(),
|
||||
beforePath = cursor.text2.orEmpty(),
|
||||
limit = limit,
|
||||
sort == "title" -> (if (direction == "desc") {
|
||||
dao::getTitlePageBeforeDescending
|
||||
} else {
|
||||
dao::getTitlePageBefore
|
||||
})(
|
||||
cursor.text1.orEmpty(),
|
||||
cursor.text2.orEmpty(),
|
||||
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() }
|
||||
?.let { row -> trackCursor(revision, sort, direction, row).encode() }
|
||||
mapOf(
|
||||
"items" to descending.reversed().map(ActiveTrackView::toBridgeMap),
|
||||
"nextCursor" to null,
|
||||
@@ -733,11 +745,16 @@ class AstraLibraryRepository private constructor(
|
||||
}
|
||||
|
||||
/** Shared by the forward and backward track pages so the two can never disagree. */
|
||||
private fun trackCursor(revision: Long, sort: String, row: ActiveTrackView): TrackPageCursor =
|
||||
private fun trackCursor(
|
||||
revision: Long,
|
||||
sort: String,
|
||||
direction: String,
|
||||
row: ActiveTrackView,
|
||||
): TrackPageCursor =
|
||||
when (sort) {
|
||||
"artist" -> TrackPageCursor(
|
||||
revision = revision,
|
||||
kind = "tracks:$sort",
|
||||
kind = "tracks:$sort:$direction",
|
||||
text1 = row.artistSortKey,
|
||||
text2 = row.albumSortKey,
|
||||
text3 = "${row.titleSortKey}\u0000${row.path}",
|
||||
@@ -748,19 +765,19 @@ class AstraLibraryRepository private constructor(
|
||||
)
|
||||
"recently_added" -> TrackPageCursor(
|
||||
revision = revision,
|
||||
kind = "tracks:$sort",
|
||||
kind = "tracks:$sort:$direction",
|
||||
text1 = row.path,
|
||||
number1 = row.addedAt,
|
||||
)
|
||||
"duration" -> TrackPageCursor(
|
||||
revision = revision,
|
||||
kind = "tracks:$sort",
|
||||
kind = "tracks:$sort:$direction",
|
||||
text1 = row.path,
|
||||
decimal1 = row.duration,
|
||||
)
|
||||
else -> TrackPageCursor(
|
||||
revision = revision,
|
||||
kind = "tracks:$sort",
|
||||
kind = "tracks:$sort:$direction",
|
||||
text1 = row.titleSortKey,
|
||||
text2 = row.path,
|
||||
)
|
||||
@@ -1882,17 +1899,19 @@ class AstraLibraryRepository private constructor(
|
||||
|
||||
suspend fun getAlbumPage(
|
||||
sort: String,
|
||||
directionRaw: 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 direction = normalizeSortDirection(directionRaw)
|
||||
val kind = "albums:$sort:$direction:${if (includeSingles) 1 else 0}"
|
||||
val cursor = validateCursor(cursorRaw, revision, kind)
|
||||
val limit = requestedLimit.coerceIn(1, MAX_PAGE_SIZE)
|
||||
val rows = when (sort) {
|
||||
"artist" -> dao.getAlbumArtistPage(
|
||||
"artist" -> (if (direction == "desc") dao::getAlbumArtistPageDescending else dao::getAlbumArtistPage)(
|
||||
revision,
|
||||
includeSingles,
|
||||
cursor?.text1,
|
||||
@@ -1900,22 +1919,23 @@ class AstraLibraryRepository private constructor(
|
||||
cursor?.text3.orEmpty(),
|
||||
limit,
|
||||
)
|
||||
"recently_added" -> dao.getAlbumRecentPage(
|
||||
"recently_added" -> (if (direction == "asc") dao::getAlbumRecentPageAscending else dao::getAlbumRecentPage)(
|
||||
revision,
|
||||
includeSingles,
|
||||
cursor?.number1,
|
||||
cursor?.text1.orEmpty(),
|
||||
limit,
|
||||
)
|
||||
"year" -> dao.getAlbumYearPage(
|
||||
"year" -> (if (direction == "asc") dao::getAlbumYearPageAscending else dao::getAlbumYearPage)(
|
||||
revision,
|
||||
includeSingles,
|
||||
cursor?.number1?.toInt(),
|
||||
cursor?.number1?.toInt() ?: 0,
|
||||
cursor?.number2?.toInt(),
|
||||
cursor?.text1.orEmpty(),
|
||||
cursor?.text2.orEmpty(),
|
||||
limit,
|
||||
)
|
||||
else -> dao.getAlbumNamePage(
|
||||
else -> (if (direction == "desc") dao::getAlbumNamePageDescending else dao::getAlbumNamePage)(
|
||||
revision,
|
||||
includeSingles,
|
||||
cursor?.text1,
|
||||
@@ -1936,18 +1956,24 @@ class AstraLibraryRepository private constructor(
|
||||
/** Backward twin of [getAlbumPage]; see [getTrackPageBefore] for the contract. */
|
||||
suspend fun getAlbumPageBefore(
|
||||
sort: String,
|
||||
directionRaw: 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 direction = normalizeSortDirection(directionRaw)
|
||||
val kind = "albums:$sort:$direction:${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(
|
||||
sort == "artist" -> (if (direction == "desc") {
|
||||
dao::getAlbumArtistPageBeforeDescending
|
||||
} else {
|
||||
dao::getAlbumArtistPageBefore
|
||||
})(
|
||||
revision,
|
||||
includeSingles,
|
||||
cursor.text1.orEmpty(),
|
||||
@@ -1955,7 +1981,11 @@ class AstraLibraryRepository private constructor(
|
||||
cursor.text3.orEmpty(),
|
||||
limit,
|
||||
)
|
||||
sort == "name" -> dao.getAlbumNamePageBefore(
|
||||
sort == "name" -> (if (direction == "desc") {
|
||||
dao::getAlbumNamePageBeforeDescending
|
||||
} else {
|
||||
dao::getAlbumNamePageBefore
|
||||
})(
|
||||
revision,
|
||||
includeSingles,
|
||||
cursor.text1.orEmpty(),
|
||||
@@ -2003,6 +2033,7 @@ class AstraLibraryRepository private constructor(
|
||||
text1 = row.nameSortKey,
|
||||
text2 = row.identityKey,
|
||||
number1 = (row.year ?: 0).toLong(),
|
||||
number2 = if (row.year == null) 1 else 0,
|
||||
)
|
||||
else -> TrackPageCursor(
|
||||
revision,
|
||||
@@ -2014,6 +2045,7 @@ class AstraLibraryRepository private constructor(
|
||||
|
||||
suspend fun getArtistPage(
|
||||
sort: String,
|
||||
directionRaw: String,
|
||||
groupingMode: String,
|
||||
includeCollaborations: Boolean,
|
||||
cursorRaw: String?,
|
||||
@@ -2022,11 +2054,12 @@ class AstraLibraryRepository private constructor(
|
||||
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 direction = normalizeSortDirection(directionRaw)
|
||||
val kind = "artists:$sort:$direction:$mode:${if (includeCollaborations) 1 else 0}"
|
||||
val cursor = validateCursor(cursorRaw, revision, kind)
|
||||
val limit = requestedLimit.coerceIn(1, MAX_PAGE_SIZE)
|
||||
val rows = if (sort == "track_count") {
|
||||
dao.getArtistCountPage(
|
||||
(if (direction == "asc") dao::getArtistCountPageAscending else dao::getArtistCountPage)(
|
||||
revision,
|
||||
mode,
|
||||
includeCollaborations,
|
||||
@@ -2036,7 +2069,7 @@ class AstraLibraryRepository private constructor(
|
||||
limit,
|
||||
)
|
||||
} else {
|
||||
dao.getArtistNamePage(
|
||||
(if (direction == "desc") dao::getArtistNamePageDescending else dao::getArtistNamePage)(
|
||||
revision,
|
||||
mode,
|
||||
includeCollaborations,
|
||||
@@ -2058,6 +2091,7 @@ class AstraLibraryRepository private constructor(
|
||||
/** Backward twin of [getArtistPage]; see [getTrackPageBefore] for the contract. */
|
||||
suspend fun getArtistPageBefore(
|
||||
sort: String,
|
||||
directionRaw: String,
|
||||
groupingMode: String,
|
||||
includeCollaborations: Boolean,
|
||||
cursorRaw: String?,
|
||||
@@ -2066,13 +2100,18 @@ class AstraLibraryRepository private constructor(
|
||||
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 direction = normalizeSortDirection(directionRaw)
|
||||
val kind = "artists:$sort:$direction:$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(
|
||||
(if (direction == "desc") {
|
||||
dao::getArtistNamePageBeforeDescending
|
||||
} else {
|
||||
dao::getArtistNamePageBefore
|
||||
})(
|
||||
revision,
|
||||
mode,
|
||||
includeCollaborations,
|
||||
@@ -2689,6 +2728,7 @@ class AstraLibraryRepository private constructor(
|
||||
suspend fun getSectionAnchors(
|
||||
kind: String,
|
||||
sort: String,
|
||||
directionRaw: String,
|
||||
includeSingles: Boolean,
|
||||
groupingMode: String,
|
||||
includeCollaborations: Boolean,
|
||||
@@ -2696,6 +2736,7 @@ class AstraLibraryRepository private constructor(
|
||||
withCatalogRecovery { database ->
|
||||
val dao = database.catalogDao()
|
||||
val revision = dao.getRevision()
|
||||
val direction = normalizeSortDirection(directionRaw)
|
||||
val anchors: List<Pair<String, TrackPageCursor>> = when (kind) {
|
||||
"albums" -> {
|
||||
val rows = dao.getAllAlbumSummaries(revision).filter { includeSingles || !it.isSingle }
|
||||
@@ -2703,17 +2744,32 @@ class AstraLibraryRepository private constructor(
|
||||
if (sort == "artist") SortKeys.sectionLabel(row.artist) else SortKeys.sectionLabel(row.album)
|
||||
}.map { (label, section) ->
|
||||
if (sort == "artist") {
|
||||
val first = section.minWith(compareBy<AlbumSummaryEntity>({ it.artistSortKey }, { it.nameSortKey }, { it.identityKey }))
|
||||
val first = section.sortedWith(
|
||||
if (direction == "desc") {
|
||||
compareByDescending<AlbumSummaryEntity> { it.artistSortKey }
|
||||
.thenBy { it.nameSortKey }
|
||||
.thenBy { it.identityKey }
|
||||
} else {
|
||||
compareBy<AlbumSummaryEntity>({ it.artistSortKey }, { it.nameSortKey }, { it.identityKey })
|
||||
},
|
||||
).first()
|
||||
label to TrackPageCursor(
|
||||
revision,
|
||||
"albums:artist:${if (includeSingles) 1 else 0}",
|
||||
"albums:artist:$direction:${if (includeSingles) 1 else 0}",
|
||||
text1 = first.artistSortKey,
|
||||
)
|
||||
} else {
|
||||
val first = section.minWith(compareBy<AlbumSummaryEntity>({ it.nameSortKey }, { it.identityKey }))
|
||||
val first = section.sortedWith(
|
||||
if (direction == "desc") {
|
||||
compareByDescending<AlbumSummaryEntity> { it.nameSortKey }
|
||||
.thenBy { it.identityKey }
|
||||
} else {
|
||||
compareBy<AlbumSummaryEntity>({ it.nameSortKey }, { it.identityKey })
|
||||
},
|
||||
).first()
|
||||
label to TrackPageCursor(
|
||||
revision,
|
||||
"albums:name:${if (includeSingles) 1 else 0}",
|
||||
"albums:name:$direction:${if (includeSingles) 1 else 0}",
|
||||
text1 = first.nameSortKey,
|
||||
)
|
||||
}
|
||||
@@ -2725,10 +2781,17 @@ class AstraLibraryRepository private constructor(
|
||||
.filter { includeCollaborations || !it.isCollaboration }
|
||||
.groupBy { row -> SortKeys.sectionLabel(row.artist) }
|
||||
.map { (label, section) ->
|
||||
val first = section.minWith(compareBy<ArtistSummaryEntity>({ it.nameSortKey }, { it.artistKey }))
|
||||
val first = section.sortedWith(
|
||||
if (direction == "desc") {
|
||||
compareByDescending<ArtistSummaryEntity> { it.nameSortKey }
|
||||
.thenBy { it.artistKey }
|
||||
} else {
|
||||
compareBy<ArtistSummaryEntity>({ it.nameSortKey }, { it.artistKey })
|
||||
},
|
||||
).first()
|
||||
label to TrackPageCursor(
|
||||
revision,
|
||||
"artists:name:$mode:${if (includeCollaborations) 1 else 0}",
|
||||
"artists:name:$direction:$mode:${if (includeCollaborations) 1 else 0}",
|
||||
text1 = first.nameSortKey,
|
||||
)
|
||||
}
|
||||
@@ -2740,22 +2803,38 @@ class AstraLibraryRepository private constructor(
|
||||
.map { (label, section) ->
|
||||
label to TrackPageCursor(
|
||||
revision,
|
||||
"tracks:artist",
|
||||
text1 = section.minOf(ArtistSectionAnchorCandidate::sortKey),
|
||||
"tracks:artist:$direction",
|
||||
text1 = if (direction == "desc") {
|
||||
section.maxOf(ArtistSectionAnchorCandidate::sortKey)
|
||||
} else {
|
||||
section.minOf(ArtistSectionAnchorCandidate::sortKey)
|
||||
},
|
||||
)
|
||||
}
|
||||
} else {
|
||||
dao.getTitleSectionAnchors().map { row ->
|
||||
val rows = if (direction == "desc") {
|
||||
dao.getTitleSectionAnchorsDescending()
|
||||
} else {
|
||||
dao.getTitleSectionAnchors()
|
||||
}
|
||||
rows.map { row ->
|
||||
row.sectionLabel to TrackPageCursor(
|
||||
revision,
|
||||
"tracks:title",
|
||||
"tracks:title:$direction",
|
||||
text1 = row.sortKey,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
anchors.sortedWith(compareBy<Pair<String, TrackPageCursor>> { it.second.text1 }.thenBy { it.first })
|
||||
val anchorComparator = compareBy<Pair<String, TrackPageCursor>> { it.second.text1 }
|
||||
.thenBy { it.first }
|
||||
val orderedAnchors = if (direction == "desc") {
|
||||
anchors.sortedWith(anchorComparator.reversed())
|
||||
} else {
|
||||
anchors.sortedWith(anchorComparator)
|
||||
}
|
||||
orderedAnchors
|
||||
.map { (label, cursor) ->
|
||||
mapOf(
|
||||
"label" to label,
|
||||
@@ -2842,11 +2921,33 @@ class AstraLibraryRepository private constructor(
|
||||
"manual" -> (context["paths"] as? List<*>)
|
||||
?.mapNotNull { it as? String }
|
||||
.orEmpty()
|
||||
else -> when (context["sort"] as? String) {
|
||||
"artist" -> catalogDao.getAllPathsByArtist()
|
||||
"recently_added" -> catalogDao.getAllPathsByRecentlyAdded()
|
||||
"duration" -> catalogDao.getAllPathsByDuration()
|
||||
else -> catalogDao.getAllPathsByTitle()
|
||||
else -> {
|
||||
val sort = context["sort"] as? String ?: "title"
|
||||
val direction = (context["direction"] as? String)
|
||||
?.takeIf { it == "asc" || it == "desc" }
|
||||
?: legacyTrackSortDirection(sort)
|
||||
when (sort) {
|
||||
"artist" -> if (direction == "desc") {
|
||||
catalogDao.getAllPathsByArtistDescending()
|
||||
} else {
|
||||
catalogDao.getAllPathsByArtist()
|
||||
}
|
||||
"recently_added" -> if (direction == "asc") {
|
||||
catalogDao.getAllPathsByRecentlyAddedAscending()
|
||||
} else {
|
||||
catalogDao.getAllPathsByRecentlyAdded()
|
||||
}
|
||||
"duration" -> if (direction == "asc") {
|
||||
catalogDao.getAllPathsByDurationAscending()
|
||||
} else {
|
||||
catalogDao.getAllPathsByDuration()
|
||||
}
|
||||
else -> if (direction == "desc") {
|
||||
catalogDao.getAllPathsByTitleDescending()
|
||||
} else {
|
||||
catalogDao.getAllPathsByTitle()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2954,6 +3055,12 @@ class AstraLibraryRepository private constructor(
|
||||
return cursor
|
||||
}
|
||||
|
||||
private fun normalizeSortDirection(value: String): String =
|
||||
if (value == "desc") "desc" else "asc"
|
||||
|
||||
private fun legacyTrackSortDirection(sort: String): String =
|
||||
if (sort == "recently_added" || sort == "duration") "desc" else "asc"
|
||||
|
||||
private fun cursorPath(cursor: TrackPageCursor): String =
|
||||
cursor.text3?.substringAfter('\u0000', "") ?: ""
|
||||
|
||||
|
||||
+353
-5
@@ -213,6 +213,9 @@ interface CatalogDao {
|
||||
@Query("SELECT path FROM active_tracks ORDER BY title_sort_key, path")
|
||||
suspend fun getAllPathsByTitle(): List<String>
|
||||
|
||||
@Query("SELECT path FROM active_tracks ORDER BY title_sort_key DESC, path")
|
||||
suspend fun getAllPathsByTitleDescending(): List<String>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT path FROM active_tracks
|
||||
@@ -221,12 +224,26 @@ interface CatalogDao {
|
||||
)
|
||||
suspend fun getAllPathsByArtist(): List<String>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT path FROM active_tracks
|
||||
ORDER BY artist_sort_key DESC, album_sort_key, disc_sort, track_sort, title_sort_key, path
|
||||
""",
|
||||
)
|
||||
suspend fun getAllPathsByArtistDescending(): List<String>
|
||||
|
||||
@Query("SELECT path FROM active_tracks ORDER BY added_at DESC, path")
|
||||
suspend fun getAllPathsByRecentlyAdded(): List<String>
|
||||
|
||||
@Query("SELECT path FROM active_tracks ORDER BY added_at, path")
|
||||
suspend fun getAllPathsByRecentlyAddedAscending(): List<String>
|
||||
|
||||
@Query("SELECT path FROM active_tracks ORDER BY duration DESC, path")
|
||||
suspend fun getAllPathsByDuration(): List<String>
|
||||
|
||||
@Query("SELECT path FROM active_tracks ORDER BY duration, path")
|
||||
suspend fun getAllPathsByDurationAscending(): List<String>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT path FROM active_tracks
|
||||
@@ -364,6 +381,22 @@ interface CatalogDao {
|
||||
limit: Int,
|
||||
): List<ActiveTrackView>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM active_tracks
|
||||
WHERE (:afterTitleKey IS NULL
|
||||
OR title_sort_key < :afterTitleKey
|
||||
OR (title_sort_key = :afterTitleKey AND path > :afterPath))
|
||||
ORDER BY title_sort_key DESC, path
|
||||
LIMIT :limit
|
||||
""",
|
||||
)
|
||||
suspend fun getTitlePageDescending(
|
||||
afterTitleKey: String?,
|
||||
afterPath: String,
|
||||
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
|
||||
@@ -384,6 +417,21 @@ interface CatalogDao {
|
||||
limit: Int,
|
||||
): List<ActiveTrackView>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM active_tracks
|
||||
WHERE title_sort_key > :beforeTitleKey
|
||||
OR (title_sort_key = :beforeTitleKey AND path < :beforePath)
|
||||
ORDER BY title_sort_key, path DESC
|
||||
LIMIT :limit
|
||||
""",
|
||||
)
|
||||
suspend fun getTitlePageBeforeDescending(
|
||||
beforeTitleKey: String,
|
||||
beforePath: String,
|
||||
limit: Int,
|
||||
): List<ActiveTrackView>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM active_tracks
|
||||
@@ -411,6 +459,33 @@ interface CatalogDao {
|
||||
limit: Int,
|
||||
): List<ActiveTrackView>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM active_tracks
|
||||
WHERE (:afterArtistKey IS NULL
|
||||
OR artist_sort_key < :afterArtistKey
|
||||
OR (artist_sort_key = :afterArtistKey AND album_sort_key > :afterAlbumKey)
|
||||
OR (artist_sort_key = :afterArtistKey AND album_sort_key = :afterAlbumKey AND disc_sort > :afterDisc)
|
||||
OR (artist_sort_key = :afterArtistKey AND album_sort_key = :afterAlbumKey AND disc_sort = :afterDisc
|
||||
AND track_sort > :afterTrack)
|
||||
OR (artist_sort_key = :afterArtistKey AND album_sort_key = :afterAlbumKey AND disc_sort = :afterDisc
|
||||
AND track_sort = :afterTrack AND title_sort_key > :afterTitleKey)
|
||||
OR (artist_sort_key = :afterArtistKey AND album_sort_key = :afterAlbumKey AND disc_sort = :afterDisc
|
||||
AND track_sort = :afterTrack AND title_sort_key = :afterTitleKey AND path > :afterPath))
|
||||
ORDER BY artist_sort_key DESC, album_sort_key, disc_sort, track_sort, title_sort_key, path
|
||||
LIMIT :limit
|
||||
""",
|
||||
)
|
||||
suspend fun getArtistOrderPageDescending(
|
||||
afterArtistKey: String?,
|
||||
afterAlbumKey: String,
|
||||
afterDisc: Int,
|
||||
afterTrack: Int,
|
||||
afterTitleKey: String,
|
||||
afterPath: String,
|
||||
limit: Int,
|
||||
): List<ActiveTrackView>
|
||||
|
||||
/** Mirror of [getArtistOrderPage] walking backwards; rows come out DESC. */
|
||||
@Query(
|
||||
"""
|
||||
@@ -439,6 +514,33 @@ interface CatalogDao {
|
||||
limit: Int,
|
||||
): List<ActiveTrackView>
|
||||
|
||||
@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, album_sort_key DESC, disc_sort DESC, track_sort DESC,
|
||||
title_sort_key DESC, path DESC
|
||||
LIMIT :limit
|
||||
""",
|
||||
)
|
||||
suspend fun getArtistOrderPageBeforeDescending(
|
||||
beforeArtistKey: String,
|
||||
beforeAlbumKey: String,
|
||||
beforeDisc: Int,
|
||||
beforeTrack: Int,
|
||||
beforeTitleKey: String,
|
||||
beforePath: String,
|
||||
limit: Int,
|
||||
): List<ActiveTrackView>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM active_tracks
|
||||
@@ -455,6 +557,22 @@ interface CatalogDao {
|
||||
limit: Int,
|
||||
): List<ActiveTrackView>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM active_tracks
|
||||
WHERE (:afterAddedAt IS NULL
|
||||
OR added_at > :afterAddedAt
|
||||
OR (added_at = :afterAddedAt AND path > :afterPath))
|
||||
ORDER BY added_at, path
|
||||
LIMIT :limit
|
||||
""",
|
||||
)
|
||||
suspend fun getRecentlyAddedPageAscending(
|
||||
afterAddedAt: Long?,
|
||||
afterPath: String,
|
||||
limit: Int,
|
||||
): List<ActiveTrackView>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM active_tracks
|
||||
@@ -471,6 +589,22 @@ interface CatalogDao {
|
||||
limit: Int,
|
||||
): List<ActiveTrackView>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM active_tracks
|
||||
WHERE (:afterDuration IS NULL
|
||||
OR duration > :afterDuration
|
||||
OR (duration = :afterDuration AND path > :afterPath))
|
||||
ORDER BY duration, path
|
||||
LIMIT :limit
|
||||
""",
|
||||
)
|
||||
suspend fun getDurationPageAscending(
|
||||
afterDuration: Double?,
|
||||
afterPath: String,
|
||||
limit: Int,
|
||||
): List<ActiveTrackView>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM active_tracks
|
||||
@@ -542,6 +676,16 @@ interface CatalogDao {
|
||||
)
|
||||
suspend fun getTitleSectionAnchors(): List<SectionAnchorRow>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT section_label, MAX(title_sort_key) AS sort_key
|
||||
FROM active_tracks
|
||||
GROUP BY section_label
|
||||
ORDER BY sort_key DESC
|
||||
""",
|
||||
)
|
||||
suspend fun getTitleSectionAnchorsDescending(): List<SectionAnchorRow>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT artist, MIN(artist_sort_key) AS sort_key
|
||||
@@ -595,6 +739,26 @@ interface CatalogDao {
|
||||
limit: Int,
|
||||
): List<AlbumSummaryEntity>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM album_summaries
|
||||
WHERE revision = :revision
|
||||
AND (:includeSingles OR is_single = 0)
|
||||
AND (:afterKey IS NULL
|
||||
OR name_sort_key < :afterKey
|
||||
OR (name_sort_key = :afterKey AND identity_key > :afterId))
|
||||
ORDER BY name_sort_key DESC, identity_key
|
||||
LIMIT :limit
|
||||
""",
|
||||
)
|
||||
suspend fun getAlbumNamePageDescending(
|
||||
revision: Long,
|
||||
includeSingles: Boolean,
|
||||
afterKey: String?,
|
||||
afterId: String,
|
||||
limit: Int,
|
||||
): List<AlbumSummaryEntity>
|
||||
|
||||
/** Mirror of [getAlbumNamePage] walking backwards; rows come out DESC. */
|
||||
@Query(
|
||||
"""
|
||||
@@ -615,6 +779,25 @@ interface CatalogDao {
|
||||
limit: Int,
|
||||
): List<AlbumSummaryEntity>
|
||||
|
||||
@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, identity_key DESC
|
||||
LIMIT :limit
|
||||
""",
|
||||
)
|
||||
suspend fun getAlbumNamePageBeforeDescending(
|
||||
revision: Long,
|
||||
includeSingles: Boolean,
|
||||
beforeKey: String,
|
||||
beforeId: String,
|
||||
limit: Int,
|
||||
): List<AlbumSummaryEntity>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM album_summaries
|
||||
@@ -638,6 +821,29 @@ interface CatalogDao {
|
||||
limit: Int,
|
||||
): List<AlbumSummaryEntity>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM album_summaries
|
||||
WHERE revision = :revision
|
||||
AND (:includeSingles OR is_single = 0)
|
||||
AND (:afterArtistKey IS NULL
|
||||
OR artist_sort_key < :afterArtistKey
|
||||
OR (artist_sort_key = :afterArtistKey AND name_sort_key > :afterNameKey)
|
||||
OR (artist_sort_key = :afterArtistKey AND name_sort_key = :afterNameKey
|
||||
AND identity_key > :afterId))
|
||||
ORDER BY artist_sort_key DESC, name_sort_key, identity_key
|
||||
LIMIT :limit
|
||||
""",
|
||||
)
|
||||
suspend fun getAlbumArtistPageDescending(
|
||||
revision: Long,
|
||||
includeSingles: Boolean,
|
||||
afterArtistKey: String?,
|
||||
afterNameKey: String,
|
||||
afterId: String,
|
||||
limit: Int,
|
||||
): List<AlbumSummaryEntity>
|
||||
|
||||
/** Mirror of [getAlbumArtistPage] walking backwards; rows come out DESC. */
|
||||
@Query(
|
||||
"""
|
||||
@@ -661,6 +867,28 @@ interface CatalogDao {
|
||||
limit: Int,
|
||||
): List<AlbumSummaryEntity>
|
||||
|
||||
@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, name_sort_key DESC, identity_key DESC
|
||||
LIMIT :limit
|
||||
""",
|
||||
)
|
||||
suspend fun getAlbumArtistPageBeforeDescending(
|
||||
revision: Long,
|
||||
includeSingles: Boolean,
|
||||
beforeArtistKey: String,
|
||||
beforeNameKey: String,
|
||||
beforeId: String,
|
||||
limit: Int,
|
||||
): List<AlbumSummaryEntity>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM album_summaries
|
||||
@@ -686,12 +914,36 @@ interface CatalogDao {
|
||||
SELECT * FROM album_summaries
|
||||
WHERE revision = :revision
|
||||
AND (:includeSingles OR is_single = 0)
|
||||
AND (:afterYear IS NULL
|
||||
OR COALESCE(year, 0) < :afterYear
|
||||
OR (COALESCE(year, 0) = :afterYear AND name_sort_key > :afterNameKey)
|
||||
OR (COALESCE(year, 0) = :afterYear AND name_sort_key = :afterNameKey
|
||||
AND (:afterAddedAt IS NULL
|
||||
OR latest_added_at > :afterAddedAt
|
||||
OR (latest_added_at = :afterAddedAt AND identity_key > :afterId))
|
||||
ORDER BY latest_added_at, identity_key
|
||||
LIMIT :limit
|
||||
""",
|
||||
)
|
||||
suspend fun getAlbumRecentPageAscending(
|
||||
revision: Long,
|
||||
includeSingles: Boolean,
|
||||
afterAddedAt: Long?,
|
||||
afterId: String,
|
||||
limit: Int,
|
||||
): List<AlbumSummaryEntity>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM album_summaries
|
||||
WHERE revision = :revision
|
||||
AND (:includeSingles OR is_single = 0)
|
||||
AND (:afterUnknown IS NULL
|
||||
OR CASE WHEN year IS NULL THEN 1 ELSE 0 END > :afterUnknown
|
||||
OR (CASE WHEN year IS NULL THEN 1 ELSE 0 END = :afterUnknown
|
||||
AND COALESCE(year, 0) < :afterYear)
|
||||
OR (CASE WHEN year IS NULL THEN 1 ELSE 0 END = :afterUnknown
|
||||
AND COALESCE(year, 0) = :afterYear AND name_sort_key > :afterNameKey)
|
||||
OR (CASE WHEN year IS NULL THEN 1 ELSE 0 END = :afterUnknown
|
||||
AND COALESCE(year, 0) = :afterYear AND name_sort_key = :afterNameKey
|
||||
AND identity_key > :afterId))
|
||||
ORDER BY COALESCE(year, 0) DESC, name_sort_key, identity_key
|
||||
ORDER BY CASE WHEN year IS NULL THEN 1 ELSE 0 END, year DESC, name_sort_key, identity_key
|
||||
LIMIT :limit
|
||||
""",
|
||||
)
|
||||
@@ -699,6 +951,35 @@ interface CatalogDao {
|
||||
revision: Long,
|
||||
includeSingles: Boolean,
|
||||
afterYear: Int?,
|
||||
afterUnknown: Int?,
|
||||
afterNameKey: String,
|
||||
afterId: String,
|
||||
limit: Int,
|
||||
): List<AlbumSummaryEntity>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM album_summaries
|
||||
WHERE revision = :revision
|
||||
AND (:includeSingles OR is_single = 0)
|
||||
AND (:afterUnknown IS NULL
|
||||
OR CASE WHEN year IS NULL THEN 1 ELSE 0 END > :afterUnknown
|
||||
OR (CASE WHEN year IS NULL THEN 1 ELSE 0 END = :afterUnknown
|
||||
AND COALESCE(year, 0) > :afterYear)
|
||||
OR (CASE WHEN year IS NULL THEN 1 ELSE 0 END = :afterUnknown
|
||||
AND COALESCE(year, 0) = :afterYear AND name_sort_key > :afterNameKey)
|
||||
OR (CASE WHEN year IS NULL THEN 1 ELSE 0 END = :afterUnknown
|
||||
AND COALESCE(year, 0) = :afterYear AND name_sort_key = :afterNameKey
|
||||
AND identity_key > :afterId))
|
||||
ORDER BY CASE WHEN year IS NULL THEN 1 ELSE 0 END, year, name_sort_key, identity_key
|
||||
LIMIT :limit
|
||||
""",
|
||||
)
|
||||
suspend fun getAlbumYearPageAscending(
|
||||
revision: Long,
|
||||
includeSingles: Boolean,
|
||||
afterYear: Int?,
|
||||
afterUnknown: Int?,
|
||||
afterNameKey: String,
|
||||
afterId: String,
|
||||
limit: Int,
|
||||
@@ -747,6 +1028,28 @@ interface CatalogDao {
|
||||
limit: Int,
|
||||
): List<ArtistSummaryEntity>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM artist_summaries
|
||||
WHERE revision = :revision
|
||||
AND grouping_mode = :groupingMode
|
||||
AND (:includeCollaborations OR is_collaboration = 0)
|
||||
AND (:afterKey IS NULL
|
||||
OR name_sort_key < :afterKey
|
||||
OR (name_sort_key = :afterKey AND artist_key > :afterId))
|
||||
ORDER BY name_sort_key DESC, artist_key
|
||||
LIMIT :limit
|
||||
""",
|
||||
)
|
||||
suspend fun getArtistNamePageDescending(
|
||||
revision: Long,
|
||||
groupingMode: String,
|
||||
includeCollaborations: Boolean,
|
||||
afterKey: String?,
|
||||
afterId: String,
|
||||
limit: Int,
|
||||
): List<ArtistSummaryEntity>
|
||||
|
||||
/** Mirror of [getArtistNamePage] walking backwards; rows come out DESC. */
|
||||
@Query(
|
||||
"""
|
||||
@@ -769,6 +1072,27 @@ interface CatalogDao {
|
||||
limit: Int,
|
||||
): List<ArtistSummaryEntity>
|
||||
|
||||
@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, artist_key DESC
|
||||
LIMIT :limit
|
||||
""",
|
||||
)
|
||||
suspend fun getArtistNamePageBeforeDescending(
|
||||
revision: Long,
|
||||
groupingMode: String,
|
||||
includeCollaborations: Boolean,
|
||||
beforeKey: String,
|
||||
beforeId: String,
|
||||
limit: Int,
|
||||
): List<ArtistSummaryEntity>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM artist_summaries
|
||||
@@ -793,6 +1117,30 @@ interface CatalogDao {
|
||||
limit: Int,
|
||||
): List<ArtistSummaryEntity>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM artist_summaries
|
||||
WHERE revision = :revision
|
||||
AND grouping_mode = :groupingMode
|
||||
AND (:includeCollaborations OR is_collaboration = 0)
|
||||
AND (:afterCount IS NULL
|
||||
OR track_count > :afterCount
|
||||
OR (track_count = :afterCount AND name_sort_key > :afterNameKey)
|
||||
OR (track_count = :afterCount AND name_sort_key = :afterNameKey AND artist_key > :afterId))
|
||||
ORDER BY track_count, name_sort_key, artist_key
|
||||
LIMIT :limit
|
||||
""",
|
||||
)
|
||||
suspend fun getArtistCountPageAscending(
|
||||
revision: Long,
|
||||
groupingMode: String,
|
||||
includeCollaborations: Boolean,
|
||||
afterCount: Long?,
|
||||
afterNameKey: String,
|
||||
afterId: String,
|
||||
limit: Int,
|
||||
): List<ArtistSummaryEntity>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM artist_summaries
|
||||
|
||||
Reference in New Issue
Block a user