Makes sure Filters are not updated if the since is the only change
This commit is contained in:
@@ -97,7 +97,6 @@ import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
|||||||
import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle
|
import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
|
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.acessories.downloadFirstEvent
|
import com.vitorpamplona.quartz.nip01Core.relay.client.acessories.downloadFirstEvent
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
|
import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
@@ -953,9 +952,8 @@ class Account(
|
|||||||
it.host?.let { host ->
|
it.host?.let { host ->
|
||||||
client.downloadFirstEvent(
|
client.downloadFirstEvent(
|
||||||
filters =
|
filters =
|
||||||
note.relays.map { relay ->
|
note.relays.associateWith { relay ->
|
||||||
RelayBasedFilter(
|
listOf(
|
||||||
relay,
|
|
||||||
Filter(
|
Filter(
|
||||||
ids = listOf(host.id),
|
ids = listOf(host.id),
|
||||||
),
|
),
|
||||||
|
|||||||
+5
-5
@@ -96,6 +96,7 @@ class RegisterAccounts(
|
|||||||
val readyToSend =
|
val readyToSend =
|
||||||
accounts
|
accounts
|
||||||
.mapNotNull {
|
.mapNotNull {
|
||||||
|
if (it.hasPrivKey || it.loggedInWithExternalSigner) {
|
||||||
Log.d(tag, "Register Account ${it.npub}")
|
Log.d(tag, "Register Account ${it.npub}")
|
||||||
|
|
||||||
val acc = LocalPreferences.loadCurrentAccountFromEncryptedStorage(it.npub)
|
val acc = LocalPreferences.loadCurrentAccountFromEncryptedStorage(it.npub)
|
||||||
@@ -108,11 +109,7 @@ class RegisterAccounts(
|
|||||||
|
|
||||||
Log.d(tag, "Register Account ${it.npub} NIP17 Reads ${nip17Read.joinToString(", ") { it.url } }")
|
Log.d(tag, "Register Account ${it.npub} NIP17 Reads ${nip17Read.joinToString(", ") { it.url } }")
|
||||||
|
|
||||||
val readKind3Relays = acc.backupContactList?.relays()?.mapNotNull { if (it.value.read) it.key else null } ?: emptyList()
|
val relays = (nip65Read + nip17Read)
|
||||||
|
|
||||||
Log.d(tag, "Register Account ${it.npub} Kind3 Reads ${readKind3Relays.joinToString(", ") { it.url } }")
|
|
||||||
|
|
||||||
val relays = (nip65Read + nip17Read + readKind3Relays)
|
|
||||||
|
|
||||||
if (relays.isNotEmpty()) {
|
if (relays.isNotEmpty()) {
|
||||||
Pair(acc, relays)
|
Pair(acc, relays)
|
||||||
@@ -122,6 +119,9 @@ class RegisterAccounts(
|
|||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val listOfAuthEvents = mutableListOf<RelayAuthEvent>()
|
val listOfAuthEvents = mutableListOf<RelayAuthEvent>()
|
||||||
|
|||||||
-10
@@ -34,14 +34,8 @@ abstract class ComposeSubscriptionManager<T> : ComposeSubscriptionManagerControl
|
|||||||
fun subscribe(query: T?) {
|
fun subscribe(query: T?) {
|
||||||
if (query == null) return
|
if (query == null) return
|
||||||
|
|
||||||
val wasEmpty = composeSubscriptions.isEmpty()
|
|
||||||
|
|
||||||
composeSubscriptions.put(query, query)
|
composeSubscriptions.put(query, query)
|
||||||
|
|
||||||
if (wasEmpty) {
|
|
||||||
start()
|
|
||||||
}
|
|
||||||
|
|
||||||
invalidateKeys()
|
invalidateKeys()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,10 +46,6 @@ abstract class ComposeSubscriptionManager<T> : ComposeSubscriptionManagerControl
|
|||||||
composeSubscriptions.remove(query)
|
composeSubscriptions.remove(query)
|
||||||
|
|
||||||
invalidateKeys()
|
invalidateKeys()
|
||||||
|
|
||||||
if (composeSubscriptions.isEmpty()) {
|
|
||||||
stop()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun allKeys() = composeSubscriptions.keys
|
fun allKeys() = composeSubscriptions.keys
|
||||||
|
|||||||
-4
@@ -21,10 +21,6 @@
|
|||||||
package com.vitorpamplona.amethyst.service.relayClient.composeSubscriptionManagers
|
package com.vitorpamplona.amethyst.service.relayClient.composeSubscriptionManagers
|
||||||
|
|
||||||
interface ComposeSubscriptionManagerControls {
|
interface ComposeSubscriptionManagerControls {
|
||||||
fun start()
|
|
||||||
|
|
||||||
fun stop()
|
|
||||||
|
|
||||||
fun invalidateKeys()
|
fun invalidateKeys()
|
||||||
|
|
||||||
fun invalidateFilters()
|
fun invalidateFilters()
|
||||||
|
|||||||
-10
@@ -45,8 +45,6 @@ abstract class MutableComposeSubscriptionManager<T : MutableQueryState>(
|
|||||||
fun subscribe(query: T?) {
|
fun subscribe(query: T?) {
|
||||||
if (query == null) return
|
if (query == null) return
|
||||||
|
|
||||||
val wasEmpty = composeSubscriptions.isEmpty()
|
|
||||||
|
|
||||||
composeSubscriptions[query]?.cancel()
|
composeSubscriptions[query]?.cancel()
|
||||||
composeSubscriptions[query] =
|
composeSubscriptions[query] =
|
||||||
scope.launch {
|
scope.launch {
|
||||||
@@ -55,10 +53,6 @@ abstract class MutableComposeSubscriptionManager<T : MutableQueryState>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wasEmpty) {
|
|
||||||
start()
|
|
||||||
}
|
|
||||||
|
|
||||||
invalidateKeys()
|
invalidateKeys()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,10 +64,6 @@ abstract class MutableComposeSubscriptionManager<T : MutableQueryState>(
|
|||||||
composeSubscriptions.remove(query)
|
composeSubscriptions.remove(query)
|
||||||
|
|
||||||
invalidateKeys()
|
invalidateKeys()
|
||||||
|
|
||||||
if (composeSubscriptions.isEmpty()) {
|
|
||||||
stop()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun allKeys() = composeSubscriptions.keys
|
fun allKeys() = composeSubscriptions.keys
|
||||||
|
|||||||
+13
-21
@@ -22,49 +22,41 @@ package com.vitorpamplona.amethyst.service.relayClient.eoseManagers
|
|||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.vitorpamplona.amethyst.isDebug
|
import com.vitorpamplona.amethyst.isDebug
|
||||||
|
import com.vitorpamplona.ammolite.relays.BundledUpdate
|
||||||
import com.vitorpamplona.ammolite.relays.datasources.SubscriptionController
|
import com.vitorpamplona.ammolite.relays.datasources.SubscriptionController
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
|
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
|
||||||
abstract class BaseEoseManager<T>(
|
abstract class BaseEoseManager<T>(
|
||||||
val client: NostrClient,
|
val client: NostrClient,
|
||||||
val allKeys: () -> Set<T>,
|
val allKeys: () -> Set<T>,
|
||||||
) {
|
) {
|
||||||
val orchestrator =
|
val orchestrator = SubscriptionController(client)
|
||||||
SubscriptionController(client) {
|
|
||||||
if (isDebug) Log.d("${this.javaClass.simpleName}", "Updating Subscriptions")
|
|
||||||
updateSubscriptions(allKeys())
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract fun updateSubscriptions(keys: Set<T>)
|
abstract fun updateSubscriptions(keys: Set<T>)
|
||||||
|
|
||||||
fun printStats() = orchestrator.printStats(this.javaClass.simpleName)
|
fun printStats() = orchestrator.printStats(this.javaClass.simpleName)
|
||||||
|
|
||||||
fun invalidateFilters() = orchestrator.invalidateFilters()
|
// Refreshes observers in batches.
|
||||||
|
private val bundler = BundledUpdate(300, Dispatchers.Default)
|
||||||
|
|
||||||
fun start() {
|
fun invalidateFilters() {
|
||||||
orchestrator.start()
|
bundler.invalidate {
|
||||||
if (isDebug) {
|
forceInvalidate()
|
||||||
Log.d("${this.javaClass.simpleName}", "Start")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stop() {
|
fun forceInvalidate() {
|
||||||
orchestrator.stop()
|
updateSubscriptions(allKeys())
|
||||||
if (isDebug) {
|
|
||||||
Log.d("${this.javaClass.simpleName}", "Stop")
|
orchestrator.updateRelays()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun destroy() {
|
fun destroy() {
|
||||||
|
bundler.cancel()
|
||||||
orchestrator.destroy()
|
orchestrator.destroy()
|
||||||
if (isDebug) {
|
if (isDebug) {
|
||||||
Log.d("${this.javaClass.simpleName}", "Destroy, Unsubscribe")
|
Log.d("${this.javaClass.simpleName}", "Destroy, Unsubscribe")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun init() {
|
|
||||||
if (isDebug) {
|
|
||||||
Log.d("${this.javaClass.simpleName}", "Init, Subscribe")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -25,6 +25,7 @@ import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
|||||||
import com.vitorpamplona.ammolite.relays.datasources.Subscription
|
import com.vitorpamplona.ammolite.relays.datasources.Subscription
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
|
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.groupByRelay
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
import kotlin.collections.distinctBy
|
import kotlin.collections.distinctBy
|
||||||
|
|
||||||
@@ -90,7 +91,8 @@ abstract class PerUniqueIdEoseManager<T>(
|
|||||||
|
|
||||||
uniqueSubscribedAccounts.forEach {
|
uniqueSubscribedAccounts.forEach {
|
||||||
val mainKey = id(it)
|
val mainKey = id(it)
|
||||||
findOrCreateSubFor(it).relayBasedFilters = updateFilter(it, since(it))?.ifEmpty { null }
|
val newFilters = updateFilter(it, since(it))?.ifEmpty { null }
|
||||||
|
findOrCreateSubFor(it).updateFilters(newFilters?.groupByRelay())
|
||||||
|
|
||||||
updated.add(mainKey)
|
updated.add(mainKey)
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -26,6 +26,7 @@ import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
|||||||
import com.vitorpamplona.ammolite.relays.datasources.Subscription
|
import com.vitorpamplona.ammolite.relays.datasources.Subscription
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
|
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.groupByRelay
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -89,7 +90,8 @@ abstract class PerUserAndFollowListEoseManager<T>(
|
|||||||
uniqueSubscribedAccounts.forEach {
|
uniqueSubscribedAccounts.forEach {
|
||||||
val user = user(it)
|
val user = user(it)
|
||||||
val sub = findOrCreateSubFor(it)
|
val sub = findOrCreateSubFor(it)
|
||||||
sub.relayBasedFilters = updateFilter(it, since(it))?.ifEmpty { null }
|
val newFilters = updateFilter(it, since(it))?.ifEmpty { null }
|
||||||
|
sub.updateFilters(newFilters?.groupByRelay())
|
||||||
updated.add(user)
|
updated.add(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-1
@@ -26,6 +26,7 @@ import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
|||||||
import com.vitorpamplona.ammolite.relays.datasources.Subscription
|
import com.vitorpamplona.ammolite.relays.datasources.Subscription
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
|
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.groupByRelay
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
import kotlin.collections.distinctBy
|
import kotlin.collections.distinctBy
|
||||||
|
|
||||||
@@ -87,7 +88,9 @@ abstract class PerUserEoseManager<T>(
|
|||||||
|
|
||||||
uniqueSubscribedAccounts.forEach {
|
uniqueSubscribedAccounts.forEach {
|
||||||
val user = user(it)
|
val user = user(it)
|
||||||
findOrCreateSubFor(it).relayBasedFilters = updateFilter(it, since(it))?.ifEmpty { null }
|
val newFilters = updateFilter(it, since(it))?.ifEmpty { null }
|
||||||
|
|
||||||
|
findOrCreateSubFor(it).updateFilters(newFilters?.groupByRelay())
|
||||||
|
|
||||||
updated.add(user)
|
updated.add(user)
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -24,6 +24,7 @@ import com.vitorpamplona.amethyst.service.relays.EOSERelayList
|
|||||||
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
|
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.groupByRelay
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
import kotlin.collections.distinctBy
|
import kotlin.collections.distinctBy
|
||||||
|
|
||||||
@@ -64,8 +65,9 @@ abstract class SingleSubEoseManager<T>(
|
|||||||
|
|
||||||
override fun updateSubscriptions(keys: Set<T>) {
|
override fun updateSubscriptions(keys: Set<T>) {
|
||||||
val uniqueSubscribedAccounts = keys.distinctBy { distinct(it) }
|
val uniqueSubscribedAccounts = keys.distinctBy { distinct(it) }
|
||||||
|
val newFilters = updateFilter(uniqueSubscribedAccounts, since())?.ifEmpty { null }
|
||||||
|
|
||||||
sub.relayBasedFilters = updateFilter(uniqueSubscribedAccounts, since())?.ifEmpty { null }
|
sub.updateFilters(newFilters?.groupByRelay())
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract fun updateFilter(
|
abstract fun updateFilter(
|
||||||
|
|||||||
+3
-2
@@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.service.relayClient.eoseManagers
|
|||||||
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
|
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.groupByRelay
|
||||||
import kotlin.collections.distinctBy
|
import kotlin.collections.distinctBy
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,8 +44,8 @@ abstract class SingleSubNoEoseCacheEoseManager<T>(
|
|||||||
|
|
||||||
override fun updateSubscriptions(keys: Set<T>) {
|
override fun updateSubscriptions(keys: Set<T>) {
|
||||||
val uniqueSubscribedAccounts = keys.distinctBy { distinct(it) }
|
val uniqueSubscribedAccounts = keys.distinctBy { distinct(it) }
|
||||||
|
val newFilters = updateFilter(uniqueSubscribedAccounts)?.ifEmpty { null }
|
||||||
sub.relayBasedFilters = updateFilter(uniqueSubscribedAccounts)?.ifEmpty { null }
|
sub.updateFilters(newFilters?.groupByRelay())
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract fun updateFilter(keys: List<T>): List<RelayBasedFilter>?
|
abstract fun updateFilter(keys: List<T>): List<RelayBasedFilter>?
|
||||||
|
|||||||
+1
-5
@@ -47,15 +47,11 @@ class AccountFilterAssembler(
|
|||||||
AccountNotificationsEoseManager(client, ::allKeys),
|
AccountNotificationsEoseManager(client, ::allKeys),
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun start() = group.forEach { it.start() }
|
|
||||||
|
|
||||||
override fun stop() = group.forEach { it.stop() }
|
|
||||||
|
|
||||||
override fun invalidateKeys() = invalidateFilters()
|
override fun invalidateKeys() = invalidateFilters()
|
||||||
|
|
||||||
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
||||||
|
|
||||||
override fun destroy() = group.forEach { it.start() }
|
override fun destroy() = group.forEach { it.destroy() }
|
||||||
|
|
||||||
override fun printStats() = group.forEach { it.printStats() }
|
override fun printStats() = group.forEach { it.printStats() }
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -38,7 +38,7 @@ class AccountMetadataEoseManager(
|
|||||||
client: NostrClient,
|
client: NostrClient,
|
||||||
allKeys: () -> Set<AccountQueryState>,
|
allKeys: () -> Set<AccountQueryState>,
|
||||||
) : PerUserEoseManager<AccountQueryState>(client, allKeys) {
|
) : PerUserEoseManager<AccountQueryState>(client, allKeys) {
|
||||||
override fun user(query: AccountQueryState) = query.account.userProfile()
|
override fun user(key: AccountQueryState) = key.account.userProfile()
|
||||||
|
|
||||||
fun relayFlow(query: AccountQueryState) = query.account.outboxRelays.flow
|
fun relayFlow(query: AccountQueryState) = query.account.outboxRelays.flow
|
||||||
|
|
||||||
|
|||||||
-4
@@ -44,10 +44,6 @@ class ChannelFinderFilterAssemblyGroup(
|
|||||||
ChannelMetadataAndLiveActivityWatcherSubAssembler(client, ::allKeys),
|
ChannelMetadataAndLiveActivityWatcherSubAssembler(client, ::allKeys),
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun start() = group.forEach { it.start() }
|
|
||||||
|
|
||||||
override fun stop() = group.forEach { it.stop() }
|
|
||||||
|
|
||||||
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
||||||
|
|
||||||
override fun invalidateKeys() = invalidateFilters()
|
override fun invalidateKeys() = invalidateFilters()
|
||||||
|
|||||||
-4
@@ -40,10 +40,6 @@ class EventFinderFilterAssembler(
|
|||||||
EventWatcherSubAssembler(client, ::allKeys),
|
EventWatcherSubAssembler(client, ::allKeys),
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun start() = group.forEach { it.start() }
|
|
||||||
|
|
||||||
override fun stop() = group.forEach { it.stop() }
|
|
||||||
|
|
||||||
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
||||||
|
|
||||||
override fun invalidateKeys() = invalidateFilters()
|
override fun invalidateKeys() = invalidateFilters()
|
||||||
|
|||||||
-4
@@ -41,10 +41,6 @@ class NWCPaymentFilterAssembler(
|
|||||||
NWCPaymentWatcherSubAssembler(client, ::allKeys),
|
NWCPaymentWatcherSubAssembler(client, ::allKeys),
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun start() = group.forEach { it.start() }
|
|
||||||
|
|
||||||
override fun stop() = group.forEach { it.stop() }
|
|
||||||
|
|
||||||
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
||||||
|
|
||||||
override fun invalidateKeys() = invalidateFilters()
|
override fun invalidateKeys() = invalidateFilters()
|
||||||
|
|||||||
-4
@@ -42,10 +42,6 @@ class UserFinderFilterAssembler(
|
|||||||
UserWatcherSubAssembler(client, ::allKeys),
|
UserWatcherSubAssembler(client, ::allKeys),
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun start() = group.forEach { it.start() }
|
|
||||||
|
|
||||||
override fun stop() = group.forEach { it.stop() }
|
|
||||||
|
|
||||||
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
||||||
|
|
||||||
override fun invalidateKeys() = invalidateFilters()
|
override fun invalidateKeys() = invalidateFilters()
|
||||||
|
|||||||
-4
@@ -49,10 +49,6 @@ class SearchFilterAssembler(
|
|||||||
SearchWatcherSubAssembler(cache, client, ::allKeys),
|
SearchWatcherSubAssembler(cache, client, ::allKeys),
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun start() = group.forEach { it.start() }
|
|
||||||
|
|
||||||
override fun stop() = group.forEach { it.stop() }
|
|
||||||
|
|
||||||
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
||||||
|
|
||||||
override fun invalidateKeys() = invalidateFilters()
|
override fun invalidateKeys() = invalidateFilters()
|
||||||
|
|||||||
@@ -109,7 +109,8 @@ private fun RelayOptions(
|
|||||||
onAddRelay: () -> Unit,
|
onAddRelay: () -> Unit,
|
||||||
onRemoveRelay: () -> Unit,
|
onRemoveRelay: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val userState by accountViewModel.normalizedKind3RelaySetFlow.collectAsStateWithLifecycle()
|
val userState by accountViewModel.account.trustedRelays.flow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
if (!userState.contains(relay.url)) {
|
if (!userState.contains(relay.url)) {
|
||||||
AddRelayButton(onAddRelay)
|
AddRelayButton(onAddRelay)
|
||||||
|
|||||||
-16
@@ -161,22 +161,6 @@ class AccountViewModel(
|
|||||||
|
|
||||||
var firstRoute: Route? = null
|
var firstRoute: Route? = null
|
||||||
|
|
||||||
val normalizedKind3RelaySetFlow: StateFlow<Set<NormalizedRelayUrl>> =
|
|
||||||
account
|
|
||||||
.userProfile()
|
|
||||||
.flow()
|
|
||||||
.relays.stateFlow
|
|
||||||
.map { contactListState ->
|
|
||||||
contactListState.user.latestContactList
|
|
||||||
?.relays()
|
|
||||||
?.keys ?: emptySet()
|
|
||||||
}.flowOn(Dispatchers.Default)
|
|
||||||
.stateIn(
|
|
||||||
viewModelScope,
|
|
||||||
SharingStarted.WhileSubscribed(10000, 10000),
|
|
||||||
emptySet(),
|
|
||||||
)
|
|
||||||
|
|
||||||
val toastManager = ToastManager()
|
val toastManager = ToastManager()
|
||||||
|
|
||||||
val feedStates = AccountFeedContentStates(this)
|
val feedStates = AccountFeedContentStates(this)
|
||||||
|
|||||||
+1
-5
@@ -41,15 +41,11 @@ class ChatroomFilterAssembler(
|
|||||||
ChatroomFilterSubAssembler(client, ::allKeys),
|
ChatroomFilterSubAssembler(client, ::allKeys),
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun start() = group.forEach { it.start() }
|
|
||||||
|
|
||||||
override fun stop() = group.forEach { it.stop() }
|
|
||||||
|
|
||||||
override fun invalidateKeys() = invalidateFilters()
|
override fun invalidateKeys() = invalidateFilters()
|
||||||
|
|
||||||
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
||||||
|
|
||||||
override fun destroy() = group.forEach { it.start() }
|
override fun destroy() = group.forEach { it.destroy() }
|
||||||
|
|
||||||
override fun printStats() = group.forEach { it.printStats() }
|
override fun printStats() = group.forEach { it.printStats() }
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-5
@@ -44,15 +44,11 @@ class ChannelFilterAssembler(
|
|||||||
ChannelFromUserFilterSubAssembler(client, ::allKeys),
|
ChannelFromUserFilterSubAssembler(client, ::allKeys),
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun start() = group.forEach { it.start() }
|
|
||||||
|
|
||||||
override fun stop() = group.forEach { it.stop() }
|
|
||||||
|
|
||||||
override fun invalidateKeys() = invalidateFilters()
|
override fun invalidateKeys() = invalidateFilters()
|
||||||
|
|
||||||
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
||||||
|
|
||||||
override fun destroy() = group.forEach { it.start() }
|
override fun destroy() = group.forEach { it.destroy() }
|
||||||
|
|
||||||
override fun printStats() = group.forEach { it.printStats() }
|
override fun printStats() = group.forEach { it.printStats() }
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-5
@@ -39,15 +39,11 @@ class ChatroomListFilterAssembler(
|
|||||||
FollowingEphemeralChatSubAssembler(client, ::allKeys),
|
FollowingEphemeralChatSubAssembler(client, ::allKeys),
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun start() = group.forEach { it.start() }
|
|
||||||
|
|
||||||
override fun stop() = group.forEach { it.stop() }
|
|
||||||
|
|
||||||
override fun invalidateKeys() = invalidateFilters()
|
override fun invalidateKeys() = invalidateFilters()
|
||||||
|
|
||||||
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
||||||
|
|
||||||
override fun destroy() = group.forEach { it.start() }
|
override fun destroy() = group.forEach { it.destroy() }
|
||||||
|
|
||||||
override fun printStats() = group.forEach { it.printStats() }
|
override fun printStats() = group.forEach { it.printStats() }
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-5
@@ -37,15 +37,11 @@ class CommunityFilterAssembler(
|
|||||||
CommunityFeedFilterSubAssembler(client, ::allKeys),
|
CommunityFeedFilterSubAssembler(client, ::allKeys),
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun start() = group.forEach { it.start() }
|
|
||||||
|
|
||||||
override fun stop() = group.forEach { it.stop() }
|
|
||||||
|
|
||||||
override fun invalidateKeys() = invalidateFilters()
|
override fun invalidateKeys() = invalidateFilters()
|
||||||
|
|
||||||
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
||||||
|
|
||||||
override fun destroy() = group.forEach { it.start() }
|
override fun destroy() = group.forEach { it.destroy() }
|
||||||
|
|
||||||
override fun printStats() = group.forEach { it.printStats() }
|
override fun printStats() = group.forEach { it.printStats() }
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-5
@@ -41,15 +41,11 @@ class DiscoveryFilterAssembler(
|
|||||||
DiscoveryFollowsDiscoverySubAssembler3(client, ::allKeys),
|
DiscoveryFollowsDiscoverySubAssembler3(client, ::allKeys),
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun start() = group.forEach { it.start() }
|
|
||||||
|
|
||||||
override fun stop() = group.forEach { it.stop() }
|
|
||||||
|
|
||||||
override fun invalidateKeys() = invalidateFilters()
|
override fun invalidateKeys() = invalidateFilters()
|
||||||
|
|
||||||
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
||||||
|
|
||||||
override fun destroy() = group.forEach { it.start() }
|
override fun destroy() = group.forEach { it.destroy() }
|
||||||
|
|
||||||
override fun printStats() = group.forEach { it.printStats() }
|
override fun printStats() = group.forEach { it.printStats() }
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-5
@@ -45,15 +45,11 @@ class GeoHashFilterAssembler(
|
|||||||
GeoHashFeedFilterSubAssembler(client, ::allKeys),
|
GeoHashFeedFilterSubAssembler(client, ::allKeys),
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun start() = group.forEach { it.start() }
|
|
||||||
|
|
||||||
override fun stop() = group.forEach { it.stop() }
|
|
||||||
|
|
||||||
override fun invalidateKeys() = invalidateFilters()
|
override fun invalidateKeys() = invalidateFilters()
|
||||||
|
|
||||||
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
||||||
|
|
||||||
override fun destroy() = group.forEach { it.start() }
|
override fun destroy() = group.forEach { it.destroy() }
|
||||||
|
|
||||||
override fun printStats() = group.forEach { it.printStats() }
|
override fun printStats() = group.forEach { it.printStats() }
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-5
@@ -40,15 +40,11 @@ class HashtagFilterAssembler(
|
|||||||
HashtagFeedFilterSubAssembler(client, ::allKeys),
|
HashtagFeedFilterSubAssembler(client, ::allKeys),
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun start() = group.forEach { it.start() }
|
|
||||||
|
|
||||||
override fun stop() = group.forEach { it.stop() }
|
|
||||||
|
|
||||||
override fun invalidateKeys() = invalidateFilters()
|
override fun invalidateKeys() = invalidateFilters()
|
||||||
|
|
||||||
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
||||||
|
|
||||||
override fun destroy() = group.forEach { it.start() }
|
override fun destroy() = group.forEach { it.destroy() }
|
||||||
|
|
||||||
override fun printStats() = group.forEach { it.printStats() }
|
override fun printStats() = group.forEach { it.printStats() }
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-5
@@ -46,15 +46,11 @@ class HomeFilterAssembler(
|
|||||||
// MixGeohashHashtagsCommunityEoseManager(client, ::allKeys),
|
// MixGeohashHashtagsCommunityEoseManager(client, ::allKeys),
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun start() = group.forEach { it.start() }
|
|
||||||
|
|
||||||
override fun stop() = group.forEach { it.stop() }
|
|
||||||
|
|
||||||
override fun invalidateKeys() = invalidateFilters()
|
override fun invalidateKeys() = invalidateFilters()
|
||||||
|
|
||||||
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
||||||
|
|
||||||
override fun destroy() = group.forEach { it.start() }
|
override fun destroy() = group.forEach { it.destroy() }
|
||||||
|
|
||||||
override fun printStats() = group.forEach { it.printStats() }
|
override fun printStats() = group.forEach { it.printStats() }
|
||||||
}
|
}
|
||||||
|
|||||||
-4
@@ -42,10 +42,6 @@ class UserProfileFilterAssembler(
|
|||||||
UserProfileZapsFilterSubAssembler(client, ::allKeys),
|
UserProfileZapsFilterSubAssembler(client, ::allKeys),
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun start() = group.forEach { it.start() }
|
|
||||||
|
|
||||||
override fun stop() = group.forEach { it.stop() }
|
|
||||||
|
|
||||||
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
||||||
|
|
||||||
override fun invalidateKeys() = invalidateFilters()
|
override fun invalidateKeys() = invalidateFilters()
|
||||||
|
|||||||
+1
-5
@@ -40,15 +40,11 @@ class ThreadFilterAssembler(
|
|||||||
ThreadEventLoaderSubAssembler(client, ::allKeys),
|
ThreadEventLoaderSubAssembler(client, ::allKeys),
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun start() = group.forEach { it.start() }
|
|
||||||
|
|
||||||
override fun stop() = group.forEach { it.stop() }
|
|
||||||
|
|
||||||
override fun invalidateKeys() = invalidateFilters()
|
override fun invalidateKeys() = invalidateFilters()
|
||||||
|
|
||||||
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
||||||
|
|
||||||
override fun destroy() = group.forEach { it.start() }
|
override fun destroy() = group.forEach { it.destroy() }
|
||||||
|
|
||||||
override fun printStats() = group.forEach { it.printStats() }
|
override fun printStats() = group.forEach { it.printStats() }
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-5
@@ -40,15 +40,11 @@ class VideoFilterAssembler(
|
|||||||
VideoOutboxEventsFilterSubAssembler(client, ::allKeys),
|
VideoOutboxEventsFilterSubAssembler(client, ::allKeys),
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun start() = group.forEach { it.start() }
|
|
||||||
|
|
||||||
override fun stop() = group.forEach { it.stop() }
|
|
||||||
|
|
||||||
override fun invalidateKeys() = invalidateFilters()
|
override fun invalidateKeys() = invalidateFilters()
|
||||||
|
|
||||||
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
||||||
|
|
||||||
override fun destroy() = group.forEach { it.start() }
|
override fun destroy() = group.forEach { it.destroy() }
|
||||||
|
|
||||||
override fun printStats() = group.forEach { it.printStats() }
|
override fun printStats() = group.forEach { it.printStats() }
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-48
@@ -20,70 +20,34 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.ammolite.relays.datasources
|
package com.vitorpamplona.ammolite.relays.datasources
|
||||||
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.single.newSubId
|
import com.vitorpamplona.quartz.nip01Core.relay.client.single.newSubId
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
import kotlin.collections.forEachIndexed
|
import kotlin.contracts.ExperimentalContracts
|
||||||
|
|
||||||
data class Subscription(
|
data class Subscription(
|
||||||
val id: String = newSubId(),
|
val id: String = newSubId(),
|
||||||
val onEose: ((time: Long, relayUrl: NormalizedRelayUrl) -> Unit)? = null,
|
val onEose: ((time: Long, relayUrl: NormalizedRelayUrl) -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
var relayBasedFilters: List<RelayBasedFilter>? = null // Inactive when null
|
private var filters: Map<NormalizedRelayUrl, List<Filter>>? = null // Inactive when null
|
||||||
|
|
||||||
fun reset() {
|
fun reset() {
|
||||||
relayBasedFilters = null
|
filters = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateFilters(newFilters: Map<NormalizedRelayUrl, List<Filter>>?) {
|
||||||
|
filters = newFilters
|
||||||
|
}
|
||||||
|
|
||||||
|
fun filters() = filters
|
||||||
|
|
||||||
|
@OptIn(ExperimentalContracts::class)
|
||||||
|
fun isActive() = filters != null
|
||||||
|
|
||||||
fun callEose(
|
fun callEose(
|
||||||
time: Long,
|
time: Long,
|
||||||
relay: NormalizedRelayUrl,
|
relay: NormalizedRelayUrl,
|
||||||
) {
|
) {
|
||||||
onEose?.let { it(time, relay) }
|
onEose?.let { it(time, relay) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hasChangedFiltersFrom(otherFilters: List<RelayBasedFilter>?): Boolean {
|
|
||||||
if (relayBasedFilters == null && otherFilters == null) return false
|
|
||||||
if (relayBasedFilters?.size != otherFilters?.size) return true
|
|
||||||
|
|
||||||
relayBasedFilters?.forEachIndexed { index, relaySetFilter ->
|
|
||||||
val otherFilter = otherFilters?.getOrNull(index) ?: return true
|
|
||||||
|
|
||||||
if (relaySetFilter.relay != otherFilter.relay) return true
|
|
||||||
|
|
||||||
return isDifferent(relaySetFilter.filter, otherFilter.filter)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isDifferent(
|
|
||||||
filter1: Filter,
|
|
||||||
filter2: Filter,
|
|
||||||
): Boolean {
|
|
||||||
// Does not check SINCE on purpose. Avoids replacing the filter if SINCE was all that changed.
|
|
||||||
// fast check
|
|
||||||
if (filter1.authors?.size != filter2.authors?.size ||
|
|
||||||
filter1.ids?.size != filter2.ids?.size ||
|
|
||||||
filter1.tags?.size != filter2.tags?.size ||
|
|
||||||
filter1.kinds?.size != filter2.kinds?.size ||
|
|
||||||
filter1.limit != filter2.limit ||
|
|
||||||
filter1.search?.length != filter2.search?.length ||
|
|
||||||
filter1.until != filter2.until
|
|
||||||
) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// deep check
|
|
||||||
if (filter1.ids != filter2.ids ||
|
|
||||||
filter1.authors != filter2.authors ||
|
|
||||||
filter1.tags != filter2.tags ||
|
|
||||||
filter1.kinds != filter2.kinds ||
|
|
||||||
filter1.search != filter2.search
|
|
||||||
) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-117
@@ -20,16 +20,13 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.ammolite.relays.datasources
|
package com.vitorpamplona.ammolite.relays.datasources
|
||||||
|
|
||||||
import com.vitorpamplona.ammolite.relays.BundledUpdate
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
|
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.IRelayClientListener
|
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.IRelayClientListener
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
|
import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import com.vitorpamplona.quartz.utils.LargeCache
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Semantically groups Nostr filters and subscriptions in data source objects that
|
* Semantically groups Nostr filters and subscriptions in data source objects that
|
||||||
@@ -37,13 +34,9 @@ import java.util.concurrent.atomic.AtomicBoolean
|
|||||||
*/
|
*/
|
||||||
class SubscriptionController(
|
class SubscriptionController(
|
||||||
val client: NostrClient,
|
val client: NostrClient,
|
||||||
val updateSubscriptions: () -> Unit,
|
) {
|
||||||
) : SubscriptionControllerService {
|
private val subscriptions = LargeCache<String, Subscription>()
|
||||||
private val subscriptions = SubscriptionSet()
|
private val stats = SubscriptionStats()
|
||||||
private var active: Boolean = false
|
|
||||||
private val changingFilters = AtomicBoolean()
|
|
||||||
|
|
||||||
val stats = SubscriptionStats()
|
|
||||||
|
|
||||||
private val clientListener =
|
private val clientListener =
|
||||||
object : IRelayClientListener {
|
object : IRelayClientListener {
|
||||||
@@ -54,10 +47,10 @@ class SubscriptionController(
|
|||||||
arrivalTime: Long,
|
arrivalTime: Long,
|
||||||
afterEOSE: Boolean,
|
afterEOSE: Boolean,
|
||||||
) {
|
) {
|
||||||
if (subscriptions.contains(subId)) {
|
if (subscriptions.containsKey(subId)) {
|
||||||
stats.add(subId, event.kind)
|
stats.add(subId, event.kind)
|
||||||
if (afterEOSE) {
|
if (afterEOSE) {
|
||||||
runAfterEOSE(subId, relay, arrivalTime)
|
subscriptions.get(subId)?.callEose(arrivalTime, relay.url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,145 +60,63 @@ class SubscriptionController(
|
|||||||
subId: String,
|
subId: String,
|
||||||
arrivalTime: Long,
|
arrivalTime: Long,
|
||||||
) {
|
) {
|
||||||
if (subscriptions.contains(subId)) {
|
if (subscriptions.containsKey(subId)) {
|
||||||
runAfterEOSE(subId, relay, arrivalTime)
|
subscriptions.get(subId)?.callEose(arrivalTime, relay.url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun runAfterEOSE(
|
|
||||||
subscriptionId: String,
|
|
||||||
relay: IRelayClient,
|
|
||||||
arrivalTime: Long,
|
|
||||||
) {
|
|
||||||
subscriptions[subscriptionId]?.callEose(arrivalTime, relay.url)
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
client.subscribe(clientListener)
|
client.subscribe(clientListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun destroy() {
|
fun destroy() {
|
||||||
// makes sure to run
|
|
||||||
stop()
|
|
||||||
client.unsubscribe(clientListener)
|
client.unsubscribe(clientListener)
|
||||||
bundler.cancel()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun start() {
|
fun printStats(tag: String) = stats.printCounter(tag)
|
||||||
active = true
|
|
||||||
invalidateFilters()
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
|
||||||
override fun stop() {
|
|
||||||
active = false
|
|
||||||
|
|
||||||
subscriptions.forEach { subscription ->
|
|
||||||
client.close(subscription.id)
|
|
||||||
subscription.reset()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun printStats(tag: String) = stats.printCounter(tag)
|
|
||||||
|
|
||||||
fun getSub(subId: String) = subscriptions.get(subId)
|
fun getSub(subId: String) = subscriptions.get(subId)
|
||||||
|
|
||||||
fun requestNewSubscription(onEOSE: ((Long, NormalizedRelayUrl) -> Unit)? = null): Subscription = subscriptions.newSub(onEOSE)
|
fun requestNewSubscription(onEOSE: ((Long, NormalizedRelayUrl) -> Unit)? = null): Subscription = Subscription(onEose = onEOSE).also { subscriptions.put(it.id, it) }
|
||||||
|
|
||||||
fun dismissSubscription(subId: String) = getSub(subId)?.let { dismissSubscription(it) }
|
fun dismissSubscription(subId: String) = getSub(subId)?.let { dismissSubscription(it) }
|
||||||
|
|
||||||
fun dismissSubscription(subscription: Subscription) {
|
fun dismissSubscription(subscription: Subscription) {
|
||||||
client.close(subscription.id)
|
client.close(subscription.id)
|
||||||
subscription.reset()
|
subscription.reset()
|
||||||
subscriptions.remove(subscription)
|
subscriptions.remove(subscription.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isUpdatingFilters() = changingFilters.get()
|
fun updateRelays() {
|
||||||
|
val currentFilters =
|
||||||
// Refreshes observers in batches.
|
subscriptions.associateWith { id, sub ->
|
||||||
private val bundler = BundledUpdate(300, Dispatchers.Default)
|
client.getSubscriptionFiltersOrNull(id)
|
||||||
|
|
||||||
override fun invalidateFilters() {
|
|
||||||
bundler.invalidate {
|
|
||||||
// println("DataSource: ${this.javaClass.simpleName} InvalidateFilters")
|
|
||||||
|
|
||||||
// adds the time to perform the refresh into this delay
|
|
||||||
// holding off new updates in case of heavy refresh routines.
|
|
||||||
resetFiltersSuspend()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resetFiltersSuspend() {
|
subscriptions.forEach { id, sub ->
|
||||||
// only runs one at a time. Ignores the others
|
updateRelaysIfNeeded(id, sub.filters(), currentFilters[id])
|
||||||
if (changingFilters.compareAndSet(false, true)) {
|
|
||||||
try {
|
|
||||||
resetFiltersSuspendInner()
|
|
||||||
} finally {
|
|
||||||
changingFilters.getAndSet(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun resetFiltersSuspendInner() {
|
|
||||||
// saves the channels that are currently active
|
|
||||||
val activeSubscriptions = subscriptions.actives()
|
|
||||||
// saves the current content to only update if it changes
|
|
||||||
val currentFilters = activeSubscriptions.associate { it.id to client.getSubscriptionFiltersOrNull(it.id) }
|
|
||||||
|
|
||||||
// updates all filters
|
|
||||||
updateSubscriptions()
|
|
||||||
|
|
||||||
// Makes sure to only send an updated filter when it actually changes.
|
|
||||||
subscriptions.forEach { newSubscriptionFilters ->
|
|
||||||
val currentFilters = currentFilters[newSubscriptionFilters.id]
|
|
||||||
updateRelaysIfNeeded(newSubscriptionFilters, currentFilters)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateRelaysIfNeeded(
|
fun updateRelaysIfNeeded(
|
||||||
updatedSubscription: Subscription,
|
subId: String,
|
||||||
currentFilters: List<RelayBasedFilter>?,
|
updatedFilters: Map<NormalizedRelayUrl, List<Filter>>?,
|
||||||
|
currentFilters: Map<NormalizedRelayUrl, List<Filter>>?,
|
||||||
) {
|
) {
|
||||||
val updatedSubscriptionNewFilters = updatedSubscription.relayBasedFilters
|
|
||||||
|
|
||||||
val isActive = client.isActive(updatedSubscription.id)
|
|
||||||
|
|
||||||
if (!isActive && updatedSubscriptionNewFilters != null) {
|
|
||||||
// Filter was removed from the active list
|
|
||||||
// but it is supposed to be there. Send again.
|
|
||||||
if (active) {
|
|
||||||
client.sendFilter(updatedSubscription.id, updatedSubscriptionNewFilters)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (currentFilters != null) {
|
if (currentFilters != null) {
|
||||||
if (updatedSubscriptionNewFilters == null) {
|
if (updatedFilters == null) {
|
||||||
// was active and is not active anymore, just close.
|
// was active and is not active anymore, just close.
|
||||||
client.close(updatedSubscription.id)
|
client.close(subId)
|
||||||
} else {
|
} else {
|
||||||
// was active and is still active, check if it has changed.
|
client.sendRequest(subId, updatedFilters)
|
||||||
if (updatedSubscription.hasChangedFiltersFrom(currentFilters)) {
|
|
||||||
client.close(updatedSubscription.id)
|
|
||||||
if (active) {
|
|
||||||
client.sendFilter(updatedSubscription.id, updatedSubscriptionNewFilters)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// hasn't changed, does nothing.
|
if (updatedFilters == null) {
|
||||||
// unless the relay has disconnected, then reconnect.
|
|
||||||
if (active) {
|
|
||||||
client.sendFilterOnlyIfDisconnected(updatedSubscription.id, updatedSubscriptionNewFilters)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (updatedSubscriptionNewFilters == null) {
|
|
||||||
// was not active and is still not active, does nothing
|
// was not active and is still not active, does nothing
|
||||||
} else {
|
} else {
|
||||||
// was not active and becomes active, sends the filter.
|
// was not active and becomes active, sends the entire filter.
|
||||||
if (active) {
|
client.sendRequest(subId, updatedFilters)
|
||||||
client.sendFilter(updatedSubscription.id, updatedSubscriptionNewFilters)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-33
@@ -1,33 +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.ammolite.relays.datasources
|
|
||||||
|
|
||||||
interface SubscriptionControllerService {
|
|
||||||
fun start()
|
|
||||||
|
|
||||||
fun stop()
|
|
||||||
|
|
||||||
fun invalidateFilters()
|
|
||||||
|
|
||||||
fun destroy()
|
|
||||||
|
|
||||||
fun printStats(tag: String)
|
|
||||||
}
|
|
||||||
-47
@@ -1,47 +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.ammolite.relays.datasources
|
|
||||||
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
|
||||||
|
|
||||||
class SubscriptionSet {
|
|
||||||
private var subscriptions = mapOf<String, Subscription>()
|
|
||||||
|
|
||||||
fun contains(subId: String) = subscriptions.containsKey(subId)
|
|
||||||
|
|
||||||
fun add(sub: Subscription) {
|
|
||||||
subscriptions = subscriptions + Pair(sub.id, sub)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun remove(subId: String) {
|
|
||||||
subscriptions = subscriptions.minus(subId)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun remove(sub: Subscription) = remove(sub.id)
|
|
||||||
|
|
||||||
fun newSub(onEOSE: ((Long, NormalizedRelayUrl) -> Unit)? = null): Subscription = Subscription(onEose = onEOSE).also { add(it) }
|
|
||||||
|
|
||||||
fun forEach(action: (Subscription) -> Unit) = subscriptions.values.forEach(action)
|
|
||||||
|
|
||||||
operator fun get(subId: String) = subscriptions[subId]
|
|
||||||
|
|
||||||
fun actives() = subscriptions.values.filter { it.relayBasedFilters != null }
|
|
||||||
}
|
|
||||||
+10
-12
@@ -21,17 +21,19 @@
|
|||||||
package com.vitorpamplona.ammolite.relays.datasources
|
package com.vitorpamplona.ammolite.relays.datasources
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import com.vitorpamplona.quartz.utils.LargeCache
|
||||||
|
|
||||||
class SubscriptionStats {
|
class SubscriptionStats {
|
||||||
data class Counter(
|
data class Counter(
|
||||||
val subscriptionId: String,
|
val subscriptionId: String,
|
||||||
val eventKind: Int,
|
val eventKind: Int,
|
||||||
var counter: Int,
|
) {
|
||||||
)
|
var counter: Int = 0
|
||||||
|
}
|
||||||
|
|
||||||
private var eventCounter = mapOf<Int, Counter>()
|
private var eventCounter = LargeCache<Int, Counter>()
|
||||||
|
|
||||||
fun eventCounterIndex(
|
private fun eventCounterIndex(
|
||||||
str1: String,
|
str1: String,
|
||||||
str2: Int,
|
str2: Int,
|
||||||
): Int = 31 * str1.hashCode() + str2.hashCode()
|
): Int = 31 * str1.hashCode() + str2.hashCode()
|
||||||
@@ -41,19 +43,15 @@ class SubscriptionStats {
|
|||||||
eventKind: Int,
|
eventKind: Int,
|
||||||
) {
|
) {
|
||||||
val key = eventCounterIndex(subscriptionId, eventKind)
|
val key = eventCounterIndex(subscriptionId, eventKind)
|
||||||
val keyValue = eventCounter[key]
|
val stats = eventCounter.getOrCreate(key) { Counter(subscriptionId, eventKind) }
|
||||||
if (keyValue != null) {
|
stats.counter++
|
||||||
keyValue.counter++
|
|
||||||
} else {
|
|
||||||
eventCounter = eventCounter + Pair(key, Counter(subscriptionId, eventKind, 1))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun printCounter(tag: String) {
|
fun printCounter(tag: String) {
|
||||||
eventCounter.forEach {
|
eventCounter.forEach { _, stats ->
|
||||||
Log.d(
|
Log.d(
|
||||||
tag,
|
tag,
|
||||||
"Received Events ${it.value.subscriptionId} ${it.value.eventKind}: ${it.value.counter}",
|
"Received Events ${stats.subscriptionId} ${stats.eventKind}: ${stats.counter}",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+130
-24
@@ -25,12 +25,12 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.IRelayClientLis
|
|||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.RelayState
|
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.RelayState
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.PoolEventOutboxRepository
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.PoolEventOutboxRepository
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.PoolSubscriptionRepository
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.PoolSubscriptionRepository
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayPool
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayPool
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
|
import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.single.basic.BasicRelayClient
|
import com.vitorpamplona.quartz.nip01Core.relay.client.single.basic.BasicRelayClient
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.single.newSubId
|
import com.vitorpamplona.quartz.nip01Core.relay.client.single.newSubId
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.stats.RelayStats
|
import com.vitorpamplona.quartz.nip01Core.relay.client.stats.RelayStats
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.sockets.WebsocketBuilder
|
import com.vitorpamplona.quartz.nip01Core.relay.sockets.WebsocketBuilder
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -44,14 +44,15 @@ import kotlinx.coroutines.flow.onStart
|
|||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Nostr Client manages a relay pool, keeps active subscriptions and manages sending of events.
|
* The Nostr Client manages a relay pool, keeps active subscriptions and manages re-sending of events.
|
||||||
*/
|
*/
|
||||||
class NostrClient(
|
class NostrClient(
|
||||||
private val websocketBuilder: WebsocketBuilder,
|
private val websocketBuilder: WebsocketBuilder,
|
||||||
private val scope: CoroutineScope,
|
private val scope: CoroutineScope,
|
||||||
) : IRelayClientListener {
|
) : IRelayClientListener {
|
||||||
private val relayPool: RelayPool = RelayPool(this, ::buildRelay)
|
private val relayPool: RelayPool = RelayPool(this, ::buildRelay)
|
||||||
private val activeSubscriptions: PoolSubscriptionRepository = PoolSubscriptionRepository()
|
private val activeRequests: PoolSubscriptionRepository = PoolSubscriptionRepository()
|
||||||
|
private val activeCounts: PoolSubscriptionRepository = PoolSubscriptionRepository()
|
||||||
private val eventOutbox: PoolEventOutboxRepository = PoolEventOutboxRepository()
|
private val eventOutbox: PoolEventOutboxRepository = PoolEventOutboxRepository()
|
||||||
|
|
||||||
private var listeners = setOf<IRelayClientListener>()
|
private var listeners = setOf<IRelayClientListener>()
|
||||||
@@ -62,19 +63,20 @@ class NostrClient(
|
|||||||
*/
|
*/
|
||||||
private val allRelays =
|
private val allRelays =
|
||||||
combine(
|
combine(
|
||||||
activeSubscriptions.relays,
|
activeRequests.relays,
|
||||||
|
activeCounts.relays,
|
||||||
eventOutbox.relays,
|
eventOutbox.relays,
|
||||||
) { subs, outbox ->
|
) { reqs, counts, outbox ->
|
||||||
subs + outbox
|
reqs + counts + outbox
|
||||||
}.onStart {
|
}.onStart {
|
||||||
activeSubscriptions.relays.value + eventOutbox.relays.value
|
activeRequests.relays.value + activeCounts.relays.value + eventOutbox.relays.value
|
||||||
}.onEach {
|
}.onEach {
|
||||||
relayPool.updatePool(it)
|
relayPool.updatePool(it)
|
||||||
}.flowOn(Dispatchers.Default)
|
}.flowOn(Dispatchers.Default)
|
||||||
.stateIn(
|
.stateIn(
|
||||||
scope,
|
scope,
|
||||||
SharingStarted.Companion.Eagerly,
|
SharingStarted.Companion.Eagerly,
|
||||||
activeSubscriptions.relays.value + eventOutbox.relays.value,
|
activeRequests.relays.value + activeCounts.relays.value + eventOutbox.relays.value,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun buildRelay(relay: NormalizedRelayUrl): IRelayClient =
|
fun buildRelay(relay: NormalizedRelayUrl): IRelayClient =
|
||||||
@@ -84,7 +86,8 @@ class NostrClient(
|
|||||||
listener = relayPool,
|
listener = relayPool,
|
||||||
stats = RelayStats.get(relay),
|
stats = RelayStats.get(relay),
|
||||||
) { liveRelay ->
|
) { liveRelay ->
|
||||||
activeSubscriptions.forEachSub(relay, liveRelay::sendRequest)
|
activeRequests.forEachSub(relay, liveRelay::sendRequest)
|
||||||
|
activeCounts.forEachSub(relay, liveRelay::sendCount)
|
||||||
eventOutbox.forEachUnsentEvent(relay, liveRelay::send)
|
eventOutbox.forEachUnsentEvent(relay, liveRelay::send)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,20 +112,124 @@ class NostrClient(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendFilter(
|
fun needsToResendRequest(
|
||||||
subscriptionId: String = newSubId(),
|
oldFilters: List<Filter>,
|
||||||
filters: List<RelayBasedFilter> = listOf(),
|
newFilters: List<Filter>,
|
||||||
) {
|
): Boolean {
|
||||||
activeSubscriptions.addOrUpdate(subscriptionId, filters)
|
if (oldFilters.size != newFilters.size) return true
|
||||||
relayPool.sendRequest(subscriptionId, filters)
|
|
||||||
|
oldFilters.forEachIndexed { index, oldFilter ->
|
||||||
|
val newFilter = newFilters.getOrNull(index) ?: return true
|
||||||
|
|
||||||
|
return needsToResendRequest(oldFilter, newFilter)
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendFilterOnlyIfDisconnected(
|
/**
|
||||||
subscriptionId: String = newSubId(),
|
* Checks if the filter has changed, with a special case for when the since changes due to new
|
||||||
filters: List<RelayBasedFilter> = listOf(),
|
* EOSE times.
|
||||||
|
*/
|
||||||
|
fun needsToResendRequest(
|
||||||
|
oldFilter: Filter,
|
||||||
|
newFilter: Filter,
|
||||||
|
): Boolean {
|
||||||
|
// Does not check SINCE on purpose. Avoids replacing the filter if SINCE was all that changed.
|
||||||
|
// fast check
|
||||||
|
if (oldFilter.authors?.size != newFilter.authors?.size ||
|
||||||
|
oldFilter.ids?.size != newFilter.ids?.size ||
|
||||||
|
oldFilter.tags?.size != newFilter.tags?.size ||
|
||||||
|
oldFilter.kinds?.size != newFilter.kinds?.size ||
|
||||||
|
oldFilter.limit != newFilter.limit ||
|
||||||
|
oldFilter.search?.length != newFilter.search?.length ||
|
||||||
|
oldFilter.until != newFilter.until
|
||||||
) {
|
) {
|
||||||
activeSubscriptions.addOrUpdate(subscriptionId, filters)
|
return true
|
||||||
relayPool.connectIfDisconnected()
|
}
|
||||||
|
|
||||||
|
// deep check
|
||||||
|
if (oldFilter.ids != newFilter.ids ||
|
||||||
|
oldFilter.authors != newFilter.authors ||
|
||||||
|
oldFilter.tags != newFilter.tags ||
|
||||||
|
oldFilter.kinds != newFilter.kinds ||
|
||||||
|
oldFilter.search != newFilter.search
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldFilter.since != null) {
|
||||||
|
if (newFilter.since == null) {
|
||||||
|
// went was checking the future only and now wants everything
|
||||||
|
return true
|
||||||
|
} else if (oldFilter.since > newFilter.since) {
|
||||||
|
// went backwards in time, forces update
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendRequest(
|
||||||
|
subId: String = newSubId(),
|
||||||
|
filters: Map<NormalizedRelayUrl, List<Filter>>,
|
||||||
|
) {
|
||||||
|
val oldFilters = activeRequests.getSubscriptionFiltersOrNull(subId) ?: emptyMap()
|
||||||
|
activeRequests.addOrUpdate(subId, filters)
|
||||||
|
|
||||||
|
val allRelays = filters.keys + oldFilters.keys
|
||||||
|
|
||||||
|
allRelays.forEach { relay ->
|
||||||
|
val oldFilters = oldFilters[relay]
|
||||||
|
val newFilters = filters[relay]
|
||||||
|
|
||||||
|
if (newFilters.isNullOrEmpty()) {
|
||||||
|
// some relays are not in this sub anymore. Stop their subscriptions
|
||||||
|
relayPool.close(relay, subId)
|
||||||
|
} else if (oldFilters.isNullOrEmpty()) {
|
||||||
|
// new relays were added. Start a new sub in them
|
||||||
|
relayPool.sendRequest(relay, subId, newFilters)
|
||||||
|
} else if (needsToResendRequest(oldFilters, newFilters)) {
|
||||||
|
// filters were changed enough (not only an update in since) to warn a new update
|
||||||
|
relayPool.sendRequest(relay, subId, newFilters)
|
||||||
|
} else {
|
||||||
|
// makes sure the relay wakes up if it was disconnected by the server
|
||||||
|
// upon connection, the relay will run the default Sync and update all
|
||||||
|
// filters, including this one.
|
||||||
|
relayPool.connectIfDisconnected(relay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendCount(
|
||||||
|
subId: String = newSubId(),
|
||||||
|
filters: Map<NormalizedRelayUrl, List<Filter>>,
|
||||||
|
) {
|
||||||
|
val oldFilters = activeCounts.getSubscriptionFiltersOrNull(subId) ?: emptyMap()
|
||||||
|
activeCounts.addOrUpdate(subId, filters)
|
||||||
|
|
||||||
|
val allRelays = filters.keys + oldFilters.keys
|
||||||
|
|
||||||
|
allRelays.forEach { relay ->
|
||||||
|
val oldFilters = oldFilters[relay]
|
||||||
|
val newFilters = filters[relay]
|
||||||
|
|
||||||
|
if (newFilters.isNullOrEmpty()) {
|
||||||
|
// some relays are not in this sub anymore. Stop their subscriptions
|
||||||
|
relayPool.close(relay, subId)
|
||||||
|
} else if (oldFilters.isNullOrEmpty()) {
|
||||||
|
// new relays were added. Start a new sub in them
|
||||||
|
relayPool.sendCount(relay, subId, newFilters)
|
||||||
|
} else if (needsToResendRequest(oldFilters, newFilters)) {
|
||||||
|
// filters were changed enough (not only an update in since) to warn a new update
|
||||||
|
relayPool.sendCount(relay, subId, newFilters)
|
||||||
|
} else {
|
||||||
|
// makes sure the relay wakes up if it was disconnected by the server
|
||||||
|
// upon connection, the relay will run the default Sync and update all
|
||||||
|
// filters, including this one.
|
||||||
|
relayPool.connectIfDisconnected(relay)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendIfExists(
|
fun sendIfExists(
|
||||||
@@ -142,11 +249,10 @@ class NostrClient(
|
|||||||
|
|
||||||
fun close(subscriptionId: String) {
|
fun close(subscriptionId: String) {
|
||||||
relayPool.close(subscriptionId)
|
relayPool.close(subscriptionId)
|
||||||
activeSubscriptions.remove(subscriptionId)
|
activeRequests.remove(subscriptionId)
|
||||||
|
activeCounts.remove(subscriptionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isActive(subscriptionId: String): Boolean = activeSubscriptions.isActive(subscriptionId)
|
|
||||||
|
|
||||||
override fun onEvent(
|
override fun onEvent(
|
||||||
relay: IRelayClient,
|
relay: IRelayClient,
|
||||||
subId: String,
|
subId: String,
|
||||||
@@ -236,7 +342,7 @@ class NostrClient(
|
|||||||
listeners = listeners.minus(listener)
|
listeners = listeners.minus(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSubscriptionFiltersOrNull(subId: String): List<RelayBasedFilter>? = activeSubscriptions.getSubscriptionFiltersOrNull(subId)
|
fun getSubscriptionFiltersOrNull(subId: String): Map<NormalizedRelayUrl, List<Filter>>? = activeRequests.getSubscriptionFiltersOrNull(subId)
|
||||||
|
|
||||||
fun relayStatusFlow() = relayPool.statusFlow
|
fun relayStatusFlow() = relayPool.statusFlow
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-3
@@ -23,9 +23,10 @@ package com.vitorpamplona.quartz.nip01Core.relay.client.acessories
|
|||||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
|
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.IRelayClientListener
|
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.IRelayClientListener
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
|
import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.single.newSubId
|
import com.vitorpamplona.quartz.nip01Core.relay.client.single.newSubId
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
@@ -33,7 +34,7 @@ import kotlinx.coroutines.launch
|
|||||||
|
|
||||||
fun NostrClient.downloadFirstEvent(
|
fun NostrClient.downloadFirstEvent(
|
||||||
subscriptionId: String = newSubId(),
|
subscriptionId: String = newSubId(),
|
||||||
filters: List<RelayBasedFilter> = listOf(),
|
filters: Map<NormalizedRelayUrl, List<Filter>>,
|
||||||
onResponse: (Event) -> Unit,
|
onResponse: (Event) -> Unit,
|
||||||
) {
|
) {
|
||||||
val listener =
|
val listener =
|
||||||
@@ -56,7 +57,7 @@ fun NostrClient.downloadFirstEvent(
|
|||||||
|
|
||||||
subscribe(listener)
|
subscribe(listener)
|
||||||
|
|
||||||
sendFilter(subscriptionId, filters)
|
sendRequest(subscriptionId, filters)
|
||||||
|
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
delay(30000)
|
delay(30000)
|
||||||
|
|||||||
-29
@@ -1,29 +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.quartz.nip01Core.relay.client.pool
|
|
||||||
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
|
||||||
|
|
||||||
class PoolSubscription(
|
|
||||||
var filters: List<RelayBasedFilter> = emptyList(),
|
|
||||||
) {
|
|
||||||
fun toFilter(relay: NormalizedRelayUrl) = filters.mapNotNull { it.toFilter(relay) }
|
|
||||||
}
|
|
||||||
+12
-26
@@ -22,20 +22,17 @@ package com.vitorpamplona.quartz.nip01Core.relay.client.pool
|
|||||||
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
|
import com.vitorpamplona.quartz.utils.LargeCache
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
||||||
class PoolSubscriptionRepository {
|
class PoolSubscriptionRepository {
|
||||||
private var subscriptions = mapOf<String, PoolSubscription>()
|
private var subscriptions = LargeCache<String, Map<NormalizedRelayUrl, List<Filter>>>()
|
||||||
val relays = MutableStateFlow(setOf<NormalizedRelayUrl>())
|
val relays = MutableStateFlow(setOf<NormalizedRelayUrl>())
|
||||||
|
|
||||||
fun updateRelays() {
|
fun updateRelays() {
|
||||||
val myRelays = mutableSetOf<NormalizedRelayUrl>()
|
val myRelays = mutableSetOf<NormalizedRelayUrl>()
|
||||||
subscriptions.values.forEach {
|
subscriptions.forEach { sub, perRelayFilters ->
|
||||||
it.filters.forEach {
|
myRelays.addAll(perRelayFilters.keys)
|
||||||
if (!myRelays.contains(it.relay)) {
|
|
||||||
myRelays.add(it.relay)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (relays.value != myRelays) {
|
if (relays.value != myRelays) {
|
||||||
@@ -45,20 +42,15 @@ class PoolSubscriptionRepository {
|
|||||||
|
|
||||||
fun addOrUpdate(
|
fun addOrUpdate(
|
||||||
subscriptionId: String,
|
subscriptionId: String,
|
||||||
filters: List<RelayBasedFilter> = listOf(),
|
filters: Map<NormalizedRelayUrl, List<Filter>>,
|
||||||
) {
|
) {
|
||||||
val currentFilter = subscriptions[subscriptionId]
|
subscriptions.put(subscriptionId, filters)
|
||||||
if (currentFilter == null) {
|
|
||||||
subscriptions = subscriptions + Pair(subscriptionId, PoolSubscription(filters))
|
|
||||||
} else {
|
|
||||||
currentFilter.filters = filters
|
|
||||||
}
|
|
||||||
updateRelays()
|
updateRelays()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun remove(subscriptionId: String) {
|
fun remove(subscriptionId: String) {
|
||||||
if (subscriptions.contains(subscriptionId)) {
|
if (subscriptions.containsKey(subscriptionId)) {
|
||||||
subscriptions = subscriptions.minus(subscriptionId)
|
subscriptions.remove(subscriptionId)
|
||||||
updateRelays()
|
updateRelays()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,9 +59,9 @@ class PoolSubscriptionRepository {
|
|||||||
relay: NormalizedRelayUrl,
|
relay: NormalizedRelayUrl,
|
||||||
run: (String, List<Filter>) -> Unit,
|
run: (String, List<Filter>) -> Unit,
|
||||||
) {
|
) {
|
||||||
subscriptions.forEach { (subId, filters) ->
|
subscriptions.forEach { subId, filters ->
|
||||||
val filters = filters.toFilter(relay)
|
val filters = filters[relay]
|
||||||
if (filters.isNotEmpty()) {
|
if (!filters.isNullOrEmpty()) {
|
||||||
run(subId, filters)
|
run(subId, filters)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
@@ -77,11 +69,5 @@ class PoolSubscriptionRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isActive(subscriptionId: String): Boolean = subscriptions.contains(subscriptionId)
|
fun getSubscriptionFiltersOrNull(subId: String): Map<NormalizedRelayUrl, List<Filter>>? = subscriptions.get(subId)
|
||||||
|
|
||||||
fun allSubscriptions(): Map<String, PoolSubscription> = subscriptions
|
|
||||||
|
|
||||||
fun getSubscriptionFilters(subId: String): List<RelayBasedFilter> = subscriptions[subId]?.filters ?: emptyList()
|
|
||||||
|
|
||||||
fun getSubscriptionFiltersOrNull(subId: String): List<RelayBasedFilter>? = subscriptions[subId]?.filters
|
|
||||||
}
|
}
|
||||||
|
|||||||
+8
@@ -40,3 +40,11 @@ class RelayBasedFilter(
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun List<RelayBasedFilter>.groupByRelay(): Map<NormalizedRelayUrl, List<Filter>> {
|
||||||
|
val result = mutableMapOf<NormalizedRelayUrl, MutableList<Filter>>()
|
||||||
|
for (relayBasedFilter in this) {
|
||||||
|
result.getOrPut(relayBasedFilter.relay) { mutableListOf() }.add(relayBasedFilter.filter)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|||||||
+32
-8
@@ -26,6 +26,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.EmptyClientList
|
|||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.IRelayClientListener
|
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.IRelayClientListener
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.RelayState
|
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.RelayState
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
|
import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
import com.vitorpamplona.quartz.utils.LargeCache
|
import com.vitorpamplona.quartz.utils.LargeCache
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
@@ -33,7 +34,7 @@ import kotlinx.coroutines.flow.StateFlow
|
|||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlin.collections.forEach
|
import kotlin.collections.forEach
|
||||||
import kotlin.collections.isNotEmpty
|
import kotlin.collections.isNotEmpty
|
||||||
import kotlin.collections.mapNotNull
|
import kotlin.collections.isNullOrEmpty
|
||||||
|
|
||||||
val UnsupportedRelayCreation: (url: NormalizedRelayUrl) -> IRelayClient = {
|
val UnsupportedRelayCreation: (url: NormalizedRelayUrl) -> IRelayClient = {
|
||||||
throw UnsupportedOperationException("Cannot create new relays")
|
throw UnsupportedOperationException("Cannot create new relays")
|
||||||
@@ -77,30 +78,48 @@ class RelayPool(
|
|||||||
relay.connectAndSyncFiltersIfDisconnected()
|
relay.connectAndSyncFiltersIfDisconnected()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun connectIfDisconnected(relay: NormalizedRelayUrl) = relays.get(relay)?.connectAndSyncFiltersIfDisconnected()
|
||||||
|
|
||||||
fun disconnect() =
|
fun disconnect() =
|
||||||
relays.forEach { url, relay ->
|
relays.forEach { url, relay ->
|
||||||
relay.disconnect()
|
relay.disconnect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendRequest(
|
fun sendRequest(
|
||||||
|
relay: NormalizedRelayUrl,
|
||||||
subId: String,
|
subId: String,
|
||||||
filters: List<RelayBasedFilter>,
|
filters: List<Filter>,
|
||||||
|
) {
|
||||||
|
relays.get(relay)?.sendRequest(subId, filters)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendRequest(
|
||||||
|
subId: String,
|
||||||
|
filters: Map<NormalizedRelayUrl, List<Filter>>,
|
||||||
) {
|
) {
|
||||||
relays.forEach { url, relay ->
|
relays.forEach { url, relay ->
|
||||||
val filters = filters.mapNotNull { it.toFilter(url) }
|
val filters = filters[relay.url]
|
||||||
if (filters.isNotEmpty()) {
|
if (!filters.isNullOrEmpty()) {
|
||||||
relay.sendRequest(subId, filters)
|
relay.sendRequest(subId, filters)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendCounter(
|
fun sendCount(
|
||||||
|
relay: NormalizedRelayUrl,
|
||||||
subId: String,
|
subId: String,
|
||||||
filters: List<RelayBasedFilter>,
|
filters: List<Filter>,
|
||||||
|
) {
|
||||||
|
relays.get(relay)?.sendRequest(subId, filters)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendCount(
|
||||||
|
subId: String,
|
||||||
|
filters: Map<NormalizedRelayUrl, List<Filter>>,
|
||||||
) {
|
) {
|
||||||
relays.forEach { url, relay ->
|
relays.forEach { url, relay ->
|
||||||
val filters = filters.mapNotNull { it.toFilter(url) }
|
val filters = filters[relay.url]
|
||||||
if (filters.isNotEmpty()) {
|
if (!filters.isNullOrEmpty()) {
|
||||||
relay.sendCount(subId, filters)
|
relay.sendCount(subId, filters)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,6 +130,11 @@ class RelayPool(
|
|||||||
relay.close(subscriptionId)
|
relay.close(subscriptionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun close(
|
||||||
|
relay: NormalizedRelayUrl,
|
||||||
|
subscriptionId: String,
|
||||||
|
) = relays.get(relay)?.close(subscriptionId)
|
||||||
|
|
||||||
fun send(
|
fun send(
|
||||||
signedEvent: Event,
|
signedEvent: Event,
|
||||||
list: Set<NormalizedRelayUrl>,
|
list: Set<NormalizedRelayUrl>,
|
||||||
|
|||||||
+1
-1
@@ -293,7 +293,7 @@ open class BasicRelayClient(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun processClosed(msg: ClosedMessage) {
|
private fun processClosed(msg: ClosedMessage) {
|
||||||
// Log.w(logTag, "Relay Closed Subscription $newMessage")
|
Log.w(logTag, "Relay Closed Subscription ${msg.subscriptionId} ${msg.message}")
|
||||||
afterEOSEPerSubscription[msg.subscriptionId] = false
|
afterEOSEPerSubscription[msg.subscriptionId] = false
|
||||||
listener.onClosed(this@BasicRelayClient, msg.subscriptionId, msg.message)
|
listener.onClosed(this@BasicRelayClient, msg.subscriptionId, msg.message)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,8 +20,10 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.quartz.utils
|
package com.vitorpamplona.quartz.utils
|
||||||
|
|
||||||
|
import android.R.attr.value
|
||||||
import java.util.concurrent.ConcurrentSkipListMap
|
import java.util.concurrent.ConcurrentSkipListMap
|
||||||
import java.util.function.BiConsumer
|
import java.util.function.BiConsumer
|
||||||
|
import kotlin.collections.LinkedHashMap
|
||||||
|
|
||||||
class LargeCache<K, V> {
|
class LargeCache<K, V> {
|
||||||
private val cache = ConcurrentSkipListMap<K, V>()
|
private val cache = ConcurrentSkipListMap<K, V>()
|
||||||
@@ -170,14 +172,32 @@ class LargeCache<K, V> {
|
|||||||
return runner.count
|
return runner.count
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun innerForEach(runner: BiConsumer<K, V>) {
|
public fun <T, U> associate(transform: (K, V) -> Pair<T, U>): Map<T, U> {
|
||||||
// val (value, elapsed) =
|
val runner = BiAssociateCollector(size(), transform)
|
||||||
// measureTimedValue {
|
innerForEach(runner)
|
||||||
cache.forEach(runner)
|
return runner.results
|
||||||
// }
|
}
|
||||||
// println("LargeCache full loop $elapsed \t for $runner")
|
|
||||||
|
|
||||||
listOf(1, 2, 3).joinToString()
|
public fun <T, U> associateNotNull(transform: (K, V) -> Pair<T, U>?): Map<T, U> {
|
||||||
|
val runner = BiAssociateNotNullCollector(size(), transform)
|
||||||
|
innerForEach(runner)
|
||||||
|
return runner.results
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun <U> associateWith(transform: (K, V) -> U?): Map<K, U?> {
|
||||||
|
val runner = BiAssociateWithCollector(size(), transform)
|
||||||
|
innerForEach(runner)
|
||||||
|
return runner.results
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun <U> associateNotNullWith(transform: (K, V) -> U): Map<K, U> {
|
||||||
|
val runner = BiAssociateNotNullWithCollector(size(), transform)
|
||||||
|
innerForEach(runner)
|
||||||
|
return runner.results
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun innerForEach(runner: BiConsumer<K, V>) {
|
||||||
|
cache.forEach(runner)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun joinToString(
|
fun joinToString(
|
||||||
@@ -255,6 +275,13 @@ fun interface BiMapper<K, V, R> {
|
|||||||
): R?
|
): R?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun interface BiMapperNotNull<K, V, R> {
|
||||||
|
fun map(
|
||||||
|
k: K,
|
||||||
|
v: V,
|
||||||
|
): R
|
||||||
|
}
|
||||||
|
|
||||||
class BiMapCollector<K, V, R>(
|
class BiMapCollector<K, V, R>(
|
||||||
val mapper: BiMapper<K, V, R?>,
|
val mapper: BiMapper<K, V, R?>,
|
||||||
) : BiConsumer<K, V> {
|
) : BiConsumer<K, V> {
|
||||||
@@ -271,6 +298,69 @@ class BiMapCollector<K, V, R>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class BiAssociateCollector<K, V, T, U>(
|
||||||
|
val size: Int,
|
||||||
|
val mapper: BiMapperNotNull<K, V, Pair<T, U>>,
|
||||||
|
) : BiConsumer<K, V> {
|
||||||
|
var results: LinkedHashMap<T, U> = LinkedHashMap(size)
|
||||||
|
|
||||||
|
override fun accept(
|
||||||
|
k: K,
|
||||||
|
v: V,
|
||||||
|
) {
|
||||||
|
val pair = mapper.map(k, v)
|
||||||
|
results.put(pair.first, pair.second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BiAssociateNotNullCollector<K, V, T, U>(
|
||||||
|
val size: Int,
|
||||||
|
val mapper: BiMapper<K, V, Pair<T, U>?>,
|
||||||
|
) : BiConsumer<K, V> {
|
||||||
|
var results: LinkedHashMap<T, U> = LinkedHashMap(size)
|
||||||
|
|
||||||
|
override fun accept(
|
||||||
|
k: K,
|
||||||
|
v: V,
|
||||||
|
) {
|
||||||
|
val pair = mapper.map(k, v)
|
||||||
|
if (pair != null) {
|
||||||
|
results.put(pair.first, pair.second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BiAssociateWithCollector<K, V, U>(
|
||||||
|
val size: Int,
|
||||||
|
val mapper: BiMapper<K, V, U?>,
|
||||||
|
) : BiConsumer<K, V> {
|
||||||
|
var results: LinkedHashMap<K, U?> = LinkedHashMap(size)
|
||||||
|
|
||||||
|
override fun accept(
|
||||||
|
k: K,
|
||||||
|
v: V,
|
||||||
|
) {
|
||||||
|
results.put(k, mapper.map(k, v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BiAssociateNotNullWithCollector<K, V, U>(
|
||||||
|
val size: Int,
|
||||||
|
val mapper: BiMapper<K, V, U>,
|
||||||
|
) : BiConsumer<K, V> {
|
||||||
|
var results: LinkedHashMap<K, U> = LinkedHashMap(size)
|
||||||
|
|
||||||
|
override fun accept(
|
||||||
|
k: K,
|
||||||
|
v: V,
|
||||||
|
) {
|
||||||
|
val newValue = mapper.map(k, v)
|
||||||
|
if (newValue != null) {
|
||||||
|
results.put(k, newValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class BiMapUniqueCollector<K, V, R>(
|
class BiMapUniqueCollector<K, V, R>(
|
||||||
val mapper: BiMapper<K, V, R?>,
|
val mapper: BiMapper<K, V, R?>,
|
||||||
) : BiConsumer<K, V> {
|
) : BiConsumer<K, V> {
|
||||||
|
|||||||
Reference in New Issue
Block a user