amethyst/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt

224 wiersze
6.4 KiB
Kotlin

package com.vitorpamplona.amethyst.model
import android.util.Log
import androidx.lifecycle.LiveData
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.vitorpamplona.amethyst.service.model.ReactionEvent
import com.vitorpamplona.amethyst.service.model.RepostEvent
import java.io.ByteArrayInputStream
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.util.Collections
import java.util.concurrent.ConcurrentHashMap
import nostr.postr.events.ContactListEvent
import nostr.postr.events.DeletionEvent
import nostr.postr.events.MetadataEvent
import nostr.postr.events.PrivateDmEvent
import nostr.postr.events.RecommendRelayEvent
import nostr.postr.events.TextNoteEvent
import nostr.postr.toHex
object LocalCache {
val metadataParser = jacksonObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.readerFor(UserMetadata::class.java)
val users = ConcurrentHashMap<HexKey, User>()
val notes = ConcurrentHashMap<HexKey, Note>()
@Synchronized
fun getOrCreateUser(pubkey: ByteArray): User {
val key = pubkey.toHexKey()
return users[key] ?: run {
val answer = User(pubkey)
users.put(key, answer)
answer
}
}
@Synchronized
fun getOrCreateNote(idHex: String): Note {
return notes[idHex] ?: run {
val answer = Note(idHex)
notes.put(idHex, answer)
answer
}
}
fun consume(event: MetadataEvent) {
//Log.d("MT", "New User ${users.size} ${event.contactMetaData.name}")
// new event
val oldUser = getOrCreateUser(event.pubKey)
if (event.createdAt > oldUser.updatedMetadataAt) {
val newUser = try {
metadataParser.readValue<UserMetadata>(ByteArrayInputStream(event.content.toByteArray(Charsets.UTF_8)), UserMetadata::class.java)
} catch (e: Exception) {
e.printStackTrace()
Log.w("MT", "Content Parse Error ${e.localizedMessage} ${event.content}")
return
}
oldUser.updateUserInfo(newUser, event.createdAt)
} else {
//Log.d("MT","Relay sent a previous Metadata Event ${oldUser.toBestDisplayName()} ${formattedDateTime(event.createdAt)} > ${formattedDateTime(oldUser.updatedAt)}")
}
}
fun formattedDateTime(timestamp: Long): String {
return Instant.ofEpochSecond(timestamp).atZone(ZoneId.systemDefault())
.format(DateTimeFormatter.ofPattern("uuuu MMM d hh:mm a"))
}
fun consume(event: TextNoteEvent) {
val note = getOrCreateNote(event.id.toHex())
// Already processed this event.
if (note.event != null) return
val author = getOrCreateUser(event.pubKey)
val mentions = Collections.synchronizedList(event.mentions.map { getOrCreateUser(decodePublicKey(it)) })
val replyTo = Collections.synchronizedList(event.replyTos.map { getOrCreateNote(it) }.toMutableList())
note.loadEvent(event, author, mentions, replyTo)
//Log.d("TN", "New Note (${notes.size},${users.size}) ${note.author?.toBestDisplayName()} ${note.event?.content} ${formattedDateTime(event.createdAt)}")
// Prepares user's profile view.
author.notes.add(note)
// Adds notifications to users.
mentions.forEach {
it.taggedPosts.add(note)
}
replyTo.forEach {
it.author?.taggedPosts?.add(note)
}
// Counts the replies
replyTo.forEach {
it.addReply(note)
}
refreshObservers()
}
fun consume(event: RecommendRelayEvent) {
//Log.d("RR", event.toJson())
}
fun consume(event: ContactListEvent) {
val user = getOrCreateUser(event.pubKey)
//Log.d("CL", "${user.toBestDisplayName()} ${event.follows}")
if (event.createdAt > user.updatedFollowsAt) {
user.updateFollows(
event.follows.map {
try {
val pubKey = decodePublicKey(it.pubKeyHex)
getOrCreateUser(pubKey)
} catch (e: Exception) {
println("Could not parse Hex key: ${it.pubKeyHex}")
println(event.toJson())
e.printStackTrace()
null
}
}.filterNotNull(),
event.createdAt
)
}
refreshObservers()
}
fun consume(event: PrivateDmEvent) {
//Log.d("PM", event.toJson())
}
fun consume(event: DeletionEvent) {
//Log.d("DEL", event.toJson())
}
fun consume(event: RepostEvent) {
val note = getOrCreateNote(event.id.toHex())
// Already processed this event.
if (note.event != null) return
//Log.d("TN", "New Boost (${notes.size},${users.size}) ${note.author.toBestDisplayName()} ${formattedDateTime(event.createdAt)}")
val author = getOrCreateUser(event.pubKey)
val mentions = event.originalAuthor.map { getOrCreateUser(decodePublicKey(it)) }.toList()
val repliesTo = event.boostedPost.map { getOrCreateNote(it) }.toMutableList()
note.loadEvent(event, author, mentions, repliesTo)
// Prepares user's profile view.
author.notes.add(note)
// Adds notifications to users.
mentions.forEach {
it.taggedPosts.add(note)
}
repliesTo.forEach {
it.author?.taggedPosts?.add(note)
}
// Counts the replies
repliesTo.forEach {
it.addBoost(note)
}
refreshObservers()
}
fun consume(event: ReactionEvent) {
val note = getOrCreateNote(event.id.toHex())
// Already processed this event.
if (note.event != null) return
val author = getOrCreateUser(event.pubKey)
val mentions = event.originalAuthor.map { getOrCreateUser(decodePublicKey(it)) }
val repliesTo = event.originalPost.map { getOrCreateNote(it) }.toMutableList()
note.loadEvent(event, author, mentions, repliesTo)
//Log.d("RE", "New Reaction ${event.content} (${notes.size},${users.size}) ${note.author?.toBestDisplayName()} ${formattedDateTime(event.createdAt)}")
// Adds notifications to users.
mentions.forEach {
it.taggedPosts.add(note)
}
repliesTo.forEach {
it.author?.taggedPosts?.add(note)
}
if (event.content == "" || event.content == "+" || event.content == "\uD83E\uDD19") {
// Counts the replies
repliesTo.forEach {
it.addReaction(note)
}
}
}
// Observers line up here.
val live: LocalCacheLiveData = LocalCacheLiveData(this)
private fun refreshObservers() {
live.refresh()
}
}
class LocalCacheLiveData(val cache: LocalCache): LiveData<LocalCacheState>(LocalCacheState(cache)) {
fun refresh() {
postValue(LocalCacheState(cache))
}
}
class LocalCacheState(val cache: LocalCache) {
}