package link.roadio.riff.player.library import link.roadio.cuetree.CatalogView import link.roadio.cuetree.Flags import link.roadio.cuetree.FlatCatalogs import link.roadio.cuetree.Life import link.roadio.cuetree.MediaId import link.roadio.cuetree.Node import link.roadio.cuetree.NodeKind import link.roadio.cuetree.ROOT_IDENTITY_ATTRIBUTE import link.roadio.cuetree.Snapshot import link.roadio.cuetree.SourceInfo import org.junit.Assert.assertEquals import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Test import link.roadio.riff.indexer.core.media.MediaId as PlayerId class LazyCatalogConsumptionTest { @Test fun startupAndIdentityEnumerationDoNotDecodeMediaRecords() { val count = 2000 val root = Node( NodeKind.DIRECTORY, MediaId(-1), source = SourceInfo("Music", provider = "test", locator = "root"), attributes = mapOf(ROOT_IDENTITY_ATTRIBUTE to listOf("root")), life = Life(1), ) val media = List(count) { Node( NodeKind.MEDIA, MediaId(it.toLong()), root.id, SourceInfo("$it.mp3"), attributes = mapOf("title" to listOf("Track $it")), life = Life(1), ) } val flat = FlatCatalogs.encode(Snapshot(1, "test", listOf(root) + media)) val view = FlatCatalogs.readPublishedMapped(FlatCatalogs.names.associateWith(flat::buffer)).view() var decoded = 0 val observed = object : CatalogView by view { override val nodes = object : AbstractList() { override val size get() = view.nodes.size override fun get(index: Int): Node { decoded++ return view.nodes[index] } } } val models = CuetreeBrowserTree.build(observed, PlayerLibraryProjection(), emptySet(), hash = { 1L }) assertEquals(count, models.music.tracks.size) assertEquals(count, models.music.mediaIds.size) assertEquals(0, decoded) val first = models.music.tracksByMediaId[PlayerId(0, 0, false)] assertEquals("Track 0", first?.label) assertTrue("Only requested records may decode: $decoded", decoded < 10) assertNull(first?.playbackSource) assertTrue(models.music.tocChaptersByBindingId.isEmpty()) } @Test fun snapshotKnownUnavailableCopyRemainsAPlaybackCandidateWithoutProbing() { val root = Node( NodeKind.DIRECTORY, MediaId(-1), source = SourceInfo("Music", provider = "test", locator = "root"), attributes = mapOf(ROOT_IDENTITY_ATTRIBUTE to listOf("root")), life = Life(2), ) val first = Node(NodeKind.MEDIA, MediaId(1), root.id, SourceInfo("first.mp3"), life = Life(1, Flags.ACSS)) val second = first.copy(source = SourceInfo("second.mp3"), life = Life(2)) val model = CuetreeBrowserTree .build( Snapshot( 2, "test", listOf(root, first, second), ), PlayerLibraryProjection(), emptySet(), hash = { it.contentHashCode().toLong() }, ).music val lookup = model.lookup(PlayerId(1, 0, false)) assertEquals(listOf("first.mp3", "second.mp3"), lookup.sources.map { it.segments.last().displayName }) assertEquals(1, model.tracks.size) } @Test fun fileContextSurvivesRecordReorderingAndKeepsTheSelectedPhysicalCopy() { val root = Node( NodeKind.DIRECTORY, MediaId(-1), source = SourceInfo("Music", provider = "test", locator = "root"), attributes = mapOf(ROOT_IDENTITY_ATTRIBUTE to listOf("root")), life = Life(1), ) val first = Node(NodeKind.MEDIA, MediaId(10), root.id, SourceInfo("first.mp3"), life = Life(1)) val second = first.copy(source = SourceInfo("second.mp3")) val fragment = Node(NodeKind.FRAGMENT, MediaId(20), first.id, span = link.roadio.cuetree.Span(0, 1000000), life = Life(1)) fun model(nodes: List) = CuetreeBrowserTree .build( Snapshot( 1, "test", nodes, albums = listOf( link.roadio.cuetree.Album( 100, link.roadio.cuetree.AlbumKind.EXPLICIT, "Ready Album", listOf(fragment.id), listOf(nodes.indexOf(fragment)), emptyMap(), Life(1), ), ), ), PlayerLibraryProjection(), emptySet(), hash = { it.contentHashCode().toLong() }, ).music val before = model(listOf(root, first, second, fragment)) val folders = before.root.children .single { it.id == "category:folders" } .children .single() val selected = folders.children.single { it.children .single() .id .endsWith(":2") } val restored = model(listOf(root, second, first, fragment)) val context = restored.root.catalog!!.find(selected.id)!! assertEquals("Ready Album", context.label) assertTrue(context.children.single().fragment) val row = context.children.first() val preference = restored.root.catalog!!.preferredBinding(row.id) val lookup = restored.lookup(row.mediaId!!, context.id, 0) assertEquals(preference, lookup.contextBindingId) assertEquals( "second.mp3", lookup.sources .single { it.bindingId == preference } .segments .last() .displayName, ) } @Test fun aggregateQueueOrderUsesTheSameAlbumComparisonKeysAsBrowsing() { val root = Node(NodeKind.DIRECTORY, MediaId(-1), source = SourceInfo("Music", provider = "test", locator = "root"), life = Life(1)) val first = Node(NodeKind.MEDIA, MediaId(1), root.id, SourceInfo("first.mp3"), life = Life(1)) val second = Node(NodeKind.MEDIA, MediaId(2), root.id, SourceInfo("second.mp3"), life = Life(1)) val albums = listOf( link.roadio.cuetree.Album( 20, link.roadio.cuetree.AlbumKind.ORDINARY, "The Zebra", listOf(first.id), listOf(1), emptyMap(), Life(1), ), link.roadio.cuetree.Album( 30, link.roadio.cuetree.AlbumKind.ORDINARY, "Tree", listOf(second.id), listOf(2), emptyMap(), Life(1), ), ) val view = Snapshot(1, "test", listOf(root, first, second), albums = albums) val catalog = CuetreeBrowserTree .build(view, PlayerLibraryProjection(), emptySet(), hash = { 1L }) .music.root.catalog!! assertEquals(listOf(PlayerId(1, 0, false), PlayerId(2, 0, false)), catalog.tracks("category:albums").map { it.mediaId }) assertEquals(listOf(PlayerId(2, 0, false), PlayerId(1, 0, false)), catalog.tracks("category:albums", true, true).map { it.mediaId }) } }