From faaa7a6d8efb3f92fc239e7d77ec2f9a46c3a958 Mon Sep 17 00:00:00 2001
From: Koen <koen@pop-os.localdomain>
Date: Wed, 29 Nov 2023 12:48:02 +0100
Subject: [PATCH] Fixes to properly handle deletions.

---
 .idea/androidTestResultsUserPreferences.xml   | 70 ++++++++++++++++
 .../futo/polycentric/core/DatabaseTests.kt    | 79 +++++++++++++++++++
 .../polycentric/core/ProcessHandleTests.kt    | 72 +++++++++++++++++
 .../com/futo/polycentric/core/MemoryStore.kt  | 12 ++-
 .../futo/polycentric/core/ProcessHandle.kt    | 18 -----
 .../futo/polycentric/core/SqlLiteDbHelper.kt  | 29 ++++++-
 .../com/futo/polycentric/core/SqlLiteStore.kt |  2 +-
 7 files changed, 259 insertions(+), 23 deletions(-)

diff --git a/.idea/androidTestResultsUserPreferences.xml b/.idea/androidTestResultsUserPreferences.xml
index 2082d41..c72e04f 100644
--- a/.idea/androidTestResultsUserPreferences.xml
+++ b/.idea/androidTestResultsUserPreferences.xml
@@ -56,6 +56,20 @@
             </AndroidTestResultsTableState>
           </value>
         </entry>
+        <entry key="-1834143290">
+          <value>
+            <AndroidTestResultsTableState>
+              <option name="preferredColumnWidths">
+                <map>
+                  <entry key="29211JEGR13699" value="120" />
+                  <entry key="Duration" value="90" />
+                  <entry key="Google&#10; Pixel 6a" value="120" />
+                  <entry key="Tests" value="360" />
+                </map>
+              </option>
+            </AndroidTestResultsTableState>
+          </value>
+        </entry>
         <entry key="-1766545009">
           <value>
             <AndroidTestResultsTableState>
@@ -113,6 +127,34 @@
             </AndroidTestResultsTableState>
           </value>
         </entry>
+        <entry key="-1531674397">
+          <value>
+            <AndroidTestResultsTableState>
+              <option name="preferredColumnWidths">
+                <map>
+                  <entry key="29211JEGR13699" value="120" />
+                  <entry key="Duration" value="90" />
+                  <entry key="Google&#10; Pixel 6a" value="120" />
+                  <entry key="Tests" value="360" />
+                </map>
+              </option>
+            </AndroidTestResultsTableState>
+          </value>
+        </entry>
+        <entry key="-1528278223">
+          <value>
+            <AndroidTestResultsTableState>
+              <option name="preferredColumnWidths">
+                <map>
+                  <entry key="29211JEGR13699" value="120" />
+                  <entry key="Duration" value="90" />
+                  <entry key="Google&#10; Pixel 6a" value="120" />
+                  <entry key="Tests" value="360" />
+                </map>
+              </option>
+            </AndroidTestResultsTableState>
+          </value>
+        </entry>
         <entry key="-1495363119">
           <value>
             <AndroidTestResultsTableState>
@@ -230,6 +272,20 @@
             </AndroidTestResultsTableState>
           </value>
         </entry>
+        <entry key="-991781541">
+          <value>
+            <AndroidTestResultsTableState>
+              <option name="preferredColumnWidths">
+                <map>
+                  <entry key="29211JEGR13699" value="120" />
+                  <entry key="Duration" value="90" />
+                  <entry key="Google&#10; Pixel 6a" value="120" />
+                  <entry key="Tests" value="360" />
+                </map>
+              </option>
+            </AndroidTestResultsTableState>
+          </value>
+        </entry>
         <entry key="-693989101">
           <value>
             <AndroidTestResultsTableState>
@@ -290,6 +346,20 @@
             </AndroidTestResultsTableState>
           </value>
         </entry>
+        <entry key="-91332955">
+          <value>
+            <AndroidTestResultsTableState>
+              <option name="preferredColumnWidths">
+                <map>
+                  <entry key="29211JEGR13699" value="120" />
+                  <entry key="Duration" value="90" />
+                  <entry key="Google&#10; Pixel 6a" value="120" />
+                  <entry key="Tests" value="360" />
+                </map>
+              </option>
+            </AndroidTestResultsTableState>
+          </value>
+        </entry>
         <entry key="-43042216">
           <value>
             <AndroidTestResultsTableState>
