diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt index b1b0ccf634..d481d152b7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt @@ -221,15 +221,6 @@ import com.vitorpamplona.quartz.nip18Reposts.BaseRepostEvent import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent import com.vitorpamplona.quartz.nip18Reposts.RepostEvent import com.vitorpamplona.quartz.nip18Reposts.quotes.taggedQuoteIds -import com.vitorpamplona.quartz.nip19Bech32.entities.Entity -import com.vitorpamplona.quartz.nip19Bech32.entities.NAddress -import com.vitorpamplona.quartz.nip19Bech32.entities.NEmbed -import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent -import com.vitorpamplona.quartz.nip19Bech32.entities.NNote -import com.vitorpamplona.quartz.nip19Bech32.entities.NProfile -import com.vitorpamplona.quartz.nip19Bech32.entities.NPub -import com.vitorpamplona.quartz.nip19Bech32.entities.NRelay -import com.vitorpamplona.quartz.nip19Bech32.entities.NSec import com.vitorpamplona.quartz.nip19Bech32.isATag import com.vitorpamplona.quartz.nip22Comments.CommentEvent import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent @@ -745,7 +736,7 @@ object LocalCache : ILocalCache, ICacheProvider, Dao { /** Every relay group we know of that is hosted on [relay] (its channel directory). */ override fun getRelayGroupChannelsOnRelay(relay: NormalizedRelayUrl): List = relayGroupChannels.filter { key, _ -> key.relayUrl == relay } - override fun allRelayGroupChannels(): List = relayGroupChannels.filter { _, _ -> true } + override fun allRelayGroupChannels(): List = relayGroupChannels.values().toList() /** * The [RelayGroupChannel] a group-scoped content [note] belongs to, resolved the same way @@ -3302,53 +3293,8 @@ object LocalCache : ILocalCache, ICacheProvider, Dao { wasVerified: Boolean, ): Boolean = !event.isDeleted() && consumeBaseReplaceable(event, relay, wasVerified) - override fun consume(nip19: Entity) { - when (nip19) { - is NSec -> { - getOrCreateUser(nip19.toPubKeyHex()) - } - - is NPub -> { - getOrCreateUser(nip19.hex) - } - - is NProfile -> { - nip19.relay.forEach { relayHint -> - relayHints.addKey(nip19.hex, relayHint) - } - getOrCreateUser(nip19.hex) - } - - is NNote -> { - getOrCreateNote(nip19.hex) - } - - is NEvent -> { - nip19.relay.forEach { relayHint -> - relayHints.addEvent(nip19.hex, relayHint) - } - val note = getOrCreateNote(nip19.hex) - if (note.author == null) { - nip19.author?.let { note.author = checkGetOrCreateUser(it) } - } - } - - is NEmbed -> { - justConsume(nip19.event, null, false) - } - - is NRelay -> {} - - is NAddress -> { - val aTag = nip19.aTag() - nip19.relay.forEach { relayHint -> - relayHints.addAddress(aTag, relayHint) - } - getOrCreateAddressableNote(nip19.address()) - } - - else -> { } - } + override fun consumeEmbedded(event: Event) { + justConsume(event, null, false) } override fun justConsumeMyOwnEvent(event: Event) = justConsumeAndUpdateIndexes(event, null, true) diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/ICacheProvider.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/ICacheProvider.kt index 0084e0f638..16570d3d22 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/ICacheProvider.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/ICacheProvider.kt @@ -31,6 +31,13 @@ import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.hints.HintIndexer import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip19Bech32.entities.Entity +import com.vitorpamplona.quartz.nip19Bech32.entities.NAddress +import com.vitorpamplona.quartz.nip19Bech32.entities.NEmbed +import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent +import com.vitorpamplona.quartz.nip19Bech32.entities.NNote +import com.vitorpamplona.quartz.nip19Bech32.entities.NProfile +import com.vitorpamplona.quartz.nip19Bech32.entities.NPub +import com.vitorpamplona.quartz.nip19Bech32.entities.NSec import com.vitorpamplona.quartz.utils.Log /** @@ -176,7 +183,37 @@ interface ICacheProvider { * placeholder User / Note / AddressableNote, so a REQ built for it has something to * attach to. Used by the search sub-assemblers when the query is an npub/nevent/naddr. */ - fun consume(nip19: Entity) + fun consume(nip19: Entity) { + when (nip19) { + is NSec -> getOrCreateUser(nip19.toPubKeyHex()) + is NPub -> getOrCreateUser(nip19.hex) + is NProfile -> { + nip19.relay.forEach { relayHints.addKey(nip19.hex, it) } + getOrCreateUser(nip19.hex) + } + is NNote -> checkGetOrCreateNote(nip19.hex) + is NEvent -> { + nip19.relay.forEach { relayHints.addEvent(nip19.hex, it) } + val note = checkGetOrCreateNote(nip19.hex) + if (note != null && note.author == null) { + nip19.author?.let { note.author = checkGetOrCreateUser(it) } + } + } + is NEmbed -> consumeEmbedded(nip19.event) + is NAddress -> { + val aTag = nip19.aTag() + nip19.relay.forEach { relayHints.addAddress(aTag, it) } + getOrCreateAddressableNote(nip19.address()) + } + else -> {} + } + } + + /** + * Ingests an event carried inline in an `nembed`. Unlike [justConsumeMyOwnEvent] the event + * did not come from this user, so implementations verify its signature like a relay event. + */ + fun consumeEmbedded(event: Event) /** * Every NIP-29 relay group (kind 39000 metadata + rosters) this cache holds. The discovery diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/music/FilterMusicEventsByAuthors.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/music/FilterMusicEventsByAuthors.kt index 9e53b79852..afbabac9a8 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/music/FilterMusicEventsByAuthors.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/music/FilterMusicEventsByAuthors.kt @@ -36,8 +36,8 @@ import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl // also fan out as separate subscriptions — keeping them split avoids cross-kind over-fetching // and lets `makeMusicTracksFilter` / `makeMusicPlaylistsFilter` reuse the same parameterized // `filterMusicEventsByX` helpers below. -val MUSIC_TRACK_KINDS = listOf(MusicTrackEvent.KIND) -val MUSIC_PLAYLIST_KINDS = listOf(MusicPlaylistEvent.KIND) +internal val MUSIC_TRACK_KINDS = listOf(MusicTrackEvent.KIND) +internal val MUSIC_PLAYLIST_KINDS = listOf(MusicPlaylistEvent.KIND) fun filterMusicEventsByAuthors( relay: NormalizedRelayUrl, diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/podcasts/FilterPodcastEventsByAuthors.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/podcasts/FilterPodcastEventsByAuthors.kt index 8dd4ad0515..3d2d64edf6 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/podcasts/FilterPodcastEventsByAuthors.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/podcasts/FilterPodcastEventsByAuthors.kt @@ -39,15 +39,15 @@ import com.vitorpamplona.quartz.nipXXPodcasting20.metadata.Podcasting20PodcastMe // Episodes span both podcast drafts: NIP-F4 kind 54 (podcast-is-a-keypair) and Podcasting-2.0 // kind 30054 (creator-is-a-keypair). Both are authored by the followed pubkey, so the same // author-scoped REQ pulls them into one merged feed. -val PODCAST_EPISODE_KINDS = listOf(PodcastEpisodeEvent.KIND, Podcasting20EpisodeEvent.KIND) -val PODCAST_KINDS = listOf(PodcastMetadataEvent.KIND) +internal val PODCAST_EPISODE_KINDS = listOf(PodcastEpisodeEvent.KIND, Podcasting20EpisodeEvent.KIND) +internal val PODCAST_KINDS = listOf(PodcastMetadataEvent.KIND) // Podcasting-2.0 stores show metadata as a kind:30078 NIP-78 app-data event. That kind is // heavily overloaded, so this REQ MUST be constrained by `#d=["podcast-metadata"]` (see // [PODCAST_METADATA_D_FILTER]) or it would pull every client's app-data. Kept separate from // [PODCAST_KINDS] because the NIP-F4 kind:10154 metadata must NOT carry the `#d` constraint. -val PODCASTING20_METADATA_KINDS = listOf(AppSpecificDataEvent.KIND) -val PODCAST_METADATA_D_FILTER = mapOf("d" to listOf(Podcasting20PodcastMetadata.PODCAST_METADATA_D_TAG)) +internal val PODCASTING20_METADATA_KINDS = listOf(AppSpecificDataEvent.KIND) +internal val PODCAST_METADATA_D_FILTER = mapOf("d" to listOf(Podcasting20PodcastMetadata.PODCAST_METADATA_D_TAG)) /** * Merges two relay-filter tag maps, unioning the value lists per key. Used to layer an extra diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/topNavFeeds/TopNavFeedSubAssembler.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/topNavFeeds/TopNavFeedSubAssembler.kt index e166810378..7814d09d72 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/topNavFeeds/TopNavFeedSubAssembler.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/topNavFeeds/TopNavFeedSubAssembler.kt @@ -63,6 +63,9 @@ abstract class TopNavFeedSubAssembler( /** Runs when the key's top-nav selection changes, right before the filters are re-issued. */ protected open fun onListChanged(key: K) {} + /** How often (ms) a moving feed floor may re-issue the REQ; the video swipe feed wants a tighter cadence. */ + protected open val floorSampleMs: Long = 5000 + /** The feed floors that re-issue the REQ (sampled) when they move. Defaults to every feed on the key. */ protected open fun floors(key: K): List> = key.feeds.map { it.lastNoteCreatedAtWhenFullyLoaded } @@ -96,7 +99,7 @@ abstract class TopNavFeedSubAssembler( if (floors.isNotEmpty()) { add( key.scope.launch(Dispatchers.IO) { - combine(floors) { }.sample(5000).collectLatest { + combine(floors) { }.sample(floorSampleMs).collectLatest { invalidateFilters() } }, @@ -121,7 +124,7 @@ abstract class TopNavFeedSubAssembler( subId: String, ) { super.endSub(key, subId) - userJobMap[key]?.forEach { it.cancel() } + userJobMap.remove(key)?.forEach { it.cancel() } } } @@ -139,6 +142,7 @@ open class SingleTopNavFeedSubAssembler( allKeys: () -> Set, private val makeFilter: TopNavFeedFilterMaker, private val resetEoseOnListChange: Boolean = false, + override val floorSampleMs: Long = 5000, ) : TopNavFeedSubAssembler(client, allKeys) { override fun updateFilter( key: TopNavFeedQueryState, diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/video/VideoFilterAssembler.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/video/VideoFilterAssembler.kt index 07c0336d09..b1e8852e91 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/video/VideoFilterAssembler.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/video/VideoFilterAssembler.kt @@ -27,4 +27,4 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient class VideoFilterAssembler( client: INostrClient, -) : TopNavFeedFilterAssembler({ keys -> listOf(SingleTopNavFeedSubAssembler(client, keys, ::makePictureAndVideoFilter)) }) +) : TopNavFeedFilterAssembler({ keys -> listOf(SingleTopNavFeedSubAssembler(client, keys, ::makePictureAndVideoFilter, floorSampleMs = 1000)) }) diff --git a/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/model/ThreadAssemblerTest.kt b/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/model/ThreadAssemblerTest.kt index f4835e7ac1..5bed98dc6f 100644 --- a/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/model/ThreadAssemblerTest.kt +++ b/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/model/ThreadAssemblerTest.kt @@ -27,7 +27,6 @@ import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.hints.HintIndexer import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent -import com.vitorpamplona.quartz.nip19Bech32.entities.Entity import com.vitorpamplona.quartz.nip22Comments.CommentEvent import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent @@ -193,7 +192,7 @@ class ThreadAssemblerTest { override fun getOrCreateUser(pubkey: HexKey): User? = null - override fun consume(nip19: Entity) = Unit + override fun consumeEmbedded(event: Event) = Unit override fun justConsumeMyOwnEvent(event: Event): Boolean = false } diff --git a/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/model/concord/ConcordChannelListLeaveTest.kt b/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/model/concord/ConcordChannelListLeaveTest.kt index 3048269560..272e9da38c 100644 --- a/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/model/concord/ConcordChannelListLeaveTest.kt +++ b/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/model/concord/ConcordChannelListLeaveTest.kt @@ -35,7 +35,6 @@ import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair import com.vitorpamplona.quartz.nip01Core.hints.HintIndexer import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal -import com.vitorpamplona.quartz.nip19Bech32.entities.Entity import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.test.runTest @@ -102,7 +101,7 @@ class ConcordChannelListLeaveTest { override fun getOrCreateUser(pubkey: HexKey): User? = null - override fun consume(nip19: Entity) = Unit + override fun consumeEmbedded(event: Event) = Unit override fun justConsumeMyOwnEvent(event: Event): Boolean = false } diff --git a/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/model/concord/ConcordListLateArrivalTest.kt b/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/model/concord/ConcordListLateArrivalTest.kt index 5528d37c54..af29a84fff 100644 --- a/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/model/concord/ConcordListLateArrivalTest.kt +++ b/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/model/concord/ConcordListLateArrivalTest.kt @@ -35,7 +35,6 @@ import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair import com.vitorpamplona.quartz.nip01Core.hints.HintIndexer import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal -import com.vitorpamplona.quartz.nip19Bech32.entities.Entity import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.test.runTest @@ -100,7 +99,7 @@ class ConcordListLateArrivalTest { override fun getOrCreateUser(pubkey: HexKey): User? = null - override fun consume(nip19: Entity) = Unit + override fun consumeEmbedded(event: Event) = Unit override fun justConsumeMyOwnEvent(event: Event): Boolean = false } diff --git a/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/ui/note/StubCache.kt b/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/ui/note/StubCache.kt index 937089755b..4785fd6f75 100644 --- a/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/ui/note/StubCache.kt +++ b/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/ui/note/StubCache.kt @@ -30,7 +30,6 @@ import com.vitorpamplona.quartz.nip01Core.core.Address import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.hints.HintIndexer -import com.vitorpamplona.quartz.nip19Bech32.entities.Entity /** * Minimal [ICacheProvider] for the pure reply-resolution tests in this package: a fixed set of @@ -69,7 +68,7 @@ internal class StubCache( override fun getOrCreateUser(pubkey: HexKey): User? = users[pubkey] - override fun consume(nip19: Entity) = Unit + override fun consumeEmbedded(event: Event) = Unit override fun justConsumeMyOwnEvent(event: Event): Boolean = false } diff --git a/commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/relayClient/event/EventFinderFilterAssemblyTest.kt b/commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/relayClient/event/EventFinderFilterAssemblyTest.kt index 388d29afed..0447b7bee3 100644 --- a/commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/relayClient/event/EventFinderFilterAssemblyTest.kt +++ b/commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/relayClient/event/EventFinderFilterAssemblyTest.kt @@ -33,7 +33,6 @@ import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.hints.HintIndexer import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl -import com.vitorpamplona.quartz.nip19Bech32.entities.Entity import com.vitorpamplona.quartz.nip85TrustedAssertions.list.tags.ServiceProviderTag import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse @@ -156,7 +155,7 @@ class EventFinderFilterAssemblyTest { override fun getOrCreateUser(pubkey: HexKey): User? = null - override fun consume(nip19: Entity) = Unit + override fun consumeEmbedded(event: Event) = Unit override fun justConsumeMyOwnEvent(event: Event): Boolean = false } diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/cache/DesktopLocalCache.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/cache/DesktopLocalCache.kt index fe661c01fa..da14a783f0 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/cache/DesktopLocalCache.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/cache/DesktopLocalCache.kt @@ -50,14 +50,6 @@ import com.vitorpamplona.quartz.nip18Reposts.quotes.QAddressableTag import com.vitorpamplona.quartz.nip18Reposts.quotes.QEventTag import com.vitorpamplona.quartz.nip18Reposts.quotes.taggedQuotes import com.vitorpamplona.quartz.nip19Bech32.decodePublicKeyAsHexOrNull -import com.vitorpamplona.quartz.nip19Bech32.entities.Entity -import com.vitorpamplona.quartz.nip19Bech32.entities.NAddress -import com.vitorpamplona.quartz.nip19Bech32.entities.NEmbed -import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent -import com.vitorpamplona.quartz.nip19Bech32.entities.NNote -import com.vitorpamplona.quartz.nip19Bech32.entities.NProfile -import com.vitorpamplona.quartz.nip19Bech32.entities.NPub -import com.vitorpamplona.quartz.nip19Bech32.entities.NSec import com.vitorpamplona.quartz.nip22Comments.CommentEvent import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent @@ -1037,31 +1029,8 @@ class DesktopLocalCache : ICacheProvider { AddressableNote(address) } - /** Same seeding as the Android cache: relay hints + a placeholder for whatever the entity names. */ - override fun consume(nip19: Entity) { - when (nip19) { - is NSec -> getOrCreateUser(nip19.toPubKeyHex()) - is NPub -> getOrCreateUser(nip19.hex) - is NProfile -> { - nip19.relay.forEach { relayHints.addKey(nip19.hex, it) } - getOrCreateUser(nip19.hex) - } - is NNote -> getOrCreateNote(nip19.hex) - is NEvent -> { - nip19.relay.forEach { relayHints.addEvent(nip19.hex, it) } - val note = getOrCreateNote(nip19.hex) - if (note.author == null) { - nip19.author?.let { note.author = checkGetOrCreateUser(it) } - } - } - is NEmbed -> consume(nip19.event, null) - is NAddress -> { - val aTag = nip19.aTag() - nip19.relay.forEach { relayHints.addAddress(aTag, it) } - getOrCreateAddressableNote(nip19.address()) - } - else -> {} - } + override fun consumeEmbedded(event: Event) { + consume(event, null) } // ----- Channel operations -----