Adds a Deletion event cache.

This commit is contained in:
Vitor Pamplona
2024-04-05 11:19:15 -04:00
parent 1b7ba3de01
commit 7bb72d0c2d
6 changed files with 106 additions and 3 deletions
@@ -24,6 +24,7 @@ import android.util.Log
import android.util.LruCache
import androidx.compose.runtime.Stable
import com.vitorpamplona.amethyst.Amethyst
import com.vitorpamplona.amethyst.commons.data.DeletionIndex
import com.vitorpamplona.amethyst.commons.data.LargeCache
import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.amethyst.service.relays.Relay
@@ -133,6 +134,8 @@ object LocalCache {
val channels = LargeCache<HexKey, Channel>()
val awaitingPaymentRequests = ConcurrentHashMap<HexKey, Pair<Note?, (LnZapPaymentResponseEvent) -> Unit>>(10)
val deletionIndex = DeletionIndex()
fun checkGetOrCreateUser(key: String): User? {
// checkNotInMainThread()
@@ -956,10 +959,11 @@ object LocalCache {
}
fun consume(event: DeletionEvent) {
deletionIndex.add(event)
var deletedAtLeastOne = false
event
.deleteEvents()
event.deleteEvents()
.mapNotNull { getNoteIfExists(it) }
.forEach { deleteNote ->
// must be the same author
@@ -2210,6 +2214,8 @@ object LocalCache {
event: Event,
relay: Relay?,
) {
if (deletionIndex.hasBeenDeleted(event)) return
checkNotInMainThread()
try {
@@ -0,0 +1,80 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.commons.data
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.events.AddressableEvent
import com.vitorpamplona.quartz.events.DeletionEvent
import com.vitorpamplona.quartz.events.Event
class DeletionIndex {
data class DeletionRequest(val reference: String, val publicKey: HexKey)
// stores a set of id OR atags (kind:pubkey:dtag) by pubkey with the created at of the deletion event.
// Anything newer than the date should not be deleted.
private val deletedReferencesBefore = LargeCache<DeletionRequest, Long>()
fun add(event: DeletionEvent) {
event.tags.forEach {
if (it.size > 1 && (it[0] == "a" || it[0] == "e")) {
add(it[1], event.createdAt, event.pubKey)
}
}
}
private fun add(
ref: String,
createdAt: Long,
byPubKey: HexKey,
) {
val key = DeletionRequest(ref, byPubKey)
val deletionTime = deletedReferencesBefore.get(key)
if (deletionTime == null) {
deletedReferencesBefore.put(key, createdAt)
} else {
// updates with newer deletion.
if (createdAt > deletionTime) {
deletedReferencesBefore.put(key, createdAt)
}
}
}
fun hasBeenDeleted(event: Event): Boolean {
val key = DeletionRequest(event.id, event.pubKey)
if (hasBeenDeleted(key)) return true
if (event is AddressableEvent) {
if (hasBeenDeleted(key, event.createdAt)) return true
}
return false
}
private fun hasBeenDeleted(key: DeletionRequest) = deletedReferencesBefore.containsKey(key)
private fun hasBeenDeleted(
key: DeletionRequest,
createdAt: Long,
): Boolean {
val deletionTime = deletedReferencesBefore.get(key)
return deletionTime != null && createdAt < deletionTime
}
}
@@ -25,7 +25,7 @@ import androidx.compose.runtime.Immutable
@Immutable
data class ATag(val kind: Int, val pubKeyHex: String, val dTag: String, val relay: String?) {
fun toTag() = "$kind:$pubKeyHex:$dTag"
fun toTag() = assembleATag(kind, pubKeyHex, dTag)
fun toNAddr(): String {
return TlvBuilder()
@@ -40,6 +40,12 @@ data class ATag(val kind: Int, val pubKeyHex: String, val dTag: String, val rela
}
companion object {
fun assembleATag(
kind: Int,
pubKeyHex: String,
dTag: String,
) = "$kind:$pubKeyHex:$dTag"
fun isATag(key: String): Boolean {
return key.startsWith("naddr1") || key.contains(":")
}
@@ -514,6 +514,8 @@ interface AddressableEvent {
fun dTag(): String
fun address(): ATag
fun addressTag(): String
}
@Immutable
@@ -529,6 +531,11 @@ open class BaseAddressableEvent(
override fun dTag() = tags.firstOrNull { it.size > 1 && it[0] == "d" }?.get(1) ?: ""
override fun address() = ATag(kind, pubKey, dTag(), null)
/**
* Creates the tag in a memory effecient way (without creating the ATag class
*/
override fun addressTag() = ATag.assembleATag(kind, pubKey, dTag())
}
fun String.bytesUsedInMemory(): Int {
@@ -39,6 +39,8 @@ class LongTextNoteEvent(
override fun address() = ATag(kind, pubKey, dTag(), null)
override fun addressTag() = ATag.assembleATag(kind, pubKey, dTag())
fun topics() = hashtags()
fun title() = tags.firstOrNull { it.size > 1 && it[0] == "title" }?.get(1)
@@ -39,6 +39,8 @@ class WikiNoteEvent(
override fun address() = ATag(kind, pubKey, dTag(), null)
override fun addressTag() = ATag.assembleATag(kind, pubKey, dTag())
fun topics() = hashtags()
fun title() = tags.firstOrNull { it.size > 1 && it[0] == "title" }?.get(1)