Moves Relay Stats to it's own cache.
Refactors the isLocalRelay function Removes unnecessary onError methods from the Relay class Moves byte counts from Int to Long
This commit is contained in:
@@ -26,6 +26,7 @@ import androidx.compose.runtime.Stable
|
|||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||||
import com.vitorpamplona.amethyst.service.relays.Relay
|
import com.vitorpamplona.amethyst.service.relays.Relay
|
||||||
|
import com.vitorpamplona.amethyst.service.relays.RelayStats
|
||||||
import com.vitorpamplona.amethyst.ui.components.BundledUpdate
|
import com.vitorpamplona.amethyst.ui.components.BundledUpdate
|
||||||
import com.vitorpamplona.quartz.encoders.HexKey
|
import com.vitorpamplona.quartz.encoders.HexKey
|
||||||
import com.vitorpamplona.quartz.encoders.Nip19Bech32
|
import com.vitorpamplona.quartz.encoders.Nip19Bech32
|
||||||
@@ -69,10 +70,6 @@ class AntiSpamFilter {
|
|||||||
if (
|
if (
|
||||||
(recentMessages[hash] != null && recentMessages[hash] != idHex) || spamMessages[hash] != null
|
(recentMessages[hash] != null && recentMessages[hash] != idHex) || spamMessages[hash] != null
|
||||||
) {
|
) {
|
||||||
Log.w(
|
|
||||||
"Potential SPAM Message for sharing",
|
|
||||||
"${Nip19Bech32.createNEvent(event.id, event.pubKey, event.kind, null)}",
|
|
||||||
)
|
|
||||||
Log.w(
|
Log.w(
|
||||||
"Potential SPAM Message",
|
"Potential SPAM Message",
|
||||||
"${event.id} ${recentMessages[hash]} ${spamMessages[hash] != null} ${relay?.url} ${event.content.replace("\n", " | ")}",
|
"${event.id} ${recentMessages[hash]} ${spamMessages[hash] != null} ${relay?.url} ${event.content.replace("\n", " | ")}",
|
||||||
@@ -81,6 +78,10 @@ class AntiSpamFilter {
|
|||||||
// Log down offenders
|
// Log down offenders
|
||||||
logOffender(hash, event)
|
logOffender(hash, event)
|
||||||
|
|
||||||
|
if (relay != null) {
|
||||||
|
RelayStats.newSpam(relay.url, "Potential SPAM Message ${event.id} nostr:${Nip19Bech32.createNEvent(event.id, event.pubKey, event.kind, relay.url)}")
|
||||||
|
}
|
||||||
|
|
||||||
liveSpam.invalidateData()
|
liveSpam.invalidateData()
|
||||||
|
|
||||||
return true
|
return true
|
||||||
@@ -100,7 +101,7 @@ class AntiSpamFilter {
|
|||||||
spamMessages.put(hashCode, Spammer(event.pubKey, setOf(recentMessages[hashCode], event.id)))
|
spamMessages.put(hashCode, Spammer(event.pubKey, setOf(recentMessages[hashCode], event.id)))
|
||||||
} else {
|
} else {
|
||||||
val spammer = spamMessages.get(hashCode)
|
val spammer = spamMessages.get(hashCode)
|
||||||
spammer.duplicatedMessages = spammer.duplicatedMessages + event.id
|
spammer.duplicatedMessages += event.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -450,7 +450,6 @@ object LocalCache {
|
|||||||
if (note.event != null) return
|
if (note.event != null) return
|
||||||
|
|
||||||
if (antiSpam.isSpam(event, relay)) {
|
if (antiSpam.isSpam(event, relay)) {
|
||||||
relay?.let { it.spamCounter++ }
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -474,6 +473,8 @@ object LocalCache {
|
|||||||
val note = getOrCreateNote(event.id)
|
val note = getOrCreateNote(event.id)
|
||||||
val author = getOrCreateUser(event.pubKey)
|
val author = getOrCreateUser(event.pubKey)
|
||||||
|
|
||||||
|
// Log.d("TN", "New Response ${event.taggedEvents().joinToString(", ") { it }}}")
|
||||||
|
|
||||||
if (relay != null) {
|
if (relay != null) {
|
||||||
author.addRelayBeingUsed(relay, event.createdAt)
|
author.addRelayBeingUsed(relay, event.createdAt)
|
||||||
note.addRelay(relay)
|
note.addRelay(relay)
|
||||||
@@ -623,7 +624,6 @@ object LocalCache {
|
|||||||
if (note.event != null) return
|
if (note.event != null) return
|
||||||
|
|
||||||
if (antiSpam.isSpam(event, relay)) {
|
if (antiSpam.isSpam(event, relay)) {
|
||||||
relay?.let { it.spamCounter++ }
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -648,7 +648,6 @@ object LocalCache {
|
|||||||
if (note.event != null) return
|
if (note.event != null) return
|
||||||
|
|
||||||
if (antiSpam.isSpam(event, relay)) {
|
if (antiSpam.isSpam(event, relay)) {
|
||||||
relay?.let { it.spamCounter++ }
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -673,7 +672,6 @@ object LocalCache {
|
|||||||
if (note.event != null) return
|
if (note.event != null) return
|
||||||
|
|
||||||
if (antiSpam.isSpam(event, relay)) {
|
if (antiSpam.isSpam(event, relay)) {
|
||||||
relay?.let { it.spamCounter++ }
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -711,7 +709,6 @@ object LocalCache {
|
|||||||
if (note.event?.id() == event.id()) return
|
if (note.event?.id() == event.id()) return
|
||||||
|
|
||||||
if (antiSpam.isSpam(event, relay)) {
|
if (antiSpam.isSpam(event, relay)) {
|
||||||
relay?.let { it.spamCounter++ }
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -746,7 +743,6 @@ object LocalCache {
|
|||||||
if (note.event?.id() == event.id()) return
|
if (note.event?.id() == event.id()) return
|
||||||
|
|
||||||
if (antiSpam.isSpam(event, relay)) {
|
if (antiSpam.isSpam(event, relay)) {
|
||||||
relay?.let { it.spamCounter++ }
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -827,7 +823,6 @@ object LocalCache {
|
|||||||
if (note.event != null) return
|
if (note.event != null) return
|
||||||
|
|
||||||
if (antiSpam.isSpam(event, relay)) {
|
if (antiSpam.isSpam(event, relay)) {
|
||||||
relay?.let { it.spamCounter++ }
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1533,7 +1528,6 @@ object LocalCache {
|
|||||||
if (note.event != null) return
|
if (note.event != null) return
|
||||||
|
|
||||||
if (antiSpam.isSpam(event, relay)) {
|
if (antiSpam.isSpam(event, relay)) {
|
||||||
relay?.let { it.spamCounter++ }
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1572,7 +1566,6 @@ object LocalCache {
|
|||||||
if (note.event != null) return
|
if (note.event != null) return
|
||||||
|
|
||||||
if (antiSpam.isSpam(event, relay)) {
|
if (antiSpam.isSpam(event, relay)) {
|
||||||
relay?.let { it.spamCounter++ }
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,16 +22,14 @@ package com.vitorpamplona.amethyst.model
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.vitorpamplona.amethyst.service.relays.FeedType
|
import com.vitorpamplona.amethyst.service.relays.FeedType
|
||||||
|
import com.vitorpamplona.amethyst.service.relays.RelayStat
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class RelaySetupInfo(
|
data class RelaySetupInfo(
|
||||||
val url: String,
|
val url: String,
|
||||||
val read: Boolean,
|
val read: Boolean,
|
||||||
val write: Boolean,
|
val write: Boolean,
|
||||||
val errorCount: Int = 0,
|
val relayStat: RelayStat = RelayStat(),
|
||||||
val downloadCountInBytes: Int = 0,
|
|
||||||
val uploadCountInBytes: Int = 0,
|
|
||||||
val spamCount: Int = 0,
|
|
||||||
val feedTypes: Set<FeedType>,
|
val feedTypes: Set<FeedType>,
|
||||||
val paidRelay: Boolean = false,
|
val paidRelay: Boolean = false,
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -103,6 +103,16 @@ object HttpClientManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getHttpClientForUrl(url: String): OkHttpClient {
|
||||||
|
// TODO: How to identify relays on the local network?
|
||||||
|
val isLocalHost = url.startsWith("ws://127.0.0.1") || url.startsWith("ws://localhost")
|
||||||
|
return if (isLocalHost) {
|
||||||
|
getHttpClient(false)
|
||||||
|
} else {
|
||||||
|
getHttpClient()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getHttpClient(useProxy: Boolean = true): OkHttpClient {
|
fun getHttpClient(useProxy: Boolean = true): OkHttpClient {
|
||||||
return if (useProxy) {
|
return if (useProxy) {
|
||||||
if (this.defaultHttpClient == null) {
|
if (this.defaultHttpClient == null) {
|
||||||
|
|||||||
@@ -112,9 +112,8 @@ class Nip11Retriever {
|
|||||||
try {
|
try {
|
||||||
val request: Request =
|
val request: Request =
|
||||||
Request.Builder().header("Accept", "application/nostr+json").url(url).build()
|
Request.Builder().header("Accept", "application/nostr+json").url(url).build()
|
||||||
val isLocalHost = dirtyUrl.startsWith("ws://127.0.0.1") || dirtyUrl.startsWith("ws://localhost")
|
|
||||||
|
|
||||||
HttpClientManager.getHttpClient(useProxy = !isLocalHost)
|
HttpClientManager.getHttpClientForUrl(dirtyUrl)
|
||||||
.newCall(request)
|
.newCall(request)
|
||||||
.enqueue(
|
.enqueue(
|
||||||
object : Callback {
|
object : Callback {
|
||||||
|
|||||||
@@ -92,19 +92,6 @@ abstract class NostrDataSource(val debugName: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onError(
|
|
||||||
error: Error,
|
|
||||||
subscriptionId: String,
|
|
||||||
relay: Relay,
|
|
||||||
) {
|
|
||||||
// if (subscriptions.containsKey(subscriptionId)) {
|
|
||||||
// Log.e(
|
|
||||||
// this@NostrDataSource.javaClass.simpleName,
|
|
||||||
// "Relay OnError ${relay.url}: ${error.message}"
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onRelayStateChange(
|
override fun onRelayStateChange(
|
||||||
type: Relay.StateType,
|
type: Relay.StateType,
|
||||||
relay: Relay,
|
relay: Relay,
|
||||||
|
|||||||
@@ -194,19 +194,6 @@ object Client : RelayPool.Listener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
|
||||||
override fun onError(
|
|
||||||
error: Error,
|
|
||||||
subscriptionId: String,
|
|
||||||
relay: Relay,
|
|
||||||
) {
|
|
||||||
// Releases the Web thread for the new payload.
|
|
||||||
// May need to add a processing queue if processing new events become too costly.
|
|
||||||
GlobalScope.launch(Dispatchers.Default) {
|
|
||||||
listeners.forEach { it.onError(error, subscriptionId, relay) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
override fun onRelayStateChange(
|
override fun onRelayStateChange(
|
||||||
type: Relay.StateType,
|
type: Relay.StateType,
|
||||||
@@ -284,13 +271,6 @@ object Client : RelayPool.Listener {
|
|||||||
afterEOSE: Boolean,
|
afterEOSE: Boolean,
|
||||||
) = Unit
|
) = Unit
|
||||||
|
|
||||||
/** A new or repeat message was received */
|
|
||||||
open fun onError(
|
|
||||||
error: Error,
|
|
||||||
subscriptionId: String,
|
|
||||||
relay: Relay,
|
|
||||||
) = Unit
|
|
||||||
|
|
||||||
/** Connected to or disconnected from a relay */
|
/** Connected to or disconnected from a relay */
|
||||||
open fun onRelayStateChange(
|
open fun onRelayStateChange(
|
||||||
type: Relay.StateType,
|
type: Relay.StateType,
|
||||||
|
|||||||
@@ -59,38 +59,24 @@ class Relay(
|
|||||||
val write: Boolean = true,
|
val write: Boolean = true,
|
||||||
val activeTypes: Set<FeedType> = FeedType.values().toSet(),
|
val activeTypes: Set<FeedType> = FeedType.values().toSet(),
|
||||||
) {
|
) {
|
||||||
val brief = RelayBriefInfoCache.get(url)
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
// waits 3 minutes to reconnect once things fail
|
// waits 3 minutes to reconnect once things fail
|
||||||
const val RECONNECTING_IN_SECONDS = 60 * 3
|
const val RECONNECTING_IN_SECONDS = 60 * 3
|
||||||
}
|
}
|
||||||
|
|
||||||
private val httpClient =
|
val brief = RelayBriefInfoCache.get(url)
|
||||||
if (url.startsWith("ws://127.0.0.1") || url.startsWith("ws://localhost")) {
|
|
||||||
HttpClientManager.getHttpClient(false)
|
|
||||||
} else {
|
|
||||||
HttpClientManager.getHttpClient()
|
|
||||||
}
|
|
||||||
|
|
||||||
private var listeners = setOf<Listener>()
|
private var listeners = setOf<Listener>()
|
||||||
private var socket: WebSocket? = null
|
private var socket: WebSocket? = null
|
||||||
private var isReady: Boolean = false
|
private var isReady: Boolean = false
|
||||||
private var usingCompression: Boolean = false
|
private var usingCompression: Boolean = false
|
||||||
|
|
||||||
var eventDownloadCounterInBytes = 0
|
private var lastConnectTentative: Long = 0L
|
||||||
var eventUploadCounterInBytes = 0
|
|
||||||
|
|
||||||
var spamCounter = 0
|
private var afterEOSEPerSubscription = mutableMapOf<String, Boolean>()
|
||||||
var errorCounter = 0
|
|
||||||
var pingInMs: Long? = null
|
|
||||||
|
|
||||||
var lastConnectTentative: Long = 0L
|
private val authResponse = mutableMapOf<HexKey, Boolean>()
|
||||||
|
private val sendWhenReady = mutableListOf<EventInterface>()
|
||||||
var afterEOSEPerSubscription = mutableMapOf<String, Boolean>()
|
|
||||||
|
|
||||||
val authResponse = mutableMapOf<HexKey, Boolean>()
|
|
||||||
val sendWhenReady = mutableListOf<EventInterface>()
|
|
||||||
|
|
||||||
fun register(listener: Listener) {
|
fun register(listener: Listener) {
|
||||||
listeners = listeners.plus(listener)
|
listeners = listeners.plus(listener)
|
||||||
@@ -116,7 +102,7 @@ class Relay(
|
|||||||
private var connectingBlock = AtomicBoolean()
|
private var connectingBlock = AtomicBoolean()
|
||||||
|
|
||||||
fun connectAndRun(onConnected: (Relay) -> Unit) {
|
fun connectAndRun(onConnected: (Relay) -> Unit) {
|
||||||
Log.d("Relay", "Relay.connect $url connecting: ${connectingBlock.get()} hasProxy: ${this.httpClient.proxy != null}")
|
Log.d("Relay", "Relay.connect $url isAlreadyConnecting: ${connectingBlock.get()}")
|
||||||
// BRB is crashing OkHttp Deflater object :(
|
// BRB is crashing OkHttp Deflater object :(
|
||||||
if (url.contains("brb.io")) return
|
if (url.contains("brb.io")) return
|
||||||
|
|
||||||
@@ -141,13 +127,13 @@ class Relay(
|
|||||||
.url(url.trim())
|
.url(url.trim())
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
socket = httpClient.newWebSocket(request, RelayListener(onConnected))
|
socket = HttpClientManager.getHttpClientForUrl(url).newWebSocket(request, RelayListener(onConnected))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
if (e is CancellationException) throw e
|
if (e is CancellationException) throw e
|
||||||
|
|
||||||
errorCounter++
|
RelayStats.newError(url, e.message)
|
||||||
|
|
||||||
markConnectionAsClosed()
|
markConnectionAsClosed()
|
||||||
Log.e("Relay", "Relay Invalid $url")
|
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
} finally {
|
} finally {
|
||||||
connectingBlock.set(false)
|
connectingBlock.set(false)
|
||||||
@@ -187,7 +173,7 @@ class Relay(
|
|||||||
) {
|
) {
|
||||||
checkNotInMainThread()
|
checkNotInMainThread()
|
||||||
|
|
||||||
eventDownloadCounterInBytes += text.bytesUsedInMemory()
|
RelayStats.addBytesReceived(url, text.bytesUsedInMemory())
|
||||||
|
|
||||||
try {
|
try {
|
||||||
processNewRelayMessage(text)
|
processNewRelayMessage(text)
|
||||||
@@ -239,20 +225,23 @@ class Relay(
|
|||||||
) {
|
) {
|
||||||
checkNotInMainThread()
|
checkNotInMainThread()
|
||||||
|
|
||||||
errorCounter++
|
|
||||||
|
|
||||||
socket?.cancel() // 1000, "Normal close"
|
socket?.cancel() // 1000, "Normal close"
|
||||||
// Failures disconnect the relay.
|
// Failures disconnect the relay.
|
||||||
markConnectionAsClosed()
|
markConnectionAsClosed()
|
||||||
|
|
||||||
Log.w("Relay", "Relay onFailure $url, ${response?.message} $response")
|
// checks if this is an actual failure. Closing the socket generates an onFailure as well.
|
||||||
t.printStackTrace()
|
if (!(socket == null && t.message == "Socket closed")) {
|
||||||
listeners.forEach {
|
RelayStats.newError(url, response?.message ?: t.message)
|
||||||
it.onError(
|
|
||||||
this@Relay,
|
Log.w("Relay", "Relay onFailure $url, ${response?.message} $response ${t.message} $socket")
|
||||||
"",
|
t.printStackTrace()
|
||||||
Error("WebSocket Failure. Response: $response. Exception: ${t.message}", t),
|
listeners.forEach {
|
||||||
)
|
it.onError(
|
||||||
|
this@Relay,
|
||||||
|
"",
|
||||||
|
Error("WebSocket Failure. Response: $response. Exception: ${t.message}", t),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -263,8 +252,9 @@ class Relay(
|
|||||||
) {
|
) {
|
||||||
this.resetEOSEStatuses()
|
this.resetEOSEStatuses()
|
||||||
this.isReady = true
|
this.isReady = true
|
||||||
this.pingInMs = pingInMs
|
|
||||||
this.usingCompression = usingCompression
|
this.usingCompression = usingCompression
|
||||||
|
|
||||||
|
RelayStats.setPing(url, pingInMs)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun markConnectionAsClosed() {
|
fun markConnectionAsClosed() {
|
||||||
@@ -306,6 +296,8 @@ class Relay(
|
|||||||
val message = msgArray.get(1).asText()
|
val message = msgArray.get(1).asText()
|
||||||
Log.w("Relay", "Relay onNotice $url, $message")
|
Log.w("Relay", "Relay onNotice $url, $message")
|
||||||
|
|
||||||
|
RelayStats.newNotice(url, message)
|
||||||
|
|
||||||
it.onError(this@Relay, message, Error("Relay sent notice: $message"))
|
it.onError(this@Relay, message, Error("Relay sent notice: $message"))
|
||||||
}
|
}
|
||||||
"OK" ->
|
"OK" ->
|
||||||
@@ -336,7 +328,9 @@ class Relay(
|
|||||||
it.onNotify(this@Relay, msgArray[1].asText())
|
it.onNotify(this@Relay, msgArray[1].asText())
|
||||||
}
|
}
|
||||||
"CLOSED" -> listeners.forEach { Log.w("Relay", "Relay onClosed $url, $newMessage") }
|
"CLOSED" -> listeners.forEach { Log.w("Relay", "Relay onClosed $url, $newMessage") }
|
||||||
else ->
|
else -> {
|
||||||
|
RelayStats.newError(url, "Unsupported message: $newMessage")
|
||||||
|
|
||||||
listeners.forEach {
|
listeners.forEach {
|
||||||
Log.w("Relay", "Unsupported message: $newMessage")
|
Log.w("Relay", "Unsupported message: $newMessage")
|
||||||
it.onError(
|
it.onError(
|
||||||
@@ -345,6 +339,7 @@ class Relay(
|
|||||||
Error("Unknown type $type on channel. Msg was $newMessage"),
|
Error("Unknown type $type on channel. Msg was $newMessage"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,10 +384,8 @@ class Relay(
|
|||||||
it.filter.toJson(url)
|
it.filter.toJson(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log.d("Relay", "onFilterSent $url $requestId $request")
|
writeToSocket(request)
|
||||||
|
|
||||||
socket?.send(request)
|
|
||||||
eventUploadCounterInBytes += request.bytesUsedInMemory()
|
|
||||||
resetEOSEStatuses()
|
resetEOSEStatuses()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -457,30 +450,9 @@ class Relay(
|
|||||||
checkNotInMainThread()
|
checkNotInMainThread()
|
||||||
|
|
||||||
if (signedEvent is RelayAuthEvent) {
|
if (signedEvent is RelayAuthEvent) {
|
||||||
authResponse.put(signedEvent.id, false)
|
sendAuth(signedEvent)
|
||||||
// specific protocol for this event.
|
|
||||||
val event = """["AUTH",${signedEvent.toJson()}]"""
|
|
||||||
socket?.send(event)
|
|
||||||
eventUploadCounterInBytes += event.bytesUsedInMemory()
|
|
||||||
} else {
|
} else {
|
||||||
val event = """["EVENT",${signedEvent.toJson()}]"""
|
sendEvent(signedEvent)
|
||||||
if (isConnected()) {
|
|
||||||
if (isReady) {
|
|
||||||
socket?.send(event)
|
|
||||||
eventUploadCounterInBytes += event.bytesUsedInMemory()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// sends all filters after connection is successful.
|
|
||||||
connectAndRun {
|
|
||||||
checkNotInMainThread()
|
|
||||||
|
|
||||||
socket?.send(event)
|
|
||||||
eventUploadCounterInBytes += event.bytesUsedInMemory()
|
|
||||||
|
|
||||||
// Sends everything.
|
|
||||||
renewFilters()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -488,18 +460,12 @@ class Relay(
|
|||||||
checkNotInMainThread()
|
checkNotInMainThread()
|
||||||
|
|
||||||
if (signedEvent is RelayAuthEvent) {
|
if (signedEvent is RelayAuthEvent) {
|
||||||
authResponse.put(signedEvent.id, false)
|
sendAuth(signedEvent)
|
||||||
// specific protocol for this event.
|
|
||||||
val event = """["AUTH",${signedEvent.toJson()}]"""
|
|
||||||
socket?.send(event)
|
|
||||||
eventUploadCounterInBytes += event.bytesUsedInMemory()
|
|
||||||
} else {
|
} else {
|
||||||
if (write) {
|
if (write) {
|
||||||
val event = """["EVENT",${signedEvent.toJson()}]"""
|
|
||||||
if (isConnected()) {
|
if (isConnected()) {
|
||||||
if (isReady) {
|
if (isReady) {
|
||||||
socket?.send(event)
|
writeToSocket("""["EVENT",${signedEvent.toJson()}]""")
|
||||||
eventUploadCounterInBytes += event.bytesUsedInMemory()
|
|
||||||
} else {
|
} else {
|
||||||
synchronized(sendWhenReady) {
|
synchronized(sendWhenReady) {
|
||||||
sendWhenReady.add(signedEvent)
|
sendWhenReady.add(signedEvent)
|
||||||
@@ -508,10 +474,7 @@ class Relay(
|
|||||||
} else {
|
} else {
|
||||||
// sends all filters after connection is successful.
|
// sends all filters after connection is successful.
|
||||||
connectAndRun {
|
connectAndRun {
|
||||||
checkNotInMainThread()
|
writeToSocket("""["EVENT",${signedEvent.toJson()}]""")
|
||||||
|
|
||||||
socket?.send(event)
|
|
||||||
eventUploadCounterInBytes += event.bytesUsedInMemory()
|
|
||||||
|
|
||||||
// Sends everything.
|
// Sends everything.
|
||||||
renewFilters()
|
renewFilters()
|
||||||
@@ -521,12 +484,40 @@ class Relay(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun close(subscriptionId: String) {
|
private fun sendAuth(signedEvent: RelayAuthEvent) {
|
||||||
checkNotInMainThread()
|
authResponse.put(signedEvent.id, false)
|
||||||
|
writeToSocket("""["AUTH",${signedEvent.toJson()}]""")
|
||||||
|
}
|
||||||
|
|
||||||
val msg = """["CLOSE","$subscriptionId"]"""
|
private fun sendEvent(signedEvent: EventInterface) {
|
||||||
// Log.d("Relay", "Close Subscription $url $msg")
|
if (isConnected()) {
|
||||||
socket?.send(msg)
|
if (isReady) {
|
||||||
|
writeToSocket("""["EVENT",${signedEvent.toJson()}]""")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// sends all filters after connection is successful.
|
||||||
|
connectAndRun {
|
||||||
|
writeToSocket("""["EVENT",${signedEvent.toJson()}]""")
|
||||||
|
|
||||||
|
// Sends everything.
|
||||||
|
renewFilters()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun writeToSocket(str: String) {
|
||||||
|
socket?.let {
|
||||||
|
checkNotInMainThread()
|
||||||
|
|
||||||
|
it.send(str)
|
||||||
|
RelayStats.addBytesSent(url, str.bytesUsedInMemory())
|
||||||
|
|
||||||
|
Log.d("Relay", "Relay send $url $str")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun close(subscriptionId: String) {
|
||||||
|
writeToSocket("""["CLOSE","$subscriptionId"]""")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isSameRelayConfig(other: Relay): Boolean {
|
fun isSameRelayConfig(other: Relay): Boolean {
|
||||||
|
|||||||
@@ -197,12 +197,6 @@ object RelayPool : Relay.Listener {
|
|||||||
afterEOSE: Boolean,
|
afterEOSE: Boolean,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun onError(
|
|
||||||
error: Error,
|
|
||||||
subscriptionId: String,
|
|
||||||
relay: Relay,
|
|
||||||
)
|
|
||||||
|
|
||||||
fun onRelayStateChange(
|
fun onRelayStateChange(
|
||||||
type: Relay.StateType,
|
type: Relay.StateType,
|
||||||
relay: Relay,
|
relay: Relay,
|
||||||
@@ -241,7 +235,6 @@ object RelayPool : Relay.Listener {
|
|||||||
subscriptionId: String,
|
subscriptionId: String,
|
||||||
error: Error,
|
error: Error,
|
||||||
) {
|
) {
|
||||||
listeners.forEach { it.onError(error, subscriptionId, relay) }
|
|
||||||
updateStatus()
|
updateStatus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,138 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2024 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.relays
|
||||||
|
|
||||||
|
import androidx.collection.LruCache
|
||||||
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
|
||||||
|
object RelayStats {
|
||||||
|
private val innerCache = mutableMapOf<String, RelayStat>()
|
||||||
|
|
||||||
|
fun get(url: String): RelayStat {
|
||||||
|
return innerCache.getOrPut(url) { RelayStat() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addBytesReceived(
|
||||||
|
url: String,
|
||||||
|
bytesUsedInMemory: Int,
|
||||||
|
) {
|
||||||
|
get(url).addBytesReceived(bytesUsedInMemory)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addBytesSent(
|
||||||
|
url: String,
|
||||||
|
bytesUsedInMemory: Int,
|
||||||
|
) {
|
||||||
|
get(url).addBytesSent(bytesUsedInMemory)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun newError(
|
||||||
|
url: String,
|
||||||
|
error: String?,
|
||||||
|
) {
|
||||||
|
get(url).newError(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun newNotice(
|
||||||
|
url: String,
|
||||||
|
notice: String?,
|
||||||
|
) {
|
||||||
|
get(url).newNotice(notice)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setPing(
|
||||||
|
url: String,
|
||||||
|
pingInMs: Long,
|
||||||
|
) {
|
||||||
|
get(url).pingInMs = pingInMs
|
||||||
|
}
|
||||||
|
|
||||||
|
fun newSpam(
|
||||||
|
url: String,
|
||||||
|
explanation: String,
|
||||||
|
) {
|
||||||
|
get(url).newSpam(explanation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RelayStat(
|
||||||
|
var receivedBytes: Long = 0L,
|
||||||
|
var sentBytes: Long = 0L,
|
||||||
|
var spamCounter: Long = 0L,
|
||||||
|
var errorCounter: Long = 0L,
|
||||||
|
var pingInMs: Long = 0L,
|
||||||
|
) {
|
||||||
|
val messages = LruCache<RelayDebugMessage, RelayDebugMessage>(100)
|
||||||
|
|
||||||
|
fun newNotice(notice: String?) {
|
||||||
|
val debugMessage =
|
||||||
|
RelayDebugMessage(
|
||||||
|
type = RelayDebugMessageType.NOTICE,
|
||||||
|
message = notice ?: "No error message provided",
|
||||||
|
)
|
||||||
|
|
||||||
|
messages.put(debugMessage, debugMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun newError(error: String?) {
|
||||||
|
errorCounter++
|
||||||
|
|
||||||
|
val debugMessage =
|
||||||
|
RelayDebugMessage(
|
||||||
|
type = RelayDebugMessageType.ERROR,
|
||||||
|
message = error ?: "No error message provided",
|
||||||
|
)
|
||||||
|
|
||||||
|
messages.put(debugMessage, debugMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addBytesReceived(bytesUsedInMemory: Int) {
|
||||||
|
receivedBytes += bytesUsedInMemory
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addBytesSent(bytesUsedInMemory: Int) {
|
||||||
|
sentBytes += bytesUsedInMemory
|
||||||
|
}
|
||||||
|
|
||||||
|
fun newSpam(spamDescriptor: String) {
|
||||||
|
spamCounter++
|
||||||
|
|
||||||
|
val debugMessage =
|
||||||
|
RelayDebugMessage(
|
||||||
|
type = RelayDebugMessageType.SPAM,
|
||||||
|
message = spamDescriptor,
|
||||||
|
)
|
||||||
|
|
||||||
|
messages.put(debugMessage, debugMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RelayDebugMessage(
|
||||||
|
val type: RelayDebugMessageType,
|
||||||
|
val message: String,
|
||||||
|
val time: Long = TimeUtils.now(),
|
||||||
|
)
|
||||||
|
|
||||||
|
enum class RelayDebugMessageType {
|
||||||
|
SPAM,
|
||||||
|
NOTICE,
|
||||||
|
ERROR,
|
||||||
|
}
|
||||||
@@ -48,6 +48,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.service.relays.Constants
|
import com.vitorpamplona.amethyst.service.relays.Constants
|
||||||
|
import com.vitorpamplona.amethyst.service.relays.RelayStat
|
||||||
import com.vitorpamplona.amethyst.ui.actions.CloseButton
|
import com.vitorpamplona.amethyst.ui.actions.CloseButton
|
||||||
import com.vitorpamplona.amethyst.ui.actions.SaveButton
|
import com.vitorpamplona.amethyst.ui.actions.SaveButton
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
@@ -249,7 +250,7 @@ fun ResetSearchRelays(postViewModel: SearchRelayListViewModel) {
|
|||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
postViewModel.deleteAll()
|
postViewModel.deleteAll()
|
||||||
Constants.defaultSearchRelaySet.forEach { postViewModel.addRelay(BasicRelaySetupInfo(it)) }
|
Constants.defaultSearchRelaySet.forEach { postViewModel.addRelay(BasicRelaySetupInfo(it, RelayStat())) }
|
||||||
postViewModel.loadRelayDocuments()
|
postViewModel.loadRelayDocuments()
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
|||||||
+2
-4
@@ -22,14 +22,12 @@ package com.vitorpamplona.amethyst.ui.actions.relays
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.vitorpamplona.amethyst.model.RelayBriefInfoCache
|
import com.vitorpamplona.amethyst.model.RelayBriefInfoCache
|
||||||
|
import com.vitorpamplona.amethyst.service.relays.RelayStat
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class BasicRelaySetupInfo(
|
data class BasicRelaySetupInfo(
|
||||||
val url: String,
|
val url: String,
|
||||||
val errorCount: Int = 0,
|
val relayStat: RelayStat,
|
||||||
val downloadCountInBytes: Int = 0,
|
|
||||||
val uploadCountInBytes: Int = 0,
|
|
||||||
val spamCount: Int = 0,
|
|
||||||
val paidRelay: Boolean = false,
|
val paidRelay: Boolean = false,
|
||||||
) {
|
) {
|
||||||
val briefInfo: RelayBriefInfoCache.RelayBriefInfo = RelayBriefInfoCache.RelayBriefInfo(url)
|
val briefInfo: RelayBriefInfoCache.RelayBriefInfo = RelayBriefInfoCache.RelayBriefInfo(url)
|
||||||
|
|||||||
+3
-12
@@ -24,7 +24,7 @@ import androidx.lifecycle.ViewModel
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.service.Nip11CachedRetriever
|
import com.vitorpamplona.amethyst.service.Nip11CachedRetriever
|
||||||
import com.vitorpamplona.amethyst.service.relays.RelayPool
|
import com.vitorpamplona.amethyst.service.relays.RelayStats
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
@@ -78,20 +78,11 @@ abstract class BasicRelaySetupInfoModel : ViewModel() {
|
|||||||
val relayList = getRelayList() ?: emptyList()
|
val relayList = getRelayList() ?: emptyList()
|
||||||
|
|
||||||
relayList.map { relayUrl ->
|
relayList.map { relayUrl ->
|
||||||
val liveRelay = RelayPool.getRelay(relayUrl)
|
|
||||||
val errorCounter = liveRelay?.errorCounter ?: 0
|
|
||||||
val eventDownloadCounter = liveRelay?.eventDownloadCounterInBytes ?: 0
|
|
||||||
val eventUploadCounter = liveRelay?.eventUploadCounterInBytes ?: 0
|
|
||||||
val spamCounter = liveRelay?.spamCounter ?: 0
|
|
||||||
|
|
||||||
BasicRelaySetupInfo(
|
BasicRelaySetupInfo(
|
||||||
relayUrl,
|
relayUrl,
|
||||||
errorCounter,
|
RelayStats.get(relayUrl),
|
||||||
eventDownloadCounter,
|
|
||||||
eventUploadCounter,
|
|
||||||
spamCounter,
|
|
||||||
)
|
)
|
||||||
}.distinctBy { it.url }.sortedBy { it.downloadCountInBytes }.reversed()
|
}.distinctBy { it.url }.sortedBy { it.relayStat.receivedBytes }.reversed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,14 @@ fun countToHumanReadableBytes(counter: Int) =
|
|||||||
else -> "$counter"
|
else -> "$counter"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun countToHumanReadableBytes(counter: Long) =
|
||||||
|
when {
|
||||||
|
counter >= 1000000000 -> "${Math.round(counter / 1000000000f)} GB"
|
||||||
|
counter >= 1000000 -> "${Math.round(counter / 1000000f)} MB"
|
||||||
|
counter >= 1000 -> "${Math.round(counter / 1000f)} KB"
|
||||||
|
else -> "$counter"
|
||||||
|
}
|
||||||
|
|
||||||
fun countToHumanReadable(
|
fun countToHumanReadable(
|
||||||
counter: Int,
|
counter: Int,
|
||||||
str: String,
|
str: String,
|
||||||
@@ -37,3 +45,13 @@ fun countToHumanReadable(
|
|||||||
counter >= 1000 -> "${Math.round(counter / 1000f)}K $str"
|
counter >= 1000 -> "${Math.round(counter / 1000f)}K $str"
|
||||||
else -> "$counter $str"
|
else -> "$counter $str"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun countToHumanReadable(
|
||||||
|
counter: Long,
|
||||||
|
str: String,
|
||||||
|
) = when {
|
||||||
|
counter >= 1000000000 -> "${Math.round(counter / 1000000000f)}G $str"
|
||||||
|
counter >= 1000000 -> "${Math.round(counter / 1000000f)}M $str"
|
||||||
|
counter >= 1000 -> "${Math.round(counter / 1000f)}K $str"
|
||||||
|
else -> "$counter $str"
|
||||||
|
}
|
||||||
|
|||||||
+15
-11
@@ -73,6 +73,7 @@ import com.vitorpamplona.amethyst.service.Nip11CachedRetriever
|
|||||||
import com.vitorpamplona.amethyst.service.Nip11Retriever
|
import com.vitorpamplona.amethyst.service.Nip11Retriever
|
||||||
import com.vitorpamplona.amethyst.service.relays.Constants
|
import com.vitorpamplona.amethyst.service.relays.Constants
|
||||||
import com.vitorpamplona.amethyst.service.relays.FeedType
|
import com.vitorpamplona.amethyst.service.relays.FeedType
|
||||||
|
import com.vitorpamplona.amethyst.service.relays.RelayStat
|
||||||
import com.vitorpamplona.amethyst.ui.actions.RelayInfoDialog
|
import com.vitorpamplona.amethyst.ui.actions.RelayInfoDialog
|
||||||
import com.vitorpamplona.amethyst.ui.note.RenderRelayIcon
|
import com.vitorpamplona.amethyst.ui.note.RenderRelayIcon
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
@@ -151,10 +152,13 @@ fun ServerConfigPreview() {
|
|||||||
url = "nostr.mom",
|
url = "nostr.mom",
|
||||||
read = true,
|
read = true,
|
||||||
write = true,
|
write = true,
|
||||||
errorCount = 23,
|
relayStat =
|
||||||
downloadCountInBytes = 10000,
|
RelayStat(
|
||||||
uploadCountInBytes = 10000000,
|
errorCounter = 23,
|
||||||
spamCount = 10,
|
receivedBytes = 10000,
|
||||||
|
sentBytes = 10000000,
|
||||||
|
spamCounter = 10,
|
||||||
|
),
|
||||||
feedTypes = Constants.activeTypesGlobalChats,
|
feedTypes = Constants.activeTypesGlobalChats,
|
||||||
paidRelay = true,
|
paidRelay = true,
|
||||||
),
|
),
|
||||||
@@ -370,7 +374,7 @@ private fun StatusRow(
|
|||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = countToHumanReadableBytes(item.downloadCountInBytes),
|
text = countToHumanReadableBytes(item.relayStat.receivedBytes),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
@@ -406,7 +410,7 @@ private fun StatusRow(
|
|||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = countToHumanReadableBytes(item.uploadCountInBytes),
|
text = countToHumanReadableBytes(item.relayStat.sentBytes),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
@@ -432,7 +436,7 @@ private fun StatusRow(
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
tint =
|
tint =
|
||||||
if (item.errorCount > 0) {
|
if (item.relayStat.errorCounter > 0) {
|
||||||
MaterialTheme.colorScheme.warningColor
|
MaterialTheme.colorScheme.warningColor
|
||||||
} else {
|
} else {
|
||||||
MaterialTheme.colorScheme.allGoodColor
|
MaterialTheme.colorScheme.allGoodColor
|
||||||
@@ -440,7 +444,7 @@ private fun StatusRow(
|
|||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = countToHumanReadable(item.errorCount, "errors"),
|
text = countToHumanReadable(item.relayStat.errorCounter, "errors"),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
@@ -466,7 +470,7 @@ private fun StatusRow(
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
tint =
|
tint =
|
||||||
if (item.spamCount > 0) {
|
if (item.relayStat.spamCounter > 0) {
|
||||||
MaterialTheme.colorScheme.warningColor
|
MaterialTheme.colorScheme.warningColor
|
||||||
} else {
|
} else {
|
||||||
MaterialTheme.colorScheme.allGoodColor
|
MaterialTheme.colorScheme.allGoodColor
|
||||||
@@ -474,7 +478,7 @@ private fun StatusRow(
|
|||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = countToHumanReadable(item.spamCount, "spam"),
|
text = countToHumanReadable(item.relayStat.spamCounter, "spam"),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
@@ -739,7 +743,7 @@ fun Kind3RelayEditBox(
|
|||||||
addedWSS,
|
addedWSS,
|
||||||
read,
|
read,
|
||||||
write,
|
write,
|
||||||
feedTypes = FeedType.values().toSet(),
|
feedTypes = FeedType.entries.toSet(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
url = ""
|
url = ""
|
||||||
|
|||||||
+5
-24
@@ -27,7 +27,7 @@ import com.vitorpamplona.amethyst.model.RelaySetupInfo
|
|||||||
import com.vitorpamplona.amethyst.service.Nip11CachedRetriever
|
import com.vitorpamplona.amethyst.service.Nip11CachedRetriever
|
||||||
import com.vitorpamplona.amethyst.service.relays.Constants
|
import com.vitorpamplona.amethyst.service.relays.Constants
|
||||||
import com.vitorpamplona.amethyst.service.relays.FeedType
|
import com.vitorpamplona.amethyst.service.relays.FeedType
|
||||||
import com.vitorpamplona.amethyst.service.relays.RelayPool
|
import com.vitorpamplona.amethyst.service.relays.RelayStats
|
||||||
import kotlinx.collections.immutable.toImmutableSet
|
import kotlinx.collections.immutable.toImmutableSet
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
@@ -80,7 +80,6 @@ class Kind3RelayListViewModel : ViewModel() {
|
|||||||
if (relayFile != null) {
|
if (relayFile != null) {
|
||||||
relayFile
|
relayFile
|
||||||
.map {
|
.map {
|
||||||
val liveRelay = RelayPool.getRelay(it.key)
|
|
||||||
val localInfoFeedTypes =
|
val localInfoFeedTypes =
|
||||||
account.localRelays
|
account.localRelays
|
||||||
.filter { localRelay -> localRelay.url == it.key }
|
.filter { localRelay -> localRelay.url == it.key }
|
||||||
@@ -92,48 +91,30 @@ class Kind3RelayListViewModel : ViewModel() {
|
|||||||
?.feedTypes
|
?.feedTypes
|
||||||
?: FeedType.values().toSet().toImmutableSet()
|
?: FeedType.values().toSet().toImmutableSet()
|
||||||
|
|
||||||
val errorCounter = liveRelay?.errorCounter ?: 0
|
|
||||||
val eventDownloadCounter = liveRelay?.eventDownloadCounterInBytes ?: 0
|
|
||||||
val eventUploadCounter = liveRelay?.eventUploadCounterInBytes ?: 0
|
|
||||||
val spamCounter = liveRelay?.spamCounter ?: 0
|
|
||||||
|
|
||||||
RelaySetupInfo(
|
RelaySetupInfo(
|
||||||
it.key,
|
it.key,
|
||||||
it.value.read,
|
it.value.read,
|
||||||
it.value.write,
|
it.value.write,
|
||||||
errorCounter,
|
RelayStats.get(it.key),
|
||||||
eventDownloadCounter,
|
|
||||||
eventUploadCounter,
|
|
||||||
spamCounter,
|
|
||||||
localInfoFeedTypes,
|
localInfoFeedTypes,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.distinctBy { it.url }
|
.distinctBy { it.url }
|
||||||
.sortedBy { it.downloadCountInBytes }
|
.sortedBy { it.relayStat.receivedBytes }
|
||||||
.reversed()
|
.reversed()
|
||||||
} else {
|
} else {
|
||||||
account.localRelays
|
account.localRelays
|
||||||
.map {
|
.map {
|
||||||
val liveRelay = RelayPool.getRelay(it.url)
|
|
||||||
|
|
||||||
val errorCounter = liveRelay?.errorCounter ?: 0
|
|
||||||
val eventDownloadCounter = liveRelay?.eventDownloadCounterInBytes ?: 0
|
|
||||||
val eventUploadCounter = liveRelay?.eventUploadCounterInBytes ?: 0
|
|
||||||
val spamCounter = liveRelay?.spamCounter ?: 0
|
|
||||||
|
|
||||||
RelaySetupInfo(
|
RelaySetupInfo(
|
||||||
it.url,
|
it.url,
|
||||||
it.read,
|
it.read,
|
||||||
it.write,
|
it.write,
|
||||||
errorCounter,
|
RelayStats.get(it.url),
|
||||||
eventDownloadCounter,
|
|
||||||
eventUploadCounter,
|
|
||||||
spamCounter,
|
|
||||||
it.feedTypes,
|
it.feedTypes,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.distinctBy { it.url }
|
.distinctBy { it.url }
|
||||||
.sortedBy { it.downloadCountInBytes }
|
.sortedBy { it.relayStat.receivedBytes }
|
||||||
.reversed()
|
.reversed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-23
@@ -24,7 +24,7 @@ import androidx.lifecycle.ViewModel
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.service.Nip11CachedRetriever
|
import com.vitorpamplona.amethyst.service.Nip11CachedRetriever
|
||||||
import com.vitorpamplona.amethyst.service.relays.RelayPool
|
import com.vitorpamplona.amethyst.service.relays.RelayStats
|
||||||
import com.vitorpamplona.quartz.events.AdvertisedRelayListEvent
|
import com.vitorpamplona.quartz.events.AdvertisedRelayListEvent
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
@@ -105,40 +105,22 @@ class Nip65RelayListViewModel : ViewModel() {
|
|||||||
val relayList = account.getNIP65RelayList()?.writeRelays() ?: emptyList()
|
val relayList = account.getNIP65RelayList()?.writeRelays() ?: emptyList()
|
||||||
|
|
||||||
relayList.map { relayUrl ->
|
relayList.map { relayUrl ->
|
||||||
val liveRelay = RelayPool.getRelay(relayUrl)
|
|
||||||
val errorCounter = liveRelay?.errorCounter ?: 0
|
|
||||||
val eventDownloadCounter = liveRelay?.eventDownloadCounterInBytes ?: 0
|
|
||||||
val eventUploadCounter = liveRelay?.eventUploadCounterInBytes ?: 0
|
|
||||||
val spamCounter = liveRelay?.spamCounter ?: 0
|
|
||||||
|
|
||||||
BasicRelaySetupInfo(
|
BasicRelaySetupInfo(
|
||||||
relayUrl,
|
relayUrl,
|
||||||
errorCounter,
|
RelayStats.get(relayUrl),
|
||||||
eventDownloadCounter,
|
|
||||||
eventUploadCounter,
|
|
||||||
spamCounter,
|
|
||||||
)
|
)
|
||||||
}.distinctBy { it.url }.sortedBy { it.downloadCountInBytes }.reversed()
|
}.distinctBy { it.url }.sortedBy { it.relayStat.receivedBytes }.reversed()
|
||||||
}
|
}
|
||||||
|
|
||||||
_notificationRelays.update {
|
_notificationRelays.update {
|
||||||
val relayList = account.getNIP65RelayList()?.readRelays() ?: emptyList()
|
val relayList = account.getNIP65RelayList()?.readRelays() ?: emptyList()
|
||||||
|
|
||||||
relayList.map { relayUrl ->
|
relayList.map { relayUrl ->
|
||||||
val liveRelay = RelayPool.getRelay(relayUrl)
|
|
||||||
val errorCounter = liveRelay?.errorCounter ?: 0
|
|
||||||
val eventDownloadCounter = liveRelay?.eventDownloadCounterInBytes ?: 0
|
|
||||||
val eventUploadCounter = liveRelay?.eventUploadCounterInBytes ?: 0
|
|
||||||
val spamCounter = liveRelay?.spamCounter ?: 0
|
|
||||||
|
|
||||||
BasicRelaySetupInfo(
|
BasicRelaySetupInfo(
|
||||||
relayUrl,
|
relayUrl,
|
||||||
errorCounter,
|
RelayStats.get(relayUrl),
|
||||||
eventDownloadCounter,
|
|
||||||
eventUploadCounter,
|
|
||||||
spamCounter,
|
|
||||||
)
|
)
|
||||||
}.distinctBy { it.url }.sortedBy { it.downloadCountInBytes }.reversed()
|
}.distinctBy { it.url }.sortedBy { it.relayStat.receivedBytes }.reversed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ fun RelayStatusRow(
|
|||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = countToHumanReadableBytes(item.downloadCountInBytes),
|
text = countToHumanReadableBytes(item.relayStat.receivedBytes),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
@@ -101,7 +101,7 @@ fun RelayStatusRow(
|
|||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = countToHumanReadableBytes(item.uploadCountInBytes),
|
text = countToHumanReadableBytes(item.relayStat.sentBytes),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
@@ -124,7 +124,7 @@ fun RelayStatusRow(
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
tint =
|
tint =
|
||||||
if (item.errorCount > 0) {
|
if (item.relayStat.errorCounter > 0) {
|
||||||
MaterialTheme.colorScheme.warningColor
|
MaterialTheme.colorScheme.warningColor
|
||||||
} else {
|
} else {
|
||||||
MaterialTheme.colorScheme.allGoodColor
|
MaterialTheme.colorScheme.allGoodColor
|
||||||
@@ -132,7 +132,7 @@ fun RelayStatusRow(
|
|||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = countToHumanReadable(item.errorCount, "errors"),
|
text = countToHumanReadable(item.relayStat.errorCounter, "errors"),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
@@ -163,7 +163,7 @@ fun RelayStatusRow(
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
tint =
|
tint =
|
||||||
if (item.spamCount > 0) {
|
if (item.relayStat.spamCounter > 0) {
|
||||||
MaterialTheme.colorScheme.warningColor
|
MaterialTheme.colorScheme.warningColor
|
||||||
} else {
|
} else {
|
||||||
MaterialTheme.colorScheme.allGoodColor
|
MaterialTheme.colorScheme.allGoodColor
|
||||||
@@ -171,7 +171,7 @@ fun RelayStatusRow(
|
|||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = countToHumanReadable(item.spamCount, "spam"),
|
text = countToHumanReadable(item.relayStat.spamCounter, "spam"),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.service.relays.RelayStat
|
||||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size10dp
|
import com.vitorpamplona.amethyst.ui.theme.Size10dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||||
@@ -65,8 +66,7 @@ fun RelayUrlEditField(onNewRelay: (BasicRelaySetupInfo) -> Unit) {
|
|||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (url.isNotBlank() && url != "/") {
|
if (url.isNotBlank() && url != "/") {
|
||||||
val addedWSS = RelayUrlFormatter.normalize(url)
|
onNewRelay(BasicRelaySetupInfo(RelayUrlFormatter.normalize(url), RelayStat()))
|
||||||
onNewRelay(BasicRelaySetupInfo(addedWSS))
|
|
||||||
url = ""
|
url = ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user