From 521107ed5ab0a3be4af6522add5354c6e3a831ec Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Blin?=
 <sebastien.blin@savoirfairelinux.com>
Date: Tue, 9 Aug 2022 17:12:26 -0400
Subject: [PATCH] swarm: do not send message displayed notification if unneeded

Change-Id: Iafc78c75cc935bd7b91f39cbd43159f1a7eeaa02
---
 src/jamidht/conversation.cpp                |  13 +-
 src/jamidht/conversation.h                  |   3 +-
 src/jamidht/conversation_module.cpp         |   8 +-
 src/jamidht/conversation_module.h           |   2 +-
 src/jamidht/jamiaccount.cpp                 |  22 +--
 test/unitTest/conversation/conversation.cpp | 158 +++++++++++++++-----
 6 files changed, 149 insertions(+), 57 deletions(-)

diff --git a/src/jamidht/conversation.cpp b/src/jamidht/conversation.cpp
index f4cc6c8654..7886b95764 100644
--- a/src/jamidht/conversation.cpp
+++ b/src/jamidht/conversation.cpp
@@ -1273,16 +1273,21 @@ Conversation::hasFetched(const std::string& deviceId)
     pimpl_->saveFetched();
 }
 
-void
+bool
 Conversation::setMessageDisplayed(const std::string& uri, const std::string& interactionId)
 {
     if (auto acc = pimpl_->account_.lock()) {
+        {
+            std::lock_guard<std::mutex> lk(pimpl_->lastDisplayedMtx_);
+            if (pimpl_->lastDisplayed_[uri] == interactionId)
+                return false;
+            pimpl_->lastDisplayed_[uri] = interactionId;
+            pimpl_->saveLastDisplayed();
+        }
         if (uri == acc->getUsername() && pimpl_->lastDisplayedUpdatedCb_)
             pimpl_->lastDisplayedUpdatedCb_(pimpl_->repository_->id(), interactionId);
-        std::lock_guard<std::mutex> lk(pimpl_->lastDisplayedMtx_);
-        pimpl_->lastDisplayed_[uri] = interactionId;
-        pimpl_->saveLastDisplayed();
     }
+    return true;
 }
 
 void
diff --git a/src/jamidht/conversation.h b/src/jamidht/conversation.h
index 8538490c4c..592b96ff8d 100644
--- a/src/jamidht/conversation.h
+++ b/src/jamidht/conversation.h
@@ -350,8 +350,9 @@ public:
      * Store last read commit (returned in getMembers)
      * @param uri               Of the member
      * @param interactionId     Last interaction displayed
+     * @return if updated
      */
