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