From 11f64a1d1aa5ceb0abb644eb90f06f344c3a9bb4 Mon Sep 17 00:00:00 2001
From: Harris <hjubb@users.noreply.github.com>
Date: Thu, 17 Jun 2021 18:29:05 +1000
Subject: [PATCH] feat: add snode method delete_all with data class for params,
 refactoring ClearAllDataDialog.kt to handle async requests better and prevent
 ANR

---
 .../loki/dialogs/ClearAllDataDialog.kt        | 53 ++++++++++++++++---
 .../org/session/libsession/snode/SnodeAPI.kt  | 25 ++++++++-
 .../libsession/snode/SnodeDeleteMessage.kt    | 30 +++++++++++
 .../org/session/libsignal/utilities/Snode.kt  |  3 +-
 4 files changed, 102 insertions(+), 9 deletions(-)
 create mode 100644 libsession/src/main/java/org/session/libsession/snode/SnodeDeleteMessage.kt

diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/dialogs/ClearAllDataDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/dialogs/ClearAllDataDialog.kt
index cb1bf22566..ac18e224a1 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/loki/dialogs/ClearAllDataDialog.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/loki/dialogs/ClearAllDataDialog.kt
@@ -4,38 +4,77 @@ import android.app.Dialog
 import android.graphics.Color
 import android.graphics.drawable.ColorDrawable
 import android.os.Bundle
-import androidx.fragment.app.DialogFragment
-import androidx.appcompat.app.AlertDialog
 import android.view.LayoutInflater
+import androidx.appcompat.app.AlertDialog
+import androidx.core.view.isVisible
+import androidx.fragment.app.DialogFragment
+import androidx.lifecycle.lifecycleScope
+import kotlinx.android.synthetic.main.dialog_clear_all_data.*
 import kotlinx.android.synthetic.main.dialog_clear_all_data.view.*
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
 import network.loki.messenger.R
-import org.thoughtcrime.securesms.ApplicationContext
-import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol
+import org.session.libsession.messaging.MessagingModuleConfiguration
+import org.session.libsession.snode.SnodeAPI
+import org.session.libsession.snode.SnodeDeleteMessage
 import org.session.libsession.utilities.KeyPairUtilities
 
 class ClearAllDataDialog : DialogFragment() {
 
+    var clearJob: Job? = null
+    set(value) {
+        field = value
+        updateUI()
+    }
+
     override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
         val builder = AlertDialog.Builder(requireContext())
         val contentView = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_clear_all_data, null)
         contentView.cancelButton.setOnClickListener { dismiss() }
         contentView.clearAllDataButton.setOnClickListener { clearAllData() }
         builder.setView(contentView)
+        builder.setCancelable(false)
         val result = builder.create()
         result.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
         return result
     }
 
+    private fun updateUI() {
+        if (clearJob?.isActive == true) {
+            // clear background job is running, prevent interaction
+            dialog?.let { view ->
+                view.cancelButton.isVisible = false
+                view.clearAllDataButton.isVisible = false
+            }
+        } else {
+            dialog?.let { view ->
+                view.cancelButton.isVisible = false
+                view.clearAllDataButton.isVisible = false
+            }
+        }
+    }
+
     private fun clearAllData() {
         if (KeyPairUtilities.hasV2KeyPair(requireContext())) {
-            MultiDeviceProtocol.forceSyncConfigurationNowIfNeeded(requireContext())
-            ApplicationContext.getInstance(context).clearAllData(false)
+            clearJob = lifecycleScope.launch {
+                delay(5_000)
+                // finish
+                val userPublicKey = MessagingModuleConfiguration.shared.storage.getUserPublicKey()
+
+
+                val deleteMessage = SnodeDeleteMessage(userKey, System.currentTimeMillis(), )
+                SnodeAPI.deleteAllMessages()
+                // TODO: re-add the clear data here
+                //ApplicationContext.getInstance(context).clearAllData(false)
+            }
         } else {
             val dialog = AlertDialog.Builder(requireContext())
             val message = "We’ve upgraded the way Session IDs are generated, so you will be unable to restore your current Session ID."
             dialog.setMessage(message)
             dialog.setPositiveButton("Yes") { _, _ ->
-                ApplicationContext.getInstance(context).clearAllData(false)
+                // TODO: re-add the clear data here
+                // ApplicationContext.getInstance(context).clearAllData(false)
             }
             dialog.setNegativeButton("Cancel") { _, _ ->
                 // Do nothing
diff --git a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt
index f126871a40..32c221b0e3 100644
--- a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt
+++ b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt
@@ -6,7 +6,6 @@ import android.os.Build
 import com.goterl.lazysodium.LazySodiumAndroid
 import com.goterl.lazysodium.SodiumAndroid
 import com.goterl.lazysodium.exceptions.SodiumException
-import com.goterl.lazysodium.interfaces.AEAD
 import com.goterl.lazysodium.interfaces.GenericHash
 import com.goterl.lazysodium.interfaces.PwHash
 import com.goterl.lazysodium.interfaces.SecretBox
@@ -298,6 +297,30 @@ object SnodeAPI {
         }
     }
 
+    /** Deletes all messages owned by the given pubkey on this SN and broadcasts the delete request to
+     *  all other swarm members.
+     *  Returns dict of:
+     *  - "swarms" dict mapping ed25519 pubkeys (in hex) of swarm members to dict values of:
+     *  - "failed" and other failure keys -- see `recursive`.
+     *  - "deleted": hashes of deleted messages.
+     *  - "signature": signature of ( PUBKEY_HEX || TIMESTAMP || DELETEDHASH[0] || ... || DELETEDHASH[N] ), signed
+     *  by the node's ed25519 pubkey.
+     */
+    fun deleteAllMessages(deleteMessage: SnodeDeleteMessage): Promise<Set<RawResponsePromise>, Exception> {
+        // considerations: timestamp off in retrying logic, not being able to re-sign with latest timestamp? do we just not retry this as it will be synchronous
+        val destination = if (useTestnet) deleteMessage.pubKey.removing05PrefixIfNeeded() else deleteMessage.pubKey
+        return retryIfNeeded(maxRetryCount) {
+            getTargetSnodes(destination).map { swarm ->
+                swarm.map { snode ->
+                    val parameters = deleteMessage.toJSON()
+                    retryIfNeeded(maxRetryCount) {
+                        invoke(Snode.Method.DeleteAll, snode, destination, parameters)
+                    }
+                }.toSet()
+            }
+        }
+    }
+
     // Parsing
     private fun parseSnodes(rawResponse: Any): List<Snode> {
         val json = rawResponse as? Map<*, *>
diff --git a/libsession/src/main/java/org/session/libsession/snode/SnodeDeleteMessage.kt b/libsession/src/main/java/org/session/libsession/snode/SnodeDeleteMessage.kt
new file mode 100644
index 0000000000..a9f11fef08
--- /dev/null
+++ b/libsession/src/main/java/org/session/libsession/snode/SnodeDeleteMessage.kt
@@ -0,0 +1,30 @@
+package org.session.libsession.snode
+
+import org.session.libsignal.utilities.removing05PrefixIfNeeded
+
+data class SnodeDeleteMessage(
+    /**
+     * The hex encoded public key of the user.
+     */
+    val pubKey: String,
+    /**
+     * The timestamp at which this request was initiated, in milliseconds since unix epoch.
+     * Must be within Must be within ±60s of the current time.
+     * (For clients it is recommended to retrieve a timestamp via `info` first, to avoid client time sync issues).
+     */
+    val timestamp: Long,
+    /**
+     * an Ed25519 signature of ( "delete_all" || timestamp ), signed by the ed25519
+     */
+    val signature: String,
+) {
+
+    internal fun toJSON(): Map<String, String> {
+        return mapOf(
+            "pubkey" to if (SnodeAPI.useTestnet) pubKey.removing05PrefixIfNeeded() else pubKey,
+            "timestamp" to timestamp.toString(),
+            "signature" to signature
+        )
+    }
+
+}
\ No newline at end of file
diff --git a/libsignal/src/main/java/org/session/libsignal/utilities/Snode.kt b/libsignal/src/main/java/org/session/libsignal/utilities/Snode.kt
index 92c30095ef..251116f317 100644
--- a/libsignal/src/main/java/org/session/libsignal/utilities/Snode.kt
+++ b/libsignal/src/main/java/org/session/libsignal/utilities/Snode.kt
@@ -7,7 +7,8 @@ class Snode(val address: String, val port: Int, val publicKeySet: KeySet?) {
         GetSwarm("get_snodes_for_pubkey"),
         GetMessages("retrieve"),
         SendMessage("store"),
-        OxenDaemonRPCCall("oxend_request")
+        OxenDaemonRPCCall("oxend_request"),
+        DeleteAll("delete_all")
     }
 
     data class KeySet(val ed25519Key: String, val x25519Key: String)