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:
* 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:
/**
......
......@@ -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;
};
......
......@@ -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
......
......@@ -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;
......
......@@ -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}});
}
......
......@@ -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
*/
......
......@@ -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
......@@ -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;
......
......@@ -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;
}
......
......@@ -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
......
......@@ -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))";
......
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