diff --git a/app/src/androidTest/java/com/futo/polycentric/core/DatabaseTests.kt b/app/src/androidTest/java/com/futo/polycentric/core/DatabaseTests.kt
index ace1e20..6e7f0de 100644
--- a/app/src/androidTest/java/com/futo/polycentric/core/DatabaseTests.kt
+++ b/app/src/androidTest/java/com/futo/polycentric/core/DatabaseTests.kt
@@ -1,10 +1,12 @@
 package com.futo.polycentric.core
 
 import androidx.test.platform.app.InstrumentationRegistry
+import kotlinx.coroutines.runBlocking
 import org.junit.After
 import org.junit.Assert
 import org.junit.Before
 import org.junit.Test
+import userpackage.Protocol
 
 class DatabaseTests {
     private lateinit var _db: SqlLiteDbHelper
@@ -41,6 +43,83 @@ class DatabaseTests {
         Assert.assertArrayEquals(handle.processSecret.system.secretKey, secrets[0].system.secretKey)
     }
 
+    @Test
+    fun deletePost() {
+        _db.recreate()
+
+        Store.initializeSqlLiteStore(_db)
+
+        //Create handle and post
+        val processHandle = ProcessHandle.create()
+        processHandle.addServer(TestConstants.SERVER)
+
+        val ev = processHandle.post("TEST")
+        runBlocking {
+            processHandle.fullyBackfillServers()
+        }
+
+        //Query local for event existing
+        val sev = Store.instance.getSignedEvent(ev)!!
+        Assert.assertEquals(ContentType.POST.value, sev.event.contentType)
+
+        var count = 0
+        Store.instance.enumerateSignedEvents(ev.system, ContentType.POST) {
+            count++
+        }
+        Assert.assertEquals(1, count)
+
+        //Query remote for event existing
+        runBlocking {
+            val events = ApiMethods.getEvents(TestConstants.SERVER, processHandle.system.toProto(), Protocol.RangesForSystem.newBuilder()
+                .addRangesForProcesses(
+                    Protocol.RangesForProcess.newBuilder()
+                    .setProcess(processHandle.processSecret.process.toProto())
+                    .addRanges(
+                        Protocol.Range.newBuilder()
+                        .setLow(ev.logicalClock)
+                        .setHigh(ev.logicalClock)
+                        .build())
+                    .build())
+                .build())
+
+            Assert.assertEquals(1, events.eventsList.size)
+            val sevRemote = SignedEvent.fromProto(events.eventsList.first())
+            Assert.assertEquals(ContentType.POST.value, sevRemote.event.contentType)
+        }
+
+        //Delete the event
+        processHandle.delete(ev.process, ev.logicalClock)
+        runBlocking {
+            processHandle.fullyBackfillServers()
+        }
+
+        //Query local for non existence
+        Assert.assertNull(null, Store.instance.getSignedEvent(ev))
+
+        count = 0
+        Store.instance.enumerateSignedEvents(ev.system, ContentType.POST) {
+            count++
+        }
+        Assert.assertEquals(0, count)
+
+        //Query remotely for non existence
+        runBlocking {
+            val events = ApiMethods.getEvents(TestConstants.SERVER, processHandle.system.toProto(), Protocol.RangesForSystem.newBuilder()
+                .addRangesForProcesses(
+                    Protocol.RangesForProcess.newBuilder()
+                    .setProcess(processHandle.processSecret.process.toProto())
+                    .addRanges(
+                        Protocol.Range.newBuilder()
+                        .setLow(ev.logicalClock)
+                        .setHigh(ev.logicalClock)
+                        .build())
+                    .build())
+                .build())
+
+            Assert.assertEquals(0, events.eventsList.size)
+        }
+    }
+
     @After
     fun cleanup() {
         _db.close()
diff --git a/app/src/androidTest/java/com/futo/polycentric/core/ProcessHandleTests.kt b/app/src/androidTest/java/com/futo/polycentric/core/ProcessHandleTests.kt
index eddd088..694d91f 100644
--- a/app/src/androidTest/java/com/futo/polycentric/core/ProcessHandleTests.kt
+++ b/app/src/androidTest/java/com/futo/polycentric/core/ProcessHandleTests.kt
@@ -9,6 +9,7 @@ import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
 import com.google.protobuf.ByteString
+import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.runTest
 import org.junit.Assert.*
 import org.junit.Test
@@ -235,6 +236,77 @@ class ProcessHandleTests {
         assertEquals("alice", serverState2.username)
     }
 
+    @Test
+    fun deletePost() {
+        Store.initializeMemoryStore()
+
+        //Create handle and post
+        val processHandle = ProcessHandle.create()
+        processHandle.addServer(TestConstants.SERVER)
+
+        val ev = processHandle.post("TEST")
+        runBlocking {
+            processHandle.fullyBackfillServers()
+        }
+
+        //Query local for event existing
+        val sev = Store.instance.getSignedEvent(ev)!!
+        assertEquals(ContentType.POST.value, sev.event.contentType)
+
+        var count = 0
+        Store.instance.enumerateSignedEvents(ev.system, ContentType.POST) {
+            count++
+        }
+        assertEquals(1, count)
+
+        //Query remote for event existing
+        runBlocking {
+            val events = ApiMethods.getEvents(TestConstants.SERVER, processHandle.system.toProto(), Protocol.RangesForSystem.newBuilder()
+                .addRangesForProcesses(Protocol.RangesForProcess.newBuilder()
+                    .setProcess(processHandle.processSecret.process.toProto())
+                    .addRanges(Protocol.Range.newBuilder()
+                        .setLow(ev.logicalClock)
+                        .setHigh(ev.logicalClock)
+                        .build())
+                    .build())
+                .build())
+
+            assertEquals(1, events.eventsList.size)
+            val sevRemote = SignedEvent.fromProto(events.eventsList.first())
+            assertEquals(ContentType.POST.value, sevRemote.event.contentType)
+        }
+
+        //Delete the event
+        processHandle.delete(ev.process, ev.logicalClock)
+        runBlocking {
+            processHandle.fullyBackfillServers()
+        }
+
+        //Query local for non existence
+        count = 0
+        Store.instance.enumerateSignedEvents(ev.system, ContentType.POST) {
+            count++
+        }
+        assertEquals(0, count)
+
+        assertNull(null, Store.instance.getSignedEvent(ev))
+
+        //Query remotely for non existence
+        runBlocking {
+            val events = ApiMethods.getEvents(TestConstants.SERVER, processHandle.system.toProto(), Protocol.RangesForSystem.newBuilder()
+                .addRangesForProcesses(Protocol.RangesForProcess.newBuilder()
+                    .setProcess(processHandle.processSecret.process.toProto())
+                    .addRanges(Protocol.Range.newBuilder()
+                        .setLow(ev.logicalClock)
+                        .setHigh(ev.logicalClock)
+                        .build())
+                    .build())
+                .build())
+
+            assertEquals(0, events.eventsList.size)
+        }
+    }
+
     @Test
     fun comment() = runTest {
         Store.initializeMemoryStore()
diff --git a/app/src/main/java/com/futo/polycentric/core/MemoryStore.kt b/app/src/main/java/com/futo/polycentric/core/MemoryStore.kt
index 54755cd..830eba3 100644
--- a/app/src/main/java/com/futo/polycentric/core/MemoryStore.kt
+++ b/app/src/main/java/com/futo/polycentric/core/MemoryStore.kt
@@ -56,12 +56,20 @@ class MemoryStore : Store() {
     override fun updateProcessState(system: PublicKey, process: ByteArray, storageTypeProcessState: StorageTypeProcessState) { }
 
     override fun getSignedEvent(system: PublicKey, process: Process, logicalClock: Long): SignedEvent? {
-        val e = _signedEvents[EventKey(system, process.process, logicalClock)]?.event ?: return null
-        return SignedEvent.fromProto(e)
+        val e = _signedEvents[EventKey(system, process.process, logicalClock)] ?: return null
+        if (!e.hasEvent()) {
+            return null
+        }
+
+        return SignedEvent.fromProto(e.event)
     }
 
     override fun enumerateSignedEvents(system: PublicKey, contentType: ContentType?, handler: (SignedEvent) -> Unit) {
         for (pair in _signedEvents) {
+            if (!pair.value.hasEvent()) {
+                continue
+            }
+
             if (pair.key.system != system) {
                 continue
             }
diff --git a/app/src/main/java/com/futo/polycentric/core/ProcessHandle.kt b/app/src/main/java/com/futo/polycentric/core/ProcessHandle.kt
index 05f56d3..4b4b8c5 100644
--- a/app/src/main/java/com/futo/polycentric/core/ProcessHandle.kt
+++ b/app/src/main/java/com/futo/polycentric/core/ProcessHandle.kt
@@ -252,24 +252,6 @@ class ProcessHandle constructor(
 
             Ranges.insert(deleteProcessState.ranges, deleteProto.logicalClock)
             Store.instance.putTombstone(event.system, deleteProcess.process, deleteProto.logicalClock, signedEvent.toPointer())
-
-            /* TODO
-            actions.push(
-                this._store.deleteIndexClaim(
-                    event.system,
-                    event.process,
-                    event.logicalClock,
-                ),
-            )*/
-        } else if (event.contentType == ContentType.CLAIM.value) {
-            /* TODO
-            actions.push(
-                this._store.putIndexClaim(
-                    event.system,
-                    event.process,
-                    event.logicalClock,
-                ),
-            )*/
         }
 
         systemState.update(event)
diff --git a/app/src/main/java/com/futo/polycentric/core/SqlLiteDbHelper.kt b/app/src/main/java/com/futo/polycentric/core/SqlLiteDbHelper.kt
index 70cd702..2304845 100644
--- a/app/src/main/java/com/futo/polycentric/core/SqlLiteDbHelper.kt
+++ b/app/src/main/java/com/futo/polycentric/core/SqlLiteDbHelper.kt
@@ -59,13 +59,38 @@ class SqlLiteDbHelper : SQLiteOpenHelper {
 
             currentVersion = 3
         }
+
+        if (currentVersion == 3 && newVersion == 4) {
+            db.execSQL("""
+                CREATE TABLE new_signed_events (
+                    id INTEGER PRIMARY KEY, 
+                    public_key VARCHAR, 
+                    process VARCHAR, 
+                    logical_clock INTEGER, 
+                    content_type INTEGER, 
+                    value BLOB,
+                    UNIQUE(public_key, process, logical_clock)
+                )
+            """)
+
+            db.execSQL("""
+                INSERT INTO new_signed_events (id, public_key, process, logical_clock, content_type, value)
+                SELECT MAX(id) as id, public_key, process, logical_clock, content_type, value 
+                FROM signed_events
+                GROUP BY public_key, process, logical_clock
+            """)
+
+            db.execSQL("DROP TABLE signed_events")
+            db.execSQL("ALTER TABLE new_signed_events RENAME TO signed_events")
+            db.execSQL("CREATE INDEX idx_event_pointer ON signed_events (public_key, process, logical_clock)")
+        }
     }
 
     private fun createTables(db: SQLiteDatabase) {
         db.execSQL("CREATE TABLE system_states (public_key VARCHAR PRIMARY KEY, value BLOB)")
         db.execSQL("CREATE TABLE process_states (public_key_process VARCHAR PRIMARY KEY, value BLOB)")
         db.execSQL("CREATE TABLE process_secrets (public_key VARCHAR PRIMARY KEY, value BLOB)")
-        db.execSQL("CREATE TABLE signed_events (id INTEGER PRIMARY KEY, public_key VARCHAR, process VARCHAR, logical_clock INTEGER, content_type INTEGER, value BLOB)")
+        db.execSQL("CREATE TABLE signed_events (id INTEGER PRIMARY KEY, public_key VARCHAR, process VARCHAR, logical_clock INTEGER, content_type INTEGER, value BLOB, UNIQUE(public_key, process, logical_clock))")
         db.execSQL("CREATE INDEX idx_event_pointer ON signed_events (public_key, process, logical_clock)")
     }
 
@@ -88,6 +113,6 @@ class SqlLiteDbHelper : SQLiteOpenHelper {
     companion object {
         private const val TAG = "SqlLiteDbHelper"
         private const val DATABASE_NAME = "polycentric_core.db"
-        private const val DATABASE_VERSION = 3
+        private const val DATABASE_VERSION = 4
     }
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/polycentric/core/SqlLiteStore.kt b/app/src/main/java/com/futo/polycentric/core/SqlLiteStore.kt
index 8592849..624636d 100644
--- a/app/src/main/java/com/futo/polycentric/core/SqlLiteStore.kt
+++ b/app/src/main/java/com/futo/polycentric/core/SqlLiteStore.kt
@@ -58,7 +58,7 @@ class SqlLiteStore(private val _db: SqlLiteDbHelper) : Store() {
             .build()
 
         _db.writableDatabase.execSQL("REPLACE INTO signed_events(public_key, process, logical_clock, content_type, value) VALUES(?, ?, ?, 0, ?)",
-            arrayOf(system.key.toBase64(), process.toBase64(), logicalClock.toString(),  storageTypeEvent.toByteArray()))
+            arrayOf(system.key.toBase64(), process.toBase64(), logicalClock.toString(), storageTypeEvent.toByteArray()))
     }
 
     override fun getSignedEvent(system: PublicKey, process: Process, logicalClock: Long): SignedEvent? {
-- 
GitLab