From 0448842f604577c5546bee39bcff3618966ef37e Mon Sep 17 00:00:00 2001 From: austinried <4966622+austinried@users.noreply.github.com> Date: Thu, 12 Nov 2026 12:25:12 +0000 Subject: [PATCH] queue: retain reset identity across duplicate edits --- .../subtracks/data/repo/QueueRepository.kt | 14 ++++-- .../data/repo/QueueRepositoryTest.kt | 43 +++++++++++++++++++ 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/com/subtracks/data/repo/QueueRepository.kt b/app/src/main/kotlin/com/subtracks/data/repo/QueueRepository.kt index 7db5d99f..19950e8c 100644 --- a/app/src/main/kotlin/com/subtracks/data/repo/QueueRepository.kt +++ b/app/src/main/kotlin/com/subtracks/data/repo/QueueRepository.kt @@ -169,6 +169,7 @@ data class QueueResetItem( val upNext: Boolean, val entryOccurrence: Int = 0, val occurrence: Long = 0, + val originEntryId: Long? = null, ) data class QueueResetPoint( @@ -720,7 +721,7 @@ class QueueRepository( } else { 0L } - return QueueResetItem(origin, resolvedSongId, position, upNext, entryOccurrence, occurrence) + return QueueResetItem(origin, resolvedSongId, position, upNext, entryOccurrence, occurrence, entry.originEntryId) } private data class OriginEntryKey( @@ -797,6 +798,7 @@ class QueueRepository( } private data class OriginEntryGroup( + val key: OriginEntryKey, val starts: List, val entries: List, ) @@ -814,7 +816,7 @@ class QueueRepository( val index = groupIndices[key] if (index == null) { groupIndices[key] = groups.size - groups += OriginEntryGroup(listOf(start), listOf(resolved)) + groups += OriginEntryGroup(key, listOf(start), listOf(resolved)) } else { groups[index] = groups[index].copy( @@ -834,9 +836,15 @@ class QueueRepository( ): Long? { val entries = if (item.upNext) snapshot.upNext else snapshot.entries val groups = originEntryGroups(entries, item.origin) - val group = groups.getOrNull(item.entryOccurrence) ?: return null + val group = + if (item.originEntryId == null) { + groups.getOrNull(item.entryOccurrence) + } else { + groups.firstOrNull { it.key == OriginEntryKey(item.origin, item.originEntryId) } + } ?: return null for ((index, resolved) in group.entries.withIndex()) { val entry = resolved.entry + if (entry.originOccurrence != null && entry.originOccurrence != item.occurrence) continue val rank = if (entry.kind == QueueKind.Playlist) { val position = dao.playlistPositionForOccurrence(entry.sourceId, entry.refId, item.songId, item.occurrence) diff --git a/app/src/test/kotlin/com/subtracks/data/repo/QueueRepositoryTest.kt b/app/src/test/kotlin/com/subtracks/data/repo/QueueRepositoryTest.kt index 1681d42f..3004c2da 100644 --- a/app/src/test/kotlin/com/subtracks/data/repo/QueueRepositoryTest.kt +++ b/app/src/test/kotlin/com/subtracks/data/repo/QueueRepositoryTest.kt @@ -508,6 +508,29 @@ class QueueRepositoryTest { assertEquals(2L, repository.cursor()) } + @Test + fun anUnrelatedOriginResetSkipsMovedSongsFromOtherPlaylistOccurrences() = + runTest { + seedLibrary() + db.libraryDao().upsertSongs(listOf(song("s0", "al1", track = 0, album = "First Album"))) + setPlaylist("p2", "s4") + setPlaylist("p1", "s1", "s2", "s1") + val snapshot = repository.snapshotAfter(listOf(repository.playlistEntry(1, "p2"), repository.playlistEntry(1, "p1"))) + repository.move(snapshot, from = 3, to = 1) + repository.setCursor(2) + repository.setPosition(42_000L) + val point = repository.captureResetPoint() + + setPlaylist("p2", "s0", "s4") + repository.resetChangedOrigins(setOf(QueueOrigin(QueueKind.Playlist, 1, "p2")), point) + + val updated = repository.snapshot() + assertEquals(listOf("s0", "s4", "s1", "s1", "s2"), resolveAll(updated)) + assertEquals(3L, repository.remapPosition(updated, point.current!!)) + assertEquals(3L, repository.cursor()) + assertEquals(42_000L, repository.cursorPositionMs()) + } + @Test fun aRemovedDuplicateOccurrenceDoesNotLeaveItsOffsetOnTheSurvivor() = runTest { @@ -613,6 +636,26 @@ class QueueRepositoryTest { assertEquals(3L, repository.snapshot().upNextAnchor) } + @Test + fun aResetKeepsTheCapturedDuplicateGroupAfterReordering() = + runTest { + seedLibrary() + setPlaylist("p1", "s1", "s2") + repository.snapshotAfter(listOf(repository.playlistEntry(1, "p1"), repository.playlistEntry(1, "p1"))) + repository.setCursor(3) + repository.setPosition(42_000L) + val point = repository.captureResetPoint() + repository.move(repository.snapshot(), from = 2, to = 0) + + repository.resetChangedOrigins(setOf(QueueOrigin(QueueKind.Playlist, 1, "p1")), point) + + val updated = repository.snapshot() + assertEquals(listOf("s1", "s2", "s1", "s2"), resolveAll(updated)) + assertEquals(1L, repository.remapPosition(updated, point.current!!)) + assertEquals(1L, repository.cursor()) + assertEquals(42_000L, repository.cursorPositionMs()) + } + @Test fun aChangedOriginResetRestoresFullyFragmentedDuplicateEntries() = runTest {