From 1d6bc93fff336149e934d013cc037b50f3a92302 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Blin?=
 <sebastien.blin@savoirfairelinux.com>
Date: Thu, 28 Sep 2017 12:22:04 -0400
Subject: [PATCH] interactions: add methods to update interaction status

If a message is outgoing, update the status from daemon's signals. If a
message is incoming, the client have the ability to change the status of
the interaction from UNREAD to READ.

Change-Id: If47fed5e1a2ecbdd52745e150fc08146ca7a6cbd
Reviewed-by: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
---
 src/api/contactmodel.h           |   3 +-
 src/api/conversation.h           |   4 +-
 src/api/conversationmodel.h      |  17 ++++-
 src/api/interaction.h            |   7 +-
 src/authority/databasehelper.cpp |  38 +++++++++--
 src/authority/databasehelper.h   |  35 +++++++++-
 src/callbackshandler.cpp         |  15 ++++
 src/callbackshandler.h           |  20 ++++++
 src/contactmodel.cpp             |   5 +-
 src/conversationmodel.cpp        | 114 +++++++++++++++++++++++++++++--
 src/database.cpp                 |   1 +
 11 files changed, 241 insertions(+), 18 deletions(-)

diff --git a/src/api/contactmodel.h b/src/api/contactmodel.h
index 36ebe881..9ef26dd6 100644
--- a/src/api/contactmodel.h
+++ b/src/api/contactmodel.h
@@ -99,8 +99,9 @@ public:
      * Send a text interaction to a contact over the Dht.
      * @param contactUri
      * @param body
+     * @return id from daemon
      */
-    void sendDhtMessage(const std::string& uri, const std::string& body) const;
+    uint64_t sendDhtMessage(const std::string& uri, const std::string& body) const;
 
 Q_SIGNALS:
     /**
diff --git a/src/api/conversation.h b/src/api/conversation.h
index 015c81d5..62ba0d79 100644
--- a/src/api/conversation.h
+++ b/src/api/conversation.h
@@ -40,8 +40,8 @@ struct Info
     std::string accountId;
     std::vector<std::string> participants;
     std::string callId;
-    std::map<int, interaction::Info> interactions;
-    unsigned int lastMessageUid = 0;
+    std::map<uint64_t, interaction::Info> interactions;
+    uint64_t lastMessageUid = 0;
     unsigned int unreadMessages = 0;
 };
 
diff --git a/src/api/conversationmodel.h b/src/api/conversationmodel.h
index 18116ecd..f099e784 100644
--- a/src/api/conversationmodel.h
+++ b/src/api/conversationmodel.h
@@ -127,6 +127,12 @@ public:
      * @param uid of the conversation
      */
     void clearHistory(const std::string& uid);
+    /**
+     * change the status of the interaction from UNREAD to READ
+     * @param convId, id of the conversation
+     * @param msgId, id of the interaction
+     */
+    void setInteractionRead(const std::string& convId, const uint64_t& msgId);
 
 Q_SIGNALS:
     /**
@@ -134,7 +140,16 @@ Q_SIGNALS:
      * @param uid of msg
      * @param msg
      */
-    void newUnreadMessage(const std::string& uid, const interaction::Info& msg) const;
+    void newUnreadMessage(const std::string& uid, uint64_t msgId, const interaction::Info& msg) const;
+    /**
+     * Emitted when an interaction got a new status
+     * @param convUid conversation which owns the interaction
+     * @param msgId
+     * @param msg
+     */
+    void interactionStatusUpdated(const std::string& convUid,
+                                  uint64_t msgId,
+                                  const api::interaction::Info& msg) const;
     /**
      * Emitted when user clear the history of a conversation
      * @param uid
diff --git a/src/api/interaction.h b/src/api/interaction.h
index 752f8870..a44cfd90 100644
--- a/src/api/interaction.h
+++ b/src/api/interaction.h
@@ -70,6 +70,7 @@ to_type(const std::string& type)
 
 enum class Status {
     INVALID,
+    UNKNOWN,
     SENDING,
     FAILED,
     SUCCEED,
@@ -81,6 +82,8 @@ static const std::string
 to_string(const Status& status)
 {
     switch(status) {
+    case Status::UNKNOWN:
+        return "UNKNOWN";
     case Status::SENDING:
         return "SENDING";
     case Status::FAILED:
@@ -100,7 +103,9 @@ to_string(const Status& status)
 static Status
 to_status(const std::string& status)
 {
-    if (status == "SENDING")
+    if (status == "UNKNOWN")
+        return interaction::Status::UNKNOWN;
+    else if (status == "SENDING")
         return interaction::Status::SENDING;
     else if (status == "FAILED")
         return interaction::Status::FAILED;
diff --git a/src/authority/databasehelper.cpp b/src/authority/databasehelper.cpp
index 16c61186..09a0347b 100644
--- a/src/authority/databasehelper.cpp
+++ b/src/authority/databasehelper.cpp
@@ -170,8 +170,8 @@ getHistory(Database& db, api::conversation::Info& conversation)
                                          std::stoi(payloads[i + 3]),
                                          api::interaction::to_type(payloads[i + 4]),
                                          api::interaction::to_status(payloads[i + 5])});
-            conversation.interactions.emplace(std::stoi(payloads[i]), std::move(msg));
-            conversation.lastMessageUid = std::stoi(payloads[i]);
+            conversation.interactions.emplace(std::stoull(payloads[i]), std::move(msg));
+            conversation.lastMessageUid = std::stoull(payloads[i]);
         }
     }
 }
@@ -194,8 +194,38 @@ addMessageToConversation(Database& db,
                           {":status", to_string(msg.status)}});
 }
 
-void
-clearHistory(Database& db, const std::string& conversationId)
+void addDaemonMsgId(Database& db,
+                    const std::string& interactionId,
+                    const std::string& daemonId)
+{
+    db.update("interactions", "daemon_id=:daemon_id",
+              {{":daemon_id", daemonId}},
+              "id=:id", {{":id", interactionId}});
+}
+
+std::string getDaemonIdByInteractionId(Database& db, const std::string& id)
+{
+    auto ids = db.select("daemon_id", "interactions", "id=:id", {{":id", id}}).payloads;
+    return ids.empty() ? "" : ids[0];
+}
+
+std::string getInteractionIdByDaemonId(Database& db, const std::string& id)
+{
+    auto ids = db.select("id", "interactions", "daemon_id=:daemon_id", {{":daemon_id", id}}).payloads;
+    return ids.empty() ? "" : ids[0];
+}
+
+
+void updateInteractionStatus(Database& db, unsigned int id,
+                             api::interaction::Status& newStatus)
+{
+    db.update("interactions", "status=:status",
+              {{":status", api::interaction::to_string(newStatus)}},
+              "id=:id", {{":id", std::to_string(id)}});
+}
+
+void clearHistory(Database& db,
+                  const std::string& conversationId)
 {
     db.deleteFrom("interactions", "conversation_id=:id AND type!='CONTACT'", {{":id", conversationId}});
 }
diff --git a/src/authority/databasehelper.h b/src/authority/databasehelper.h
index 72160f67..59cc183a 100644
--- a/src/authority/databasehelper.h
+++ b/src/authority/databasehelper.h
@@ -123,7 +123,40 @@ int addMessageToConversation(Database& db,
                               const api::interaction::Info& msg);
 
 /**
- * Reset the history (remove all interaction for a conversation), keep The conversation.
+ * Change the daemon_id column for an interaction
+ * @param db
+ * @param interactionId
+ * @param daemonId
+ */
+void addDaemonMsgId(Database& db,
+                    const std::string& interactionId,
+                    const std::string& daemonId);
+
+/**
+ * @param  db
+ * @param  id
+ * @return the daemon id for an interaction else an empty string
+ */
+std::string getDaemonIdByInteractionId(Database& db, const std::string& id);
+
+/**
+ * @param  db
+ * @param  id
+ * @return the interaction id for a daemon id else an empty string
+ */
+std::string getInteractionIdByDaemonId(Database& db, const std::string& id);
+
+/**
+ * Change the status of an interaction
+ * @param db
+ * @param id
+ * @param newStatus
+ */
+void updateInteractionStatus(Database& db, unsigned int id,
+                             api::interaction::Status& newStatus);
+
+/**
+ * Clear history but not the conversation started interaction
  * @param  db
  * @param  conversationId
  */
