Commit 1d6bc93f authored by Sébastien Blin's avatar Sébastien Blin Committed by Guillaume Roguez

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's avatarGuillaume Roguez <guillaume.roguez@savoirfairelinux.com>
parent e245f4eb
...@@ -99,8 +99,9 @@ public: ...@@ -99,8 +99,9 @@ public:
* Send a text interaction to a contact over the Dht. * Send a text interaction to a contact over the Dht.
* @param contactUri * @param contactUri
* @param body * @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: Q_SIGNALS:
/** /**
......
...@@ -40,8 +40,8 @@ struct Info ...@@ -40,8 +40,8 @@ struct Info
std::string accountId; std::string accountId;
std::vector<std::string> participants; std::vector<std::string> participants;
std::string callId; std::string callId;
std::map<int, interaction::Info> interactions; std::map<uint64_t, interaction::Info> interactions;
unsigned int lastMessageUid = 0; uint64_t lastMessageUid = 0;
unsigned int unreadMessages = 0; unsigned int unreadMessages = 0;
}; };
......
...@@ -127,6 +127,12 @@ public: ...@@ -127,6 +127,12 @@ public:
* @param uid of the conversation * @param uid of the conversation
*/ */
void clearHistory(const std::string& uid); 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: Q_SIGNALS:
/** /**
...@@ -134,7 +140,16 @@ Q_SIGNALS: ...@@ -134,7 +140,16 @@ Q_SIGNALS:
* @param uid of msg * @param uid of msg
* @param 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 * Emitted when user clear the history of a conversation
* @param uid * @param uid
......
...@@ -70,6 +70,7 @@ to_type(const std::string& type) ...@@ -70,6 +70,7 @@ to_type(const std::string& type)
enum class Status { enum class Status {
INVALID, INVALID,
UNKNOWN,
SENDING, SENDING,
FAILED, FAILED,
SUCCEED, SUCCEED,
...@@ -81,6 +82,8 @@ static const std::string ...@@ -81,6 +82,8 @@ static const std::string
to_string(const Status& status) to_string(const Status& status)
{ {
switch(status) { switch(status) {
case Status::UNKNOWN:
return "UNKNOWN";
case Status::SENDING: case Status::SENDING:
return "SENDING"; return "SENDING";
case Status::FAILED: case Status::FAILED:
...@@ -100,7 +103,9 @@ to_string(const Status& status) ...@@ -100,7 +103,9 @@ to_string(const Status& status)
static Status static Status
to_status(const std::string& 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; return interaction::Status::SENDING;
else if (status == "FAILED") else if (status == "FAILED")
return interaction::Status::FAILED; return interaction::Status::FAILED;
......
...@@ -170,8 +170,8 @@ getHistory(Database& db, api::conversation::Info& conversation) ...@@ -170,8 +170,8 @@ getHistory(Database& db, api::conversation::Info& conversation)
std::stoi(payloads[i + 3]), std::stoi(payloads[i + 3]),
api::interaction::to_type(payloads[i + 4]), api::interaction::to_type(payloads[i + 4]),
api::interaction::to_status(payloads[i + 5])}); api::interaction::to_status(payloads[i + 5])});
conversation.interactions.emplace(std::stoi(payloads[i]), std::move(msg)); conversation.interactions.emplace(std::stoull(payloads[i]), std::move(msg));
conversation.lastMessageUid = std::stoi(payloads[i]); conversation.lastMessageUid = std::stoull(payloads[i]);
} }
} }
} }
...@@ -194,8 +194,38 @@ addMessageToConversation(Database& db, ...@@ -194,8 +194,38 @@ addMessageToConversation(Database& db,
{":status", to_string(msg.status)}}); {":status", to_string(msg.status)}});
} }
void void addDaemonMsgId(Database& db,
clearHistory(Database& db, const std::string& conversationId) 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}}); db.deleteFrom("interactions", "conversation_id=:id AND type!='CONTACT'", {{":id", conversationId}});
} }
......
...@@ -123,7 +123,40 @@ int addMessageToConversation(Database& db, ...@@ -123,7 +123,40 @@ int addMessageToConversation(Database& db,
const api::interaction::Info& msg); 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 db
* @param conversationId * @param conversationId
*/ */
......
...@@ -64,6 +64,12 @@ CallbacksHandler::CallbacksHandler(const Lrc& parent) ...@@ -64,6 +64,12 @@ CallbacksHandler::CallbacksHandler(const Lrc& parent)
&ConfigurationManagerInterface::incomingTrustRequest, &ConfigurationManagerInterface::incomingTrustRequest,
this, this,
&CallbacksHandler::slotIncomingContactRequest); &CallbacksHandler::slotIncomingContactRequest);
connect(&ConfigurationManager::instance(),
&ConfigurationManagerInterface::accountMessageStatusChanged,
this,
&CallbacksHandler::slotAccountMessageStatusChanged);
connect(&NameDirectory::instance(), connect(&NameDirectory::instance(),
&NameDirectory::registeredNameFound, &NameDirectory::registeredNameFound,
this, this,
...@@ -257,5 +263,14 @@ CallbacksHandler::slotConferenceRemoved(const QString& callId) ...@@ -257,5 +263,14 @@ CallbacksHandler::slotConferenceRemoved(const QString& callId)
emit conferenceRemoved(callId.toStdString()); 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 } // namespace lrc
...@@ -149,6 +149,16 @@ Q_SIGNALS: ...@@ -149,6 +149,16 @@ Q_SIGNALS:
* @param callId of the conference * @param callId of the conference
*/ */
void conferenceRemoved(const std::string& callId); 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: private Q_SLOTS:
/** /**
...@@ -257,6 +267,16 @@ private Q_SLOTS: ...@@ -257,6 +267,16 @@ private Q_SLOTS:
* @param state, new state * @param state, new state
*/ */
void slotConferenceChanged(const QString& callId, const QString& 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: private:
const api::Lrc& parent; const api::Lrc& parent;
......
...@@ -303,16 +303,17 @@ ContactModel::searchContact(const std::string& query) ...@@ -303,16 +303,17 @@ ContactModel::searchContact(const std::string& query)
account->lookupName(QString(query.c_str())); account->lookupName(QString(query.c_str()));
} }
void uint64_t
ContactModel::sendDhtMessage(const std::string& contactUri, const std::string& body) const ContactModel::sendDhtMessage(const std::string& contactUri, const std::string& body) const
{ {
// Send interaction // Send interaction
QMap<QString, QString> payloads; QMap<QString, QString> payloads;
payloads["text/plain"] = body.c_str(); 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()), QString(contactUri.c_str()),
payloads); payloads);
// NOTE: ConversationModel should store the interaction into the database // NOTE: ConversationModel should store the interaction into the database
return msgId;
} }
......
...@@ -19,6 +19,9 @@ ...@@ -19,6 +19,9 @@
***************************************************************************/ ***************************************************************************/
#include "api/conversationmodel.h" #include "api/conversationmodel.h"
// daemon
#include <account_const.h>
// std // std
#include <regex> #include <regex>
#include <algorithm> #include <algorithm>
...@@ -103,6 +106,16 @@ public: ...@@ -103,6 +106,16 @@ public:
void addIncomingMessage(const std::string& from, void addIncomingMessage(const std::string& from,
const std::string& body, const std::string& body,
const std::string& authorProfileId=""); 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; const ConversationModel& linked;
Database& db; Database& db;
...@@ -423,13 +436,14 @@ ConversationModel::sendMessage(const std::string& uid, const std::string& body) ...@@ -423,13 +436,14 @@ ConversationModel::sendMessage(const std::string& uid, const std::string& body)
// Send interaction to all participants // Send interaction to all participants
// NOTE: conferences are not implemented yet, so we have only one participant // NOTE: conferences are not implemented yet, so we have only one participant
uint64_t daemonMsgId = 0;
for (const auto& participant: conversation.participants) { for (const auto& participant: conversation.participants) {
auto contactInfo = owner.contactModel->getContact(participant); auto contactInfo = owner.contactModel->getContact(participant);
pimpl_->sendContactRequest(participant); pimpl_->sendContactRequest(participant);
if (not conversation.callId.empty()) if (not conversation.callId.empty())
owner.callModel->sendSipMessage(conversation.callId, body); owner.callModel->sendSipMessage(conversation.callId, body);
else else
owner.contactModel->sendDhtMessage(contactInfo.profileInfo.uri, body); daemonMsgId = owner.contactModel->sendDhtMessage(contactInfo.profileInfo.uri, body);
if (convId.empty()) { if (convId.empty()) {
// The conversation has changed because it was with the temporary item // The conversation has changed because it was with the temporary item
auto contactProfileId = database::getProfileId(pimpl_->db, contactInfo.profileInfo.uri); auto contactProfileId = database::getProfileId(pimpl_->db, contactInfo.profileInfo.uri);
...@@ -445,14 +459,20 @@ ConversationModel::sendMessage(const std::string& uid, const std::string& body) ...@@ -445,14 +459,20 @@ ConversationModel::sendMessage(const std::string& uid, const std::string& body)
} }
// Add interaction to database // Add interaction to database
auto status = (conversation.callId.empty()) ? interaction::Status::SENDING : interaction::Status::UNKNOWN;
auto msg = interaction::Info {accountId, body, std::time(nullptr), 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); int msgId = database::addMessageToConversation(pimpl_->db, accountId, convId, msg);
// Update conversation // 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; conversation.lastMessageUid = msgId;
pimpl_->dirtyConversations = true;
// Emit this signal for chatview in the client // 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 // This conversation is now at the top of the list
pimpl_->sortConversations(); pimpl_->sortConversations();
// The order has changed, informs the client to redraw the list // The order has changed, informs the client to redraw the list
...@@ -511,6 +531,24 @@ ConversationModel::clearHistory(const std::string& uid) ...@@ -511,6 +531,24 @@ ConversationModel::clearHistory(const std::string& uid)
emit conversationCleared(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, ConversationModelPimpl::ConversationModelPimpl(const ConversationModel& linked,
Database& db, Database& db,
const CallbacksHandler& callbacksHandler, const CallbacksHandler& callbacksHandler,
...@@ -537,6 +575,8 @@ ConversationModelPimpl::ConversationModelPimpl(const ConversationModel& linked, ...@@ -537,6 +575,8 @@ ConversationModelPimpl::ConversationModelPimpl(const ConversationModel& linked,
this, &ConversationModelPimpl::slotNewAccountMessage); this, &ConversationModelPimpl::slotNewAccountMessage);
connect(&callbacksHandler, &CallbacksHandler::incomingCallMessage, connect(&callbacksHandler, &CallbacksHandler::incomingCallMessage,
this, &ConversationModelPimpl::slotIncomingCallMessage); this, &ConversationModelPimpl::slotIncomingCallMessage);
connect(&callbacksHandler, &CallbacksHandler::accountMessageStatusChanged,
this, &ConversationModelPimpl::slotUpdateInteractionStatus);
// Call related // Call related
...@@ -737,6 +777,20 @@ ConversationModelPimpl::addConversationWith(const std::string& convId, ...@@ -737,6 +777,20 @@ ConversationModelPimpl::addConversationWith(const std::string& convId,
conversation.callId = ""; conversation.callId = "";
} }
database::getHistory(db, conversation); 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); conversations.emplace_front(conversation);
dirtyConversations = true; dirtyConversations = true;
} }
...@@ -826,7 +880,7 @@ ConversationModelPimpl::addCallMessage(const std::string& callId, const std::str ...@@ -826,7 +880,7 @@ ConversationModelPimpl::addCallMessage(const std::string& callId, const std::str
conversation.interactions.emplace(msgId, msg); conversation.interactions.emplace(msgId, msg);
conversation.lastMessageUid = msgId; conversation.lastMessageUid = msgId;
dirtyConversations = true; dirtyConversations = true;
emit linked.newUnreadMessage(conversation.uid, msg); emit linked.newUnreadMessage(conversation.uid, msgId, msg);
sortConversations(); sortConversations();
emit linked.modelSorted(); emit linked.modelSorted();
} }
...@@ -891,7 +945,7 @@ ConversationModelPimpl::addIncomingMessage(const std::string& from, ...@@ -891,7 +945,7 @@ ConversationModelPimpl::addIncomingMessage(const std::string& from,
conversations[conversationIdx].lastMessageUid = msgId; conversations[conversationIdx].lastMessageUid = msgId;
} }
dirtyConversations = true; dirtyConversations = true;
emit linked.newUnreadMessage(conv[0], msg); emit linked.newUnreadMessage(conv[0], msgId, msg);
sortConversations(); sortConversations();
emit linked.modelSorted(); emit linked.modelSorted();
} }
...@@ -908,6 +962,54 @@ ConversationModelPimpl::slotCallAddedToConference(const std::string& callId, con ...@@ -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 } // namespace lrc
......
...@@ -105,6 +105,7 @@ Database::createTables() ...@@ -105,6 +105,7 @@ Database::createTables()
body TEXT, \ body TEXT, \
type TEXT, \ type TEXT, \
status TEXT, \ status TEXT, \
daemon_id TEXT, \
FOREIGN KEY(account_id) REFERENCES profiles(id), \ FOREIGN KEY(account_id) REFERENCES profiles(id), \
FOREIGN KEY(author_id) REFERENCES profiles(id), \ FOREIGN KEY(author_id) REFERENCES profiles(id), \
FOREIGN KEY(conversation_id) REFERENCES conversations(id))"; FOREIGN KEY(conversation_id) REFERENCES conversations(id))";
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment