mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-12 09:13:23 +00:00
refactor: move per-user metadata finder to commons on the new upstream model (Phase 2)
Move UserFinderFilterAssembler(+QueryState), the four sub-assemblers (UserOutboxFinder, UserWatcher, UserReports, UserCards), FilterUserMetadataForKey, and FilterReportsToKey from amethyst/reqCommand/user/ into commons/relayClient/user/, plus the inner Account-free pickRelaysToLoadUsers overload into commons PickRelaysToLoadUsers.kt. Reconciled with upstream's re-architecture: - UserFinderQueryState carries the narrow UserFinderAccount (Phase 1) instead of the full Account; DROPS AccountScopedQuery. The finder sub-assemblers extend the commons base managers and attribute inline via soleAccountPubKey — now sourced from UserFinderAccount.userFinderPubkeyHex — so upstream's per-account attribution + ExplainedFilter/SubPurpose tags are preserved unchanged. - cache: LocalCache -> ICacheProvider; FilterUserMetadataForKey + pickRelaysToLoadUsers take an injected relayHints: HintIndexer instead of the static LocalCache.relayHints. - UserOutboxFinderSubAssembler inlines the relay-tier union over the UserFinderAccount getters (behaviour-preserving vs the old Account-based outer overload). Android unchanged: UserFinderShims.kt typealiases keep call sites (incl. reqCommand/event EventFinder*) compiling; UserFinderFilterAssemblerSubscription + UserObservers stay in amethyst (Account is-a UserFinderAccount). New commons UserFinderSubscription (LocalUserFinder/LocalUserFinderAccount + overloads) and UserMetadataObservers (observeUser*) added for Desktop. amethyst FilterFindFollowMetadataForKey's outer overload now delegates to the commons inner one. Green: commons JVM + iOS purity (verifyKmpPurity), :amethyst compilePlayDebugKotlin, :desktopApp compile, spotless. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
1511a8018b
commit
983fc1aab2
+2
-119
@@ -23,13 +23,13 @@ package com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.follow
|
||||
import com.vitorpamplona.amethyst.commons.defaults.Constants
|
||||
import com.vitorpamplona.amethyst.commons.defaults.DefaultIndexerRelayList
|
||||
import com.vitorpamplona.amethyst.commons.defaults.DefaultSearchRelayList
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.user.pickRelaysToLoadUsers
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.relays.EOSEAccountFast
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.utils.mapOfSet
|
||||
|
||||
fun pickRelaysToLoadUsers(
|
||||
users: Set<User>,
|
||||
@@ -68,6 +68,7 @@ fun pickRelaysToLoadUsers(
|
||||
|
||||
return pickRelaysToLoadUsers(
|
||||
users,
|
||||
LocalCache.relayHints,
|
||||
indexRelays - cannotConnectRelays,
|
||||
homeRelays - cannotConnectRelays,
|
||||
searchRelays - cannotConnectRelays,
|
||||
@@ -77,121 +78,3 @@ fun pickRelaysToLoadUsers(
|
||||
hasTried,
|
||||
)
|
||||
}
|
||||
|
||||
fun pickRelaysToLoadUsers(
|
||||
users: Set<User>,
|
||||
indexRelays: Set<NormalizedRelayUrl>,
|
||||
homeRelays: Set<NormalizedRelayUrl>,
|
||||
searchRelays: Set<NormalizedRelayUrl>,
|
||||
connected: Set<NormalizedRelayUrl>,
|
||||
commonRelays: Set<NormalizedRelayUrl>,
|
||||
cannotConnectRelays: Set<NormalizedRelayUrl>,
|
||||
hasTried: EOSEAccountFast<User>,
|
||||
): Map<NormalizedRelayUrl, Set<HexKey>> =
|
||||
mapOfSet {
|
||||
users.forEachIndexed { _, key ->
|
||||
val tried = (hasTried.since(key)?.keys ?: emptySet()) + cannotConnectRelays
|
||||
|
||||
val outbox = key.authorRelayList()?.writeRelaysNorm()
|
||||
|
||||
if (!outbox.isNullOrEmpty()) {
|
||||
// If there is a home, get from it.
|
||||
|
||||
// if it tried all outbox relays, stop.
|
||||
// the UserWatch will take over from here.
|
||||
val leftToTry = (outbox - tried)
|
||||
leftToTry.forEach {
|
||||
add(it, key.pubkeyHex)
|
||||
}
|
||||
} else {
|
||||
// if not, tries hints first.
|
||||
val hints = key.allUsedRelays() + LocalCache.relayHints.hintsForKey(key.pubkeyHex)
|
||||
|
||||
val leftToTryOnHints = hints - tried
|
||||
|
||||
leftToTryOnHints.forEach {
|
||||
add(it, key.pubkeyHex)
|
||||
}
|
||||
|
||||
// if there are only a few hints, broadens the search
|
||||
if (leftToTryOnHints.size < 3) {
|
||||
// This creates a pre-deterministic order of the array such that
|
||||
// if this function is called twice, it returns the same arrays
|
||||
// which gets ignored by the relay client if we send it twice
|
||||
val indexRelaysLeftToTry =
|
||||
(indexRelays - tried).sortedBy { relay ->
|
||||
key.pubkeyHex.hashCode() xor relay.url.hashCode()
|
||||
}
|
||||
// This creates a pre-deterministic order of the array such that
|
||||
// if this function is called twice, it returns the same arrays
|
||||
// which gets ignored by the relay client if we send it twice
|
||||
val homeRelaysLeftToTry =
|
||||
(homeRelays - tried).sortedBy { relay ->
|
||||
key.pubkeyHex.hashCode() xor relay.url.hashCode()
|
||||
}
|
||||
|
||||
// picks one at random to avoid overloading these relays
|
||||
if (users.size > 300) {
|
||||
if (indexRelaysLeftToTry.size >= 2) {
|
||||
add(indexRelaysLeftToTry[0], key.pubkeyHex)
|
||||
add(indexRelaysLeftToTry[1], key.pubkeyHex)
|
||||
} else if (indexRelaysLeftToTry.size == 1) {
|
||||
add(indexRelaysLeftToTry.first(), key.pubkeyHex)
|
||||
}
|
||||
|
||||
homeRelaysLeftToTry.forEach {
|
||||
add(it, key.pubkeyHex)
|
||||
}
|
||||
} else {
|
||||
indexRelaysLeftToTry.forEach {
|
||||
add(it, key.pubkeyHex)
|
||||
}
|
||||
|
||||
homeRelaysLeftToTry.forEach {
|
||||
add(it, key.pubkeyHex)
|
||||
}
|
||||
}
|
||||
|
||||
if (indexRelaysLeftToTry.size < 2) {
|
||||
val searchRelaysLeftToTry = searchRelays - tried
|
||||
|
||||
searchRelaysLeftToTry.forEach {
|
||||
add(it, key.pubkeyHex)
|
||||
}
|
||||
|
||||
val connectedRelaysLeftToTry =
|
||||
(connected - tried)
|
||||
.sortedBy { relay ->
|
||||
key.pubkeyHex.hashCode() xor relay.url.hashCode()
|
||||
}.take(100)
|
||||
|
||||
// picks one at random to avoid overloading these relays
|
||||
if (users.size > 300) {
|
||||
connectedRelaysLeftToTry.take(20).forEach {
|
||||
add(it, key.pubkeyHex)
|
||||
}
|
||||
} else {
|
||||
connectedRelaysLeftToTry.forEach {
|
||||
add(it, key.pubkeyHex)
|
||||
}
|
||||
}
|
||||
|
||||
if (searchRelaysLeftToTry.size < 2) {
|
||||
// This creates a pre-deterministic order of the array such that
|
||||
// if this function is called twice, it returns the same arrays
|
||||
// which gets ignored by the relay client if we send it twice
|
||||
val allRelaysLeftToTry =
|
||||
(commonRelays - tried)
|
||||
.sortedBy { relay ->
|
||||
key.pubkeyHex.hashCode() xor relay.url.hashCode()
|
||||
}.take(100)
|
||||
|
||||
allRelaysLeftToTry.forEach {
|
||||
add(it, key.pubkeyHex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.service.relayClient.reqCommand.user
|
||||
|
||||
/**
|
||||
* Back-compat aliases: the per-user metadata finder moved to commons
|
||||
* (`com.vitorpamplona.amethyst.commons.relayClient.user`). Existing Android call
|
||||
* sites that reference these by their old names resolve here.
|
||||
*/
|
||||
typealias UserFinderFilterAssembler = com.vitorpamplona.amethyst.commons.relayClient.user.UserFinderFilterAssembler
|
||||
|
||||
typealias UserFinderQueryState = com.vitorpamplona.amethyst.commons.relayClient.user.UserFinderQueryState
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* 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.commons.relayClient.user
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.model.User
|
||||
import com.vitorpamplona.amethyst.commons.relays.EOSEAccountFast
|
||||
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.utils.mapOfSet
|
||||
|
||||
fun pickRelaysToLoadUsers(
|
||||
users: Set<User>,
|
||||
relayHints: HintIndexer,
|
||||
indexRelays: Set<NormalizedRelayUrl>,
|
||||
homeRelays: Set<NormalizedRelayUrl>,
|
||||
searchRelays: Set<NormalizedRelayUrl>,
|
||||
connected: Set<NormalizedRelayUrl>,
|
||||
commonRelays: Set<NormalizedRelayUrl>,
|
||||
cannotConnectRelays: Set<NormalizedRelayUrl>,
|
||||
hasTried: EOSEAccountFast<User>,
|
||||
): Map<NormalizedRelayUrl, Set<HexKey>> =
|
||||
mapOfSet {
|
||||
users.forEachIndexed { _, key ->
|
||||
val tried = (hasTried.since(key)?.keys ?: emptySet()) + cannotConnectRelays
|
||||
|
||||
val outbox = key.authorRelayList()?.writeRelaysNorm()
|
||||
|
||||
if (!outbox.isNullOrEmpty()) {
|
||||
// If there is a home, get from it.
|
||||
|
||||
// if it tried all outbox relays, stop.
|
||||
// the UserWatch will take over from here.
|
||||
val leftToTry = (outbox - tried)
|
||||
leftToTry.forEach {
|
||||
add(it, key.pubkeyHex)
|
||||
}
|
||||
} else {
|
||||
// if not, tries hints first.
|
||||
val hints = key.allUsedRelays() + relayHints.hintsForKey(key.pubkeyHex)
|
||||
|
||||
val leftToTryOnHints = hints - tried
|
||||
|
||||
leftToTryOnHints.forEach {
|
||||
add(it, key.pubkeyHex)
|
||||
}
|
||||
|
||||
// if there are only a few hints, broadens the search
|
||||
if (leftToTryOnHints.size < 3) {
|
||||
// This creates a pre-deterministic order of the array such that
|
||||
// if this function is called twice, it returns the same arrays
|
||||
// which gets ignored by the relay client if we send it twice
|
||||
val indexRelaysLeftToTry =
|
||||
(indexRelays - tried).sortedBy { relay ->
|
||||
key.pubkeyHex.hashCode() xor relay.url.hashCode()
|
||||
}
|
||||
// This creates a pre-deterministic order of the array such that
|
||||
// if this function is called twice, it returns the same arrays
|
||||
// which gets ignored by the relay client if we send it twice
|
||||
val homeRelaysLeftToTry =
|
||||
(homeRelays - tried).sortedBy { relay ->
|
||||
key.pubkeyHex.hashCode() xor relay.url.hashCode()
|
||||
}
|
||||
|
||||
// picks one at random to avoid overloading these relays
|
||||
if (users.size > 300) {
|
||||
if (indexRelaysLeftToTry.size >= 2) {
|
||||
add(indexRelaysLeftToTry[0], key.pubkeyHex)
|
||||
add(indexRelaysLeftToTry[1], key.pubkeyHex)
|
||||
} else if (indexRelaysLeftToTry.size == 1) {
|
||||
add(indexRelaysLeftToTry.first(), key.pubkeyHex)
|
||||
}
|
||||
|
||||
homeRelaysLeftToTry.forEach {
|
||||
add(it, key.pubkeyHex)
|
||||
}
|
||||
} else {
|
||||
indexRelaysLeftToTry.forEach {
|
||||
add(it, key.pubkeyHex)
|
||||
}
|
||||
|
||||
homeRelaysLeftToTry.forEach {
|
||||
add(it, key.pubkeyHex)
|
||||
}
|
||||
}
|
||||
|
||||
if (indexRelaysLeftToTry.size < 2) {
|
||||
val searchRelaysLeftToTry = searchRelays - tried
|
||||
|
||||
searchRelaysLeftToTry.forEach {
|
||||
add(it, key.pubkeyHex)
|
||||
}
|
||||
|
||||
val connectedRelaysLeftToTry =
|
||||
(connected - tried)
|
||||
.sortedBy { relay ->
|
||||
key.pubkeyHex.hashCode() xor relay.url.hashCode()
|
||||
}.take(100)
|
||||
|
||||
// picks one at random to avoid overloading these relays
|
||||
if (users.size > 300) {
|
||||
connectedRelaysLeftToTry.take(20).forEach {
|
||||
add(it, key.pubkeyHex)
|
||||
}
|
||||
} else {
|
||||
connectedRelaysLeftToTry.forEach {
|
||||
add(it, key.pubkeyHex)
|
||||
}
|
||||
}
|
||||
|
||||
if (searchRelaysLeftToTry.size < 2) {
|
||||
// This creates a pre-deterministic order of the array such that
|
||||
// if this function is called twice, it returns the same arrays
|
||||
// which gets ignored by the relay client if we send it twice
|
||||
val allRelaysLeftToTry =
|
||||
(commonRelays - tried)
|
||||
.sortedBy { relay ->
|
||||
key.pubkeyHex.hashCode() xor relay.url.hashCode()
|
||||
}.take(100)
|
||||
|
||||
allRelaysLeftToTry.forEach {
|
||||
add(it, key.pubkeyHex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
-12
@@ -18,18 +18,16 @@
|
||||
* 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.service.relayClient.reqCommand.user
|
||||
package com.vitorpamplona.amethyst.commons.relayClient.user
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.vitorpamplona.amethyst.commons.model.User
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.ICacheProvider
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.relayClient.AccountScopedQuery
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.loaders.UserOutboxFinderSubAssembler
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.watchers.UserCardsSubAssembler
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.watchers.UserReportsSubAssembler
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.watchers.UserWatcherSubAssembler
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.user.loaders.UserOutboxFinderSubAssembler
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.user.watchers.UserCardsSubAssembler
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.user.watchers.UserReportsSubAssembler
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.user.watchers.UserWatcherSubAssembler
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.accessories.RelayOfflineTracker
|
||||
|
||||
@@ -37,13 +35,13 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.accessories.RelayOfflineT
|
||||
@Stable
|
||||
class UserFinderQueryState(
|
||||
val user: User,
|
||||
override val account: Account,
|
||||
) : AccountScopedQuery
|
||||
val account: UserFinderAccount,
|
||||
)
|
||||
|
||||
@Stable
|
||||
class UserFinderFilterAssembler(
|
||||
client: INostrClient,
|
||||
cache: LocalCache,
|
||||
cache: ICacheProvider,
|
||||
failureTracker: RelayOfflineTracker,
|
||||
) : ComposeSubscriptionManager<UserFinderQueryState>() {
|
||||
val group =
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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.commons.relayClient.user
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import com.vitorpamplona.amethyst.commons.model.User
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.LifecycleAwareKeyDataSourceSubscription
|
||||
|
||||
/**
|
||||
* The shared per-user metadata data source for the current front end. A front
|
||||
* end provides this once near its composition root (Android via AppModules,
|
||||
* Desktop via its subscriptions coordinator). Reading it without a provider is
|
||||
* a programming error — the `observeUser*` composables must never be reachable
|
||||
* from a composition that has no relay client (e.g. the Android `:napplet`
|
||||
* sandbox process).
|
||||
*/
|
||||
val LocalUserFinder =
|
||||
staticCompositionLocalOf<UserFinderFilterAssembler> {
|
||||
error("LocalUserFinder not provided")
|
||||
}
|
||||
|
||||
/**
|
||||
* The current logged-in account, in the narrow [UserFinderAccount] view the
|
||||
* finder needs to route REQs. Provided alongside [LocalUserFinder].
|
||||
*/
|
||||
val LocalUserFinderAccount =
|
||||
staticCompositionLocalOf<UserFinderAccount> {
|
||||
error("LocalUserFinderAccount not provided")
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribes to relay updates for [user]'s metadata (and relay list / reports /
|
||||
* contact cards) for as long as this composable is in composition, coalesced
|
||||
* with every other on-screen user into batched REQs by [dataSource].
|
||||
*
|
||||
* Because a `LazyColumn` composes only the visible window (+ a small prefetch
|
||||
* buffer), this naturally means "load metadata only for users currently on
|
||||
* screen" — the [LifecycleAwareKeyDataSourceSubscription] unsubscribes ~30s
|
||||
* after the row leaves composition or the app is backgrounded.
|
||||
*/
|
||||
@Composable
|
||||
fun UserFinderFilterAssemblerSubscription(
|
||||
user: User,
|
||||
account: UserFinderAccount,
|
||||
dataSource: UserFinderFilterAssembler,
|
||||
) {
|
||||
// Different screens get their own query-state instance even when tracking
|
||||
// the same user; the assembler dedups to one REQ per pubkey.
|
||||
val state = remember(user, account) { UserFinderQueryState(user, account) }
|
||||
|
||||
LifecycleAwareKeyDataSourceSubscription(state, dataSource)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience overload that reads the front end's [LocalUserFinder] and
|
||||
* [LocalUserFinderAccount] from the composition.
|
||||
*/
|
||||
@Composable
|
||||
fun UserFinderFilterAssemblerSubscription(user: User) {
|
||||
UserFinderFilterAssemblerSubscription(
|
||||
user = user,
|
||||
account = LocalUserFinderAccount.current,
|
||||
dataSource = LocalUserFinder.current,
|
||||
)
|
||||
}
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* 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.commons.relayClient.user
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.commons.model.User
|
||||
import com.vitorpamplona.amethyst.commons.model.nip01Core.UserInfo
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
/**
|
||||
* Shared, platform-agnostic observers for a single user's metadata (kind 0).
|
||||
*
|
||||
* Each observer both (a) opens a composition-scoped relay subscription for the
|
||||
* user via [UserFinderFilterAssemblerSubscription] — so metadata is fetched only
|
||||
* while the user is on screen — and (b) collects the resulting cache flow so the
|
||||
* UI recomposes when the metadata arrives. The `(user)` overloads read the
|
||||
* front end's [LocalUserFinder] / [LocalUserFinderAccount]; the explicit-param
|
||||
* overloads are for callers that already hold both (and for tests).
|
||||
*
|
||||
* These are metadata-only. Richer per-user observers that depend on account
|
||||
* subsystems not yet in commons (contact-card petnames, follow counts,
|
||||
* bookmarks, statuses) remain in the Android layer for now and layer on top of
|
||||
* the same subscription.
|
||||
*/
|
||||
@Composable
|
||||
fun observeUserInfo(
|
||||
user: User,
|
||||
userFinder: UserFinderFilterAssembler,
|
||||
account: UserFinderAccount,
|
||||
): State<UserInfo?> {
|
||||
UserFinderFilterAssemblerSubscription(user, account, userFinder)
|
||||
return user.metadata().flow.collectAsStateWithLifecycle()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun observeUserInfo(user: User): State<UserInfo?> = observeUserInfo(user, LocalUserFinder.current, LocalUserFinderAccount.current)
|
||||
|
||||
@Composable
|
||||
fun observeUserPicture(
|
||||
user: User,
|
||||
userFinder: UserFinderFilterAssembler,
|
||||
account: UserFinderAccount,
|
||||
): State<String?> {
|
||||
UserFinderFilterAssemblerSubscription(user, account, userFinder)
|
||||
|
||||
val flow =
|
||||
remember(user) {
|
||||
user
|
||||
.metadata()
|
||||
.flow
|
||||
.map { it?.info?.picture }
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
return flow.collectAsStateWithLifecycle(
|
||||
user
|
||||
.metadataOrNull()
|
||||
?.flow
|
||||
?.value
|
||||
?.info
|
||||
?.picture,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun observeUserPicture(user: User): State<String?> = observeUserPicture(user, LocalUserFinder.current, LocalUserFinderAccount.current)
|
||||
|
||||
@Composable
|
||||
fun observeUserBanner(
|
||||
user: User,
|
||||
userFinder: UserFinderFilterAssembler,
|
||||
account: UserFinderAccount,
|
||||
): State<String?> {
|
||||
UserFinderFilterAssemblerSubscription(user, account, userFinder)
|
||||
|
||||
val flow =
|
||||
remember(user) {
|
||||
user
|
||||
.metadata()
|
||||
.flow
|
||||
.map { it?.info?.banner }
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
return flow.collectAsStateWithLifecycle(
|
||||
user
|
||||
.metadataOrNull()
|
||||
?.flow
|
||||
?.value
|
||||
?.info
|
||||
?.banner,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun observeUserBanner(user: User): State<String?> = observeUserBanner(user, LocalUserFinder.current, LocalUserFinderAccount.current)
|
||||
|
||||
@Composable
|
||||
fun observeUserAboutMe(
|
||||
user: User,
|
||||
userFinder: UserFinderFilterAssembler,
|
||||
account: UserFinderAccount,
|
||||
): State<String> {
|
||||
UserFinderFilterAssemblerSubscription(user, account, userFinder)
|
||||
|
||||
val flow =
|
||||
remember(user) {
|
||||
user
|
||||
.metadata()
|
||||
.flow
|
||||
.map { it?.info?.about ?: "" }
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
return flow.collectAsStateWithLifecycle(
|
||||
user
|
||||
.metadataOrNull()
|
||||
?.flow
|
||||
?.value
|
||||
?.info
|
||||
?.about ?: "",
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun observeUserAboutMe(user: User): State<String> = observeUserAboutMe(user, LocalUserFinder.current, LocalUserFinderAccount.current)
|
||||
|
||||
/**
|
||||
* The user's best available display name from their own metadata (kind 0),
|
||||
* falling back to a truncated pubkey. Metadata-only: it does NOT apply the
|
||||
* viewing account's private contact-card petname (that stays in the Android
|
||||
* layer, which wraps this).
|
||||
*/
|
||||
@Composable
|
||||
fun observeUserName(
|
||||
user: User,
|
||||
userFinder: UserFinderFilterAssembler,
|
||||
account: UserFinderAccount,
|
||||
): State<String> {
|
||||
UserFinderFilterAssemblerSubscription(user, account, userFinder)
|
||||
|
||||
val flow =
|
||||
remember(user) {
|
||||
user
|
||||
.metadata()
|
||||
.flow
|
||||
.map { it?.info?.bestName() ?: user.toBestDisplayName() }
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
return flow.collectAsStateWithLifecycle(user.toBestDisplayName())
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun observeUserName(user: User): State<String> = observeUserName(user, LocalUserFinder.current, LocalUserFinderAccount.current)
|
||||
+30
-12
@@ -18,18 +18,18 @@
|
||||
* 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.service.relayClient.reqCommand.user.loaders
|
||||
package com.vitorpamplona.amethyst.commons.relayClient.user.loaders
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.defaults.DefaultIndexerRelayList
|
||||
import com.vitorpamplona.amethyst.commons.defaults.DefaultSearchRelayList
|
||||
import com.vitorpamplona.amethyst.commons.model.User
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.ICacheProvider
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.eoseManagers.BaseEoseManager
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.ExplainedFilter
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.SubPurpose
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.follows.pickRelaysToLoadUsers
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.UserFinderQueryState
|
||||
import com.vitorpamplona.amethyst.service.relays.EOSEAccountFast
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.user.UserFinderQueryState
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.user.pickRelaysToLoadUsers
|
||||
import com.vitorpamplona.amethyst.commons.relays.EOSEAccountFast
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
||||
@@ -44,7 +44,7 @@ import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
class UserOutboxFinderSubAssembler(
|
||||
client: INostrClient,
|
||||
val cache: LocalCache,
|
||||
val cache: ICacheProvider,
|
||||
val failureTracker: RelayOfflineTracker,
|
||||
allKeys: () -> Set<UserFinderQueryState>,
|
||||
) : BaseEoseManager<UserFinderQueryState>(client, allKeys) {
|
||||
@@ -108,19 +108,37 @@ class UserOutboxFinderSubAssembler(
|
||||
// and the users being resolved are whoever is on screen rather than anyone's follow list — so
|
||||
// with several accounts active there is no single honest owner for a given filter, and
|
||||
// splitting the sweep per account would re-issue the same lookups once per account.
|
||||
// Deduped by pubkey, not by `Account`: that class uses identity equality, so two objects for
|
||||
// the same logged-in user would look like two accounts and suppress attribution entirely.
|
||||
// Deduped by pubkey, not by account identity: two objects for the same logged-in user would
|
||||
// look like two accounts and suppress attribution entirely.
|
||||
val soleAccountPubKey =
|
||||
accounts
|
||||
.mapTo(mutableSetOf()) { it.userProfile().pubkeyHex }
|
||||
.mapTo(mutableSetOf()) { it.userFinderPubkeyHex }
|
||||
.singleOrNull()
|
||||
|
||||
// Union of every asking account's relay tiers. The UserFinderAccount getters already apply the
|
||||
// platform default fallbacks (index/search), matching the prior outer pickRelaysToLoadUsers.
|
||||
val cannotConnect = failureTracker.cannotConnectRelays
|
||||
val indexRelays = mutableSetOf<NormalizedRelayUrl>()
|
||||
val homeRelays = mutableSetOf<NormalizedRelayUrl>()
|
||||
val searchRelays = mutableSetOf<NormalizedRelayUrl>()
|
||||
val commonRelays = mutableSetOf<NormalizedRelayUrl>()
|
||||
accounts.forEach { account ->
|
||||
indexRelays.addAll(account.indexRelays())
|
||||
homeRelays.addAll(account.outboxHomeRelays())
|
||||
searchRelays.addAll(account.searchRelays())
|
||||
commonRelays.addAll(account.commonRelays())
|
||||
}
|
||||
|
||||
val perRelayKeysBoth =
|
||||
pickRelaysToLoadUsers(
|
||||
noOutboxList,
|
||||
accounts,
|
||||
cache.relayHints,
|
||||
indexRelays - cannotConnect,
|
||||
homeRelays - cannotConnect,
|
||||
searchRelays - cannotConnect,
|
||||
connectedRelays,
|
||||
failureTracker.cannotConnectRelays,
|
||||
commonRelays - cannotConnect,
|
||||
cannotConnect,
|
||||
hasTried,
|
||||
)
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
* 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.service.relayClient.reqCommand.user.watchers
|
||||
package com.vitorpamplona.amethyst.commons.relayClient.user.watchers
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.ExplainedFilter
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.SubPurpose
|
||||
+6
-5
@@ -18,16 +18,16 @@
|
||||
* 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.service.relayClient.reqCommand.user.watchers
|
||||
package com.vitorpamplona.amethyst.commons.relayClient.user.watchers
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.model.User
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.ExplainedFilter
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.SubPurpose
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.relays.EOSEAccountFast
|
||||
import com.vitorpamplona.amethyst.commons.relays.EOSEAccountFast
|
||||
import com.vitorpamplona.quartz.experimental.nipA3.PaymentTargetsEvent
|
||||
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.KeyPackageRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.hints.HintIndexer
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
@@ -60,6 +60,7 @@ val UserMetadataForKeyKinds =
|
||||
|
||||
fun filterUserMetadataForKey(
|
||||
authors: Set<User>,
|
||||
relayHints: HintIndexer,
|
||||
indexRelays: Set<NormalizedRelayUrl>,
|
||||
cannotConnectRelays: Set<NormalizedRelayUrl>,
|
||||
since: EOSEAccountFast<User>,
|
||||
@@ -72,7 +73,7 @@ fun filterUserMetadataForKey(
|
||||
val relays =
|
||||
when {
|
||||
outbox == null ->
|
||||
key.allUsedRelays() + LocalCache.relayHints.hintsForKey(key.pubkeyHex) + indexRelays
|
||||
key.allUsedRelays() + relayHints.hintsForKey(key.pubkeyHex) + indexRelays
|
||||
// Outbox is published but exhausted (every relay either EOSE'd
|
||||
// or is known-unreachable) and metadata is still missing —
|
||||
// widen to indexers so a misconfigured outbox doesn't strand
|
||||
+12
-12
@@ -18,16 +18,16 @@
|
||||
* 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.service.relayClient.reqCommand.user.watchers
|
||||
package com.vitorpamplona.amethyst.commons.relayClient.user.watchers
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.model.User
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.ICacheProvider
|
||||
import com.vitorpamplona.amethyst.commons.model.toHexSet
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.assemblers.filterContactCardsToTargetKeysFromTrustedAccountsInTheRelay
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.eoseManagers.SingleSubEoseManager
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.UserFinderQueryState
|
||||
import com.vitorpamplona.amethyst.service.relays.MutableTime
|
||||
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.user.UserFinderQueryState
|
||||
import com.vitorpamplona.amethyst.commons.relays.MutableTime
|
||||
import com.vitorpamplona.amethyst.commons.relays.SincePerRelayMap
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||
@@ -38,7 +38,7 @@ import com.vitorpamplona.quartz.utils.mapOfSet
|
||||
|
||||
class UserCardsSubAssembler(
|
||||
client: INostrClient,
|
||||
val cache: LocalCache,
|
||||
val cache: ICacheProvider,
|
||||
allKeys: () -> Set<UserFinderQueryState>,
|
||||
) : SingleSubEoseManager<UserFinderQueryState>(client, allKeys) {
|
||||
override fun newEose(
|
||||
@@ -74,22 +74,22 @@ class UserCardsSubAssembler(
|
||||
// accounts, so with several active none of them owns a given filter.
|
||||
val soleAccountPubKey =
|
||||
accounts
|
||||
.mapTo(mutableSetOf()) { it.userProfile().pubkeyHex }
|
||||
.mapTo(mutableSetOf()) { it.userFinderPubkeyHex }
|
||||
.singleOrNull()
|
||||
|
||||
val trustedAccounts: Map<NormalizedRelayUrl, Set<HexKey>> =
|
||||
mapOfSet {
|
||||
accounts.forEach { account ->
|
||||
account.homeRelays.flow.value.forEach {
|
||||
add(it, account.userProfile().pubkeyHex)
|
||||
account.cardHomeRelays().forEach {
|
||||
add(it, account.userFinderPubkeyHex)
|
||||
}
|
||||
}
|
||||
accounts.map { it.trustProviderList.liveUserRankProvider.value }.forEach { provider ->
|
||||
accounts.map { it.trustProvider() }.forEach { provider ->
|
||||
if (provider != null) {
|
||||
add(provider.relayUrl, provider.pubkey)
|
||||
}
|
||||
}
|
||||
accounts.map { it.trustProviderList.liveUserFollowerCount.value }.forEach { provider ->
|
||||
accounts.map { it.followerCountProvider() }.forEach { provider ->
|
||||
if (provider != null) {
|
||||
add(provider.relayUrl, provider.pubkey)
|
||||
}
|
||||
+12
-11
@@ -18,16 +18,16 @@
|
||||
* 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.service.relayClient.reqCommand.user.watchers
|
||||
package com.vitorpamplona.amethyst.commons.relayClient.user.watchers
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.model.User
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.ICacheProvider
|
||||
import com.vitorpamplona.amethyst.commons.model.toHexSet
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.eoseManagers.SingleSubEoseManager
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.UserFinderQueryState
|
||||
import com.vitorpamplona.amethyst.service.relays.MutableTime
|
||||
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.user.UserFinderAccount
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.user.UserFinderQueryState
|
||||
import com.vitorpamplona.amethyst.commons.relays.MutableTime
|
||||
import com.vitorpamplona.amethyst.commons.relays.SincePerRelayMap
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
@@ -35,7 +35,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
|
||||
class UserReportsSubAssembler(
|
||||
client: INostrClient,
|
||||
val cache: LocalCache,
|
||||
val cache: ICacheProvider,
|
||||
allKeys: () -> Set<UserFinderQueryState>,
|
||||
) : SingleSubEoseManager<UserFinderQueryState>(client, allKeys) {
|
||||
override fun newEose(
|
||||
@@ -72,12 +72,13 @@ class UserReportsSubAssembler(
|
||||
}
|
||||
|
||||
private fun filtersFor(
|
||||
account: Account,
|
||||
account: UserFinderAccount,
|
||||
lastUsersOnFilter: Set<User>,
|
||||
): List<RelayBasedFilter> {
|
||||
val accountPubKey = account.userProfile().pubkeyHex
|
||||
val accountPubKey = account.userFinderPubkeyHex
|
||||
|
||||
return account.declaredFollowsPerOutboxRelay.value
|
||||
return account
|
||||
.declaredFollowsByOutboxRelay()
|
||||
.flatMap { (relay, trustedUsersInThisRelay) ->
|
||||
// this relay + accounts are where we could find reports.
|
||||
// we might have already loaded them, so let's separate new targets that were checked before from the others
|
||||
+9
-12
@@ -18,14 +18,13 @@
|
||||
* 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.service.relayClient.reqCommand.user.watchers
|
||||
package com.vitorpamplona.amethyst.commons.relayClient.user.watchers
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.defaults.DefaultIndexerRelayList
|
||||
import com.vitorpamplona.amethyst.commons.model.User
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.ICacheProvider
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.eoseManagers.BaseEoseManager
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.UserFinderQueryState
|
||||
import com.vitorpamplona.amethyst.service.relays.EOSEAccountFast
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.user.UserFinderQueryState
|
||||
import com.vitorpamplona.amethyst.commons.relays.EOSEAccountFast
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.accessories.RelayOfflineTracker
|
||||
@@ -37,7 +36,7 @@ import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
class UserWatcherSubAssembler(
|
||||
client: INostrClient,
|
||||
val cache: LocalCache,
|
||||
val cache: ICacheProvider,
|
||||
val failureTracker: RelayOfflineTracker,
|
||||
allKeys: () -> Set<UserFinderQueryState>,
|
||||
) : BaseEoseManager<UserFinderQueryState>(client, allKeys) {
|
||||
@@ -100,20 +99,18 @@ class UserWatcherSubAssembler(
|
||||
// account and the users are whoever is on screen, so with several askers none of them owns it.
|
||||
val soleAccountPubKey =
|
||||
keys
|
||||
.mapTo(mutableSetOf()) { it.account.userProfile().pubkeyHex }
|
||||
.mapTo(mutableSetOf()) { it.account.userFinderPubkeyHex }
|
||||
.singleOrNull()
|
||||
|
||||
val indexRelays = mutableSetOf<NormalizedRelayUrl>()
|
||||
keys.mapTo(mutableSetOf()) { it.account }.forEach {
|
||||
indexRelays.addAll(
|
||||
it.indexerRelayList.flow.value
|
||||
.ifEmpty { DefaultIndexerRelayList },
|
||||
)
|
||||
indexRelays.addAll(it.indexRelays())
|
||||
}
|
||||
|
||||
val newFilters =
|
||||
filterUserMetadataForKey(
|
||||
users,
|
||||
cache.relayHints,
|
||||
indexRelays,
|
||||
failureTracker.cannotConnectRelays,
|
||||
latestEOSEs,
|
||||
Reference in New Issue
Block a user