A1 extract to commons
This commit is contained in:
@@ -23,6 +23,8 @@ package com.vitorpamplona.amethyst.model
|
||||
import android.util.LruCache
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.ICacheProvider
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.IChannel
|
||||
import com.vitorpamplona.amethyst.isDebug
|
||||
import com.vitorpamplona.amethyst.model.emphChat.EphemeralChatChannel
|
||||
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel
|
||||
@@ -211,7 +213,7 @@ interface ILocalCache {
|
||||
) {}
|
||||
}
|
||||
|
||||
object LocalCache : ILocalCache {
|
||||
object LocalCache : ILocalCache, ICacheProvider {
|
||||
val antiSpam = AntiSpamFilter()
|
||||
|
||||
val users = LargeSoftCache<HexKey, User>()
|
||||
@@ -316,16 +318,35 @@ object LocalCache : ILocalCache {
|
||||
}
|
||||
}
|
||||
|
||||
fun getUserIfExists(key: String): User? {
|
||||
override fun getUserIfExists(key: String): User? {
|
||||
if (key.isEmpty()) return null
|
||||
return users.get(key)
|
||||
}
|
||||
|
||||
override fun countUsers(predicate: (String, Any) -> Boolean): Int {
|
||||
var count = 0
|
||||
users.forEach { key, user ->
|
||||
if (predicate(key, user)) count++
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
override fun getAnyChannel(note: Any?): IChannel? {
|
||||
val channelNote = note as? Note ?: return null
|
||||
val channel = getAnyChannel(channelNote)
|
||||
// Wrap Channel to implement IChannel interface
|
||||
return channel?.let {
|
||||
object : IChannel {
|
||||
override fun relays(): List<Any>? = it.relays().toList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getAddressableNoteIfExists(key: String): AddressableNote? = Address.parse(key)?.let { addressables.get(it) }
|
||||
|
||||
fun getAddressableNoteIfExists(address: Address): AddressableNote? = addressables.get(address)
|
||||
|
||||
fun getNoteIfExists(key: String): Note? = if (key.length == 64) notes.get(key) else Address.parse(key)?.let { addressables.get(it) }
|
||||
override fun getNoteIfExists(key: String): Note? = if (key.length == 64) notes.get(key) else Address.parse(key)?.let { addressables.get(it) }
|
||||
|
||||
fun getNoteIfExists(key: ETag): Note? = notes.get(key.eventId)
|
||||
|
||||
@@ -356,7 +377,7 @@ object LocalCache : ILocalCache {
|
||||
return null
|
||||
}
|
||||
|
||||
fun checkGetOrCreateNote(key: String): Note? {
|
||||
override fun checkGetOrCreateNote(key: String): Note? {
|
||||
if (ATag.isATag(key)) {
|
||||
return checkGetOrCreateAddressableNote(key)
|
||||
}
|
||||
|
||||
+1
-1
@@ -26,8 +26,8 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.vitorpamplona.amethyst.commons.model.ThreadLevelCalculator
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.ThreadLevelCalculator
|
||||
import com.vitorpamplona.amethyst.ui.dal.FeedFilter
|
||||
import com.vitorpamplona.amethyst.ui.feeds.FeedState
|
||||
import com.vitorpamplona.amethyst.ui.screen.FeedViewModel
|
||||
|
||||
+5
-4
@@ -21,11 +21,12 @@
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview.dal
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.amethyst.commons.model.LevelSignature
|
||||
import com.vitorpamplona.amethyst.commons.model.ThreadAssembler
|
||||
import com.vitorpamplona.amethyst.commons.model.ThreadLevelCalculator
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.LevelSignature
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.ThreadAssembler
|
||||
import com.vitorpamplona.amethyst.model.ThreadLevelCalculator
|
||||
import com.vitorpamplona.amethyst.ui.dal.FeedFilter
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import kotlinx.collections.immutable.toImmutableSet
|
||||
@@ -40,7 +41,7 @@ class ThreadFeedFilter(
|
||||
override fun feed(): List<Note> {
|
||||
val cachedSignatures: MutableMap<Note, LevelSignature> = mutableMapOf()
|
||||
val followingKeySet = account.kind3FollowList.flow.value.authors
|
||||
val eventsToWatch = ThreadAssembler().findThreadFor(noteId) ?: return emptyList()
|
||||
val eventsToWatch = ThreadAssembler(LocalCache).findThreadFor(noteId) ?: return emptyList()
|
||||
|
||||
// Filter out drafts made by other accounts on device
|
||||
val filteredEvents =
|
||||
|
||||
+5
-4
@@ -20,8 +20,8 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview.datasources.subassembies
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.model.ThreadAssembler
|
||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.model.ThreadAssembler
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders.filterMissingAddressables
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders.filterMissingEvents
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders.potentialRelaysToFindAddress
|
||||
@@ -53,9 +53,10 @@ fun filterMissingEventsForThread(
|
||||
|
||||
val missingAddresses =
|
||||
mapOfSet {
|
||||
if (threadInfo.root.event == null && threadInfo.root is AddressableNote) {
|
||||
potentialRelaysToFindEvent(threadInfo.root).ifEmpty { defaultRelays }.forEach { relayUrl ->
|
||||
add(relayUrl, threadInfo.root.address)
|
||||
val rootNote = threadInfo.root
|
||||
if (rootNote.event == null && rootNote is AddressableNote) {
|
||||
potentialRelaysToFindEvent(rootNote).ifEmpty { defaultRelays }.forEach { relayUrl ->
|
||||
add(relayUrl, rootNote.address)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-2
@@ -20,7 +20,8 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview.datasources.subassembies
|
||||
|
||||
import com.vitorpamplona.amethyst.model.ThreadAssembler
|
||||
import com.vitorpamplona.amethyst.commons.model.ThreadAssembler
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUniqueIdEoseManager
|
||||
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview.datasources.ThreadQueryState
|
||||
@@ -45,7 +46,7 @@ class ThreadEventLoaderSubAssembler(
|
||||
key: ThreadQueryState,
|
||||
since: SincePerRelayMap?,
|
||||
): List<RelayBasedFilter>? {
|
||||
val branches = ThreadAssembler().findThreadFor(key.eventId) ?: return null
|
||||
val branches = ThreadAssembler(LocalCache).findThreadFor(key.eventId) ?: return null
|
||||
val defaultRelays = key.account.followPlusAllMineWithSearch.flow.value
|
||||
return filterMissingEventsForThread(branches, defaultRelays)
|
||||
}
|
||||
|
||||
+3
-2
@@ -20,7 +20,8 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview.datasources.subassembies
|
||||
|
||||
import com.vitorpamplona.amethyst.model.ThreadAssembler
|
||||
import com.vitorpamplona.amethyst.commons.model.ThreadAssembler
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUniqueIdEoseManager
|
||||
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview.datasources.ThreadQueryState
|
||||
@@ -42,7 +43,7 @@ class ThreadFilterSubAssembler(
|
||||
key: ThreadQueryState,
|
||||
since: SincePerRelayMap?,
|
||||
): List<RelayBasedFilter>? {
|
||||
val root = ThreadAssembler().findRoot(key.eventId) ?: return null
|
||||
val root = ThreadAssembler(LocalCache).findRoot(key.eventId) ?: return null
|
||||
|
||||
return filterEventsInThreadForRoot(root, since)
|
||||
}
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.commons.model
|
||||
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
private val levelFormatter = DateTimeFormatter.ofPattern("uuuu-MM-dd-HH:mm:ss")
|
||||
|
||||
actual fun formattedDateTime(timestamp: Long): String =
|
||||
Instant
|
||||
.ofEpochSecond(timestamp)
|
||||
.atZone(ZoneId.systemDefault())
|
||||
.format(levelFormatter)
|
||||
+12
-8
@@ -18,10 +18,11 @@
|
||||
* 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
|
||||
package com.vitorpamplona.amethyst.commons.model
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.ICacheProvider
|
||||
import com.vitorpamplona.amethyst.commons.threading.checkNotInMainThread
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
||||
@@ -29,7 +30,9 @@ import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||
import kotlinx.collections.immutable.ImmutableSet
|
||||
import kotlinx.collections.immutable.toImmutableSet
|
||||
|
||||
class ThreadAssembler {
|
||||
class ThreadAssembler(
|
||||
private val cache: ICacheProvider,
|
||||
) {
|
||||
private fun searchRoot(
|
||||
note: Note,
|
||||
testedNotes: MutableSet<Note> = mutableSetOf(),
|
||||
@@ -48,9 +51,10 @@ class ThreadAssembler {
|
||||
?.firstOrNull { it[0] == "e" && it.size > 3 && it[3] == "root" }
|
||||
?.getOrNull(1)
|
||||
if (markedAsRoot != null) {
|
||||
// Check to ssee if there is an error in the tag and the root has replies
|
||||
if (LocalCache.getNoteIfExists(markedAsRoot)?.replyTo?.isEmpty() == true) {
|
||||
return LocalCache.checkGetOrCreateNote(markedAsRoot)
|
||||
// Check to see if there is an error in the tag and the root has replies
|
||||
val rootNote = cache.getNoteIfExists(markedAsRoot) as? Note
|
||||
if (rootNote?.replyTo?.isEmpty() == true) {
|
||||
return cache.checkGetOrCreateNote(markedAsRoot) as? Note
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +88,7 @@ class ThreadAssembler {
|
||||
)
|
||||
|
||||
fun findRoot(noteId: String): Note? {
|
||||
val note = LocalCache.checkGetOrCreateNote(noteId) ?: return null
|
||||
val note = cache.checkGetOrCreateNote(noteId) as? Note ?: return null
|
||||
|
||||
return if (note.event != null) {
|
||||
val thread = OnlyLatestVersionSet()
|
||||
@@ -98,7 +102,7 @@ class ThreadAssembler {
|
||||
fun findThreadFor(noteId: String): ThreadInfo? {
|
||||
checkNotInMainThread()
|
||||
|
||||
val note = LocalCache.checkGetOrCreateNote(noteId) ?: return null
|
||||
val note = cache.checkGetOrCreateNote(noteId) as? Note ?: return null
|
||||
|
||||
return if (note.event != null) {
|
||||
val thread = OnlyLatestVersionSet()
|
||||
+8
-13
@@ -18,15 +18,12 @@
|
||||
* 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
|
||||
package com.vitorpamplona.amethyst.commons.model
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||
import java.lang.Long.min
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
import kotlin.math.min
|
||||
|
||||
data class LevelSignature(
|
||||
val signature: String,
|
||||
@@ -34,15 +31,13 @@ data class LevelSignature(
|
||||
val author: User?,
|
||||
)
|
||||
|
||||
/**
|
||||
* Platform-specific date-time formatter for thread signatures.
|
||||
* Returns formatted timestamp in pattern "uuuu-MM-dd-HH:mm:ss"
|
||||
*/
|
||||
expect fun formattedDateTime(timestamp: Long): String
|
||||
|
||||
object ThreadLevelCalculator {
|
||||
val levelFormatter = DateTimeFormatter.ofPattern("uuuu-MM-dd-HH:mm:ss")
|
||||
|
||||
private fun formattedDateTime(timestamp: Long): String =
|
||||
Instant
|
||||
.ofEpochSecond(timestamp)
|
||||
.atZone(ZoneId.systemDefault())
|
||||
.format(levelFormatter)
|
||||
|
||||
/**
|
||||
* This method caches signatures during each execution to avoid recalculation in longer threads
|
||||
*/
|
||||
Vendored
+18
@@ -61,6 +61,24 @@ interface ICacheProvider {
|
||||
* @return Count of users matching the predicate
|
||||
*/
|
||||
fun countUsers(predicate: (String, Any) -> Boolean): Int
|
||||
|
||||
/**
|
||||
* Gets a Note if it exists in cache.
|
||||
* Used by ThreadAssembler for finding existing notes.
|
||||
*
|
||||
* @param hexKey The note's ID in hex format
|
||||
* @return The Note if exists in cache, null otherwise
|
||||
*/
|
||||
fun getNoteIfExists(hexKey: HexKey): Any?
|
||||
|
||||
/**
|
||||
* Gets an existing Note or creates a new one if it doesn't exist.
|
||||
* Used by ThreadAssembler for building thread structures.
|
||||
*
|
||||
* @param hexKey The note's ID in hex format
|
||||
* @return The Note (existing or newly created)
|
||||
*/
|
||||
fun checkGetOrCreateNote(hexKey: HexKey): Any?
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.commons.model
|
||||
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
private val levelFormatter = DateTimeFormatter.ofPattern("uuuu-MM-dd-HH:mm:ss")
|
||||
|
||||
actual fun formattedDateTime(timestamp: Long): String =
|
||||
Instant
|
||||
.ofEpochSecond(timestamp)
|
||||
.atZone(ZoneId.systemDefault())
|
||||
.format(levelFormatter)
|
||||
Reference in New Issue
Block a user