address review: renumber moves from the window start and version the length cache

This commit is contained in:
austinried
2026-10-04 00:29:59 +00:00
parent 8ae5ba5e4b
commit 8ba0aa5192
3 changed files with 34 additions and 7 deletions
@@ -21,6 +21,7 @@ const val QUEUE_CHUNK = 60
private const val LENGTH_CACHE_LIMIT = 512 private const val LENGTH_CACHE_LIMIT = 512
private data class EntryLengthKey( private data class EntryLengthKey(
val version: Long,
val sourceId: Long, val sourceId: Long,
val kind: QueueKind, val kind: QueueKind,
val refId: String, val refId: String,
@@ -147,7 +148,7 @@ class QueueRepository(
val loaded = val loaded =
db.useReaderConnection { transactor -> db.useReaderConnection { transactor ->
transactor.deferredTransaction { transactor.deferredTransaction {
val entries = dao.entries().map { ResolvedQueueEntry(it, lengthOf(it)) } val entries = dao.entries().map { ResolvedQueueEntry(it, lengthOf(it, version)) }
val row = dao.cursor() val row = dao.cursor()
val shuffled = row?.shuffleEnabled == true val shuffled = row?.shuffleEnabled == true
val order = if (shuffled) loadShuffleOrder(row.shuffleSeed, entries.sumOf { it.length }) else null val order = if (shuffled) loadShuffleOrder(row.shuffleSeed, entries.sumOf { it.length }) else null
@@ -386,8 +387,11 @@ class QueueRepository(
} }
} }
private suspend fun lengthOf(entry: QueueEntry): Long { private suspend fun lengthOf(
val key = entry.lengthKey() entry: QueueEntry,
version: Long,
): Long {
val key = entry.lengthKey(version)
lengthCache[key]?.let { return it } lengthCache[key]?.let { return it }
val length = entry.resolvedLength() val length = entry.resolvedLength()
if (lengthCache.size >= LENGTH_CACHE_LIMIT) lengthCache.clear() if (lengthCache.size >= LENGTH_CACHE_LIMIT) lengthCache.clear()
@@ -395,8 +399,9 @@ class QueueRepository(
return length return length
} }
private fun QueueEntry.lengthKey() = private fun QueueEntry.lengthKey(version: Long) =
EntryLengthKey( EntryLengthKey(
version = version,
sourceId = sourceId, sourceId = sourceId,
kind = kind, kind = kind,
refId = refId, refId = refId,
@@ -184,7 +184,7 @@ class QueueViewModel(
viewModelScope.launch { viewModelScope.launch {
playbackController.move(from, to) playbackController.move(from, to)
mutex.withLock { mutex.withLock {
val base = rows.firstOrNull()?.position ?: return@withLock val base = rows.minOfOrNull { it.position } ?: return@withLock
// The loaded order already reflects the move, so only the positions need updating. // The loaded order already reflects the move, so only the positions need updating.
val updated = val updated =
rows.mapIndexed { index, row -> rows.mapIndexed { index, row ->
@@ -199,8 +199,30 @@ class QueueViewModelTest {
viewModel.rows[13] viewModel.rows[13]
.song.song.id, .song.song.id,
) )
assertEquals(QUEUE_WINDOW_ROWS, viewModel.rows.size) assertEquals((220L..399L).toList(), viewModel.rows.map { it.position })
assertContiguous() }
@Test
fun movingTheTopRowDownRenumbersFromTheWindowStart() {
runBlocking { seedSongs(400) }
controller.playAlbum(1, "al1", 100)
await { controller.state.value.position == 100L }
viewModel.open()
await { viewModel.ready }
extendToEnd()
assertEquals((220L..399L).toList(), viewModel.rows.map { it.position })
val from = viewModel.rows.first().position
viewModel.reorder(0, 5)
val to = dropTarget(viewModel.rows, index = 5, from = from)
viewModel.move(from, to)
await {
viewModel.rows.firstOrNull()?.position == 220L &&
viewModel.rows.lastOrNull()?.position == 399L
}
assertEquals((220L..399L).toList(), viewModel.rows.map { it.position })
} }
@Test @Test