From 7bb72d0c2d152b8764ec2b9a400a332805185e30 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Fri, 5 Apr 2024 11:19:15 -0400 Subject: [PATCH] Adds a Deletion event cache. --- .../amethyst/model/LocalCache.kt | 10 ++- .../amethyst/commons/data/DeletionIndex.kt | 80 +++++++++++++++++++ .../com/vitorpamplona/quartz/encoders/ATag.kt | 8 +- .../com/vitorpamplona/quartz/events/Event.kt | 7 ++ .../quartz/events/LongTextNoteEvent.kt | 2 + .../quartz/events/WikiNoteEvent.kt | 2 + 6 files changed, 106 insertions(+), 3 deletions(-) create mode 100644 commons/src/main/java/com/vitorpamplona/amethyst/commons/data/DeletionIndex.kt diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt index 399974ce7..5f4022d5a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt @@ -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() val awaitingPaymentRequests = ConcurrentHashMap 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 { diff --git a/commons/src/main/java/com/vitorpamplona/amethyst/commons/data/DeletionIndex.kt b/commons/src/main/java/com/vitorpamplona/amethyst/commons/data/DeletionIndex.kt new file mode 100644 index 000000000..a75eda68c --- /dev/null +++ b/commons/src/main/java/com/vitorpamplona/amethyst/commons/data/DeletionIndex.kt @@ -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() + + 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 + } +} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/encoders/ATag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/encoders/ATag.kt index 6c3f3410c..69fc67c4f 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/encoders/ATag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/encoders/ATag.kt @@ -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(":") } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/Event.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/Event.kt index ab0ff1fd7..6cfb79b7f 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/Event.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/Event.kt @@ -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 { diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/LongTextNoteEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/LongTextNoteEvent.kt index fdf5cfd1a..5f95758c2 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/LongTextNoteEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/LongTextNoteEvent.kt @@ -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) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/WikiNoteEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/WikiNoteEvent.kt index a08bb9992..696e2846b 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/WikiNoteEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/WikiNoteEvent.kt @@ -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)