Merge branch 'vitorpamplona:main' into profiles-list-management
This commit is contained in:
@@ -69,7 +69,6 @@ import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
import kotlin.run
|
||||
|
||||
class AppModules(
|
||||
val appContext: Context,
|
||||
@@ -178,9 +177,6 @@ class AppModules(
|
||||
// Caches all events in Memory
|
||||
val cache: LocalCache = LocalCache
|
||||
|
||||
// Organizes cache clearing
|
||||
val trimmingService = MemoryTrimmingService(cache)
|
||||
|
||||
// Provides a relay pool
|
||||
val client: INostrClient = NostrClient(websocketBuilder, applicationDefaultScope)
|
||||
|
||||
@@ -215,6 +211,9 @@ class AppModules(
|
||||
client = client,
|
||||
)
|
||||
|
||||
// Organizes cache clearing
|
||||
val trimmingService = MemoryTrimmingService(cache)
|
||||
|
||||
// as new accounts are loaded, updates the state of the TorRelaySettings, which produces new TorRelayEvaluator
|
||||
// and reconnects relays if the configuration has been changed.
|
||||
val accountsTorStateConnector = AccountsTorStateConnector(accountsCache, torEvaluatorFlow, applicationDefaultScope)
|
||||
@@ -283,7 +282,8 @@ class AppModules(
|
||||
|
||||
fun trim() {
|
||||
applicationDefaultScope.launch {
|
||||
trimmingService.run(null, LocalPreferences.allSavedAccounts())
|
||||
val loggedIn = accountsCache.accounts.value.values
|
||||
trimmingService.run(loggedIn, LocalPreferences.allSavedAccounts())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,6 +106,42 @@ fun debugState(context: Context) {
|
||||
" / " +
|
||||
LocalCache.users.size(),
|
||||
)
|
||||
Log.d(
|
||||
"STATE DUMP",
|
||||
"Public Chat Channels: " +
|
||||
LocalCache.publicChatChannels.filter { _, it -> it.flowSet != null }.size +
|
||||
" / " +
|
||||
LocalCache.publicChatChannels.size() +
|
||||
" / " +
|
||||
LocalCache.publicChatChannels.values().sumOf { it.notes.size() },
|
||||
)
|
||||
Log.d(
|
||||
"STATE DUMP",
|
||||
"Live Chat Channels: " +
|
||||
LocalCache.liveChatChannels.filter { _, it -> it.flowSet != null }.size +
|
||||
" / " +
|
||||
LocalCache.liveChatChannels.size() +
|
||||
" / " +
|
||||
LocalCache.liveChatChannels.values().sumOf { it.notes.size() },
|
||||
)
|
||||
Log.d(
|
||||
"STATE DUMP",
|
||||
"Ephemeral Chat Channels: " +
|
||||
LocalCache.ephemeralChannels.filter { _, it -> it.flowSet != null }.size +
|
||||
" / " +
|
||||
LocalCache.ephemeralChannels.size() +
|
||||
" / " +
|
||||
LocalCache.ephemeralChannels.values().sumOf { it.notes.size() },
|
||||
)
|
||||
LocalCache.chatroomList.forEach { key, room ->
|
||||
Log.d(
|
||||
"STATE DUMP",
|
||||
"Private Chats $key: " +
|
||||
room.rooms.size() +
|
||||
" / " +
|
||||
room.rooms.sumOf { key, value -> value.messages.size },
|
||||
)
|
||||
}
|
||||
Log.d(
|
||||
"STATE DUMP",
|
||||
"Deletion Events: " +
|
||||
@@ -128,7 +164,7 @@ fun debugState(context: Context) {
|
||||
Log.d(
|
||||
"STATE DUMP",
|
||||
"Memory used by Events: " +
|
||||
LocalCache.notes.sumOfLong { _, note -> note.event?.countMemory() ?: 0L } / (1024 * 1024) +
|
||||
LocalCache.notes.sumOf { _, note -> note.event?.countMemory() ?: 0 } / (1024 * 1024) +
|
||||
" MB",
|
||||
)
|
||||
|
||||
@@ -137,10 +173,10 @@ fun debugState(context: Context) {
|
||||
|
||||
val bytesNotes =
|
||||
LocalCache.notes
|
||||
.sumByGroup(groupMap = { _, it -> it.event?.kind }, sumOf = { _, it -> it.event?.countMemory() ?: 0L })
|
||||
.sumByGroup(groupMap = { _, it -> it.event?.kind }, sumOf = { _, it -> it.event?.countMemory()?.toLong() ?: 0L })
|
||||
val bytesAddressables =
|
||||
LocalCache.addressables
|
||||
.sumByGroup(groupMap = { _, it -> it.event?.kind }, sumOf = { _, it -> it.event?.countMemory() ?: 0L })
|
||||
.sumByGroup(groupMap = { _, it -> it.event?.kind }, sumOf = { _, it -> it.event?.countMemory()?.toLong() ?: 0L })
|
||||
|
||||
qttNotes.toList().sortedByDescending { bytesNotes[it.first] }.forEach { (kind, qtt) ->
|
||||
Log.d("STATE DUMP", "Kind ${kind.toString().padStart(5,' ')}:\t${qtt.toString().padStart(6,' ')} elements\t${bytesNotes[kind]?.div((1024 * 1024))}MB ")
|
||||
|
||||
@@ -97,6 +97,9 @@ val GLOBAL_FOLLOWS = " Global "
|
||||
// This has spaces to avoid mixing with a potential NIP-51 list with the same name.
|
||||
val ALL_FOLLOWS = " All Follows "
|
||||
|
||||
// This has spaces to avoid mixing with a potential NIP-51 list with the same name.
|
||||
val ALL_USER_FOLLOWS = " All User Follows "
|
||||
|
||||
// This has spaces to avoid mixing with a potential NIP-51 list with the same name.
|
||||
val AROUND_ME = " Around Me "
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ abstract class Channel : NotesGatherer {
|
||||
fun changesFlow(): MutableSharedFlow<ListChange<Note>> {
|
||||
val current = changesFlow.get()
|
||||
if (current != null) return current
|
||||
val new = MutableSharedFlow<ListChange<Note>>(0, 100, BufferOverflow.DROP_OLDEST)
|
||||
val new = MutableSharedFlow<ListChange<Note>>(0, 10, BufferOverflow.DROP_OLDEST)
|
||||
changesFlow = WeakReference(new)
|
||||
return new
|
||||
}
|
||||
@@ -96,7 +96,7 @@ abstract class Channel : NotesGatherer {
|
||||
notes.put(note.idHex, note)
|
||||
note.addGatherer(this)
|
||||
|
||||
if ((note.createdAt() ?: 0) > (lastNote?.createdAt() ?: 0)) {
|
||||
if ((note.createdAt() ?: 0L) > (lastNote?.createdAt() ?: 0L)) {
|
||||
lastNote = note
|
||||
}
|
||||
|
||||
|
||||
@@ -700,7 +700,7 @@ object LocalCache : ILocalCache {
|
||||
if (isVerified || justVerify(event)) {
|
||||
val replyTo = computeReplyTo(event)
|
||||
|
||||
if (event.createdAt > (note.createdAt() ?: 0)) {
|
||||
if (event.createdAt > (note.createdAt() ?: 0L)) {
|
||||
note.loadEvent(event, author, replyTo)
|
||||
|
||||
refreshNewNoteObservers(note)
|
||||
@@ -743,7 +743,7 @@ object LocalCache : ILocalCache {
|
||||
}
|
||||
|
||||
if (isVerified || justVerify(event)) {
|
||||
if (event.createdAt > (note.createdAt() ?: 0)) {
|
||||
if (event.createdAt > (note.createdAt() ?: 0L)) {
|
||||
val replyTo = computeReplyTo(event)
|
||||
|
||||
note.loadEvent(event, author, replyTo)
|
||||
@@ -845,7 +845,7 @@ object LocalCache : ILocalCache {
|
||||
|
||||
if (note.event?.id == event.id) return false
|
||||
|
||||
if (event.createdAt > (note.createdAt() ?: 0) && (isVerified || justVerify(event))) {
|
||||
if (event.createdAt > (note.createdAt() ?: 0L) && (isVerified || justVerify(event))) {
|
||||
note.loadEvent(event, author, emptyList())
|
||||
|
||||
val channel = getOrCreateLiveChannel(note.address)
|
||||
@@ -1073,7 +1073,7 @@ object LocalCache : ILocalCache {
|
||||
// Already processed this event.
|
||||
if (note.event?.id == event.id) return false
|
||||
|
||||
if (event.createdAt > (note.createdAt() ?: 0) && (isVerified || justVerify(event))) {
|
||||
if (event.createdAt > (note.createdAt() ?: 0L) && (isVerified || justVerify(event))) {
|
||||
note.loadEvent(event, author, emptyList())
|
||||
|
||||
author.flowSet?.statuses?.invalidateData()
|
||||
@@ -1196,7 +1196,7 @@ object LocalCache : ILocalCache {
|
||||
// Already processed this event.
|
||||
if (replaceableNote.event?.id == event.id) return isVerified
|
||||
|
||||
if (event.createdAt > (replaceableNote.createdAt() ?: 0) && (isVerified || justVerify(event))) {
|
||||
if (event.createdAt > (replaceableNote.createdAt() ?: 0L) && (isVerified || justVerify(event))) {
|
||||
// clear index from previous tags
|
||||
replaceableNote.replyTo?.forEach {
|
||||
it.removeNote(replaceableNote)
|
||||
@@ -1257,7 +1257,7 @@ object LocalCache : ILocalCache {
|
||||
val deleteNoteEvent = deleteNote.event
|
||||
if (deleteNoteEvent is AddressableEvent) {
|
||||
val addressableNote = getAddressableNoteIfExists(deleteNoteEvent.addressTag())
|
||||
if (addressableNote?.author?.pubkeyHex == event.pubKey && (addressableNote.createdAt() ?: 0) <= event.createdAt) {
|
||||
if (addressableNote?.author?.pubkeyHex == event.pubKey && (addressableNote.createdAt() ?: 0L) <= event.createdAt) {
|
||||
// Counts the replies
|
||||
deleteNote(addressableNote)
|
||||
|
||||
@@ -1279,7 +1279,7 @@ object LocalCache : ILocalCache {
|
||||
.mapNotNull { getAddressableNoteIfExists(it) }
|
||||
.forEach { deleteNote ->
|
||||
// must be the same author
|
||||
if (deleteNote.author?.pubkeyHex == event.pubKey && (deleteNote.createdAt() ?: 0) <= event.createdAt) {
|
||||
if (deleteNote.author?.pubkeyHex == event.pubKey && (deleteNote.createdAt() ?: 0L) <= event.createdAt) {
|
||||
// Counts the replies
|
||||
deleteNote(deleteNote)
|
||||
|
||||
|
||||
@@ -746,7 +746,7 @@ open class Note(
|
||||
val dayAgo = TimeUtils.oneDayAgo()
|
||||
return reports.isNotEmpty() ||
|
||||
(
|
||||
author?.reports?.any { it.value.firstOrNull { (it.createdAt() ?: 0) > dayAgo } != null }
|
||||
author?.reports?.any { it.value.firstOrNull { (it.createdAt() ?: 0L) > dayAgo } != null }
|
||||
?: false
|
||||
)
|
||||
}
|
||||
@@ -778,14 +778,14 @@ open class Note(
|
||||
fun hasBoostedInTheLast5Minutes(loggedIn: User): Boolean {
|
||||
val fiveMinsAgo = TimeUtils.fiveMinutesAgo()
|
||||
return boosts.any {
|
||||
it.author == loggedIn && (it.createdAt() ?: 0) > fiveMinsAgo
|
||||
it.author == loggedIn && (it.createdAt() ?: 0L) > fiveMinsAgo
|
||||
}
|
||||
}
|
||||
|
||||
fun hasBoostedInTheLast5Minutes(loggedIn: HexKey): Boolean {
|
||||
val fiveMinsAgo = TimeUtils.fiveMinutesAgo()
|
||||
return boosts.any {
|
||||
(it.createdAt() ?: 0) > fiveMinsAgo && it.author?.pubkeyHex == loggedIn
|
||||
(it.createdAt() ?: 0L) > fiveMinsAgo && it.author?.pubkeyHex == loggedIn
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -70,14 +70,14 @@ class Chatroom : NotesGatherer {
|
||||
}
|
||||
}
|
||||
|
||||
val createdAt = msg.createdAt() ?: 0
|
||||
if (createdAt > (newestMessage?.createdAt() ?: 0)) {
|
||||
val createdAt = msg.createdAt() ?: 0L
|
||||
if (createdAt > (newestMessage?.createdAt() ?: 0L)) {
|
||||
newestMessage = msg
|
||||
}
|
||||
|
||||
val newSubject = msg.event?.subject()
|
||||
|
||||
if (newSubject != null && (msg.createdAt() ?: 0) > (subjectCreatedAt ?: 0)) {
|
||||
if (newSubject != null && (msg.createdAt() ?: 0L) > (subjectCreatedAt ?: 0)) {
|
||||
subject.tryEmit(newSubject)
|
||||
subjectCreatedAt = msg.createdAt()
|
||||
}
|
||||
@@ -96,7 +96,7 @@ class Chatroom : NotesGatherer {
|
||||
msg.removeGatherer(this)
|
||||
|
||||
if (msg == newestMessage) {
|
||||
newestMessage = messages.maxByOrNull { it.createdAt() ?: 0 }
|
||||
newestMessage = messages.maxByOrNull { it.createdAt() ?: 0L }
|
||||
}
|
||||
|
||||
if (msg.event?.subject() == subject.value) {
|
||||
@@ -127,7 +127,7 @@ class Chatroom : NotesGatherer {
|
||||
val sorted = messages.sortedWith(DefaultFeedOrder)
|
||||
|
||||
val toKeep =
|
||||
if ((sorted.firstOrNull()?.createdAt() ?: 0) > TimeUtils.oneWeekAgo()) {
|
||||
if ((sorted.firstOrNull()?.createdAt() ?: 0L) > TimeUtils.oneWeekAgo()) {
|
||||
// Recent messages, keep last 100
|
||||
sorted.take(100).toSet()
|
||||
} else {
|
||||
|
||||
+3
@@ -21,11 +21,13 @@
|
||||
package com.vitorpamplona.amethyst.model.topNavFeeds
|
||||
|
||||
import com.vitorpamplona.amethyst.model.ALL_FOLLOWS
|
||||
import com.vitorpamplona.amethyst.model.ALL_USER_FOLLOWS
|
||||
import com.vitorpamplona.amethyst.model.AROUND_ME
|
||||
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.nip02FollowLists.FollowListState
|
||||
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsFeedFlow
|
||||
import com.vitorpamplona.amethyst.model.topNavFeeds.allUserFollows.AllUserFollowsFeedFlow
|
||||
import com.vitorpamplona.amethyst.model.topNavFeeds.aroundMe.AroundMeFeedFlow
|
||||
import com.vitorpamplona.amethyst.model.topNavFeeds.global.GlobalFeedFlow
|
||||
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.NoteFeedFlow
|
||||
@@ -60,6 +62,7 @@ class FeedTopNavFilterState(
|
||||
when (listName) {
|
||||
GLOBAL_FOLLOWS -> GlobalFeedFlow(followsRelays, proxyRelays)
|
||||
ALL_FOLLOWS -> AllFollowsFeedFlow(allFollows, followsRelays, blockedRelays, proxyRelays)
|
||||
ALL_USER_FOLLOWS -> AllUserFollowsFeedFlow(allFollows, followsRelays, blockedRelays, proxyRelays)
|
||||
AROUND_ME -> AroundMeFeedFlow(locationFlow, followsRelays, proxyRelays)
|
||||
else -> {
|
||||
val note = LocalCache.checkGetOrCreateAddressableNote(listName)
|
||||
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.model.topNavFeeds.allUserFollows
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavFilter
|
||||
import com.vitorpamplona.amethyst.model.topNavFeeds.OutboxRelayLoader
|
||||
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsTopNavPerRelayFilter
|
||||
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsTopNavPerRelayFilterSet
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
|
||||
/**
|
||||
* This is a big OR filter on all fields.
|
||||
*/
|
||||
@Immutable
|
||||
class AllUserFollowsByOutboxTopNavFilter(
|
||||
val authors: Set<String>,
|
||||
val defaultRelays: StateFlow<Set<NormalizedRelayUrl>>,
|
||||
val blockedRelays: StateFlow<Set<NormalizedRelayUrl>>,
|
||||
) : IFeedTopNavFilter {
|
||||
override fun matchAuthor(pubkey: HexKey): Boolean = pubkey in authors
|
||||
|
||||
override fun match(noteEvent: Event): Boolean =
|
||||
when (noteEvent) {
|
||||
is LiveActivitiesEvent -> {
|
||||
noteEvent.participantsIntersect(authors)
|
||||
}
|
||||
|
||||
is CommentEvent -> {
|
||||
// ignore follows and checks only the root scope
|
||||
noteEvent.pubKey in authors
|
||||
}
|
||||
|
||||
else -> {
|
||||
noteEvent.pubKey in authors
|
||||
}
|
||||
}
|
||||
|
||||
override fun toPerRelayFlow(cache: LocalCache): Flow<AuthorsTopNavPerRelayFilterSet> {
|
||||
val authorsPerRelay = OutboxRelayLoader().toAuthorsPerRelayFlow(authors, cache) { it }
|
||||
|
||||
return combine(authorsPerRelay, defaultRelays, blockedRelays) { perRelayAuthors, default, blockedRelays ->
|
||||
val allRelays = perRelayAuthors.keys.filter { it !in blockedRelays }.ifEmpty { default }
|
||||
|
||||
AuthorsTopNavPerRelayFilterSet(
|
||||
allRelays.associateWith {
|
||||
AuthorsTopNavPerRelayFilter(
|
||||
authors = perRelayAuthors[it] ?: emptySet(),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun startValue(cache: LocalCache): AuthorsTopNavPerRelayFilterSet {
|
||||
val authorsPerRelay = OutboxRelayLoader().authorsPerRelaySnapshot(authors, cache) { it }
|
||||
|
||||
val allRelays = authorsPerRelay.keys.filter { it !in blockedRelays.value }.ifEmpty { defaultRelays.value }
|
||||
|
||||
return AuthorsTopNavPerRelayFilterSet(
|
||||
allRelays.associateWith {
|
||||
AuthorsTopNavPerRelayFilter(
|
||||
authors = authorsPerRelay[it] ?: emptySet(),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.model.topNavFeeds.allUserFollows
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavFilter
|
||||
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsTopNavPerRelayFilter
|
||||
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsTopNavPerRelayFilterSet
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
/**
|
||||
* This is a big OR filter on all fields.
|
||||
*/
|
||||
@Immutable
|
||||
class AllUserFollowsByProxyTopNavFilter(
|
||||
val authors: Set<String>,
|
||||
val proxyRelays: Set<NormalizedRelayUrl>,
|
||||
) : IFeedTopNavFilter {
|
||||
override fun matchAuthor(pubkey: HexKey): Boolean = pubkey in authors
|
||||
|
||||
override fun match(noteEvent: Event): Boolean =
|
||||
when (noteEvent) {
|
||||
is LiveActivitiesEvent -> {
|
||||
noteEvent.participantsIntersect(authors)
|
||||
}
|
||||
|
||||
is CommentEvent -> {
|
||||
// ignore follows and checks only the root scope
|
||||
noteEvent.pubKey in authors
|
||||
}
|
||||
|
||||
else -> {
|
||||
noteEvent.pubKey in authors
|
||||
}
|
||||
}
|
||||
|
||||
// forces the use of the Proxy on all connections, replacing the outbox model.
|
||||
override fun toPerRelayFlow(cache: LocalCache): Flow<AuthorsTopNavPerRelayFilterSet> =
|
||||
MutableStateFlow(
|
||||
AuthorsTopNavPerRelayFilterSet(
|
||||
proxyRelays.associateWith {
|
||||
AuthorsTopNavPerRelayFilter(
|
||||
authors = authors,
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
override fun startValue(cache: LocalCache): AuthorsTopNavPerRelayFilterSet {
|
||||
// forces the use of the Proxy on all connections, replacing the outbox model.
|
||||
return AuthorsTopNavPerRelayFilterSet(
|
||||
proxyRelays.associateWith {
|
||||
AuthorsTopNavPerRelayFilter(
|
||||
authors = authors,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.model.topNavFeeds.allUserFollows
|
||||
|
||||
import com.vitorpamplona.amethyst.model.nip02FollowLists.FollowListState
|
||||
import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedFlowsType
|
||||
import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import kotlinx.coroutines.flow.FlowCollector
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
|
||||
class AllUserFollowsFeedFlow(
|
||||
val allFollows: StateFlow<FollowListState.Kind3Follows?>,
|
||||
val followsRelays: StateFlow<Set<NormalizedRelayUrl>>,
|
||||
val blockedRelays: StateFlow<Set<NormalizedRelayUrl>>,
|
||||
val proxyRelays: StateFlow<Set<NormalizedRelayUrl>>,
|
||||
) : IFeedFlowsType {
|
||||
fun convert(
|
||||
kind3: FollowListState.Kind3Follows?,
|
||||
proxyRelays: Set<NormalizedRelayUrl>,
|
||||
): IFeedTopNavFilter =
|
||||
if (kind3 != null) {
|
||||
if (proxyRelays.isEmpty()) {
|
||||
AllUserFollowsByOutboxTopNavFilter(
|
||||
authors = kind3.authors,
|
||||
defaultRelays = followsRelays,
|
||||
blockedRelays = blockedRelays,
|
||||
)
|
||||
} else {
|
||||
AllUserFollowsByProxyTopNavFilter(
|
||||
authors = kind3.authors,
|
||||
proxyRelays = proxyRelays,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
AllUserFollowsByOutboxTopNavFilter(
|
||||
authors = emptySet(),
|
||||
defaultRelays = followsRelays,
|
||||
blockedRelays = blockedRelays,
|
||||
)
|
||||
}
|
||||
|
||||
override fun flow() = combine(allFollows, proxyRelays, ::convert)
|
||||
|
||||
override fun startValue(): IFeedTopNavFilter = convert(allFollows.value, proxyRelays.value)
|
||||
|
||||
override suspend fun startValue(collector: FlowCollector<IFeedTopNavFilter>) {
|
||||
collector.emit(startValue())
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -33,13 +33,13 @@ class MemoryTrimmingService(
|
||||
var isTrimmingMemoryMutex = AtomicBoolean(false)
|
||||
|
||||
private suspend fun doTrim(
|
||||
account: Account? = null,
|
||||
account: Collection<Account>,
|
||||
otherAccounts: List<AccountInfo>,
|
||||
) {
|
||||
cache.cleanMemory()
|
||||
cache.cleanObservers()
|
||||
|
||||
account?.let {
|
||||
account.forEach {
|
||||
cache.pruneHiddenEvents(it)
|
||||
cache.pruneHiddenMessages(it)
|
||||
}
|
||||
@@ -53,7 +53,7 @@ class MemoryTrimmingService(
|
||||
}
|
||||
|
||||
suspend fun run(
|
||||
account: Account?,
|
||||
account: Collection<Account>,
|
||||
otherAccounts: List<AccountInfo>,
|
||||
) {
|
||||
if (isTrimmingMemoryMutex.compareAndSet(false, true)) {
|
||||
|
||||
@@ -72,7 +72,7 @@ class OkHttpWebSocket(
|
||||
webSocket: okhttp3.WebSocket,
|
||||
response: Response,
|
||||
) = out.onOpen(
|
||||
response.receivedResponseAtMillis - response.sentRequestAtMillis,
|
||||
(response.receivedResponseAtMillis - response.sentRequestAtMillis).toInt(),
|
||||
response.headers["Sec-WebSocket-Extensions"]?.contains("permessage-deflate") ?: false,
|
||||
)
|
||||
|
||||
|
||||
-4
@@ -76,10 +76,6 @@ fun filterMissingAddressables(keys: List<EventFinderQueryState>): List<RelayBase
|
||||
potentialRelaysToFindAddress(key.note).ifEmpty { default }.forEach { relayUrl ->
|
||||
add(relayUrl, key.note.address)
|
||||
}
|
||||
|
||||
key.account.searchRelayList.flow.value.forEach { relayUrl ->
|
||||
add(relayUrl, key.note.address)
|
||||
}
|
||||
}
|
||||
|
||||
// loads threading that is event-based
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ class FrameStat {
|
||||
kind: Int,
|
||||
subId: String,
|
||||
relayUrl: NormalizedRelayUrl,
|
||||
memory: Long,
|
||||
memory: Int,
|
||||
) {
|
||||
eventCount.incrementAndGet()
|
||||
|
||||
|
||||
+4
-5
@@ -24,22 +24,21 @@ import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl
|
||||
import com.vitorpamplona.quartz.utils.LargeCache
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import kotlin.concurrent.atomics.ExperimentalAtomicApi
|
||||
|
||||
@OptIn(ExperimentalAtomicApi::class)
|
||||
class KindGroup(
|
||||
var count: AtomicInteger = AtomicInteger(0),
|
||||
var memory: AtomicLong = AtomicLong(0L),
|
||||
var memory: AtomicInteger = AtomicInteger(0),
|
||||
val subs: LargeCache<String, AtomicInteger> = LargeCache<String, AtomicInteger>(),
|
||||
val relays: LargeCache<NormalizedRelayUrl, AtomicInteger> = LargeCache<NormalizedRelayUrl, AtomicInteger>(),
|
||||
) {
|
||||
companion object {
|
||||
const val MB: Long = 1024
|
||||
const val MB: Int = 1024
|
||||
}
|
||||
|
||||
fun increment(
|
||||
mem: Long,
|
||||
mem: Int,
|
||||
subId: String,
|
||||
relayUrl: NormalizedRelayUrl,
|
||||
) {
|
||||
@@ -63,7 +62,7 @@ class KindGroup(
|
||||
|
||||
fun reset() {
|
||||
count.set(0)
|
||||
memory.set(0L)
|
||||
memory.set(0)
|
||||
subs.forEach { key, value -> value.set(0) }
|
||||
relays.forEach { key, value -> value.set(0) }
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@ fun RenderRelayIcon(
|
||||
iconUrl: String?,
|
||||
loadProfilePicture: Boolean,
|
||||
loadRobohash: Boolean,
|
||||
pingInMs: Long,
|
||||
pingInMs: Int,
|
||||
iconModifier: Modifier = MaterialTheme.colorScheme.relayIconModifier,
|
||||
) {
|
||||
val green = MaterialTheme.colorScheme.allGoodColor
|
||||
|
||||
@@ -63,7 +63,7 @@ fun NormalTimeAgo(
|
||||
val nowStr = stringRes(id = R.string.now)
|
||||
|
||||
val time by
|
||||
remember(baseNote) { derivedStateOf { timeAgoShort(baseNote.createdAt() ?: 0, nowStr) } }
|
||||
remember(baseNote) { derivedStateOf { timeAgoShort(baseNote.createdAt() ?: 0L, nowStr) } }
|
||||
|
||||
Text(
|
||||
text = time,
|
||||
|
||||
@@ -104,7 +104,6 @@ fun RenderTextEvent(
|
||||
note,
|
||||
accountViewModel,
|
||||
) { body ->
|
||||
val subject = (note.event as? TextNoteEvent)?.subject()?.ifEmpty { null }
|
||||
val newBody =
|
||||
if (editState.value is GenericLoadable.Loaded) {
|
||||
(editState.value as? GenericLoadable.Loaded)
|
||||
@@ -118,10 +117,13 @@ fun RenderTextEvent(
|
||||
}
|
||||
|
||||
val eventContent =
|
||||
if (!subject.isNullOrBlank() && !newBody.startsWith(subject)) {
|
||||
"### $subject\n$newBody"
|
||||
} else {
|
||||
newBody
|
||||
remember(newBody) {
|
||||
val subject = (note.event as? TextNoteEvent)?.subject()?.ifBlank { null }
|
||||
if (!subject.isNullOrBlank() && !newBody.contains(subject, ignoreCase = true)) {
|
||||
"$subject\n\n$newBody"
|
||||
} else {
|
||||
newBody
|
||||
}
|
||||
}
|
||||
|
||||
if (makeItShort && accountViewModel.isLoggedUser(note.author)) {
|
||||
|
||||
@@ -26,6 +26,7 @@ import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.ALL_FOLLOWS
|
||||
import com.vitorpamplona.amethyst.model.ALL_USER_FOLLOWS
|
||||
import com.vitorpamplona.amethyst.model.AROUND_ME
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||
@@ -82,6 +83,15 @@ class FollowListState(
|
||||
unpackList = listOf(ContactListEvent.blockListFor(account.signer.pubKey)),
|
||||
)
|
||||
|
||||
val kind3FollowUsers =
|
||||
PeopleListOutBoxFeedDefinition(
|
||||
code = ALL_USER_FOLLOWS,
|
||||
name = ResourceName(R.string.follow_list_kind3follows_users_only),
|
||||
type = CodeNameType.HARDCODED,
|
||||
kinds = DEFAULT_FEED_KINDS,
|
||||
unpackList = listOf(ContactListEvent.blockListFor(account.signer.pubKey)),
|
||||
)
|
||||
|
||||
val globalFollow =
|
||||
GlobalFeedDefinition(
|
||||
code = GLOBAL_FOLLOWS,
|
||||
@@ -107,7 +117,7 @@ class FollowListState(
|
||||
unpackList = listOf(MuteListEvent.blockListFor(account.userProfile().pubkeyHex)),
|
||||
)
|
||||
|
||||
val defaultLists = persistentListOf(kind3Follow, aroundMe, globalFollow, muteListFollow)
|
||||
val defaultLists = persistentListOf(kind3Follow, kind3FollowUsers, aroundMe, globalFollow, muteListFollow)
|
||||
|
||||
fun getPeopleLists(): List<FeedDefinition> =
|
||||
account
|
||||
@@ -218,7 +228,7 @@ class FollowListState(
|
||||
checkNotInMainThread()
|
||||
emit(
|
||||
listOf(
|
||||
listOf(kind3Follow, aroundMe, globalFollow),
|
||||
listOf(kind3Follow, kind3FollowUsers, aroundMe, globalFollow),
|
||||
myLivePeopleListsFlow,
|
||||
myLiveKind3FollowsFlow,
|
||||
listOf(muteListFollow),
|
||||
@@ -234,7 +244,7 @@ class FollowListState(
|
||||
checkNotInMainThread()
|
||||
emit(
|
||||
listOf(
|
||||
listOf(kind3Follow, aroundMe, globalFollow),
|
||||
listOf(kind3Follow, kind3FollowUsers, aroundMe, globalFollow),
|
||||
myLivePeopleListsFlow,
|
||||
listOf(muteListFollow),
|
||||
).flatten().toImmutableList(),
|
||||
|
||||
+7
@@ -1084,6 +1084,13 @@ class AccountViewModel(
|
||||
account.markAsRead("Channel/${noteEvent.channelId()}", noteEvent.createdAt)
|
||||
} else if (noteEvent is ChatroomKeyable) {
|
||||
account.markAsRead("Room/${noteEvent.chatroomKey(account.signer.pubKey).hashCode()}", noteEvent.createdAt)
|
||||
} else if (noteEvent is DraftWrapEvent) {
|
||||
val innerEvent = account.draftsDecryptionCache.preCachedDraft(noteEvent)
|
||||
if (innerEvent is IsInPublicChatChannel) {
|
||||
account.markAsRead("Channel/${innerEvent.channelId()}", noteEvent.createdAt)
|
||||
} else if (innerEvent is ChatroomKeyable) {
|
||||
account.markAsRead("Room/${innerEvent.chatroomKey(account.signer.pubKey).hashCode()}", noteEvent.createdAt)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
@Composable
|
||||
fun ChatTimeAgo(baseNote: Note) {
|
||||
val nowStr = stringRes(id = R.string.now)
|
||||
val time = remember(baseNote) { timeAgoShort(baseNote.createdAt() ?: 0, nowStr) }
|
||||
val time = remember(baseNote) { timeAgoShort(baseNote.createdAt() ?: 0L, nowStr) }
|
||||
|
||||
Text(
|
||||
text = time,
|
||||
|
||||
+6
-6
@@ -105,7 +105,7 @@ class ChatroomListKnownFeedFilter(
|
||||
val channelId = (oldNote.event as? ChannelMessageEvent)?.channelId()
|
||||
if (newNotePair.key == channelId) {
|
||||
hasUpdated = true
|
||||
if ((newNotePair.value.createdAt() ?: 0) > (oldNote.createdAt() ?: 0)) {
|
||||
if ((newNotePair.value.createdAt() ?: 0L) > (oldNote.createdAt() ?: 0L)) {
|
||||
myNewList = myNewList.replace(oldNote, newNotePair.value)
|
||||
}
|
||||
}
|
||||
@@ -121,7 +121,7 @@ class ChatroomListKnownFeedFilter(
|
||||
val noteEvent = (oldNote.event as? EphemeralChatEvent)?.roomId()
|
||||
if (newNotePair.key == noteEvent) {
|
||||
hasUpdated = true
|
||||
if ((newNotePair.value.createdAt() ?: 0) > (oldNote.createdAt() ?: 0)) {
|
||||
if ((newNotePair.value.createdAt() ?: 0L) > (oldNote.createdAt() ?: 0L)) {
|
||||
myNewList = myNewList.replace(oldNote, newNotePair.value)
|
||||
}
|
||||
}
|
||||
@@ -138,7 +138,7 @@ class ChatroomListKnownFeedFilter(
|
||||
|
||||
if (newNotePair.key == oldRoom) {
|
||||
hasUpdated = true
|
||||
if ((newNotePair.value.createdAt() ?: 0) > (oldNote.createdAt() ?: 0)) {
|
||||
if ((newNotePair.value.createdAt() ?: 0L) > (oldNote.createdAt() ?: 0L)) {
|
||||
myNewList = myNewList.replace(oldNote, newNotePair.value)
|
||||
}
|
||||
}
|
||||
@@ -179,7 +179,7 @@ class ChatroomListKnownFeedFilter(
|
||||
if (channelId in followingChannels && account.isAcceptable(newNote)) {
|
||||
val lastNote = newRelevantPublicMessages.get(channelId)
|
||||
if (lastNote != null) {
|
||||
if ((newNote.createdAt() ?: 0) > (lastNote.createdAt() ?: 0)) {
|
||||
if ((newNote.createdAt() ?: 0L) > (lastNote.createdAt() ?: 0L)) {
|
||||
newRelevantPublicMessages.put(channelId, newNote)
|
||||
}
|
||||
} else {
|
||||
@@ -205,7 +205,7 @@ class ChatroomListKnownFeedFilter(
|
||||
if (room != null && room in followingEphemeralChats && account.isAcceptable(newNote)) {
|
||||
val lastNote = newRelevantEphemeralChats.get(room)
|
||||
if (lastNote != null) {
|
||||
if ((newNote.createdAt() ?: 0) > (lastNote.createdAt() ?: 0)) {
|
||||
if ((newNote.createdAt() ?: 0L) > (lastNote.createdAt() ?: 0L)) {
|
||||
newRelevantEphemeralChats.put(room, newNote)
|
||||
}
|
||||
} else {
|
||||
@@ -241,7 +241,7 @@ class ChatroomListKnownFeedFilter(
|
||||
) {
|
||||
val lastNote = newRelevantPrivateMessages.get(roomKey)
|
||||
if (lastNote != null) {
|
||||
if ((newNote.createdAt() ?: 0) > (lastNote.createdAt() ?: 0)) {
|
||||
if ((newNote.createdAt() ?: 0L) > (lastNote.createdAt() ?: 0L)) {
|
||||
newRelevantPrivateMessages.put(roomKey, newNote)
|
||||
}
|
||||
} else {
|
||||
|
||||
+2
-2
@@ -72,7 +72,7 @@ class ChatroomListNewFeedFilter(
|
||||
|
||||
if (newNotePair.key == oldRoom) {
|
||||
hasUpdated = true
|
||||
if ((newNotePair.value.createdAt() ?: 0) > (oldNote.createdAt() ?: 0)) {
|
||||
if ((newNotePair.value.createdAt() ?: 0L) > (oldNote.createdAt() ?: 0L)) {
|
||||
myNewList = myNewList.replace(oldNote, newNotePair.value)
|
||||
}
|
||||
}
|
||||
@@ -120,7 +120,7 @@ class ChatroomListNewFeedFilter(
|
||||
) {
|
||||
val lastNote = newRelevantPrivateMessages.get(roomKey)
|
||||
if (lastNote != null) {
|
||||
if ((newNote.createdAt() ?: 0) > (lastNote.createdAt() ?: 0)) {
|
||||
if ((newNote.createdAt() ?: 0L) > (lastNote.createdAt() ?: 0L)) {
|
||||
newRelevantPrivateMessages.put(roomKey, newNote)
|
||||
}
|
||||
} else {
|
||||
|
||||
+2
-2
@@ -106,12 +106,12 @@ open class DiscoverChatFeedFilter(
|
||||
// precache to avoid breaking the contract
|
||||
val lastNote =
|
||||
items.associateWith { note ->
|
||||
LocalCache.getPublicChatChannelIfExists(note.idHex)?.lastNote?.createdAt() ?: 0
|
||||
LocalCache.getPublicChatChannelIfExists(note.idHex)?.lastNote?.createdAt() ?: 0L
|
||||
}
|
||||
|
||||
val createdNote =
|
||||
items.associateWith { note ->
|
||||
note.createdAt() ?: 0
|
||||
note.createdAt() ?: 0L
|
||||
}
|
||||
|
||||
val comparator: Comparator<Note> =
|
||||
|
||||
+1
-1
@@ -127,7 +127,7 @@ open class DiscoverCommunityFeedFilter(
|
||||
|
||||
val createdNote =
|
||||
items.associateWith { note ->
|
||||
note.createdAt() ?: 0
|
||||
note.createdAt() ?: 0L
|
||||
}
|
||||
|
||||
val comparator: Comparator<Note> =
|
||||
|
||||
+4
-3
@@ -25,6 +25,7 @@ import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
fun filterCommunitiesGlobal(
|
||||
@@ -41,8 +42,8 @@ fun filterCommunitiesGlobal(
|
||||
relay = it.key,
|
||||
filter =
|
||||
Filter(
|
||||
kinds = CommunityPostApprovalEvent.KIND_LIST,
|
||||
limit = 100,
|
||||
kinds = listOf(CommunityDefinitionEvent.KIND),
|
||||
limit = 500,
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
@@ -52,7 +53,7 @@ fun filterCommunitiesGlobal(
|
||||
Filter(
|
||||
kinds = CommunityPostApprovalEvent.KIND_LIST,
|
||||
limit = 100,
|
||||
since = since ?: TimeUtils.oneWeekAgo(),
|
||||
since = since ?: TimeUtils.oneMonthAgo(),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
+2
-2
@@ -80,9 +80,9 @@ open class NIP90ContentDiscoveryResponseFilter(
|
||||
protected open fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
|
||||
// val params = buildFilterParams(account)
|
||||
|
||||
val maxNote = collection.filter { acceptableEvent(it) }.maxByOrNull { it.createdAt() ?: 0 } ?: return emptySet()
|
||||
val maxNote = collection.filter { acceptableEvent(it) }.maxByOrNull { it.createdAt() ?: 0L } ?: return emptySet()
|
||||
|
||||
if ((maxNote.createdAt() ?: 0) > (latestNote?.createdAt() ?: 0)) {
|
||||
if ((maxNote.createdAt() ?: 0L) > (latestNote?.createdAt() ?: 0L)) {
|
||||
latestNote = maxNote
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -115,7 +115,7 @@ class HomeLiveFilter(
|
||||
oldList.filter { channel ->
|
||||
val channelTime = (channel as? LiveActivitiesChannel)?.info?.createdAt
|
||||
(channelTime == null || channelTime > fifteenMinsAgo) ||
|
||||
(channel.lastNote?.createdAt() ?: 0) > fifteenMinsAgo
|
||||
(channel.lastNote?.createdAt() ?: 0L) > fifteenMinsAgo
|
||||
}
|
||||
|
||||
val newItemsToBeAdded = applyFilter(newItems)
|
||||
@@ -194,7 +194,7 @@ class HomeLiveFilter(
|
||||
|
||||
return collection.sortedWith(
|
||||
compareByDescending<Channel> { followCounts[it] }
|
||||
.thenByDescending<Channel> { it.lastNote?.createdAt() ?: 0 }
|
||||
.thenByDescending<Channel> { it.lastNote?.createdAt() ?: 0L }
|
||||
.thenBy { it.hashCode() },
|
||||
)
|
||||
}
|
||||
|
||||
+2
-2
@@ -239,7 +239,7 @@ class CardFeedContentState(
|
||||
(boostsInCard + zapsInCard.map { it.response } + reactionsInCard).groupBy {
|
||||
sdf.format(
|
||||
Instant
|
||||
.ofEpochSecond(it.createdAt() ?: 0)
|
||||
.ofEpochSecond(it.createdAt() ?: 0L)
|
||||
.atZone(ZoneId.systemDefault())
|
||||
.toLocalDateTime(),
|
||||
)
|
||||
@@ -273,7 +273,7 @@ class CardFeedContentState(
|
||||
user.value.groupBy {
|
||||
sdf.format(
|
||||
Instant
|
||||
.ofEpochSecond(it.createdAt() ?: 0)
|
||||
.ofEpochSecond(it.createdAt() ?: 0L)
|
||||
.atZone(ZoneId.systemDefault())
|
||||
.toLocalDateTime(),
|
||||
)
|
||||
|
||||
+7
-7
@@ -43,7 +43,7 @@ abstract class Card {
|
||||
class BadgeCard(
|
||||
val note: Note,
|
||||
) : Card() {
|
||||
override fun createdAt(): Long = note.createdAt() ?: 0
|
||||
override fun createdAt(): Long = note.createdAt() ?: 0L
|
||||
|
||||
override fun id() = note.idHex
|
||||
}
|
||||
@@ -52,7 +52,7 @@ class BadgeCard(
|
||||
class NoteCard(
|
||||
val note: Note,
|
||||
) : Card() {
|
||||
override fun createdAt(): Long = note.createdAt() ?: 0
|
||||
override fun createdAt(): Long = note.createdAt() ?: 0L
|
||||
|
||||
override fun id() = note.idHex
|
||||
}
|
||||
@@ -62,7 +62,7 @@ class ZapUserSetCard(
|
||||
val user: User,
|
||||
val zapEvents: ImmutableList<CombinedZap>,
|
||||
) : Card() {
|
||||
val createdAt = zapEvents.maxOf { it.createdAt() ?: 0 }
|
||||
val createdAt = zapEvents.maxOfOrNull { it.createdAt() ?: 0L } ?: 0L
|
||||
|
||||
override fun createdAt(): Long = createdAt
|
||||
|
||||
@@ -78,9 +78,9 @@ class MultiSetCard(
|
||||
) : Card() {
|
||||
val maxCreatedAt =
|
||||
maxOf(
|
||||
zapEvents.maxOfOrNull { it.createdAt() ?: 0 } ?: 0,
|
||||
likeEvents.maxOfOrNull { it.createdAt() ?: 0 } ?: 0,
|
||||
boostEvents.maxOfOrNull { it.createdAt() ?: 0 } ?: 0,
|
||||
zapEvents.maxOfOrNull { it.createdAt() ?: 0L } ?: 0L,
|
||||
likeEvents.maxOfOrNull { it.createdAt() ?: 0L } ?: 0L,
|
||||
boostEvents.maxOfOrNull { it.createdAt() ?: 0L } ?: 0L,
|
||||
)
|
||||
|
||||
val minCreatedAt =
|
||||
@@ -109,7 +109,7 @@ class MultiSetCard(
|
||||
class MessageSetCard(
|
||||
val note: Note,
|
||||
) : Card() {
|
||||
override fun createdAt(): Long = note.createdAt() ?: 0
|
||||
override fun createdAt(): Long = note.createdAt() ?: 0L
|
||||
|
||||
override fun id() = note.idHex
|
||||
}
|
||||
|
||||
@@ -502,6 +502,7 @@
|
||||
|
||||
<string name="follow_list_selection">Follow List</string>
|
||||
<string name="follow_list_kind3follows">All Follows</string>
|
||||
<string name="follow_list_kind3follows_users_only">All User Follows</string>
|
||||
<string name="follow_list_kind3follows_proxy">Follows via Proxy</string>
|
||||
<string name="follow_list_aroundme">Around Me</string>
|
||||
<string name="follow_list_global">Global</string>
|
||||
|
||||
+2
-4
@@ -215,10 +215,8 @@ private fun TranslationMessage(
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
accountViewModel.account.toggleDontTranslateFrom(source)
|
||||
langSettingsPopupExpanded = false
|
||||
}
|
||||
accountViewModel.toggleDontTranslateFrom(source)
|
||||
langSettingsPopupExpanded = false
|
||||
},
|
||||
)
|
||||
HorizontalDivider(thickness = DividerThickness)
|
||||
|
||||
+2
-2
@@ -48,9 +48,9 @@ data class RootSceneTag(
|
||||
this.relay = relayHint
|
||||
}
|
||||
|
||||
fun countMemory(): Long =
|
||||
fun countMemory(): Int =
|
||||
5 * pointerSizeInBytes + // 7 fields, 4 bytes each reference (32bit)
|
||||
8L + // kind
|
||||
8 + // kind
|
||||
pubKeyHex.bytesUsedInMemory() +
|
||||
dTag.bytesUsedInMemory() +
|
||||
(relay?.url?.bytesUsedInMemory() ?: 0)
|
||||
|
||||
@@ -47,9 +47,9 @@ open class Event(
|
||||
|
||||
open fun extraIndexableTagNames() = emptySet<String>()
|
||||
|
||||
open fun countMemory(): Long =
|
||||
open fun countMemory(): Int =
|
||||
7 * pointerSizeInBytes + // 7 fields, 4 bytes each reference (32bit)
|
||||
12L + // createdAt + kind
|
||||
12 + // createdAt + kind
|
||||
id.bytesUsedInMemory() +
|
||||
pubKey.bytesUsedInMemory() +
|
||||
tags.sumOf { pointerSizeInBytes + it.sumOf { pointerSizeInBytes + it.bytesUsedInMemory() } } +
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ data class EventHintBundle<T : Event>(
|
||||
this.authorHomeRelay = authorHomeRelay
|
||||
}
|
||||
|
||||
fun countMemory(): Long =
|
||||
fun countMemory(): Int =
|
||||
2 * pointerSizeInBytes + // 2 fields, 4 bytes each reference (32bit)
|
||||
event.countMemory() +
|
||||
(relay?.url?.bytesUsedInMemory() ?: 0)
|
||||
|
||||
+3
-3
@@ -157,7 +157,7 @@ open class BasicRelayClient(
|
||||
val onConnected: () -> Unit,
|
||||
) : WebSocketListener {
|
||||
override fun onOpen(
|
||||
pingMillis: Long,
|
||||
pingMillis: Int,
|
||||
compression: Boolean,
|
||||
) {
|
||||
Log.d(logTag, "OnOpen (ping: ${pingMillis}ms${if (compression) ", using compression" else ""})")
|
||||
@@ -260,7 +260,7 @@ open class BasicRelayClient(
|
||||
}
|
||||
|
||||
fun markConnectionAsReady(
|
||||
pingInMs: Long,
|
||||
pingInMs: Int,
|
||||
usingCompression: Boolean,
|
||||
) {
|
||||
this.resetEOSEStatuses()
|
||||
@@ -459,7 +459,7 @@ open class BasicRelayClient(
|
||||
)
|
||||
}
|
||||
socket?.let {
|
||||
// Log.d(logTag, "Sending: $str")
|
||||
Log.d(logTag, "Sending: $str")
|
||||
val result = it.send(str)
|
||||
listener.onSend(this@BasicRelayClient, str, result)
|
||||
stats.addBytesSent(str.bytesUsedInMemory())
|
||||
|
||||
+7
-7
@@ -24,11 +24,11 @@ import androidx.collection.LruCache
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
class RelayStat(
|
||||
var receivedBytes: Long = 0L,
|
||||
var sentBytes: Long = 0L,
|
||||
var spamCounter: Long = 0L,
|
||||
var errorCounter: Long = 0L,
|
||||
var pingInMs: Long = 0L,
|
||||
var receivedBytes: Int = 0,
|
||||
var sentBytes: Int = 0,
|
||||
var spamCounter: Int = 0,
|
||||
var errorCounter: Int = 0,
|
||||
var pingInMs: Int = 0,
|
||||
) {
|
||||
val messages = LruCache<RelayDebugMessage, RelayDebugMessage>(100)
|
||||
|
||||
@@ -54,11 +54,11 @@ class RelayStat(
|
||||
messages.put(debugMessage, debugMessage)
|
||||
}
|
||||
|
||||
fun addBytesReceived(bytesUsedInMemory: Long) {
|
||||
fun addBytesReceived(bytesUsedInMemory: Int) {
|
||||
receivedBytes += bytesUsedInMemory
|
||||
}
|
||||
|
||||
fun addBytesSent(bytesUsedInMemory: Long) {
|
||||
fun addBytesSent(bytesUsedInMemory: Int) {
|
||||
sentBytes += bytesUsedInMemory
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -25,7 +25,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
|
||||
object RelayStats {
|
||||
private val innerCache =
|
||||
object : LruCache<NormalizedRelayUrl, RelayStat>(1000) {
|
||||
object : LruCache<NormalizedRelayUrl, RelayStat>(100) {
|
||||
override fun create(key: NormalizedRelayUrl?) = RelayStat()
|
||||
}
|
||||
|
||||
@@ -33,14 +33,14 @@ object RelayStats {
|
||||
|
||||
fun addBytesReceived(
|
||||
url: NormalizedRelayUrl,
|
||||
bytesUsedInMemory: Long,
|
||||
bytesUsedInMemory: Int,
|
||||
) {
|
||||
get(url).addBytesReceived(bytesUsedInMemory)
|
||||
}
|
||||
|
||||
fun addBytesSent(
|
||||
url: NormalizedRelayUrl,
|
||||
bytesUsedInMemory: Long,
|
||||
bytesUsedInMemory: Int,
|
||||
) {
|
||||
get(url).addBytesSent(bytesUsedInMemory)
|
||||
}
|
||||
@@ -61,7 +61,7 @@ object RelayStats {
|
||||
|
||||
fun setPing(
|
||||
url: NormalizedRelayUrl,
|
||||
pingInMs: Long,
|
||||
pingInMs: Int,
|
||||
) {
|
||||
get(url).pingInMs = pingInMs
|
||||
}
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ package com.vitorpamplona.quartz.nip01Core.relay.sockets
|
||||
|
||||
interface WebSocketListener {
|
||||
fun onOpen(
|
||||
pingMillis: Long,
|
||||
pingMillis: Int,
|
||||
compression: Boolean,
|
||||
)
|
||||
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ class BasicOkHttpWebSocket(
|
||||
webSocket: okhttp3.WebSocket,
|
||||
response: Response,
|
||||
) = out.onOpen(
|
||||
response.receivedResponseAtMillis - response.sentRequestAtMillis,
|
||||
(response.receivedResponseAtMillis - response.sentRequestAtMillis).toInt(),
|
||||
response.headers["Sec-WebSocket-Extensions"]?.contains("permessage-deflate") ?: false,
|
||||
)
|
||||
|
||||
|
||||
+2
-2
@@ -40,9 +40,9 @@ data class ATag(
|
||||
) {
|
||||
constructor(address: Address, relayHint: NormalizedRelayUrl? = null) : this(address.kind, address.pubKeyHex, address.dTag, relayHint)
|
||||
|
||||
fun countMemory(): Long =
|
||||
fun countMemory(): Int =
|
||||
5 * pointerSizeInBytes + // 7 fields, 4 bytes each reference (32bit)
|
||||
8L + // kind
|
||||
8 + // kind
|
||||
pubKeyHex.bytesUsedInMemory() +
|
||||
dTag.bytesUsedInMemory() +
|
||||
(relay?.url?.bytesUsedInMemory() ?: 0)
|
||||
|
||||
+2
-2
@@ -39,9 +39,9 @@ data class Address(
|
||||
Parcelable {
|
||||
fun toValue() = assemble(kind, pubKeyHex, dTag)
|
||||
|
||||
fun countMemory(): Long =
|
||||
fun countMemory(): Int =
|
||||
3 * pointerSizeInBytes +
|
||||
8L + // kind
|
||||
8 + // kind
|
||||
pubKeyHex.bytesUsedInMemory() +
|
||||
dTag.bytesUsedInMemory()
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import com.vitorpamplona.quartz.utils.pointerSizeInBytes
|
||||
class DTag(
|
||||
val dId: String,
|
||||
) {
|
||||
fun countMemory(): Long = 1 * pointerSizeInBytes + dId.bytesUsedInMemory()
|
||||
fun countMemory(): Int = 1 * pointerSizeInBytes + dId.bytesUsedInMemory()
|
||||
|
||||
fun toTagArray() = assemble(dId)
|
||||
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ data class ETag(
|
||||
this.author = authorPubKeyHex
|
||||
}
|
||||
|
||||
fun countMemory(): Long =
|
||||
fun countMemory(): Int =
|
||||
3 * pointerSizeInBytes + // 3 fields, 4 bytes each reference (32bit)
|
||||
eventId.bytesUsedInMemory() +
|
||||
(relay?.url?.bytesUsedInMemory() ?: 0) +
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@ data class PTag(
|
||||
override val pubKey: HexKey,
|
||||
override val relayHint: NormalizedRelayUrl? = null,
|
||||
) : PubKeyReferenceTag {
|
||||
fun countMemory(): Long =
|
||||
fun countMemory(): Int =
|
||||
2 * pointerSizeInBytes + // 2 fields, 4 bytes each reference (32bit)
|
||||
pubKey.bytesUsedInMemory() +
|
||||
(relayHint?.url?.bytesUsedInMemory() ?: 0)
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@ data class ContactTag(
|
||||
this.petname = petname
|
||||
}
|
||||
|
||||
fun countMemory(): Long =
|
||||
fun countMemory(): Int =
|
||||
3 * pointerSizeInBytes +
|
||||
pubKey.bytesUsedInMemory() +
|
||||
(relayUri?.url?.bytesUsedInMemory() ?: 0) +
|
||||
|
||||
@@ -30,7 +30,7 @@ class PoWTag(
|
||||
val nonce: String,
|
||||
val commitment: Int?,
|
||||
) {
|
||||
fun countMemory(): Long = 2 * pointerSizeInBytes + nonce.bytesUsedInMemory() + (commitment?.bytesUsedInMemory() ?: 0)
|
||||
fun countMemory(): Int = 2 * pointerSizeInBytes + nonce.bytesUsedInMemory() + (commitment?.bytesUsedInMemory() ?: 0)
|
||||
|
||||
fun toTagArray() = assemble(nonce, commitment)
|
||||
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ data class QAddressableTag(
|
||||
this.relay = relayHint
|
||||
}
|
||||
|
||||
fun countMemory(): Long =
|
||||
fun countMemory(): Int =
|
||||
2 * pointerSizeInBytes +
|
||||
address.countMemory() +
|
||||
(relay?.url?.bytesUsedInMemory() ?: 0)
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ data class QEventTag(
|
||||
this.author = authorPubKeyHex
|
||||
}
|
||||
|
||||
fun countMemory(): Long =
|
||||
fun countMemory(): Int =
|
||||
3 * pointerSizeInBytes + // 3 fields, 4 bytes each reference (32bit)
|
||||
eventId.bytesUsedInMemory() +
|
||||
(relay?.url?.bytesUsedInMemory() ?: 0) +
|
||||
|
||||
+12
-1
@@ -132,7 +132,18 @@ class LongTextNoteEvent(
|
||||
|
||||
fun summary() = tags.firstNotNullOfOrNull(SummaryTag::parse)
|
||||
|
||||
override fun publishedAt() = tags.firstNotNullOfOrNull(PublishedAtTag::parse)
|
||||
override fun publishedAt(): Long? {
|
||||
val publishedAt = tags.firstNotNullOfOrNull(PublishedAtTag::parse)
|
||||
|
||||
if (publishedAt == null) return null
|
||||
|
||||
// removes posts in the future.
|
||||
return if (publishedAt <= createdAt) {
|
||||
publishedAt
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val KIND = 30023
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ class ChannelTag(
|
||||
val relay: NormalizedRelayUrl? = null,
|
||||
val author: HexKey? = null,
|
||||
) {
|
||||
fun countMemory(): Long =
|
||||
fun countMemory(): Int =
|
||||
3 * pointerSizeInBytes + // 3 fields, 4 bytes each reference (32bit)
|
||||
eventId.bytesUsedInMemory() +
|
||||
(relay?.url?.bytesUsedInMemory() ?: 0) +
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ import com.fasterxml.jackson.databind.JsonNode
|
||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
|
||||
|
||||
abstract class BunkerMessage {
|
||||
abstract fun countMemory(): Long
|
||||
abstract fun countMemory(): Int
|
||||
|
||||
class BunkerMessageDeserializer : StdDeserializer<BunkerMessage>(BunkerMessage::class.java) {
|
||||
override fun deserialize(
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ open class BunkerRequest(
|
||||
val method: String,
|
||||
val params: Array<String> = emptyArray(),
|
||||
) : BunkerMessage() {
|
||||
override fun countMemory(): Long =
|
||||
override fun countMemory(): Int =
|
||||
3 * pointerSizeInBytes + // 3 fields, 4 bytes each reference (32bit)
|
||||
id.bytesUsedInMemory() +
|
||||
method.bytesUsedInMemory() +
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ open class BunkerResponse(
|
||||
val result: String?,
|
||||
val error: String?,
|
||||
) : BunkerMessage() {
|
||||
override fun countMemory(): Long =
|
||||
override fun countMemory(): Int =
|
||||
3 * pointerSizeInBytes + // 3 fields, 4 bytes each reference (32bit)
|
||||
id.bytesUsedInMemory() +
|
||||
(result?.bytesUsedInMemory() ?: 0) +
|
||||
|
||||
+9
-5
@@ -28,7 +28,7 @@ import com.vitorpamplona.quartz.utils.pointerSizeInBytes
|
||||
abstract class Response(
|
||||
@field:JsonProperty("result_type") val resultType: String,
|
||||
) {
|
||||
abstract fun countMemory(): Long
|
||||
abstract fun countMemory(): Int
|
||||
}
|
||||
|
||||
// PayInvoice Call
|
||||
@@ -39,10 +39,10 @@ class PayInvoiceSuccessResponse(
|
||||
class PayInvoiceResultParams(
|
||||
val preimage: String? = null,
|
||||
) {
|
||||
fun countMemory(): Long = pointerSizeInBytes + (preimage?.bytesUsedInMemory() ?: 0)
|
||||
fun countMemory(): Int = pointerSizeInBytes + (preimage?.bytesUsedInMemory() ?: 0)
|
||||
}
|
||||
|
||||
override fun countMemory(): Long = pointerSizeInBytes + (result?.countMemory() ?: 0)
|
||||
override fun countMemory(): Int = pointerSizeInBytes + (result?.countMemory() ?: 0)
|
||||
}
|
||||
|
||||
class PayInvoiceErrorResponse(
|
||||
@@ -52,10 +52,10 @@ class PayInvoiceErrorResponse(
|
||||
val code: ErrorType? = null,
|
||||
val message: String? = null,
|
||||
) {
|
||||
fun countMemory(): Long = pointerSizeInBytes + pointerSizeInBytes + (message?.bytesUsedInMemory() ?: 0)
|
||||
fun countMemory(): Int = pointerSizeInBytes + pointerSizeInBytes + (message?.bytesUsedInMemory() ?: 0)
|
||||
}
|
||||
|
||||
override fun countMemory(): Long = pointerSizeInBytes + (error?.countMemory() ?: 0)
|
||||
override fun countMemory(): Int = pointerSizeInBytes + (error?.countMemory() ?: 0)
|
||||
|
||||
enum class ErrorType {
|
||||
@JsonProperty(value = "RATE_LIMITED")
|
||||
@@ -69,6 +69,10 @@ class PayInvoiceErrorResponse(
|
||||
@JsonProperty(value = "INSUFFICIENT_BALANCE")
|
||||
INSUFFICIENT_BALANCE,
|
||||
|
||||
// The command is not known or is intentionally not implemented.
|
||||
@JsonProperty(value = "PAYMENT_FAILED")
|
||||
PAYMENT_FAILED,
|
||||
|
||||
// The wallet does not have enough funds to cover a fee reserve or the payment amount.
|
||||
@JsonProperty(value = "QUOTA_EXCEEDED")
|
||||
QUOTA_EXCEEDED,
|
||||
|
||||
@@ -31,7 +31,7 @@ data class ProxyTag(
|
||||
val id: String,
|
||||
val protocol: String,
|
||||
) {
|
||||
fun countMemory(): Long = 2 * pointerSizeInBytes + id.bytesUsedInMemory() + protocol.bytesUsedInMemory()
|
||||
fun countMemory(): Int = 2 * pointerSizeInBytes + id.bytesUsedInMemory() + protocol.bytesUsedInMemory()
|
||||
|
||||
fun toTagArray() = assemble(id, protocol)
|
||||
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ class AddressBookmark(
|
||||
val address: Address,
|
||||
val relayHint: NormalizedRelayUrl? = null,
|
||||
) : BookmarkIdTag {
|
||||
fun countMemory(): Long = 2 * pointerSizeInBytes + address.countMemory() + (relayHint?.url?.bytesUsedInMemory() ?: 0)
|
||||
fun countMemory(): Int = 2 * pointerSizeInBytes + address.countMemory() + (relayHint?.url?.bytesUsedInMemory() ?: 0)
|
||||
|
||||
fun toTag() = Address.assemble(address.kind, address.pubKeyHex, address.dTag)
|
||||
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ class EventBookmark(
|
||||
val relay: NormalizedRelayUrl? = null,
|
||||
val author: HexKey? = null,
|
||||
) : BookmarkIdTag {
|
||||
fun countMemory(): Long =
|
||||
fun countMemory(): Int =
|
||||
3 * pointerSizeInBytes + // 3 fields, 4 bytes each reference (32bit)
|
||||
eventId.bytesUsedInMemory() +
|
||||
(relay?.url?.bytesUsedInMemory() ?: 0) +
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ class UserTag(
|
||||
val pubKey: HexKey,
|
||||
val relayHint: NormalizedRelayUrl? = null,
|
||||
) : MuteTag {
|
||||
fun countMemory(): Long =
|
||||
fun countMemory(): Int =
|
||||
2 * pointerSizeInBytes + // 2 fields, 4 bytes each reference (32bit)
|
||||
pubKey.bytesUsedInMemory() +
|
||||
(relayHint?.url?.bytesUsedInMemory() ?: 0)
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ import com.vitorpamplona.quartz.utils.pointerSizeInBytes
|
||||
class WordTag(
|
||||
val word: String,
|
||||
) : MuteTag {
|
||||
fun countMemory(): Long =
|
||||
fun countMemory(): Int =
|
||||
1 * pointerSizeInBytes + // 1 fields, 4 bytes each reference (32bit)
|
||||
word.bytesUsedInMemory()
|
||||
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ class MeetingSpaceTag(
|
||||
val address: Address,
|
||||
val relayHint: NormalizedRelayUrl? = null,
|
||||
) {
|
||||
fun countMemory(): Long = 2 * pointerSizeInBytes + address.countMemory() + (relayHint?.url?.bytesUsedInMemory() ?: 0)
|
||||
fun countMemory(): Int = 2 * pointerSizeInBytes + address.countMemory() + (relayHint?.url?.bytesUsedInMemory() ?: 0)
|
||||
|
||||
fun toTag() = Address.assemble(address.kind, address.pubKeyHex, address.dTag)
|
||||
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ class MeetingRoomTag(
|
||||
val address: Address,
|
||||
val relayHint: NormalizedRelayUrl? = null,
|
||||
) {
|
||||
fun countMemory(): Long = 2 * pointerSizeInBytes + address.countMemory() + (relayHint?.url?.bytesUsedInMemory() ?: 0)
|
||||
fun countMemory(): Int = 2 * pointerSizeInBytes + address.countMemory() + (relayHint?.url?.bytesUsedInMemory() ?: 0)
|
||||
|
||||
fun toTag() = Address.assemble(address.kind, address.pubKeyHex, address.dTag)
|
||||
|
||||
|
||||
@@ -46,6 +46,8 @@ import com.vitorpamplona.quartz.nip19Bech32.eventHints
|
||||
import com.vitorpamplona.quartz.nip19Bech32.eventIds
|
||||
import com.vitorpamplona.quartz.nip19Bech32.pubKeyHints
|
||||
import com.vitorpamplona.quartz.nip19Bech32.pubKeys
|
||||
import com.vitorpamplona.quartz.nip23LongContent.tags.PublishedAtTag
|
||||
import com.vitorpamplona.quartz.nip23LongContent.tags.PublishedAtTag.Companion.parse
|
||||
import com.vitorpamplona.quartz.nip31Alts.AltTag
|
||||
import com.vitorpamplona.quartz.nip50Search.SearchableEvent
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
@@ -129,12 +131,18 @@ class WikiNoteEvent(
|
||||
|
||||
fun image() = tags.firstOrNull { it.size > 1 && it[0] == "image" }?.get(1)
|
||||
|
||||
override fun publishedAt() =
|
||||
try {
|
||||
tags.firstOrNull { it.size > 1 && it[0] == "published_at" }?.get(1)?.toLongOrNull()
|
||||
} catch (_: Exception) {
|
||||
override fun publishedAt(): Long? {
|
||||
val publishedAt = tags.firstNotNullOfOrNull(PublishedAtTag::parse)
|
||||
|
||||
if (publishedAt == null) return null
|
||||
|
||||
// removes posts in the future.
|
||||
return if (publishedAt <= createdAt) {
|
||||
publishedAt
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val KIND = 30818
|
||||
|
||||
@@ -72,7 +72,7 @@ class LnZapEvent(
|
||||
}
|
||||
}
|
||||
|
||||
override fun countMemory(): Long =
|
||||
override fun countMemory(): Int =
|
||||
super.countMemory() +
|
||||
pointerSizeInBytes + (zapRequest?.countMemory() ?: 0) + // rough calculation
|
||||
pointerSizeInBytes + 36 // bigdecimal size
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ import com.vitorpamplona.quartz.utils.pointerSizeInBytes
|
||||
class ZapRaiserTag(
|
||||
val amountInSats: Long,
|
||||
) {
|
||||
fun countMemory(): Long = 1 * pointerSizeInBytes + amountInSats.bytesUsedInMemory()
|
||||
fun countMemory(): Int = 1 * pointerSizeInBytes + amountInSats.bytesUsedInMemory()
|
||||
|
||||
fun toTagArray() = assemble(amountInSats)
|
||||
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ class SealedRumorEvent(
|
||||
) : WrappedEvent(id, pubKey, createdAt, KIND, tags, content, sig) {
|
||||
@Transient var innerEventId: HexKey? = null
|
||||
|
||||
override fun countMemory(): Long =
|
||||
override fun countMemory(): Int =
|
||||
super.countMemory() +
|
||||
pointerSizeInBytes + (innerEventId?.bytesUsedInMemory() ?: 0)
|
||||
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ class GiftWrapEvent(
|
||||
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
|
||||
@Transient var innerEventId: HexKey? = null
|
||||
|
||||
override fun countMemory(): Long =
|
||||
override fun countMemory(): Int =
|
||||
super.countMemory() +
|
||||
pointerSizeInBytes + (innerEventId?.bytesUsedInMemory() ?: 0)
|
||||
|
||||
|
||||
+12
-1
@@ -53,7 +53,18 @@ abstract class ReplaceableVideoEvent(
|
||||
|
||||
override fun title() = tags.firstNotNullOfOrNull(TitleTag::parse)
|
||||
|
||||
override fun publishedAt() = tags.firstNotNullOfOrNull(PublishedAtTag::parse)
|
||||
override fun publishedAt(): Long? {
|
||||
val publishedAt = tags.firstNotNullOfOrNull(PublishedAtTag::parse)
|
||||
|
||||
if (publishedAt == null) return null
|
||||
|
||||
// removes posts in the future.
|
||||
return if (publishedAt <= createdAt) {
|
||||
publishedAt
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun duration() = tags.firstNotNullOfOrNull(DurationTag::parse)
|
||||
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ data class TextTrackTag(
|
||||
val eventId: HexKey,
|
||||
var relay: String? = null,
|
||||
) {
|
||||
fun countMemory(): Long =
|
||||
fun countMemory(): Int =
|
||||
2 * pointerSizeInBytes + // 3 fields, 4 bytes each reference (32bit)
|
||||
eventId.bytesUsedInMemory() +
|
||||
(relay?.bytesUsedInMemory() ?: 0)
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ class ApprovedAddressTag(
|
||||
val address: Address,
|
||||
val relayHint: NormalizedRelayUrl? = null,
|
||||
) {
|
||||
fun countMemory(): Long = 2 * pointerSizeInBytes + address.countMemory() + (relayHint?.url?.bytesUsedInMemory() ?: 0)
|
||||
fun countMemory(): Int = 2 * pointerSizeInBytes + address.countMemory() + (relayHint?.url?.bytesUsedInMemory() ?: 0)
|
||||
|
||||
fun toTag() = Address.assemble(address.kind, address.pubKeyHex, address.dTag)
|
||||
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ class CommunityTag(
|
||||
val address: Address,
|
||||
val relayHint: NormalizedRelayUrl? = null,
|
||||
) {
|
||||
fun countMemory(): Long = 2 * pointerSizeInBytes + address.countMemory() + (relayHint?.url?.bytesUsedInMemory() ?: 0)
|
||||
fun countMemory(): Int = 2 * pointerSizeInBytes + address.countMemory() + (relayHint?.url?.bytesUsedInMemory() ?: 0)
|
||||
|
||||
fun toTag() = Address.assemble(address.kind, address.pubKeyHex, address.dTag)
|
||||
|
||||
|
||||
+13
-2
@@ -49,7 +49,7 @@ class AppDefinitionEvent(
|
||||
sig: HexKey,
|
||||
) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig),
|
||||
PublishedAtProvider {
|
||||
override fun countMemory(): Long = super.countMemory() + (cachedMetadata?.countMemory() ?: 8L)
|
||||
override fun countMemory(): Int = super.countMemory() + (cachedMetadata?.countMemory() ?: 8)
|
||||
|
||||
@Transient private var cachedMetadata: AppMetadata? = null
|
||||
|
||||
@@ -79,7 +79,18 @@ class AppDefinitionEvent(
|
||||
|
||||
fun includeKind(kind: Int) = tags.isTaggedKind(kind)
|
||||
|
||||
override fun publishedAt() = tags.firstNotNullOfOrNull(PublishedAtTag::parse)
|
||||
override fun publishedAt(): Long? {
|
||||
val publishedAt = tags.firstNotNullOfOrNull(PublishedAtTag::parse)
|
||||
|
||||
if (publishedAt == null) return null
|
||||
|
||||
// removes posts in the future.
|
||||
return if (publishedAt <= createdAt) {
|
||||
publishedAt
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val KIND = 31990
|
||||
|
||||
+18
-18
@@ -50,25 +50,25 @@ class AppMetadata {
|
||||
var lud06: String? = null
|
||||
var lud16: String? = null
|
||||
|
||||
fun countMemory(): Long =
|
||||
fun countMemory(): Int =
|
||||
20 * pointerSizeInBytes + // 20 fields, 4 bytes for each reference
|
||||
(name?.bytesUsedInMemory() ?: 0L) +
|
||||
(username?.bytesUsedInMemory() ?: 0L) +
|
||||
(displayName?.bytesUsedInMemory() ?: 0L) +
|
||||
(picture?.bytesUsedInMemory() ?: 0L) +
|
||||
(banner?.bytesUsedInMemory() ?: 0L) +
|
||||
(image?.bytesUsedInMemory() ?: 0L) +
|
||||
(website?.bytesUsedInMemory() ?: 0L) +
|
||||
(about?.bytesUsedInMemory() ?: 0L) +
|
||||
(subscription?.bytesUsedInMemory() ?: 0L) +
|
||||
(acceptsNutZaps?.bytesUsedInMemory() ?: 0L) +
|
||||
(supportsEncryption?.bytesUsedInMemory() ?: 0L) +
|
||||
(personalized?.bytesUsedInMemory() ?: 0L) + // A Boolean has 8 bytes of header, plus 1 byte of payload, for a total of 9 bytes of information. The JVM then rounds it up to the next multiple of 8. so the one instance of java.lang.Boolean takes up 16 bytes of memory.
|
||||
(amount?.bytesUsedInMemory() ?: 0L) +
|
||||
(nip05?.bytesUsedInMemory() ?: 0L) +
|
||||
(domain?.bytesUsedInMemory() ?: 0L) +
|
||||
(lud06?.bytesUsedInMemory() ?: 0L) +
|
||||
(lud16?.bytesUsedInMemory() ?: 0L)
|
||||
(name?.bytesUsedInMemory() ?: 0) +
|
||||
(username?.bytesUsedInMemory() ?: 0) +
|
||||
(displayName?.bytesUsedInMemory() ?: 0) +
|
||||
(picture?.bytesUsedInMemory() ?: 0) +
|
||||
(banner?.bytesUsedInMemory() ?: 0) +
|
||||
(image?.bytesUsedInMemory() ?: 0) +
|
||||
(website?.bytesUsedInMemory() ?: 0) +
|
||||
(about?.bytesUsedInMemory() ?: 0) +
|
||||
(subscription?.bytesUsedInMemory() ?: 0) +
|
||||
(acceptsNutZaps?.bytesUsedInMemory() ?: 0) +
|
||||
(supportsEncryption?.bytesUsedInMemory() ?: 0) +
|
||||
(personalized?.bytesUsedInMemory() ?: 0) + // A Boolean has 8 bytes of header, plus 1 byte of payload, for a total of 9 bytes of information. The JVM then rounds it up to the next multiple of 8. so the one instance of java.lang.Boolean takes up 16 bytes of memory.
|
||||
(amount?.bytesUsedInMemory() ?: 0) +
|
||||
(nip05?.bytesUsedInMemory() ?: 0) +
|
||||
(domain?.bytesUsedInMemory() ?: 0) +
|
||||
(lud06?.bytesUsedInMemory() ?: 0) +
|
||||
(lud16?.bytesUsedInMemory() ?: 0)
|
||||
|
||||
fun anyName(): String? = displayName ?: name ?: username
|
||||
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ class NIP90ContentDiscoveryResponseEvent(
|
||||
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
|
||||
@Transient var events: List<HexKey>? = null
|
||||
|
||||
override fun countMemory(): Long =
|
||||
override fun countMemory(): Int =
|
||||
super.countMemory() +
|
||||
pointerSizeInBytes + (events?.sumOf { it.bytesUsedInMemory() } ?: 0)
|
||||
|
||||
|
||||
+12
-1
@@ -70,7 +70,18 @@ class ClassifiedsEvent(
|
||||
|
||||
fun location() = tags.firstNotNullOfOrNull(LocationTag::parse)
|
||||
|
||||
override fun publishedAt() = tags.firstNotNullOfOrNull(PublishedAtTag::parse)
|
||||
override fun publishedAt(): Long? {
|
||||
val publishedAt = tags.firstNotNullOfOrNull(PublishedAtTag::parse)
|
||||
|
||||
if (publishedAt == null) return null
|
||||
|
||||
// removes posts in the future.
|
||||
return if (publishedAt <= createdAt) {
|
||||
publishedAt
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun categories() = tags.hashtags()
|
||||
|
||||
|
||||
@@ -24,13 +24,13 @@ import kotlin.math.min
|
||||
|
||||
val pointerSizeInBytes = 4
|
||||
|
||||
fun String.bytesUsedInMemory(): Long = (8 * (((this.length * 2L) + 45) / 8))
|
||||
fun String.bytesUsedInMemory(): Int = (8 * (((this.length * 2) + 45) / 8))
|
||||
|
||||
fun Long.bytesUsedInMemory(): Long = 8
|
||||
fun Long.bytesUsedInMemory(): Int = 8
|
||||
|
||||
fun Int.bytesUsedInMemory(): Long = 4
|
||||
fun Int.bytesUsedInMemory(): Int = 4
|
||||
|
||||
fun Boolean.bytesUsedInMemory(): Long = 8
|
||||
fun Boolean.bytesUsedInMemory(): Int = 8
|
||||
|
||||
fun String.containsIgnoreCase(term: String): Boolean {
|
||||
if (term.isEmpty()) return true // Empty string is contained
|
||||
|
||||
Reference in New Issue
Block a user