package link.roadio.riff.player.library import link.roadio.cuetree.CatalogView 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 link.roadio.cuetree.Span import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Test class CuetreeCatalogRegressionTest { private fun fixture(): Snapshot { val root = Node( NodeKind.DIRECTORY, MediaId(1), source = SourceInfo("Music", provider = "saf", locator = "content://fixture"), attributes = mapOf(ROOT_IDENTITY_ATTRIBUTE to listOf("root")), life = Life(1), ) val base = Node(NodeKind.MEDIA, MediaId(10), root.id, SourceInfo("recording.m4a"), life = Life(1)) fun fragment( id: Long, ordinal: Int, begin: Long, end: Long, ) = Node( NodeKind.FRAGMENT, MediaId(id), base.id, span = Span(begin, end), attributes = mapOf("title" to listOf("Part $ordinal"), "fragment" to listOf("$ordinal"), "totalFragments" to listOf("3")), life = Life(1), ) return Snapshot( 1, "test", listOf(root, base, fragment(20, 3, 4_000_000, 6_000_000), fragment(30, 1, 0, 2_000_000), fragment(40, 2, 2_000_000, 4_000_000)), albums = listOf( link.roadio.cuetree.Album( 100, link.roadio.cuetree.AlbumKind.EXPLICIT, "Recording album", listOf(MediaId(30), MediaId(40), MediaId(20)), listOf(3, 4, 2), emptyMap(), Life(1), ), ), ) } private fun views(snapshot: Snapshot): List = listOf(snapshot, FlatCatalogs.encode(snapshot).view()) @Test fun contentModeLauncherRemainsEnabledForEmptyAndLoadedCatalogsInBothModes() { for (snapshot in listOf(Snapshot.empty("test"), fixture())) { for (view in views(snapshot)) { val models = CuetreeBrowserTree.build(view, PlayerLibraryProjection(), emptySet(), hash = { it.contentHashCode().toLong() }) for (model in listOf(models.music, models.audiobook)) { assertTrue( "Books is a mode switch, independent of catalog membership", model.root.children .single { it.id == "category:books" }.enabled, ) } } } } @Test fun fragmentRowsAndAllFolderQueueScopesUseSectionOrderRatherThanHashOrder() { for (view in views(fixture())) { val model = CuetreeBrowserTree .build( view, PlayerLibraryProjection(), emptySet(), hash = { it.contentHashCode().toLong() }, ).music val catalog = requireNotNull(model.root.catalog) val rootFolder = model.root.children .single { it.id == "category:folders" } .children .single() val file = rootFolder.children.single() assertEquals("Recording album", file.label) assertEquals(listOf("Part 1", "Part 2", "Part 3"), file.children.map { it.label }) for (context in listOf(file.id, rootFolder.id, "category:folders")) { val tracks = catalog.tracks(context, true, false) assertEquals(listOf("Part 1", "Part 2", "Part 3"), tracks.filter { it.fragment }.map { it.label }) val sources = tracks.filter { it.fragment }.map { model.lookup(requireNotNull(it.mediaId)).sources.single() } assertEquals(listOf(0L, 2_000_000L, 4_000_000L), sources.map { it.beginUs }) assertEquals(listOf(2_000_000L, 4_000_000L, 6_000_000L), sources.map { it.endUs }) assertEquals(listOf("recording.m4a"), sources.flatMap { it.segments.map { segment -> segment.displayName } }.distinct()) } } } @Test fun directTrackContextUsesTheSharedIdentityIndexWithoutScanningAllRetainedRows() { val root = Node( NodeKind.DIRECTORY, MediaId(-1), source = SourceInfo("Music", provider = "saf", locator = "content://fixture"), life = Life(1), ) val snapshot = Snapshot( 1, "test", listOf(root) + List(64) { index -> Node(NodeKind.MEDIA, MediaId(index.toLong()), root.id, SourceInfo("$index.m4a"), life = Life(1)) }, ) for (view in views(snapshot)) { var sourceQueries = 0 var identityReads = 0 val counted = object : CatalogView by view { override fun sources(id: MediaId): List { sourceQueries++ return view.sources(id) } override fun nodeId(index: Int): MediaId { identityReads++ return view.nodeId(index) } } val model = CuetreeBrowserTree .build( counted, PlayerLibraryProjection(), emptySet(), hash = { it.contentHashCode().toLong() }, ).music sourceQueries = 0 identityReads = 0 val wanted = link.roadio.riff.indexer.core.media .MediaId(63, 0, false) val result = model.root.catalog!!.tracks("track:${wanted.hex()}", true, false) assertEquals(1, sourceQueries) assertTrue("A direct ID lookup decoded $identityReads unrelated identities", identityReads < 8) assertEquals(listOf(wanted), result.map { it.mediaId }) } } }