A1 extract to commons
This commit is contained in:
@@ -23,6 +23,8 @@ package com.vitorpamplona.amethyst.model
|
|||||||
import android.util.LruCache
|
import android.util.LruCache
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import com.vitorpamplona.amethyst.Amethyst
|
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.isDebug
|
||||||
import com.vitorpamplona.amethyst.model.emphChat.EphemeralChatChannel
|
import com.vitorpamplona.amethyst.model.emphChat.EphemeralChatChannel
|
||||||
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel
|
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel
|
||||||
@@ -211,7 +213,7 @@ interface ILocalCache {
|
|||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
object LocalCache : ILocalCache {
|
object LocalCache : ILocalCache, ICacheProvider {
|
||||||
val antiSpam = AntiSpamFilter()
|
val antiSpam = AntiSpamFilter()
|
||||||
|
|
||||||
val users = LargeSoftCache<HexKey, User>()
|
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
|
if (key.isEmpty()) return null
|
||||||
return users.get(key)
|
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(key: String): AddressableNote? = Address.parse(key)?.let { addressables.get(it) }
|
||||||
|
|
||||||
fun getAddressableNoteIfExists(address: Address): AddressableNote? = addressables.get(address)
|
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)
|
fun getNoteIfExists(key: ETag): Note? = notes.get(key.eventId)
|
||||||
|
|
||||||
@@ -356,7 +377,7 @@ object LocalCache : ILocalCache {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkGetOrCreateNote(key: String): Note? {
|
override fun checkGetOrCreateNote(key: String): Note? {
|
||||||
if (ATag.isATag(key)) {
|
if (ATag.isATag(key)) {
|
||||||
return checkGetOrCreateAddressableNote(key)
|
return checkGetOrCreateAddressableNote(key)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -26,8 +26,8 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.ThreadLevelCalculator
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.ThreadLevelCalculator
|
|
||||||
import com.vitorpamplona.amethyst.ui.dal.FeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.FeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.FeedState
|
import com.vitorpamplona.amethyst.ui.feeds.FeedState
|
||||||
import com.vitorpamplona.amethyst.ui.screen.FeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.FeedViewModel
|
||||||
|
|||||||
+5
-4
@@ -21,11 +21,12 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview.dal
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview.dal
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
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.Account
|
||||||
import com.vitorpamplona.amethyst.model.LevelSignature
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
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.amethyst.ui.dal.FeedFilter
|
||||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
import kotlinx.collections.immutable.toImmutableSet
|
import kotlinx.collections.immutable.toImmutableSet
|
||||||
@@ -40,7 +41,7 @@ class ThreadFeedFilter(
|
|||||||
override fun feed(): List<Note> {
|
override fun feed(): List<Note> {
|
||||||
val cachedSignatures: MutableMap<Note, LevelSignature> = mutableMapOf()
|
val cachedSignatures: MutableMap<Note, LevelSignature> = mutableMapOf()
|
||||||
val followingKeySet = account.kind3FollowList.flow.value.authors
|
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
|
// Filter out drafts made by other accounts on device
|
||||||
val filteredEvents =
|
val filteredEvents =
|
||||||
|
|||||||
+5
-4
@@ -20,8 +20,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview.datasources.subassembies
|
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.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.filterMissingAddressables
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders.filterMissingEvents
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders.filterMissingEvents
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders.potentialRelaysToFindAddress
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders.potentialRelaysToFindAddress
|
||||||
@@ -53,9 +53,10 @@ fun filterMissingEventsForThread(
|
|||||||
|
|
||||||
val missingAddresses =
|
val missingAddresses =
|
||||||
mapOfSet {
|
mapOfSet {
|
||||||
if (threadInfo.root.event == null && threadInfo.root is AddressableNote) {
|
val rootNote = threadInfo.root
|
||||||
potentialRelaysToFindEvent(threadInfo.root).ifEmpty { defaultRelays }.forEach { relayUrl ->
|
if (rootNote.event == null && rootNote is AddressableNote) {
|
||||||
add(relayUrl, threadInfo.root.address)
|
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
|
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.relayClient.eoseManagers.PerUniqueIdEoseManager
|
||||||
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview.datasources.ThreadQueryState
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview.datasources.ThreadQueryState
|
||||||
@@ -45,7 +46,7 @@ class ThreadEventLoaderSubAssembler(
|
|||||||
key: ThreadQueryState,
|
key: ThreadQueryState,
|
||||||
since: SincePerRelayMap?,
|
since: SincePerRelayMap?,
|
||||||
): List<RelayBasedFilter>? {
|
): 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
|
val defaultRelays = key.account.followPlusAllMineWithSearch.flow.value
|
||||||
return filterMissingEventsForThread(branches, defaultRelays)
|
return filterMissingEventsForThread(branches, defaultRelays)
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-2
@@ -20,7 +20,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview.datasources.subassembies
|
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.relayClient.eoseManagers.PerUniqueIdEoseManager
|
||||||
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview.datasources.ThreadQueryState
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview.datasources.ThreadQueryState
|
||||||
@@ -42,7 +43,7 @@ class ThreadFilterSubAssembler(
|
|||||||
key: ThreadQueryState,
|
key: ThreadQueryState,
|
||||||
since: SincePerRelayMap?,
|
since: SincePerRelayMap?,
|
||||||
): List<RelayBasedFilter>? {
|
): List<RelayBasedFilter>? {
|
||||||
val root = ThreadAssembler().findRoot(key.eventId) ?: return null
|
val root = ThreadAssembler(LocalCache).findRoot(key.eventId) ?: return null
|
||||||
|
|
||||||
return filterEventsInThreadForRoot(root, since)
|
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
|
* 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.
|
* 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 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.Address
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
|
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
|
||||||
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
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.ImmutableSet
|
||||||
import kotlinx.collections.immutable.toImmutableSet
|
import kotlinx.collections.immutable.toImmutableSet
|
||||||
|
|
||||||
class ThreadAssembler {
|
class ThreadAssembler(
|
||||||
|
private val cache: ICacheProvider,
|
||||||
|
) {
|
||||||
private fun searchRoot(
|
private fun searchRoot(
|
||||||
note: Note,
|
note: Note,
|
||||||
testedNotes: MutableSet<Note> = mutableSetOf(),
|
testedNotes: MutableSet<Note> = mutableSetOf(),
|
||||||
@@ -48,9 +51,10 @@ class ThreadAssembler {
|
|||||||
?.firstOrNull { it[0] == "e" && it.size > 3 && it[3] == "root" }
|
?.firstOrNull { it[0] == "e" && it.size > 3 && it[3] == "root" }
|
||||||
?.getOrNull(1)
|
?.getOrNull(1)
|
||||||
if (markedAsRoot != null) {
|
if (markedAsRoot != null) {
|
||||||
// Check to ssee if there is an error in the tag and the root has replies
|
// Check to see if there is an error in the tag and the root has replies
|
||||||
if (LocalCache.getNoteIfExists(markedAsRoot)?.replyTo?.isEmpty() == true) {
|
val rootNote = cache.getNoteIfExists(markedAsRoot) as? Note
|
||||||
return LocalCache.checkGetOrCreateNote(markedAsRoot)
|
if (rootNote?.replyTo?.isEmpty() == true) {
|
||||||
|
return cache.checkGetOrCreateNote(markedAsRoot) as? Note
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +88,7 @@ class ThreadAssembler {
|
|||||||
)
|
)
|
||||||
|
|
||||||
fun findRoot(noteId: String): Note? {
|
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) {
|
return if (note.event != null) {
|
||||||
val thread = OnlyLatestVersionSet()
|
val thread = OnlyLatestVersionSet()
|
||||||
@@ -98,7 +102,7 @@ class ThreadAssembler {
|
|||||||
fun findThreadFor(noteId: String): ThreadInfo? {
|
fun findThreadFor(noteId: String): ThreadInfo? {
|
||||||
checkNotInMainThread()
|
checkNotInMainThread()
|
||||||
|
|
||||||
val note = LocalCache.checkGetOrCreateNote(noteId) ?: return null
|
val note = cache.checkGetOrCreateNote(noteId) as? Note ?: return null
|
||||||
|
|
||||||
return if (note.event != null) {
|
return if (note.event != null) {
|
||||||
val thread = OnlyLatestVersionSet()
|
val thread = OnlyLatestVersionSet()
|
||||||
+8
-13
@@ -18,15 +18,12 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* 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.
|
* 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.nip01Core.core.HexKey
|
||||||
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
||||||
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||||
import java.lang.Long.min
|
import kotlin.math.min
|
||||||
import java.time.Instant
|
|
||||||
import java.time.ZoneId
|
|
||||||
import java.time.format.DateTimeFormatter
|
|
||||||
|
|
||||||
data class LevelSignature(
|
data class LevelSignature(
|
||||||
val signature: String,
|
val signature: String,
|
||||||
@@ -34,15 +31,13 @@ data class LevelSignature(
|
|||||||
val author: User?,
|
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 {
|
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
|
* 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
|
* @return Count of users matching the predicate
|
||||||
*/
|
*/
|
||||||
fun countUsers(predicate: (String, Any) -> Boolean): Int
|
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