From cdd89b3d543afe712bd2bc96fd1ab79f16f68b9c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Blin?=
 <sebastien.blin@savoirfairelinux.com>
Date: Wed, 21 Jul 2021 17:13:31 -0400
Subject: [PATCH] jamiaccount: remove syncing with uri

A user can remove a contact while a conversation is syncing with
this contact. In this case, the conversation was not deleted
properly. Causing some UI bug when re-adding the contact.

GitLab: #596
Change-Id: I88f2549a02ba67e9030cdadfdfb45f34a0ee06b9
---
 src/jamidht/jamiaccount.cpp                 | 43 ++++++++++++------
 test/unitTest/conversation/conversation.cpp | 48 +++++++++++++++++++++
 2 files changed, 77 insertions(+), 14 deletions(-)

diff --git a/src/jamidht/jamiaccount.cpp b/src/jamidht/jamiaccount.cpp
index d60053bd37..7ca1dadb65 100644
--- a/src/jamidht/jamiaccount.cpp
+++ b/src/jamidht/jamiaccount.cpp
@@ -3060,35 +3060,50 @@ JamiAccount::addContact(const std::string& uri, bool confirmed)
 void
 JamiAccount::removeContact(const std::string& uri, bool ban)
 {
+    std::map<std::string, ConvInfo> convInfos;
     {
         std::lock_guard<std::mutex> lock(configurationMutex_);
-        if (accountManager_)
+        if (accountManager_) {
             accountManager_->removeContact(uri, ban);
-        else
+            convInfos = accountManager_->getInfo()->conversations;
+        } else {
             JAMI_WARN("[Account %s] removeContact: account not loaded", getAccountID().c_str());
+            return;
+        }
     }
 
     // Remove related conversation
     auto isSelf = uri == getUsername();
+    bool updateConvInfos = false;
     std::vector<std::string> toRm;
     {
         std::lock_guard<std::mutex> lk(conversationsMtx_);
-        for (const auto& [key, conv] : conversations_) {
-            try {
-                // Note it's important to check getUsername(), else
-                // removing self can remove all conversations
-                if (conv->mode() == ConversationMode::ONE_TO_ONE) {
-                    auto initMembers = conv->getInitialMembers();
-                    if ((isSelf && initMembers.size() == 1)
-                        || std::find(initMembers.begin(), initMembers.end(), uri)
-                               != initMembers.end())
-                        toRm.emplace_back(key);
+        for (auto& [convId, conv] : convInfos) {
+            auto itConv = conversations_.find(convId);
+            if (itConv != conversations_.end() && itConv->second) {
+                try {
+                    // Note it's important to check getUsername(), else
+                    // removing self can remove all conversations
+                    if (itConv->second->mode() == ConversationMode::ONE_TO_ONE) {
+                        auto initMembers = itConv->second->getInitialMembers();
+                        if ((isSelf && initMembers.size() == 1)
+                            || std::find(initMembers.begin(), initMembers.end(), uri)
+                                   != initMembers.end())
+                            toRm.emplace_back(convId);
+                    }
+                } catch (const std::exception& e) {
+                    JAMI_WARN("%s", e.what());
                 }
-            } catch (const std::exception& e) {
-                JAMI_WARN("%s", e.what());
+            } else if (std::find(conv.members.begin(), conv.members.end(), uri)
+                       != conv.members.end()) {
+                // It's syncing with uri, mark as removed!
+                conv.removed = std::time(nullptr);
+                updateConvInfos = true;
             }
         }
     }
+    if (updateConvInfos)
+        accountManager_->setConversations(convInfos);
     for (const auto& id : toRm) {
         // Note, if we ban the device, we don't send the leave cause the other peer will just
         // never got the notifications, so just erase the datas
diff --git a/test/unitTest/conversation/conversation.cpp b/test/unitTest/conversation/conversation.cpp
index 3bb5ee97ee..d99685b2dc 100644
--- a/test/unitTest/conversation/conversation.cpp
+++ b/test/unitTest/conversation/conversation.cpp
@@ -155,6 +155,7 @@ private:
     void testDoNotLoadIncorrectConversation();
     void testSyncingWhileAccepting();
     void testGetConversationsMembersWhileSyncing();
+    void testRemoveContactRemoveSyncing();
     void testCountInteractions();
 
     CPPUNIT_TEST_SUITE(ConversationTest);
@@ -213,6 +214,7 @@ private:
     CPPUNIT_TEST(testDoNotLoadIncorrectConversation);
     CPPUNIT_TEST(testSyncingWhileAccepting);
     CPPUNIT_TEST(testGetConversationsMembersWhileSyncing);
+    CPPUNIT_TEST(testRemoveContactRemoveSyncing);
     CPPUNIT_TEST(testCountInteractions);
     CPPUNIT_TEST_SUITE_END();
 };
@@ -4501,6 +4503,52 @@ ConversationTest::testGetConversationsMembersWhileSyncing()
                    != members.end());
 }
 
+void
+ConversationTest::testRemoveContactRemoveSyncing()
+{
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
+    auto bobUri = bobAccount->getUsername();
+    auto aliceUri = aliceAccount->getUsername();
+    std::mutex mtx;
+    std::unique_lock<std::mutex> lk {mtx};
+    std::condition_variable cv;
+    std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers;
+    bool conversationReady = false, requestReceived = false;
+    std::string convId = "";
+    confHandlers.insert(DRing::exportable_callback<DRing::ConfigurationSignal::IncomingTrustRequest>(
+        [&](const std::string& account_id,
+            const std::string& /*from*/,
+            const std::string& convId,
+            const std::vector<uint8_t>& /*payload*/,
+            time_t /*received*/) {
+            if (account_id == bobId && !convId.empty())
+                requestReceived = true;
+            cv.notify_one();
+        }));
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationReady>(
+        [&](const std::string& accountId, const std::string& conversationId) {
+            if (accountId == aliceId) {
+                convId = conversationId;
+            } else if (accountId == bobId) {
+                conversationReady = true;
+            }
+            cv.notify_one();
+        }));
+    DRing::registerSignalHandlers(confHandlers);
+    aliceAccount->addContact(bobUri);
+    aliceAccount->sendTrustRequest(bobUri, {});
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
+
+    Manager::instance().sendRegister(aliceId, false); // This avoid to sync immediately
+    CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
+
+    CPPUNIT_ASSERT(bobAccount->getConversations().size() == 1);
+    bobAccount->removeContact(aliceUri, false);
+
+    CPPUNIT_ASSERT(bobAccount->getConversations().size() == 0);
+}
+
 void
 ConversationTest::testCountInteractions()
 {
-- 
GitLab