diff --git a/src/callbackshandler.cpp b/src/callbackshandler.cpp
index 372ff5d3..58ed71fd 100644
--- a/src/callbackshandler.cpp
+++ b/src/callbackshandler.cpp
@@ -64,6 +64,12 @@ CallbacksHandler::CallbacksHandler(const Lrc& parent)
             &ConfigurationManagerInterface::incomingTrustRequest,
             this,
             &CallbacksHandler::slotIncomingContactRequest);
+
+    connect(&ConfigurationManager::instance(),
+            &ConfigurationManagerInterface::accountMessageStatusChanged,
+            this,
+            &CallbacksHandler::slotAccountMessageStatusChanged);
+
     connect(&NameDirectory::instance(),
             &NameDirectory::registeredNameFound,
             this,
@@ -257,5 +263,14 @@ CallbacksHandler::slotConferenceRemoved(const QString& callId)
     emit conferenceRemoved(callId.toStdString());
 }
 
+void
+CallbacksHandler::slotAccountMessageStatusChanged(const QString& accountId,
+                                                  const uint64_t id,
+                                                  const QString& to, int status)
+{
+    emit accountMessageStatusChanged(accountId.toStdString(), id,
+                                     to.toStdString(), status);
+}
+
 
 } // namespace lrc
diff --git a/src/callbackshandler.h b/src/callbackshandler.h
index f8f85722..2371c00f 100644
--- a/src/callbackshandler.h
+++ b/src/callbackshandler.h
@@ -149,6 +149,16 @@ Q_SIGNALS:
      * @param callId of the conference
      */
     void conferenceRemoved(const std::string& callId);
+    /**
+     * Connect this signal to know when a message sent get a new status
+     * @param accountId, account linked
+     * @param id of the message
+     * @param to, peer uri
+     * @param status, new status for this message
+     */
+    void accountMessageStatusChanged(const std::string& accountId,
+                                     const uint64_t id,
+                                     const std::string& to, int status);
 
 private Q_SLOTS:
     /**
@@ -257,6 +267,16 @@ private Q_SLOTS:
      * @param state, new state
      */
     void slotConferenceChanged(const QString& callId, const QString& state);
+    /**
+     * Emit accountMessageStatusChanged
+     * @param accountId
+     * @param id of the message for the daemon
+     * @param to peer uri
+     * @param status, new status
+     */
+    void slotAccountMessageStatusChanged(const QString& accountId,
+                                         const uint64_t id,
+                                         const QString& to, int status);
 
 private:
     const api::Lrc& parent;
diff --git a/src/contactmodel.cpp b/src/contactmodel.cpp
index dbadc19d..4ed2e3a0 100644
--- a/src/contactmodel.cpp
+++ b/src/contactmodel.cpp
@@ -303,16 +303,17 @@ ContactModel::searchContact(const std::string& query)
         account->lookupName(QString(query.c_str()));
 }
 