-    void setMessageDisplayed(const std::string& uri, const std::string& interactionId);
+    bool setMessageDisplayed(const std::string& uri, const std::string& interactionId);
 
     /**
      * Compute, with multi device support the last message displayed of a conversation
diff --git a/src/jamidht/conversation_module.cpp b/src/jamidht/conversation_module.cpp
index 2d13bdde21..357d532e30 100644
--- a/src/jamidht/conversation_module.cpp
+++ b/src/jamidht/conversation_module.cpp
@@ -1242,16 +1242,16 @@ ConversationModule::addCallHistoryMessage(const std::string& uri, uint64_t durat
     }
 }
 
-void
+bool
 ConversationModule::onMessageDisplayed(const std::string& peer,
                                        const std::string& conversationId,
                                        const std::string& interactionId)
 {
     std::unique_lock<std::mutex> lk(pimpl_->conversationsMtx_);
     auto conversation = pimpl_->conversations_.find(conversationId);
-    if (conversation != pimpl_->conversations_.end() && conversation->second) {
-        conversation->second->setMessageDisplayed(peer, interactionId);
-    }
+    if (conversation != pimpl_->conversations_.end() && conversation->second)
+        return conversation->second->setMessageDisplayed(peer, interactionId);
+    return false;
 }
 
 uint32_t
diff --git a/src/jamidht/conversation_module.h b/src/jamidht/conversation_module.h
index a4e4d4ae4f..f7e3c2e886 100644
--- a/src/jamidht/conversation_module.h
+++ b/src/jamidht/conversation_module.h
@@ -163,7 +163,7 @@ public:
     void addCallHistoryMessage(const std::string& uri, uint64_t duration_ms);
 
     // Received that a peer displayed a message
-    void onMessageDisplayed(const std::string& peer,
+    bool onMessageDisplayed(const std::string& peer,
                             const std::string& conversationId,
                             const std::string& interactionId);
 
diff --git a/src/jamidht/jamiaccount.cpp b/src/jamidht/jamiaccount.cpp
index 293593e4e6..47febb32c8 100644
--- a/src/jamidht/jamiaccount.cpp
+++ b/src/jamidht/jamiaccount.cpp
@@ -2957,9 +2957,10 @@ JamiAccount::setMessageDisplayed(const std::string& conversationUri,
     std::string conversationId = {};
     if (uri.scheme() == Uri::Scheme::SWARM)
         conversationId = uri.authority();
+    auto sendMessage = status == (int) DRing::Account::MessageStates::DISPLAYED && isReadReceiptEnabled();
     if (!conversationId.empty())
-        convModule()->onMessageDisplayed(getUsername(), conversationId, messageId);
-    if (status == (int) DRing::Account::MessageStates::DISPLAYED && isReadReceiptEnabled())
+        sendMessage &= convModule()->onMessageDisplayed(getUsername(), conversationId, messageId);
+    if (sendMessage)
         sendInstantMessage(uri.authority(),
                            {{MIME_TYPE_IMDN, getDisplayed(conversationId, messageId)}});
     return true;
@@ -3736,14 +3737,15 @@ JamiAccount::handleMessage(const std::string& from, const std::pair<std::string,
             if (conversationId.empty()) // Old method
                 messageEngine_.onMessageDisplayed(from, from_hex_string(messageId), isDisplayed);
             else if (isDisplayed) {
-                convModule()->onMessageDisplayed(from, conversationId, messageId);
-                JAMI_DBG() << "[message " << messageId << "] Displayed by peer";
-                emitSignal<DRing::ConfigurationSignal::AccountMessageStatusChanged>(
-                    accountID_,
-                    conversationId,
-                    from,
-                    messageId,
-                    static_cast<int>(DRing::Account::MessageStates::DISPLAYED));
+                if (convModule()->onMessageDisplayed(from, conversationId, messageId)) {
+                    JAMI_DBG() << "[message " << messageId << "] Displayed by peer";
+                    emitSignal<DRing::ConfigurationSignal::AccountMessageStatusChanged>(
+                        accountID_,
+                        conversationId,
+                        from,
+                        messageId,
+                        static_cast<int>(DRing::Account::MessageStates::DISPLAYED));
+                }
             }
             return true;
         } catch (const std::exception& e) {
diff --git a/test/unitTest/conversation/conversation.cpp b/test/unitTest/conversation/conversation.cpp
index 5876400bdb..f4f9c27e53 100644
--- a/test/unitTest/conversation/conversation.cpp
+++ b/test/unitTest/conversation/conversation.cpp
@@ -85,6 +85,7 @@ private:
     void testPingPongMessages();
     void testIsComposing();
     void testSetMessageDisplayed();
+    void testSetMessageDisplayedTwice();
     void testSetMessageDisplayedPreference();
     void testVoteNonEmpty();
     void testNoBadFileInInitialCommit();
@@ -111,43 +112,44 @@ private:
     void testSendReply();
 
     CPPUNIT_TEST_SUITE(ConversationTest);
-    CPPUNIT_TEST(testCreateConversation);
-    CPPUNIT_TEST(testGetConversation);
-    CPPUNIT_TEST(testGetConversationsAfterRm);
-    CPPUNIT_TEST(testRemoveInvalidConversation);
-    CPPUNIT_TEST(testSendMessage);
-    CPPUNIT_TEST(testReplaceWithBadCertificate);
-    CPPUNIT_TEST(testSendMessageTriggerMessageReceived);
-    CPPUNIT_TEST(testMergeTwoDifferentHeads);
-    CPPUNIT_TEST(testMergeAfterMigration);
-    CPPUNIT_TEST(testSendMessageToMultipleParticipants);
-    CPPUNIT_TEST(testPingPongMessages);
-    CPPUNIT_TEST(testIsComposing);
-    CPPUNIT_TEST(testSetMessageDisplayed);
-    CPPUNIT_TEST(testSetMessageDisplayedPreference);
-    CPPUNIT_TEST(testVoteNonEmpty);
-    CPPUNIT_TEST(testNoBadFileInInitialCommit);
-    CPPUNIT_TEST(testNoBadCertInInitialCommit);
-    CPPUNIT_TEST(testPlainTextNoBadFile);
-    CPPUNIT_TEST(testVoteNoBadFile);
-    CPPUNIT_TEST(testETooBigClone);
-    CPPUNIT_TEST(testETooBigFetch);
-    CPPUNIT_TEST(testUnknownModeDetected);
-    CPPUNIT_TEST(testUpdateProfile);
-    CPPUNIT_TEST(testGetProfileRequest);
-    CPPUNIT_TEST(testCheckProfileInConversationRequest);
-    CPPUNIT_TEST(testCheckProfileInTrustRequest);
-    CPPUNIT_TEST(testMemberCannotUpdateProfile);
-    CPPUNIT_TEST(testUpdateProfileWithBadFile);
-    CPPUNIT_TEST(testFetchProfileUnauthorized);
-    CPPUNIT_TEST(testDoNotLoadIncorrectConversation);
-    CPPUNIT_TEST(testSyncingWhileAccepting);
-    CPPUNIT_TEST(testCountInteractions);
-    CPPUNIT_TEST(testReplayConversation);
-    CPPUNIT_TEST(testSyncWithoutPinnedCert);
-    CPPUNIT_TEST(testImportMalformedContacts);
-    CPPUNIT_TEST(testRemoveReaddMultipleDevice);
-    CPPUNIT_TEST(testSendReply);
+    //CPPUNIT_TEST(testCreateConversation);
+    //CPPUNIT_TEST(testGetConversation);
+    //CPPUNIT_TEST(testGetConversationsAfterRm);
+    //CPPUNIT_TEST(testRemoveInvalidConversation);
+    //CPPUNIT_TEST(testSendMessage);
+    //CPPUNIT_TEST(testReplaceWithBadCertificate);
+    //CPPUNIT_TEST(testSendMessageTriggerMessageReceived);
+    //CPPUNIT_TEST(testMergeTwoDifferentHeads);
+    //CPPUNIT_TEST(testMergeAfterMigration);
+    //CPPUNIT_TEST(testSendMessageToMultipleParticipants);
+    //CPPUNIT_TEST(testPingPongMessages);
+    //CPPUNIT_TEST(testIsComposing);
+    //CPPUNIT_TEST(testSetMessageDisplayed);
+    CPPUNIT_TEST(testSetMessageDisplayedTwice);
+    //CPPUNIT_TEST(testSetMessageDisplayedPreference);
+    //CPPUNIT_TEST(testVoteNonEmpty);
+    //CPPUNIT_TEST(testNoBadFileInInitialCommit);
+    //CPPUNIT_TEST(testNoBadCertInInitialCommit);
+    //CPPUNIT_TEST(testPlainTextNoBadFile);
+    //CPPUNIT_TEST(testVoteNoBadFile);
+    //CPPUNIT_TEST(testETooBigClone);
+    //CPPUNIT_TEST(testETooBigFetch);
+    //CPPUNIT_TEST(testUnknownModeDetected);
+    //CPPUNIT_TEST(testUpdateProfile);
+    //CPPUNIT_TEST(testGetProfileRequest);
+    //CPPUNIT_TEST(testCheckProfileInConversationRequest);
+    //CPPUNIT_TEST(testCheckProfileInTrustRequest);
+    //CPPUNIT_TEST(testMemberCannotUpdateProfile);
+    //CPPUNIT_TEST(testUpdateProfileWithBadFile);
+    //CPPUNIT_TEST(testFetchProfileUnauthorized);
+    //CPPUNIT_TEST(testDoNotLoadIncorrectConversation);
+    //CPPUNIT_TEST(testSyncingWhileAccepting);
+    //CPPUNIT_TEST(testCountInteractions);
+    //CPPUNIT_TEST(testReplayConversation);
+    //CPPUNIT_TEST(testSyncWithoutPinnedCert);
+    //CPPUNIT_TEST(testImportMalformedContacts);
+    //CPPUNIT_TEST(testRemoveReaddMultipleDevice);
+    //CPPUNIT_TEST(testSendReply);
     CPPUNIT_TEST_SUITE_END();
 };
 
@@ -1010,6 +1012,88 @@ ConversationTest::testSetMessageDisplayed()
     DRing::unregisterSignalHandlers();
 }
 
+void
+ConversationTest::testSetMessageDisplayedTwice()
+{
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
+    auto aliceUri = aliceAccount->getUsername();
+    auto bobUri = bobAccount->getUsername();
+    auto convId = DRing::startConversation(aliceId);
+    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, memberMessageGenerated = false,
+         msgDisplayed = false;
+    confHandlers.insert(
+        DRing::exportable_callback<DRing::ConversationSignal::ConversationRequestReceived>(
+            [&](const std::string& /*accountId*/,
+                const std::string& /* conversationId */,
+                std::map<std::string, std::string> /*metadatas*/) {
+                requestReceived = true;
+                cv.notify_one();
+            }));
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationReady>(
+        [&](const std::string& accountId, const std::string& conversationId) {
+            if (accountId == bobId && conversationId == convId) {
+                conversationReady = true;
+                cv.notify_one();
+            }
+        }));
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>(
+        [&](const std::string& accountId,
+            const std::string& conversationId,
+            std::map<std::string, std::string> message) {
+            if (accountId == aliceId && conversationId == convId) {
+                if (message["type"] == "member")
+                    memberMessageGenerated = true;
+                cv.notify_one();
+            }
+        }));
+    std::string aliceLastMsg;
+    confHandlers.insert(
+        DRing::exportable_callback<DRing::ConfigurationSignal::AccountMessageStatusChanged>(
+            [&](const std::string& accountId,
+                const std::string& conversationId,
+                const std::string& peer,
+                const std::string& msgId,
+                int status) {
+                if (conversationId == convId && peer == aliceUri && status == 3) {
+                    if (accountId == bobId && msgId == conversationId)
+                        msgDisplayed = true;
+                    else if (accountId == aliceId)
+                        aliceLastMsg = msgId;
+                    cv.notify_one();
+                }
+            }));
+    DRing::registerSignalHandlers(confHandlers);
+    aliceLastMsg = "";
+    DRing::addConversationMember(aliceId, convId, bobUri);
+    CPPUNIT_ASSERT(
+        cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated && !aliceLastMsg.empty(); }));
+    // Assert that repository exists
+    auto repoPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + aliceAccount->getAccountID()
+                    + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + convId;
+    CPPUNIT_ASSERT(fileutils::isDirectory(repoPath));
+    // Check created files
+    auto bobInvited = repoPath + DIR_SEPARATOR_STR + "invited" + DIR_SEPARATOR_STR + bobUri;
+    CPPUNIT_ASSERT(fileutils::isFile(bobInvited));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
+    memberMessageGenerated = false;
+    DRing::acceptConversationRequest(bobId, convId);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated; }));
+
+    aliceAccount->setMessageDisplayed("swarm:" + convId, convId, 3);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return msgDisplayed; }));
+
+    msgDisplayed = false;
+    aliceAccount->setMessageDisplayed("swarm:" + convId, convId, 3);
+    CPPUNIT_ASSERT(!cv.wait_for(lk, 10s, [&]() { return msgDisplayed; }));
+
+    DRing::unregisterSignalHandlers();
+}
+
 void
 ConversationTest::testSetMessageDisplayedPreference()
 {
-- 
GitLab