From d5349490f5231126ae660855bceb2e5c8a4b5825 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Blin?=
 <sebastien.blin@savoirfairelinux.com>
Date: Wed, 31 Jan 2024 10:29:50 -0500
Subject: [PATCH] conversation: follow daemon changes for sending status

cf jami-docs/developer/delivery-status
+ Basically this patch uses the new SwarmMessage.status to show
current interaction status.
+ setRead only updates the status if the interaction is newer (else,
because signal are not guaranteed to be ordered, this can cause the
lastDisplayed_ to be incorrect).
+ Some old code is removed and unused signal
+ MessageReceived updates status if needed

GitLab: #1487
Change-Id: I4d4d5dce8dc12ab638e89e3f8431810b29a72087
---
 daemon                                        |   2 +-
 src/libclient/api/conversationmodel.h         |  11 --
 src/libclient/api/interaction.h               |  16 ++
 src/libclient/conversationmodel.cpp           | 176 ++++--------------
 src/libclient/dbus/metatypes.h                |   2 +
 src/libclient/messagelistmodel.cpp            |  15 +-
 .../qtwrapper/configurationmanager_wrap.h     |   9 +-
 src/libclient/qtwrapper/conversions_wrap.hpp  |  20 ++
 src/libclient/typedefs.h                      |   1 +
 9 files changed, 97 insertions(+), 155 deletions(-)

diff --git a/daemon b/daemon
index 66d810026..09ec8e6f4 160000
--- a/daemon
+++ b/daemon
@@ -1 +1 @@
-Subproject commit 66d81002647f712d1ee66aa8b78c3eefcc747fda
+Subproject commit 09ec8e6f43ea5c68742bffb5e40ece15fa687c46
diff --git a/src/libclient/api/conversationmodel.h b/src/libclient/api/conversationmodel.h
index edccb2121..414ba3fbf 100644
--- a/src/libclient/api/conversationmodel.h
+++ b/src/libclient/api/conversationmodel.h
@@ -513,17 +513,6 @@ Q_SIGNALS:
     void composingStatusChanged(const QString& uid,
                                 const QString& contactUri,
                                 bool isComposing) const;