-void
+uint64_t
 ContactModel::sendDhtMessage(const std::string& contactUri, const std::string& body) const
 {
     // Send interaction
     QMap<QString, QString> payloads;
     payloads["text/plain"] = body.c_str();
-    ConfigurationManager::instance().sendTextMessage(QString(owner.id.c_str()),
+    auto msgId = ConfigurationManager::instance().sendTextMessage(QString(owner.id.c_str()),
                                                      QString(contactUri.c_str()),
                                                      payloads);
     // NOTE: ConversationModel should store the interaction into the database
+    return msgId;
 }
 
 
diff --git a/src/conversationmodel.cpp b/src/conversationmodel.cpp
index 41f1e9e8..022ecac5 100644
--- a/src/conversationmodel.cpp
+++ b/src/conversationmodel.cpp
@@ -19,6 +19,9 @@
  ***************************************************************************/
 #include "api/conversationmodel.h"
 
+// daemon
+#include <account_const.h>
+
 // std
 #include <regex>
 #include <algorithm>
@@ -103,6 +106,16 @@ public:
     void addIncomingMessage(const std::string& from,
                             const std::string& body,
                             const std::string& authorProfileId="");
+    /**
+     * Change the status of an interaction. Listen from callbacksHandler
+     * @param accountId, account linked
+     * @param id, interaction to update
+     * @param to, peer uri
+     * @param status, new status for this interaction
+     */
+    void slotUpdateInteractionStatus(const std::string& accountId,
+                                     const uint64_t id,
+                                     const std::string& to, int status);
 
     const ConversationModel& linked;
     Database& db;
@@ -423,13 +436,14 @@ ConversationModel::sendMessage(const std::string& uid, const std::string& body)
 
     // Send interaction to all participants
     // NOTE: conferences are not implemented yet, so we have only one participant
+    uint64_t daemonMsgId = 0;
     for (const auto& participant: conversation.participants) {
         auto contactInfo = owner.contactModel->getContact(participant);
         pimpl_->sendContactRequest(participant);
         if (not conversation.callId.empty())
             owner.callModel->sendSipMessage(conversation.callId, body);
         else
-            owner.contactModel->sendDhtMessage(contactInfo.profileInfo.uri, body);
+            daemonMsgId = owner.contactModel->sendDhtMessage(contactInfo.profileInfo.uri, body);
         if (convId.empty()) {
             // The conversation has changed because it was with the temporary item
             auto contactProfileId = database::getProfileId(pimpl_->db, contactInfo.profileInfo.uri);
@@ -445,14 +459,20 @@ ConversationModel::sendMessage(const std::string& uid, const std::string& body)
     }
 
     // Add interaction to database
+    auto status = (conversation.callId.empty()) ? interaction::Status::SENDING : interaction::Status::UNKNOWN;
     auto msg = interaction::Info {accountId, body, std::time(nullptr),
-                                  interaction::Type::TEXT, interaction::Status::SENDING};
+                                  interaction::Type::TEXT, status};
     int msgId = database::addMessageToConversation(pimpl_->db, accountId, convId, msg);
     // Update conversation
-    conversation.interactions.insert(std::pair<int, interaction::Info>(msgId, msg));
+    if (conversation.callId.empty()) {
+        // Because the daemon already give an id for the message, we need to store it.
+        database::addDaemonMsgId(pimpl_->db, std::to_string(msgId), std::to_string(daemonMsgId));
+    }
+    conversation.interactions.insert(std::pair<uint64_t, interaction::Info>(msgId, msg));
     conversation.lastMessageUid = msgId;
+    pimpl_->dirtyConversations = true;
     // Emit this signal for chatview in the client
-    emit newUnreadMessage(convId, msg);
+    emit newUnreadMessage(convId, msgId, msg);
     // This conversation is now at the top of the list
     pimpl_->sortConversations();
     // The order has changed, informs the client to redraw the list
@@ -511,6 +531,24 @@ ConversationModel::clearHistory(const std::string& uid)
     emit conversationCleared(uid);
 }
 
+void
+ConversationModel::setInteractionRead(const std::string& convId, const uint64_t& msgId)
+{
+    auto conversationIdx = pimpl_->indexOf(convId);
+    if (conversationIdx != -1) {
+        auto& interactions = pimpl_->conversations[conversationIdx].interactions;
+        auto it = interactions.find(msgId);
+        if (it != interactions.end()) {
+            if (it->second.status != interaction::Status::UNREAD) return;
+            auto newStatus = interaction::Status::READ;
+            it->second.status = newStatus;
+            pimpl_->dirtyConversations = true;
+            database::updateInteractionStatus(pimpl_->db, msgId, newStatus);
+            emit interactionStatusUpdated(convId, msgId, it->second);
+        }
+    }
+}
+
 ConversationModelPimpl::ConversationModelPimpl(const ConversationModel& linked,
                                                Database& db,
                                                const CallbacksHandler& callbacksHandler,
@@ -537,6 +575,8 @@ ConversationModelPimpl::ConversationModelPimpl(const ConversationModel& linked,
             this, &ConversationModelPimpl::slotNewAccountMessage);
     connect(&callbacksHandler, &CallbacksHandler::incomingCallMessage,
             this, &ConversationModelPimpl::slotIncomingCallMessage);
+    connect(&callbacksHandler, &CallbacksHandler::accountMessageStatusChanged,
+            this, &ConversationModelPimpl::slotUpdateInteractionStatus);
 
 
     // Call related
@@ -737,6 +777,20 @@ ConversationModelPimpl::addConversationWith(const std::string& convId,
         conversation.callId = "";
     }
     database::getHistory(db, conversation);
+    for (auto& interaction: conversation.interactions) {
+        if (interaction.second.status == interaction::Status::SENDING) {
+            // Get the message status from daemon, else unknown
+            auto id = database::getDaemonIdByInteractionId(db, std::to_string(interaction.first));
+            int status = 0;
+            if (!id.empty()) {
+                auto msgId = std::stoull(id);
+                status = ConfigurationManager::instance().getMessageStatus(msgId);
+            }
+            slotUpdateInteractionStatus(linked.owner.id, std::stoull(id),
+                                        contactUri, status);
+        }
+    }
+
     conversations.emplace_front(conversation);
     dirtyConversations = true;
 }
