From ccf64687a81ab84891f5567b7025de780b5f24bd Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Wed, 24 Sep 2025 12:36:39 -0400 Subject: [PATCH] Separates index relays from the default and put it into the hint-based queries for unkown users. --- .../vitorpamplona/amethyst/model/Account.kt | 2 - ...usMineWithIndexAndSearchRelayListsState.kt | 92 ------------------- .../user/loaders/FilterUserMetadataForKey.kt | 10 +- .../user/loaders/UserLoaderSubAssembler.kt | 11 ++- .../subassemblies/FilterByAuthor.kt | 3 +- .../SearchWatcherSubAssembler.kt | 13 ++- 6 files changed, 26 insertions(+), 105 deletions(-) delete mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/model/serverList/MergedFollowPlusMineWithIndexAndSearchRelayListsState.kt diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index 3191aae0a..a54781a1a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -80,7 +80,6 @@ import com.vitorpamplona.amethyst.model.nip96FileStorage.FileStorageServerListSt import com.vitorpamplona.amethyst.model.nipB7Blossom.BlossomServerListState import com.vitorpamplona.amethyst.model.serverList.MergedFollowListsState import com.vitorpamplona.amethyst.model.serverList.MergedFollowPlusMineRelayListsState -import com.vitorpamplona.amethyst.model.serverList.MergedFollowPlusMineWithIndexAndSearchRelayListsState import com.vitorpamplona.amethyst.model.serverList.MergedFollowPlusMineWithIndexRelayListsState import com.vitorpamplona.amethyst.model.serverList.MergedFollowPlusMineWithSearchRelayListsState import com.vitorpamplona.amethyst.model.serverList.MergedServerListState @@ -312,7 +311,6 @@ class Account( val followOutboxesOrProxy = FollowListOutboxOrProxyRelays(kind3FollowList, blockedRelayList, proxyRelayList, cache, scope) val followPlusAllMineWithIndex = MergedFollowPlusMineWithIndexRelayListsState(followOutboxesOrProxy, nip65RelayList, privateStorageRelayList, localRelayList, indexerRelayList, scope) val followPlusAllMineWithSearch = MergedFollowPlusMineWithSearchRelayListsState(followOutboxesOrProxy, nip65RelayList, privateStorageRelayList, localRelayList, searchRelayList, scope) - val followPlusAllMineWithIndexAndSearch = MergedFollowPlusMineWithIndexAndSearchRelayListsState(followOutboxesOrProxy, nip65RelayList, privateStorageRelayList, localRelayList, indexerRelayList, searchRelayList, scope) val defaultGlobalRelays = MergedFollowPlusMineRelayListsState(followOutboxesOrProxy, nip65RelayList, privateStorageRelayList, localRelayList, scope) // keeps a cache of the outbox relays for each author diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/serverList/MergedFollowPlusMineWithIndexAndSearchRelayListsState.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/serverList/MergedFollowPlusMineWithIndexAndSearchRelayListsState.kt deleted file mode 100644 index 68956135b..000000000 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/serverList/MergedFollowPlusMineWithIndexAndSearchRelayListsState.kt +++ /dev/null @@ -1,92 +0,0 @@ -/** - * Copyright (c) 2025 Vitor Pamplona - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the - * Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN - * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -package com.vitorpamplona.amethyst.model.serverList - -import com.vitorpamplona.amethyst.model.edits.PrivateStorageRelayListState -import com.vitorpamplona.amethyst.model.localRelays.LocalRelayListState -import com.vitorpamplona.amethyst.model.nip02FollowLists.FollowListOutboxOrProxyRelays -import com.vitorpamplona.amethyst.model.nip51Lists.indexerRelays.IndexerRelayListState -import com.vitorpamplona.amethyst.model.nip51Lists.searchRelays.SearchRelayListState -import com.vitorpamplona.amethyst.model.nip65RelayList.Nip65RelayListState -import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.flow.stateIn - -class MergedFollowPlusMineWithIndexAndSearchRelayListsState( - val followsOutboxOrProxyRelayList: FollowListOutboxOrProxyRelays, - val nip65RelayList: Nip65RelayListState, - val privateOutboxRelayList: PrivateStorageRelayListState, - val localRelayList: LocalRelayListState, - val indexerRelayList: IndexerRelayListState, - val searchRelayListsState: SearchRelayListState, - val scope: CoroutineScope, -) { - fun mergeLists(lists: Array>): Set = lists.reduce { acc, set -> acc + set } - - val flow: StateFlow> = - combine( - listOf( - followsOutboxOrProxyRelayList.flow, - nip65RelayList.outboxFlow, - nip65RelayList.inboxFlow, - privateOutboxRelayList.flow, - localRelayList.flow, - indexerRelayList.flow, - searchRelayListsState.flow, - ), - ::mergeLists, - ).onStart { - emit( - mergeLists( - arrayOf( - followsOutboxOrProxyRelayList.flow.value, - nip65RelayList.outboxFlow.value, - nip65RelayList.inboxFlow.value, - privateOutboxRelayList.flow.value, - localRelayList.flow.value, - indexerRelayList.flow.value, - searchRelayListsState.flow.value, - ), - ), - ) - }.flowOn(Dispatchers.Default) - .stateIn( - scope, - SharingStarted.Eagerly, - mergeLists( - arrayOf( - followsOutboxOrProxyRelayList.flow.value, - nip65RelayList.outboxFlow.value, - nip65RelayList.inboxFlow.value, - privateOutboxRelayList.flow.value, - localRelayList.flow.value, - indexerRelayList.flow.value, - searchRelayListsState.flow.value, - ), - ), - ) -} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/loaders/FilterUserMetadataForKey.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/loaders/FilterUserMetadataForKey.kt index b419e86a5..c41f9af4d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/loaders/FilterUserMetadataForKey.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/loaders/FilterUserMetadataForKey.kt @@ -38,14 +38,16 @@ val MetadataAndRelayListKinds = fun filterFindUserMetadataForKey( author: HexKey, + indexRelays: Set, defaultRelays: Set, ): List = LocalCache.checkGetOrCreateUser(author)?.let { - filterFindUserMetadataForKey(setOf(it), defaultRelays) + filterFindUserMetadataForKey(setOf(it), indexRelays, defaultRelays) } ?: emptyList() fun filterFindUserMetadataForKey( authors: Set, + indexRelays: Set, defaultRelays: Set, ): List { val perRelayKeys = @@ -53,8 +55,10 @@ fun filterFindUserMetadataForKey( authors.forEach { key -> val relays = key.authorRelayList()?.writeRelaysNorm() - ?: LocalCache.relayHints.hintsForKey(key.pubkeyHex).ifEmpty { null } - ?: (key.relaysBeingUsed.keys + defaultRelays).toList() + ?: (key.relaysBeingUsed.keys + LocalCache.relayHints.hintsForKey(key.pubkeyHex) + indexRelays) + .ifEmpty { null } + ?.also { println("Using relay hints ${it.size} for ${key.pubkeyHex}") } + ?: defaultRelays.toList() relays.forEach { add(it, key.pubkeyHex) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/loaders/UserLoaderSubAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/loaders/UserLoaderSubAssembler.kt index 357f29434..c138f365c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/loaders/UserLoaderSubAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/loaders/UserLoaderSubAssembler.kt @@ -20,6 +20,7 @@ */ package com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.loaders +import com.vitorpamplona.amethyst.model.DefaultIndexerRelayList import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.SingleSubNoEoseCacheEoseManager @@ -33,6 +34,7 @@ class UserLoaderSubAssembler( allKeys: () -> Set, ) : SingleSubNoEoseCacheEoseManager(client, allKeys, invalidateAfterEose = true) { override fun updateFilter(keys: List): List? { + println("01f6901bc401e87962fa8da15acfe16ef72b17ed965114384d69aa857a21fbfc updating user assembly filter 2") val firstTimers = mutableSetOf() keys.forEach { @@ -43,10 +45,15 @@ class UserLoaderSubAssembler( } } + val indexRelays = mutableSetOf() val defaultRelays = mutableSetOf() keys.mapTo(mutableSetOf()) { it.account }.forEach { - defaultRelays.addAll(it.followPlusAllMineWithIndexAndSearch.flow.value) + indexRelays.addAll( + it.indexerRelayList.flow.value + .ifEmpty { DefaultIndexerRelayList }, + ) + defaultRelays.addAll(it.followPlusAllMineWithSearch.flow.value) it.kind3FollowList.flow.value.authors.forEach { val user = LocalCache.getOrCreateUser(it) @@ -60,7 +67,7 @@ class UserLoaderSubAssembler( if (firstTimers.isEmpty()) return null - return filterFindUserMetadataForKey(firstTimers, defaultRelays) + return filterFindUserMetadataForKey(firstTimers, indexRelays, defaultRelays) } override fun distinct(key: UserFinderQueryState) = key.user diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/searchCommand/subassemblies/FilterByAuthor.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/searchCommand/subassemblies/FilterByAuthor.kt index c3b2efd28..916bfa09c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/searchCommand/subassemblies/FilterByAuthor.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/searchCommand/subassemblies/FilterByAuthor.kt @@ -26,5 +26,6 @@ import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl fun filterByAuthor( pubKey: HexKey, + indexRelays: Set, defaultRelays: Set, -) = filterFindUserMetadataForKey(pubKey, defaultRelays) +) = filterFindUserMetadataForKey(pubKey, indexRelays, defaultRelays) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/searchCommand/subassemblies/SearchWatcherSubAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/searchCommand/subassemblies/SearchWatcherSubAssembler.kt index 098d8e071..d7fbba00f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/searchCommand/subassemblies/SearchWatcherSubAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/searchCommand/subassemblies/SearchWatcherSubAssembler.kt @@ -20,6 +20,7 @@ */ package com.vitorpamplona.amethyst.service.relayClient.searchCommand.subassemblies +import com.vitorpamplona.amethyst.model.DefaultIndexerRelayList import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUniqueIdEoseManager import com.vitorpamplona.amethyst.service.relayClient.searchCommand.SearchQueryState @@ -55,23 +56,25 @@ class SearchWatcherSubAssembler( if (mySearchString.isBlank()) return null - val defaultRelaysWithIndexAndSearch = key.account.followPlusAllMineWithIndexAndSearch.flow.value + val indexRelays = + key.account.indexerRelayList.flow.value + .ifEmpty { DefaultIndexerRelayList } val defaultRelaysWithSearch = key.account.followPlusAllMineWithSearch.flow.value val directFilters = runCatching { if (Hex.isHex(mySearchString)) { val hexKey = Hex.decode(mySearchString).toHexKey() - filterByAuthor(hexKey, defaultRelaysWithIndexAndSearch) + filterByEvent(hexKey, defaultRelaysWithSearch) + filterByAuthor(hexKey, indexRelays, defaultRelaysWithSearch) + filterByEvent(hexKey, defaultRelaysWithSearch) } else { val parsed = Nip19Parser.uriToRoute(mySearchString)?.entity if (parsed != null) { cache.consume(parsed) when (parsed) { - is NSec -> filterByAuthor(parsed.toPubKeyHex(), defaultRelaysWithIndexAndSearch) - is NPub -> filterByAuthor(parsed.hex, defaultRelaysWithIndexAndSearch) - is NProfile -> filterByAuthor(parsed.hex, defaultRelaysWithIndexAndSearch) + is NSec -> filterByAuthor(parsed.toPubKeyHex(), indexRelays, defaultRelaysWithSearch) + is NPub -> filterByAuthor(parsed.hex, indexRelays, defaultRelaysWithSearch) + is NProfile -> filterByAuthor(parsed.hex, indexRelays, defaultRelaysWithSearch) is NNote -> filterByEvent(parsed.hex, defaultRelaysWithSearch) is NEvent -> filterByEvent(parsed.hex, defaultRelaysWithSearch) is NEmbed -> emptyList()