-    /**
-     * Emitted when last displayed interaction changed
-     * @param uid of conversation
-     * @param participant URI
-     * @param previousUid uid of a previous displayed interaction
-     * @param newdUid uid of a new displayed interaction
-     */
-    void displayedInteractionChanged(const QString& uid,
-                                     const QString& participantURI,
-                                     const QString& previousUid,
-                                     const QString& newdUid) const;
 
     /**
      * Emitted when search status changed
diff --git a/src/libclient/api/interaction.h b/src/libclient/api/interaction.h
index 8d73329f5..12dedbb94 100644
--- a/src/libclient/api/interaction.h
+++ b/src/libclient/api/interaction.h
@@ -465,6 +465,22 @@ struct Info
         }
         for (auto i = mapStringEmoji.begin(); i != mapStringEmoji.end(); i++)
             reactions.insert(i.key(), i.value());
+        // Compute the status of the message.
+        // Basically, we got the status per member.
+        // We consider the message as sent if at least one member has received it or displayed if someone displayed it.
+        auto maxStatus = 0;
+        status = Status::SENDING;
+        for (const auto& member: msg.status.keys()) {
+            if (member == accountUri)
+                continue;
+            auto stValue = msg.status.value(member);
+            if (stValue > maxStatus) {
+                maxStatus = stValue;
+                status = maxStatus <= 1 ? Status::SENDING : (stValue == 2 ? Status::SUCCESS : Status::DISPLAYED);
+            }
+            if (maxStatus == 3)
+                break;
+        }
     }
 };
 
diff --git a/src/libclient/conversationmodel.cpp b/src/libclient/conversationmodel.cpp
index 277804e4c..677bcaaa8 100644
--- a/src/libclient/conversationmodel.cpp
+++ b/src/libclient/conversationmodel.cpp
@@ -2433,6 +2433,14 @@ ConversationModelPimpl::slotMessageReceived(const QString& accountId,
                                QString(message.body.value("totalSize")).toInt());
         }
         Q_EMIT linked.dataChanged(indexOf(conversationId));
+        // Update status
+        using namespace libjami::Account;
+        for (const auto& uri : message.status.keys()) {
+            if (uri == linked.owner.profileInfo.uri)
+                continue;
+            if (message.status.value(uri) == static_cast<int>(MessageStates::DISPLAYED))
+                conversation.interactions->setRead(uri, message.id);
+        }
     } catch (const std::exception& e) {
         qDebug() << "messages received for not existing conversation";
     }
@@ -2852,7 +2860,7 @@ ConversationModelPimpl::addConversationRequest(const MapStringString& convReques
 void
 ConversationModelPimpl::slotPendingContactAccepted(const QString& uri)
 {
-    profile::Type type {};
+    profile::Type type = profile::Type::INVALID;
     try {
         type = linked.owner.contactModel->getContact(uri).profileInfo.type;
     } catch (std::out_of_range& e) {
@@ -3470,143 +3478,39 @@ ConversationModelPimpl::updateInteractionStatus(const QString& accountId,
     if (accountId != linked.owner.id) {
         return;
     }
-    // non-swarm conversation
-    if (conversationId.isEmpty() || conversationId == linked.owner.profileInfo.uri) {
-        auto convIds = storage::getConversationsWithPeer(db, peerUri);
-        if (convIds.empty()) {
-            return;
-        }
-        auto conversationIdx = indexOf(convIds[0]);
-        auto& conversation = conversations[conversationIdx];
-        auto newStatus = interaction::Status::INVALID;
-        switch (static_cast<libjami::Account::MessageStates>(status)) {
-        case libjami::Account::MessageStates::SENDING:
-            newStatus = interaction::Status::SENDING;
-            break;
-        case libjami::Account::MessageStates::CANCELLED:
-            newStatus = interaction::Status::TRANSFER_CANCELED;
-            break;
-        case libjami::Account::MessageStates::SENT:
-            newStatus = interaction::Status::SUCCESS;
-            break;
-        case libjami::Account::MessageStates::FAILURE:
-            newStatus = interaction::Status::FAILURE;
-            break;
-        case libjami::Account::MessageStates::DISPLAYED:
-            newStatus = interaction::Status::DISPLAYED;
-            break;
-        case libjami::Account::MessageStates::UNKNOWN:
-        default:
-            newStatus = interaction::Status::UNKNOWN;
-            break;
-        }
-        auto idString = messageId;
-        // for not swarm conversation messageId in hexdesimal string format. Convert to normal string
-        // TODO messageId should be received from daemon in string format
-        if (static_cast<libjami::Account::MessageStates>(status)
-            == libjami::Account::MessageStates::DISPLAYED) {
-            std::istringstream ss(messageId.toStdString());
-            ss >> std::hex;
-            uint64_t id;
-            if (!(ss >> id))
-                return;
-            idString = QString::number(id);
-        }
-        // Update database
-        auto msgId = storage::getInteractionIdByDaemonId(db, idString);
-        if (msgId.isEmpty()) {
-            return;
-        }
-        storage::updateInteractionStatus(db, msgId, newStatus);
-        // Update conversations
-        bool updated = false;
-        bool updateDisplayedUid = false;
-        QString oldDisplayedUid = 0;
-        {
-            auto& interactions = conversation.interactions;
-            // Try to update the status.
-            if (interactions->updateStatus(msgId, newStatus)) {
-                updated = true;
-                interactions->with(msgId, [&](const QString& id, interaction::Info& interaction) {
-                    // Determine if the interaction is outgoing and has been displayed.
-                    bool interactionIsDisplayed = newStatus == interaction::Status::DISPLAYED
-                                                  && interaction::isOutgoing(interaction);
-
-                    // Get the last displayed interaction ID and timestamp for this peer.
-                    auto [lastIdForPeer, lastTimestampForPeer]
-                        = interactions->getDisplayedInfoForPeer(peerUri);
-
-                    if (lastIdForPeer.isEmpty()) {
-                        oldDisplayedUid = "";
-                        if (peerUri != linked.owner.profileInfo.uri)
-                            conversation.interactions->setRead(peerUri, msgId);
-                        updateDisplayedUid = true;
-                    } else {
-                        bool interactionIsLast = lastTimestampForPeer < interaction.timestamp;
-                        updateDisplayedUid = interactionIsDisplayed && interactionIsLast;
-                        if (updateDisplayedUid) {
-                            oldDisplayedUid = messageId;
-                            if (peerUri != linked.owner.profileInfo.uri)
-                                conversation.interactions->setRead(peerUri, msgId);
-                        }
-                    }
-                });
-            }
-        }
-        if (updateDisplayedUid) {
-            Q_EMIT linked.displayedInteractionChanged(conversation.uid,
-                                                      peerUri,
-                                                      oldDisplayedUid,
-                                                      msgId);
-        }
-        if (updated) {
-            invalidateModel();
-        }
-        return;
-    }
-    // swarm conversation
     try {
         auto& conversation = getConversationForUid(conversationId).get();
-        if (conversation.isSwarm()) {
-            using namespace libjami::Account;
-            auto msgState = static_cast<MessageStates>(status);
-            auto& interactions = conversation.interactions;
-            interactions->with(messageId,
-                               [&](const QString& id, const interaction::Info& interaction) {
-                                   if (interaction.type == interaction::Type::TEXT) {
-                                       interaction::Status newState;
-                                       if (msgState == MessageStates::SENDING) {
-                                           newState = interaction::Status::SENDING;
-                                       } else if (msgState == MessageStates::SENT) {
-                                           newState = interaction::Status::SUCCESS;
-                                       } else {
-                                           return;
-                                       }
-                                       interactions->updateStatus(id, newState);
-                                   }
-                               });
-
-            if (msgState == MessageStates::DISPLAYED) {
-                auto previous = conversation.interactions->getRead(peerUri);
-                if (peerUri != linked.owner.profileInfo.uri)
-                    conversation.interactions->setRead(peerUri, messageId);
-                else {
-                    // Here, this means that the daemon synced the displayed message
-                    // so, compute the number of unread messages.
-                    conversation.unreadMessages = ConfigurationManager::instance()
-                                                      .countInteractions(linked.owner.id,
-                                                                         conversationId,
-                                                                         messageId,
-                                                                         "",
-                                                                         peerUri);
-                    Q_EMIT linked.dataChanged(indexOf(conversationId));
-                }
-                Q_EMIT linked.displayedInteractionChanged(conversationId,
-                                                          peerUri,
-                                                          previous,
-                                                          messageId);
-            }
+        // Proceed only if the conversation is a swarm
+        if (!conversation.isSwarm() || peerUri == linked.owner.profileInfo.uri) {
+            return;
         }
+
+        auto emitDisplayed = false;
+        using namespace libjami::Account;
+        auto msgState = static_cast<MessageStates>(status);
+        auto& interactions = conversation.interactions;
+        interactions->with(messageId,
+                            [&](const QString& id, const interaction::Info& interaction) {
+                                if (interaction.type == interaction::Type::TEXT) {
+                                    interaction::Status newState;
+                                    if (msgState == MessageStates::SENDING) {
+                                        newState = interaction::Status::SENDING;
+                                    } else if (msgState == MessageStates::SENT) {
+                                        newState = interaction::Status::SUCCESS;
+                                    } else if (msgState == MessageStates::DISPLAYED) {
+                                        newState = interaction::Status::DISPLAYED;
+                                    } else {
+                                        return;
+                                    }
+                                    if (interactions->updateStatus(id, newState)
+                                        && newState == interaction::Status::DISPLAYED) {
+                                        emitDisplayed = true;
+                                    }
+                                }
+                            });
+
+        if (emitDisplayed)
+            conversation.interactions->setRead(peerUri, messageId);
     } catch (const std::out_of_range& e) {
         qDebug() << "could not update message status for not existing conversation";
     }
@@ -3999,7 +3903,7 @@ ConversationModelPimpl::acceptTransfer(const QString& convUid, const QString& in
         return;
 
     auto& interactions = conversation.interactions;
-    if (!interactions->with(interactionId, [&](const QString& id, interaction::Info& interaction) {
+    if (!interactions->with(interactionId, [&](const QString&, interaction::Info& interaction) {
             auto fileId = interaction.commit["fileId"];
             if (fileId.isEmpty()) {
                 qWarning() << "Cannot download file without fileId";
diff --git a/src/libclient/dbus/metatypes.h b/src/libclient/dbus/metatypes.h
index 0441d1bc3..a0ecbca75 100644
--- a/src/libclient/dbus/metatypes.h
+++ b/src/libclient/dbus/metatypes.h
@@ -97,6 +97,7 @@ operator<<(QDBusArgument& argument, const SwarmMessage& m)
     argument << m.body;
     argument << m.reactions;
     argument << m.editions;
+    argument << m.status;
     argument.endStructure();
 
     return argument;
@@ -112,6 +113,7 @@ operator>>(const QDBusArgument& argument, SwarmMessage& m)
     argument >> m.body;
     argument >> m.reactions;
     argument >> m.editions;
+    argument >> m.status;
     argument.endStructure();
 
     return argument;
diff --git a/src/libclient/messagelistmodel.cpp b/src/libclient/messagelistmodel.cpp
index 661e50a8b..e466448ab 100644
--- a/src/libclient/messagelistmodel.cpp
+++ b/src/libclient/messagelistmodel.cpp
@@ -209,6 +209,9 @@ MessageListModel::updateStatus(const QString& id,
         return false;
     }
     VectorInt roles;
+    if (it->second.status == newStatus) {
+        return false;
+    }
     it->second.status = newStatus;
     roles.push_back(Role::Status);
     if (!newBody.isEmpty()) {
@@ -350,13 +353,18 @@ MessageListModel::setRead(const QString& peer, const QString& messageId)
 {
     std::lock_guard<std::recursive_mutex> lk(mutex_);
     auto i = lastDisplayedMessageUid_.find(peer);
+    auto msgIdx = indexOfMessage(messageId);
     if (i != lastDisplayedMessageUid_.end()) {
         auto old = i.value();
+        auto oldMsgIdx = indexOfMessage(old);
+        if (oldMsgIdx > msgIdx) {
+            // Previous latest read is newer than this message, ignore.
+            return;
+        }
         messageToReaders_[old].removeAll(peer);
-        auto msgIdx = indexOfMessage(old);
         // Remove from latest read
-        if (msgIdx != -1) {
-            QModelIndex modelIndex = QAbstractListModel::index(msgIdx, 0);
+        if (oldMsgIdx != -1) {
+            QModelIndex modelIndex = QAbstractListModel::index(oldMsgIdx, 0);
             Q_EMIT dataChanged(modelIndex, modelIndex, {Role::Readers});
         }
     }
@@ -364,7 +372,6 @@ MessageListModel::setRead(const QString& peer, const QString& messageId)
     lastDisplayedMessageUid_[peer] = messageId;
     messageToReaders_[messageId].append(peer);
     // update interaction
-    auto msgIdx = indexOfMessage(messageId);
     // Remove from latest read
     if (msgIdx != -1) {
         QModelIndex modelIndex = QAbstractListModel::index(msgIdx, 0);
diff --git a/src/libclient/qtwrapper/configurationmanager_wrap.h b/src/libclient/qtwrapper/configurationmanager_wrap.h
index 45c6fe9c8..8692f0e39 100644
--- a/src/libclient/qtwrapper/configurationmanager_wrap.h
+++ b/src/libclient/qtwrapper/configurationmanager_wrap.h
@@ -271,7 +271,8 @@ public:
                                           msg.linearizedParent.c_str(),
                                           convertMap(msg.body),
                                           convertVecMap(msg.reactions),
-                                          convertVecMap(msg.editions)});
+                                          convertVecMap(msg.editions),
+                                          convertMap(msg.status)});
                        }
 
                        Q_EMIT swarmLoaded(id,
@@ -298,7 +299,8 @@ public:
                                              message.linearizedParent.c_str(),
                                              convertMap(message.body),
                                              convertVecMap(message.reactions),
-                                             convertVecMap(message.editions)};
+                                             convertVecMap(message.editions),
+                                             convertMap(message.status)};
                        Q_EMIT swarmMessageReceived(QString(accountId.c_str()),
                                                    QString(conversationId.c_str()),
                                                    msg);
@@ -312,7 +314,8 @@ public:
                                              message.linearizedParent.c_str(),
                                              convertMap(message.body),
                                              convertVecMap(message.reactions),
-                                             convertVecMap(message.editions)};
+                                             convertVecMap(message.editions),
+                                             convertMap(message.status)};
                        Q_EMIT swarmMessageUpdated(QString(accountId.c_str()),
                                                   QString(conversationId.c_str()),
                                                   msg);
diff --git a/src/libclient/qtwrapper/conversions_wrap.hpp b/src/libclient/qtwrapper/conversions_wrap.hpp
index dc125c4a8..dfa00b851 100644
--- a/src/libclient/qtwrapper/conversions_wrap.hpp
+++ b/src/libclient/qtwrapper/conversions_wrap.hpp
@@ -83,6 +83,26 @@ convertMap(const MapStringString& m)
     return temp;
 }
 
+inline MapStringInt
+convertMap(const std::map<std::string, int32_t>& m)
+{
+    MapStringInt temp;
+    for (const auto& [key, value] : m) {
+        temp[QString(key.c_str())] = value;
+    }
+    return temp;
+}
+
+inline std::map<std::string, int32_t>
+convertMap(const MapStringInt& m)
+{
+    std::map<std::string, int32_t> temp;
+    for (const auto& [key, value] : m.toStdMap()) {
+        temp[key.toStdString()] = value;
+    }
+    return temp;
+}
+
 inline VectorMapStringString
 convertVecMap(const std::vector<std::map<std::string, std::string>>& m)
 {
diff --git a/src/libclient/typedefs.h b/src/libclient/typedefs.h
index a3e9ff88d..7c79f8930 100644
--- a/src/libclient/typedefs.h
+++ b/src/libclient/typedefs.h
@@ -89,6 +89,7 @@ struct SwarmMessage
     MapStringString body;
     VectorMapStringString reactions;
     VectorMapStringString editions;
+    MapStringInt status;
 };
 typedef QVector<SwarmMessage> VectorSwarmMessage;
 
-- 
GitLab