@@ -826,7 +880,7 @@ ConversationModelPimpl::addCallMessage(const std::string& callId, const std::str
             conversation.interactions.emplace(msgId, msg);
             conversation.lastMessageUid = msgId;
             dirtyConversations = true;
-            emit linked.newUnreadMessage(conversation.uid, msg);
+            emit linked.newUnreadMessage(conversation.uid, msgId, msg);
             sortConversations();
             emit linked.modelSorted();
         }
@@ -891,7 +945,7 @@ ConversationModelPimpl::addIncomingMessage(const std::string& from,
         conversations[conversationIdx].lastMessageUid = msgId;
     }
     dirtyConversations = true;
-    emit linked.newUnreadMessage(conv[0], msg);
+    emit linked.newUnreadMessage(conv[0], msgId, msg);
     sortConversations();
     emit linked.modelSorted();
 }
@@ -908,6 +962,54 @@ ConversationModelPimpl::slotCallAddedToConference(const std::string& callId, con
     }
 }
 
+void
+ConversationModelPimpl::slotUpdateInteractionStatus(const std::string& accountId,
+                                                    const uint64_t id,
+                                                    const std::string& to,
+                                                    int status)
+{
+    if (accountId != linked.owner.id) return;
+    auto newStatus = interaction::Status::INVALID;
+    switch (static_cast<DRing::Account::MessageStates>(status))
+    {
+    case DRing::Account::MessageStates::SENDING:
+        newStatus = interaction::Status::SENDING;
+        break;
+    case DRing::Account::MessageStates::SENT:
+        newStatus = interaction::Status::SUCCEED;
+        break;
+    case DRing::Account::MessageStates::FAILURE:
+        newStatus = interaction::Status::FAILED;
+        break;
+    case DRing::Account::MessageStates::READ:
+        newStatus = interaction::Status::READ;
+        break;
+    case DRing::Account::MessageStates::UNKNOWN:
+    default:
+        newStatus = interaction::Status::UNKNOWN;
+        break;
+    }
+    // Update database
+    auto interactionId = database::getInteractionIdByDaemonId(db, std::to_string(id));
+    if (interactionId.empty()) return;
+    auto msgId = std::stoull(interactionId);
+    database::updateInteractionStatus(db, msgId, newStatus);
+    // Update conversations
+    auto contactProfileId = database::getProfileId(db, to);
+    auto accountProfileId = database::getProfileId(db, linked.owner.profileInfo.uri);
+    auto conv = database::getConversationsBetween(db, accountProfileId, contactProfileId);
+    if (!conv.empty()) {
+        auto conversationIdx = indexOf(conv[0]);
+        if (conversationIdx != -1) {
+            auto& interactions = conversations[conversationIdx].interactions;
+            auto it = interactions.find(msgId);
+            if (it != interactions.end()) {
+                it->second.status = newStatus;
+                emit linked.interactionStatusUpdated(conv[0], msgId, it->second);
+            }
+        }
+    }
+}
 
 } // namespace lrc
 
diff --git a/src/database.cpp b/src/database.cpp
index b214e92b..faaeedcb 100644
--- a/src/database.cpp
+++ b/src/database.cpp
@@ -105,6 +105,7 @@ Database::createTables()
                                                          body TEXT,     \
                                                          type TEXT,  \
                                                          status TEXT, \
+                                                         daemon_id TEXT, \
                                                          FOREIGN KEY(account_id) REFERENCES profiles(id), \
                                                          FOREIGN KEY(author_id) REFERENCES profiles(id), \
                                                          FOREIGN KEY(conversation_id) REFERENCES conversations(id))";
-- 
GitLab