mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-15 00:42:54 +01:00
queue: preserve fragmented origin entry identity
This commit is contained in:
+14
-4
@@ -2,7 +2,7 @@
|
|||||||
"formatVersion": 1,
|
"formatVersion": 1,
|
||||||
"database": {
|
"database": {
|
||||||
"version": 39,
|
"version": 39,
|
||||||
"identityHash": "ee91ebd8ca8608584ff44c68b6d9f224",
|
"identityHash": "6e9243b8186efefb9a63425559da6cdc",
|
||||||
"entities": [
|
"entities": [
|
||||||
{
|
{
|
||||||
"tableName": "sources",
|
"tableName": "sources",
|
||||||
@@ -1515,7 +1515,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tableName": "queue_entries",
|
"tableName": "queue_entries",
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `position` INTEGER NOT NULL, `sourceId` INTEGER NOT NULL, `kind` TEXT NOT NULL, `refId` TEXT NOT NULL, `rangeStart` INTEGER, `rangeEnd` INTEGER, `originKind` TEXT, `originRefId` TEXT, `originOccurrence` INTEGER, FOREIGN KEY(`sourceId`) REFERENCES `sources`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `position` INTEGER NOT NULL, `sourceId` INTEGER NOT NULL, `kind` TEXT NOT NULL, `refId` TEXT NOT NULL, `rangeStart` INTEGER, `rangeEnd` INTEGER, `originKind` TEXT, `originRefId` TEXT, `originOccurrence` INTEGER, `originEntryId` INTEGER, FOREIGN KEY(`sourceId`) REFERENCES `sources`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"fieldPath": "id",
|
"fieldPath": "id",
|
||||||
@@ -1571,6 +1571,11 @@
|
|||||||
"fieldPath": "originOccurrence",
|
"fieldPath": "originOccurrence",
|
||||||
"columnName": "originOccurrence",
|
"columnName": "originOccurrence",
|
||||||
"affinity": "INTEGER"
|
"affinity": "INTEGER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originEntryId",
|
||||||
|
"columnName": "originEntryId",
|
||||||
|
"affinity": "INTEGER"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"primaryKey": {
|
"primaryKey": {
|
||||||
@@ -1615,7 +1620,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tableName": "up_next_entries",
|
"tableName": "up_next_entries",
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `position` INTEGER NOT NULL, `sourceId` INTEGER NOT NULL, `kind` TEXT NOT NULL, `refId` TEXT NOT NULL, `rangeStart` INTEGER, `rangeEnd` INTEGER, `originKind` TEXT, `originRefId` TEXT, `originOccurrence` INTEGER, FOREIGN KEY(`sourceId`) REFERENCES `sources`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `position` INTEGER NOT NULL, `sourceId` INTEGER NOT NULL, `kind` TEXT NOT NULL, `refId` TEXT NOT NULL, `rangeStart` INTEGER, `rangeEnd` INTEGER, `originKind` TEXT, `originRefId` TEXT, `originOccurrence` INTEGER, `originEntryId` INTEGER, FOREIGN KEY(`sourceId`) REFERENCES `sources`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"fieldPath": "id",
|
"fieldPath": "id",
|
||||||
@@ -1671,6 +1676,11 @@
|
|||||||
"fieldPath": "originOccurrence",
|
"fieldPath": "originOccurrence",
|
||||||
"columnName": "originOccurrence",
|
"columnName": "originOccurrence",
|
||||||
"affinity": "INTEGER"
|
"affinity": "INTEGER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originEntryId",
|
||||||
|
"columnName": "originEntryId",
|
||||||
|
"affinity": "INTEGER"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"primaryKey": {
|
"primaryKey": {
|
||||||
@@ -2076,7 +2086,7 @@
|
|||||||
],
|
],
|
||||||
"setupQueries": [
|
"setupQueries": [
|
||||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ee91ebd8ca8608584ff44c68b6d9f224')"
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6e9243b8186efefb9a63425559da6cdc')"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -736,9 +736,11 @@ val MIGRATION_38_39 =
|
|||||||
connection.execSQL("ALTER TABLE `queue_entries` ADD COLUMN `originKind` TEXT")
|
connection.execSQL("ALTER TABLE `queue_entries` ADD COLUMN `originKind` TEXT")
|
||||||
connection.execSQL("ALTER TABLE `queue_entries` ADD COLUMN `originRefId` TEXT")
|
connection.execSQL("ALTER TABLE `queue_entries` ADD COLUMN `originRefId` TEXT")
|
||||||
connection.execSQL("ALTER TABLE `queue_entries` ADD COLUMN `originOccurrence` INTEGER")
|
connection.execSQL("ALTER TABLE `queue_entries` ADD COLUMN `originOccurrence` INTEGER")
|
||||||
|
connection.execSQL("ALTER TABLE `queue_entries` ADD COLUMN `originEntryId` INTEGER")
|
||||||
connection.execSQL("ALTER TABLE `up_next_entries` ADD COLUMN `originKind` TEXT")
|
connection.execSQL("ALTER TABLE `up_next_entries` ADD COLUMN `originKind` TEXT")
|
||||||
connection.execSQL("ALTER TABLE `up_next_entries` ADD COLUMN `originRefId` TEXT")
|
connection.execSQL("ALTER TABLE `up_next_entries` ADD COLUMN `originRefId` TEXT")
|
||||||
connection.execSQL("ALTER TABLE `up_next_entries` ADD COLUMN `originOccurrence` INTEGER")
|
connection.execSQL("ALTER TABLE `up_next_entries` ADD COLUMN `originOccurrence` INTEGER")
|
||||||
|
connection.execSQL("ALTER TABLE `up_next_entries` ADD COLUMN `originEntryId` INTEGER")
|
||||||
connection.execSQL(
|
connection.execSQL(
|
||||||
"CREATE TABLE IF NOT EXISTS `pending_sync_origins` (`sourceId` INTEGER NOT NULL, `kind` TEXT NOT NULL, " +
|
"CREATE TABLE IF NOT EXISTS `pending_sync_origins` (`sourceId` INTEGER NOT NULL, `kind` TEXT NOT NULL, " +
|
||||||
"`refId` TEXT NOT NULL, PRIMARY KEY(`sourceId`, `kind`, `refId`), " +
|
"`refId` TEXT NOT NULL, PRIMARY KEY(`sourceId`, `kind`, `refId`), " +
|
||||||
|
|||||||
@@ -496,6 +496,7 @@ data class QueueEntry(
|
|||||||
val originKind: QueueKind? = null,
|
val originKind: QueueKind? = null,
|
||||||
val originRefId: String? = null,
|
val originRefId: String? = null,
|
||||||
val originOccurrence: Long? = null,
|
val originOccurrence: Long? = null,
|
||||||
|
val originEntryId: Long? = null,
|
||||||
) {
|
) {
|
||||||
val offset: Long get() = rangeStart ?: 0
|
val offset: Long get() = rangeStart ?: 0
|
||||||
|
|
||||||
@@ -527,13 +528,26 @@ data class UpNextEntry(
|
|||||||
val originKind: QueueKind? = null,
|
val originKind: QueueKind? = null,
|
||||||
val originRefId: String? = null,
|
val originRefId: String? = null,
|
||||||
val originOccurrence: Long? = null,
|
val originOccurrence: Long? = null,
|
||||||
|
val originEntryId: Long? = null,
|
||||||
) {
|
) {
|
||||||
val offset: Long get() = rangeStart ?: 0
|
val offset: Long get() = rangeStart ?: 0
|
||||||
|
|
||||||
val count: Long? get() = rangeEnd?.let { (it - offset + 1).coerceAtLeast(0) }
|
val count: Long? get() = rangeEnd?.let { (it - offset + 1).coerceAtLeast(0) }
|
||||||
|
|
||||||
fun toQueueEntry(): QueueEntry =
|
fun toQueueEntry(): QueueEntry =
|
||||||
QueueEntry(id, position, sourceId, kind, refId, rangeStart, rangeEnd, originKind, originRefId, originOccurrence)
|
QueueEntry(
|
||||||
|
id,
|
||||||
|
position,
|
||||||
|
sourceId,
|
||||||
|
kind,
|
||||||
|
refId,
|
||||||
|
rangeStart,
|
||||||
|
rangeEnd,
|
||||||
|
originKind,
|
||||||
|
originRefId,
|
||||||
|
originOccurrence,
|
||||||
|
originEntryId,
|
||||||
|
)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun from(entry: QueueEntry) =
|
fun from(entry: QueueEntry) =
|
||||||
@@ -548,6 +562,7 @@ data class UpNextEntry(
|
|||||||
entry.originKind,
|
entry.originKind,
|
||||||
entry.originRefId,
|
entry.originRefId,
|
||||||
entry.originOccurrence,
|
entry.originOccurrence,
|
||||||
|
entry.originEntryId,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -519,10 +519,9 @@ 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, libraryRevision)) }
|
val entries = withOriginEntryIds(dao.entries()).map { ResolvedQueueEntry(it, lengthOf(it, libraryRevision)) }
|
||||||
val upNext =
|
val upNext =
|
||||||
dao.upNextEntries().map {
|
withOriginEntryIds(dao.upNextEntries().map { it.toQueueEntry() }).map { entry ->
|
||||||
val entry = it.toQueueEntry()
|
|
||||||
ResolvedQueueEntry(entry, lengthOf(entry, libraryRevision))
|
ResolvedQueueEntry(entry, lengthOf(entry, libraryRevision))
|
||||||
}
|
}
|
||||||
val row = dao.cursor()
|
val row = dao.cursor()
|
||||||
@@ -637,10 +636,16 @@ class QueueRepository(
|
|||||||
db.useWriterConnection { connection ->
|
db.useWriterConnection { connection ->
|
||||||
connection.immediateTransaction {
|
connection.immediateTransaction {
|
||||||
dao.clear()
|
dao.clear()
|
||||||
dao.insert(entries.mapIndexed { index, entry -> entry.copy(id = 0, position = index.toLong()) })
|
dao.insert(
|
||||||
|
withOriginEntryIds(entries).mapIndexed { index, entry ->
|
||||||
|
entry.copy(id = 0, position = index.toLong())
|
||||||
|
},
|
||||||
|
)
|
||||||
dao.clearUpNext()
|
dao.clearUpNext()
|
||||||
dao.insertUpNext(
|
dao.insertUpNext(
|
||||||
upNext.mapIndexed { index, entry -> UpNextEntry.from(entry).copy(id = 0, position = index.toLong()) },
|
withOriginEntryIds(upNext).mapIndexed { index, entry ->
|
||||||
|
UpNextEntry.from(entry).copy(id = 0, position = index.toLong())
|
||||||
|
},
|
||||||
)
|
)
|
||||||
dao.setCursor(
|
dao.setCursor(
|
||||||
row.copy(
|
row.copy(
|
||||||
@@ -718,27 +723,26 @@ class QueueRepository(
|
|||||||
return QueueResetItem(origin, resolvedSongId, position, upNext, entryOccurrence, occurrence)
|
return QueueResetItem(origin, resolvedSongId, position, upNext, entryOccurrence, occurrence)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private data class OriginEntryKey(
|
||||||
|
val origin: QueueOrigin,
|
||||||
|
val entryId: Long?,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun QueueEntry.originEntryKey() = OriginEntryKey(origin, originEntryId)
|
||||||
|
|
||||||
private fun resetEntryOccurrence(
|
private fun resetEntryOccurrence(
|
||||||
entries: List<ResolvedQueueEntry>,
|
entries: List<ResolvedQueueEntry>,
|
||||||
targetIndex: Int,
|
targetIndex: Int,
|
||||||
): Int {
|
): Int {
|
||||||
val origin = entries[targetIndex].entry.origin
|
val targetKey = entries[targetIndex].entry.originEntryKey()
|
||||||
val targetWhole = entries[targetIndex].entry.isWholeOrigin()
|
val groups = mutableListOf<OriginEntryKey>()
|
||||||
var occurrence = 0
|
|
||||||
var fragmentSeen = false
|
|
||||||
for (index in 0..targetIndex) {
|
for (index in 0..targetIndex) {
|
||||||
val entry = entries[index].entry
|
val entry = entries[index].entry
|
||||||
if (entry.origin != origin) continue
|
if (entry.origin != targetKey.origin) continue
|
||||||
if (entry.isWholeOrigin()) {
|
val key = entry.originEntryKey()
|
||||||
if (index == targetIndex) return occurrence
|
if (key !in groups) groups += key
|
||||||
occurrence++
|
|
||||||
} else if (!fragmentSeen) {
|
|
||||||
fragmentSeen = true
|
|
||||||
if (!targetWhole) return occurrence
|
|
||||||
occurrence++
|
|
||||||
}
|
}
|
||||||
}
|
return groups.indexOf(targetKey)
|
||||||
return occurrence
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun QueueEntry.isWholeOrigin(): Boolean {
|
private fun QueueEntry.isWholeOrigin(): Boolean {
|
||||||
@@ -746,19 +750,47 @@ class QueueRepository(
|
|||||||
return kind == origin.kind && refId == origin.refId && rangeStart == null && rangeEnd == null
|
return kind == origin.kind && refId == origin.refId && rangeStart == null && rangeEnd == null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun withOriginEntryIds(entries: List<QueueEntry>): List<QueueEntry> {
|
||||||
|
if (entries.all { it.originEntryId != null }) return entries
|
||||||
|
val nextIds = mutableMapOf<QueueOrigin, Long>()
|
||||||
|
for (entry in entries) {
|
||||||
|
val id = entry.originEntryId ?: continue
|
||||||
|
nextIds[entry.origin] = maxOf(nextIds[entry.origin] ?: 0L, id + 1)
|
||||||
|
}
|
||||||
|
val fragmentIds = mutableMapOf<QueueOrigin, Long>()
|
||||||
|
return entries.map { entry ->
|
||||||
|
if (entry.originEntryId != null) return@map entry
|
||||||
|
val origin = entry.origin
|
||||||
|
val whole = entry.isWholeOrigin()
|
||||||
|
val existingId = if (whole) null else fragmentIds[origin]
|
||||||
|
val id = existingId ?: (nextIds[origin] ?: 0L)
|
||||||
|
if (existingId == null) {
|
||||||
|
nextIds[origin] = id + 1
|
||||||
|
if (!whole) fragmentIds[origin] = id
|
||||||
|
}
|
||||||
|
entry.copy(originEntryId = id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun canonicalEntries(
|
private fun canonicalEntries(
|
||||||
entries: List<QueueEntry>,
|
entries: List<QueueEntry>,
|
||||||
changed: Set<QueueOrigin>,
|
changed: Set<QueueOrigin>,
|
||||||
): List<QueueEntry> {
|
): List<QueueEntry> {
|
||||||
val result = mutableListOf<QueueEntry>()
|
val result = mutableListOf<QueueEntry>()
|
||||||
val reset = mutableSetOf<QueueOrigin>()
|
val reset = mutableSetOf<OriginEntryKey>()
|
||||||
for (entry in entries) {
|
for (entry in entries) {
|
||||||
val origin = entry.origin
|
val origin = entry.origin
|
||||||
val whole = entry.isWholeOrigin()
|
|
||||||
if (origin !in changed) {
|
if (origin !in changed) {
|
||||||
result += entry
|
result += entry
|
||||||
} else if (whole || reset.add(origin)) {
|
} else if (reset.add(entry.originEntryKey())) {
|
||||||
result += QueueEntry(position = 0, sourceId = origin.sourceId, kind = origin.kind, refId = origin.refId)
|
result +=
|
||||||
|
QueueEntry(
|
||||||
|
position = 0,
|
||||||
|
sourceId = origin.sourceId,
|
||||||
|
kind = origin.kind,
|
||||||
|
refId = origin.refId,
|
||||||
|
originEntryId = entry.originEntryId,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
@@ -774,16 +806,14 @@ class QueueRepository(
|
|||||||
origin: QueueOrigin,
|
origin: QueueOrigin,
|
||||||
): List<OriginEntryGroup> {
|
): List<OriginEntryGroup> {
|
||||||
val groups = mutableListOf<OriginEntryGroup>()
|
val groups = mutableListOf<OriginEntryGroup>()
|
||||||
var fragmentIndex: Int? = null
|
val groupIndices = mutableMapOf<OriginEntryKey, Int>()
|
||||||
var start = 0L
|
var start = 0L
|
||||||
for (resolved in entries) {
|
for (resolved in entries) {
|
||||||
if (resolved.entry.origin == origin) {
|
if (resolved.entry.origin == origin) {
|
||||||
if (resolved.entry.isWholeOrigin()) {
|
val key = resolved.entry.originEntryKey()
|
||||||
groups += OriginEntryGroup(listOf(start), listOf(resolved))
|
val index = groupIndices[key]
|
||||||
} else {
|
|
||||||
val index = fragmentIndex
|
|
||||||
if (index == null) {
|
if (index == null) {
|
||||||
fragmentIndex = groups.size
|
groupIndices[key] = groups.size
|
||||||
groups += OriginEntryGroup(listOf(start), listOf(resolved))
|
groups += OriginEntryGroup(listOf(start), listOf(resolved))
|
||||||
} else {
|
} else {
|
||||||
groups[index] =
|
groups[index] =
|
||||||
@@ -793,7 +823,6 @@ class QueueRepository(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
start += resolved.length
|
start += resolved.length
|
||||||
}
|
}
|
||||||
return groups
|
return groups
|
||||||
@@ -1041,7 +1070,11 @@ class QueueRepository(
|
|||||||
db.useWriterConnection { transactor ->
|
db.useWriterConnection { transactor ->
|
||||||
transactor.immediateTransaction {
|
transactor.immediateTransaction {
|
||||||
dao.clear()
|
dao.clear()
|
||||||
dao.insert(entries.mapIndexed { index, entry -> entry.copy(id = 0, position = index.toLong()) })
|
dao.insert(
|
||||||
|
withOriginEntryIds(entries).mapIndexed { index, entry ->
|
||||||
|
entry.copy(id = 0, position = index.toLong())
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
queueVersion.incrementAndGet()
|
queueVersion.incrementAndGet()
|
||||||
@@ -1052,7 +1085,7 @@ class QueueRepository(
|
|||||||
transactor.immediateTransaction {
|
transactor.immediateTransaction {
|
||||||
dao.clearUpNext()
|
dao.clearUpNext()
|
||||||
dao.insertUpNext(
|
dao.insertUpNext(
|
||||||
entries.mapIndexed { index, entry ->
|
withOriginEntryIds(entries).mapIndexed { index, entry ->
|
||||||
UpNextEntry.from(entry).copy(id = 0, position = index.toLong())
|
UpNextEntry.from(entry).copy(id = 0, position = index.toLong())
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -1348,6 +1381,7 @@ class QueueRepository(
|
|||||||
originKind = source.origin.kind,
|
originKind = source.origin.kind,
|
||||||
originRefId = source.origin.refId,
|
originRefId = source.origin.refId,
|
||||||
originOccurrence = resetItem?.occurrence ?: source.originOccurrence,
|
originOccurrence = resetItem?.occurrence ?: source.originOccurrence,
|
||||||
|
originEntryId = source.originEntryId,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1447,5 +1481,8 @@ class QueueRepository(
|
|||||||
entry.kind == next.entry.kind &&
|
entry.kind == next.entry.kind &&
|
||||||
entry.sourceId == next.entry.sourceId &&
|
entry.sourceId == next.entry.sourceId &&
|
||||||
entry.refId == next.entry.refId &&
|
entry.refId == next.entry.refId &&
|
||||||
|
entry.origin == next.entry.origin &&
|
||||||
|
entry.originOccurrence == next.entry.originOccurrence &&
|
||||||
|
entry.originEntryId == next.entry.originEntryId &&
|
||||||
entry.offset + length == next.entry.offset
|
entry.offset + length == next.entry.offset
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -613,6 +613,59 @@ class QueueRepositoryTest {
|
|||||||
assertEquals(3L, repository.snapshot().upNextAnchor)
|
assertEquals(3L, repository.snapshot().upNextAnchor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun aChangedOriginResetRestoresFullyFragmentedDuplicateEntries() =
|
||||||
|
runTest {
|
||||||
|
seedLibrary()
|
||||||
|
setPlaylist("p1", "s1", "s2")
|
||||||
|
val initial =
|
||||||
|
repository.snapshotAfter(listOf(repository.playlistEntry(1, "p1"), repository.playlistEntry(1, "p1")))
|
||||||
|
repository.removeAt(initial, 0)
|
||||||
|
repository.removeAt(repository.snapshot(), 1)
|
||||||
|
repository = QueueRepository(db)
|
||||||
|
repository.setCursor(1)
|
||||||
|
repository.setPosition(42_000L)
|
||||||
|
val point = repository.captureResetPoint()
|
||||||
|
|
||||||
|
repository.resetChangedOrigins(setOf(QueueOrigin(QueueKind.Playlist, 1, "p1")), point)
|
||||||
|
|
||||||
|
val updated = repository.snapshot()
|
||||||
|
assertEquals(2, updated.entries.size)
|
||||||
|
assertTrue(updated.entries.all { it.entry.rangeStart == null && it.entry.rangeEnd == null })
|
||||||
|
assertEquals(listOf("s1", "s2", "s1", "s2"), resolveAll(updated))
|
||||||
|
assertEquals(3L, repository.remapPosition(updated, point.current!!))
|
||||||
|
assertEquals(3L, repository.cursor())
|
||||||
|
assertEquals(42_000L, repository.cursorPositionMs())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun anUnrelatedOriginResetRemapsWithinTheSecondFullyFragmentedDuplicateEntry() =
|
||||||
|
runTest {
|
||||||
|
seedLibrary()
|
||||||
|
db.libraryDao().upsertSongs(listOf(song("s0", "al1", track = 0, album = "First Album")))
|
||||||
|
setPlaylist("p1", "s1", "s2")
|
||||||
|
setPlaylist("p2", "s4")
|
||||||
|
val initial =
|
||||||
|
repository.snapshotAfter(
|
||||||
|
listOf(
|
||||||
|
repository.playlistEntry(1, "p2"),
|
||||||
|
repository.playlistEntry(1, "p1"),
|
||||||
|
repository.playlistEntry(1, "p1"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
repository.removeAt(initial, 1)
|
||||||
|
repository.removeAt(repository.snapshot(), 2)
|
||||||
|
repository.setCursor(2)
|
||||||
|
val point = repository.captureResetPoint()
|
||||||
|
|
||||||
|
setPlaylist("p2", "s0", "s4")
|
||||||
|
repository.resetChangedOrigins(setOf(QueueOrigin(QueueKind.Playlist, 1, "p2")), point)
|
||||||
|
|
||||||
|
val updated = repository.snapshot()
|
||||||
|
assertEquals(3L, repository.remapPosition(updated, point.current!!))
|
||||||
|
assertEquals(3L, repository.cursor())
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun aResetRemapsTheSecondOccurrenceOfASongWithinAPlaylist() =
|
fun aResetRemapsTheSecondOccurrenceOfASongWithinAPlaylist() =
|
||||||
runTest {
|
runTest {
|
||||||
|
|||||||
Reference in New Issue
Block a user