Commit 62737b3e authored by Sébastien Blin's avatar Sébastien Blin

conferences: implement methods.

A call can have the "CONFERENCE" type. If it's a conference between 2
peers, the call has "DIALOG" for type.
For now, conference replace calls in conversations. When conference chat
will be implemented, the conference should be moved in a new
conversation.

Change-Id: Ia90efc9ebae41698c42a349b0227b0d1450f35c7
Reviewed-by: Guillaume Roguez's avatarGuillaume Roguez <guillaume.roguez@savoirfairelinux.com>
parent 7c0a57bd
...@@ -116,11 +116,18 @@ to_status(const std::string& status) ...@@ -116,11 +116,18 @@ to_status(const std::string& status)
return Status::INVALID; return Status::INVALID;
} }
enum class Type {
INVALID,
DIALOG,
CONFERENCE
};
struct Info struct Info
{ {
std::string id; std::string id;
std::chrono::steady_clock::time_point startTime; std::chrono::steady_clock::time_point startTime;
Status status = Status::INVALID; Status status = Status::INVALID;
Type type = Type::INVALID;
std::string peer; std::string peer;
bool audioMuted = false; bool audioMuted = false;
bool videoMuted = false; bool videoMuted = false;
......
...@@ -116,11 +116,12 @@ public: ...@@ -116,11 +116,12 @@ public:
*/ */
void setFilter(const profile::Type& filter = profile::Type::INVALID); void setFilter(const profile::Type& filter = profile::Type::INVALID);
/** /**
* Add a new participant to a conversation * Join participants from A to B and vice-versa.
* @param uid conversation linked * @note conversations must be in a call.
* @param uri peer to add * @param uidA uid of the conversation A
* @param uidB uid of the conversation B
*/ */
void addParticipant(const std::string& uid, const::std::string& uri); void joinConversations(const std::string& uidA, const std::string& uidB);
/** /**
* Clear the history of a conversation * Clear the history of a conversation
* @param uid of the conversation * @param uid of the conversation
......
...@@ -141,20 +141,20 @@ public: ...@@ -141,20 +141,20 @@ public:
*/ */
void transfer(const std::string& callId, const std::string& to) const; void transfer(const std::string& callId, const std::string& to) const;
/** /**
* Not implemented yet * Create a conference from 2 calls.
* @param callIdA uid of the call A
* @param callIdB uid of the call B
*/ */
void addParticipant(const std::string& callId, const std::string& participant) const; void joinCalls(const std::string& callIdA, const std::string& callIdB) const;
/** /**
* Not implemented yet * Not implemented yet
*/ */
void removeParticipant(const std::string& callId, const std::string& participant) const; void removeParticipant(const std::string& callId, const std::string& participant) const;
/** /**
* @param callId * @param callId
* @return the renderer linked to a call * @return the renderer linked to a call
*/ */
Video::Renderer* getRenderer(const std::string& callId) const; Video::Renderer* getRenderer(const std::string& callId) const;
/** /**
* @param callId * @param callId
* @return a human readable call duration (M:ss) * @return a human readable call duration (M:ss)
...@@ -189,6 +189,12 @@ Q_SIGNALS: ...@@ -189,6 +189,12 @@ Q_SIGNALS:
* @param renderer * @param renderer
*/ */
void remotePreviewStarted(const std::string& callId, Video::Renderer* renderer) const; void remotePreviewStarted(const std::string& callId, Video::Renderer* renderer) const;
/**
* Emitted when a call is added to a conference
* @param callId
* @param confId
*/
void callAddedToConference(const std::string& callId, const std::string& confId) const;
private: private:
std::unique_ptr<NewCallModelPimpl> pimpl_; std::unique_ptr<NewCallModelPimpl> pimpl_;
......
...@@ -84,6 +84,21 @@ CallbacksHandler::CallbacksHandler(const Lrc& parent) ...@@ -84,6 +84,21 @@ CallbacksHandler::CallbacksHandler(const Lrc& parent)
this, this,
&CallbacksHandler::slotCallStateChanged); &CallbacksHandler::slotCallStateChanged);
connect(&CallManager::instance(),
&CallManagerInterface::conferenceCreated,
this,
&CallbacksHandler::slotConferenceCreated);
connect(&CallManager::instance(),
&CallManagerInterface::conferenceRemoved,
this,
&CallbacksHandler::slotConferenceRemoved);
connect(&CallManager::instance(),
&CallManagerInterface::conferenceChanged,
this,
&CallbacksHandler::slotConferenceChanged);
connect(&CallManager::instance(), connect(&CallManager::instance(),
&CallManagerInterface::incomingMessage, &CallManagerInterface::incomingMessage,
this, this,
...@@ -224,5 +239,23 @@ CallbacksHandler::slotIncomingMessage(const QString& callId, ...@@ -224,5 +239,23 @@ CallbacksHandler::slotIncomingMessage(const QString& callId,
} }
} }
void
CallbacksHandler::slotConferenceCreated(const QString& callId)
{
emit conferenceCreated(callId.toStdString());
}
void
CallbacksHandler::slotConferenceChanged(const QString& callId, const QString& state)
{
slotCallStateChanged(callId, state, 0);
}
void
CallbacksHandler::slotConferenceRemoved(const QString& callId)
{
emit conferenceRemoved(callId.toStdString());
}
} // namespace lrc } // namespace lrc
...@@ -137,7 +137,18 @@ Q_SIGNALS: ...@@ -137,7 +137,18 @@ Q_SIGNALS:
*/ */
void incomingCallMessage(const std::string& callId, void incomingCallMessage(const std::string& callId,
const std::string& from, const std::string& from,
const std::string& body);
const std::string& body) const;
/**
* Connect this signal to know when a new conference is created
* @param callId of the conference
*/
void conferenceCreated(const std::string& callId);
/**
* Connect this signal to know when a conference is removed
* @param callId of the conference
*/
void conferenceRemoved(const std::string& callId);
private Q_SLOTS: private Q_SLOTS:
/** /**
...@@ -230,6 +241,22 @@ private Q_SLOTS: ...@@ -230,6 +241,22 @@ private Q_SLOTS:
void slotIncomingMessage(const QString& callId, void slotIncomingMessage(const QString& callId,
const QString& from, const QString& from,
const QMap<QString,QString>& interaction); const QMap<QString,QString>& interaction);
/**
* Emit conferenceCreated
* @param callId of the conference
*/
void slotConferenceCreated(const QString& callId);
/**
* Emit conferenceRemove
* @param callId of the conference
*/
void slotConferenceRemoved(const QString& callId);
/**
* Call slotCallStateChanged
* @param callId of the conference
* @param state, new state
*/
void slotConferenceChanged(const QString& callId, const QString& state);
private: private:
const api::Lrc& parent; const api::Lrc& parent;
......
...@@ -99,8 +99,10 @@ public: ...@@ -99,8 +99,10 @@ public:
* Add a new message from a peer in the database * Add a new message from a peer in the database
* @param from the peer uri * @param from the peer uri
* @param body the content of the message * @param body the content of the message
*/ * @param authorProfileId override the author of the message (if empty it's from)*/
void addIncomingMessage(const std::string& from, const std::string& body); void addIncomingMessage(const std::string& from,
const std::string& body,
const std::string& authorProfileId="");
const ConversationModel& linked; const ConversationModel& linked;
Database& db; Database& db;
...@@ -166,6 +168,12 @@ public Q_SLOTS: ...@@ -166,6 +168,12 @@ public Q_SLOTS:
* @param body of the message * @param body of the message
*/ */
void slotIncomingCallMessage(const std::string& callId, const std::string& from, const std::string& body); void slotIncomingCallMessage(const std::string& callId, const std::string& from, const std::string& body);
/**
* Listen from CallModel when a call is added to a conference
* @param callId
* @param confId
*/
void slotCallAddedToConference(const std::string& callId, const std::string& confId);
}; };
...@@ -341,6 +349,33 @@ ConversationModel::placeCall(const std::string& uid) ...@@ -341,6 +349,33 @@ ConversationModel::placeCall(const std::string& uid)
return; return;
} }
// Disallow multiple call
if (!conversation.callId.empty()) {
try {
auto call = owner.callModel->getCall(conversation.callId);
switch (call.status) {
case call::Status::INCOMING_RINGING:
case call::Status::OUTGOING_RINGING:
case call::Status::CONNECTING:
case call::Status::SEARCHING:
case call::Status::PAUSED:
case call::Status::PEER_PAUSED:
case call::Status::CONNECTED:
case call::Status::IN_PROGRESS:
case call::Status::OUTGOING_REQUESTED:
case call::Status::AUTO_ANSWERING:
return;
case call::Status::INVALID:
case call::Status::INACTIVE:
case call::Status::ENDED:
case call::Status::TERMINATING:
default:
break;
}
} catch (const std::out_of_range&) {
}
}
auto convId = uid; auto convId = uid;
auto accountId = pimpl_->accountProfileId; auto accountId = pimpl_->accountProfileId;
...@@ -444,11 +479,18 @@ ConversationModel::setFilter(const profile::Type& filter) ...@@ -444,11 +479,18 @@ ConversationModel::setFilter(const profile::Type& filter)
} }
void void
ConversationModel::addParticipant(const std::string& uid, const::std::string& uri) ConversationModel::joinConversations(const std::string& uidA, const std::string& uidB)
{ {
Q_UNUSED(uid) auto conversationAIdx = pimpl_->indexOf(uidA);
Q_UNUSED(uri) auto conversationBIdx = pimpl_->indexOf(uidB);
// TODO when conferences.will be implemented if (conversationAIdx == -1 || conversationBIdx == -1)
return;
auto& conversationA = pimpl_->conversations[conversationAIdx];
auto& conversationB = pimpl_->conversations[conversationBIdx];
// NOTE: To create a conference, we must be in call for now.
if (conversationA.callId.empty() || conversationB.callId.empty())
return;
owner.callModel->joinCalls(conversationA.callId, conversationB.callId);
} }
void void
...@@ -514,6 +556,10 @@ ConversationModelPimpl::ConversationModelPimpl(const ConversationModel& linked, ...@@ -514,6 +556,10 @@ ConversationModelPimpl::ConversationModelPimpl(const ConversationModel& linked,
&lrc::api::NewCallModel::callEnded, &lrc::api::NewCallModel::callEnded,
this, this,
&ConversationModelPimpl::slotCallEnded); &ConversationModelPimpl::slotCallEnded);
connect(&*linked.owner.callModel,
&lrc::api::NewCallModel::callAddedToConference,
this,
&ConversationModelPimpl::slotCallAddedToConference);
} }
ConversationModelPimpl::~ConversationModelPimpl() ConversationModelPimpl::~ConversationModelPimpl()
...@@ -760,40 +806,31 @@ ConversationModelPimpl::slotCallEnded(const std::string& callId) ...@@ -760,40 +806,31 @@ ConversationModelPimpl::slotCallEnded(const std::string& callId)
addCallMessage(callId, "🕽 Call ended"); addCallMessage(callId, "🕽 Call ended");
// reset the callId stored in the conversation // reset the callId stored in the conversation
auto it = std::find_if(conversations.begin(), conversations.end(), for (auto& conversation: conversations)
[&](const conversation::Info& conversation) { if (conversation.callId == callId) {
return conversation.callId == callId; conversation.callId = "";
}); dirtyConversations = true;
}
if (it != conversations.end()) {
it->callId = "";
dirtyConversations = true;
}
} }
void void
ConversationModelPimpl::addCallMessage(const std::string& callId, const std::string& body) ConversationModelPimpl::addCallMessage(const std::string& callId, const std::string& body)
{ {
// Get conversation // Get conversation
auto i = std::find_if( for (auto& conversation: conversations) {
conversations.begin(), conversations.end(), if (conversation.callId == callId) {
[callId](const conversation::Info& conversation) { auto uid = conversation.uid;
return conversation.callId == callId; auto msg = interaction::Info {accountProfileId, body, std::time(nullptr),
}); interaction::Type::CALL, interaction::Status::SUCCEED};
int msgId = database::addMessageToConversation(db, accountProfileId, conversation.uid, msg);
if (i == conversations.end()) return; conversation.interactions.emplace(msgId, msg);
conversation.lastMessageUid = msgId;
auto& conversation = *i; dirtyConversations = true;
auto uid = conversation.uid; emit linked.newUnreadMessage(conversation.uid, msg);
auto msg = interaction::Info {accountProfileId, body, std::time(nullptr), sortConversations();
interaction::Type::CALL, interaction::Status::SUCCEED}; emit linked.modelSorted();
int msgId = database::addMessageToConversation(db, accountProfileId, conversation.uid, msg); }
conversation.interactions.emplace(msgId, msg); }
conversation.lastMessageUid = msgId;
dirtyConversations = true;
emit linked.newUnreadMessage(conversation.uid, msg);
sortConversations();
emit linked.modelSorted();
} }
void void
...@@ -813,11 +850,26 @@ ConversationModelPimpl::slotIncomingCallMessage(const std::string& callId, const ...@@ -813,11 +850,26 @@ ConversationModelPimpl::slotIncomingCallMessage(const std::string& callId, const
if (not linked.owner.callModel->hasCall(callId)) if (not linked.owner.callModel->hasCall(callId))
return; return;
addIncomingMessage(from, body); auto& call = linked.owner.callModel->getCall(callId);
if (call.type == call::Type::CONFERENCE) {
// Show messages in all conversations for conferences.
for (const auto& conversation: conversations) {
if (conversation.callId == callId) {
if (conversation.participants.empty()) continue;
auto authorProfileId = database::getOrInsertProfile(db, from);
addIncomingMessage(conversation.participants.front(), body, authorProfileId);
}
}
} else {
addIncomingMessage(from, body);
}
} }
void void
ConversationModelPimpl::addIncomingMessage(const std::string& from, const std::string& body) ConversationModelPimpl::addIncomingMessage(const std::string& from,
const std::string& body,
const std::string& authorProfileId)
{ {
auto contactProfileId = database::getOrInsertProfile(db, from); auto contactProfileId = database::getOrInsertProfile(db, from);
auto accountProfileId = database::getProfileId(db, linked.owner.profileInfo.uri); auto accountProfileId = database::getProfileId(db, linked.owner.profileInfo.uri);
...@@ -825,7 +877,8 @@ ConversationModelPimpl::addIncomingMessage(const std::string& from, const std::s ...@@ -825,7 +877,8 @@ ConversationModelPimpl::addIncomingMessage(const std::string& from, const std::s
if (conv.empty()) { if (conv.empty()) {
conv.emplace_back(database::beginConversationsBetween(db, accountProfileId, contactProfileId)); conv.emplace_back(database::beginConversationsBetween(db, accountProfileId, contactProfileId));
} }
auto msg = interaction::Info {contactProfileId, body, std::time(nullptr), auto authorId = authorProfileId.empty()? contactProfileId: authorProfileId;
auto msg = interaction::Info {authorId, body, std::time(nullptr),
interaction::Type::TEXT, interaction::Status::UNREAD}; interaction::Type::TEXT, interaction::Status::UNREAD};
int msgId = database::addMessageToConversation(db, accountProfileId, conv[0], msg); int msgId = database::addMessageToConversation(db, accountProfileId, conv[0], msg);
auto conversationIdx = indexOf(conv[0]); auto conversationIdx = indexOf(conv[0]);
...@@ -843,6 +896,19 @@ ConversationModelPimpl::addIncomingMessage(const std::string& from, const std::s ...@@ -843,6 +896,19 @@ ConversationModelPimpl::addIncomingMessage(const std::string& from, const std::s
emit linked.modelSorted(); emit linked.modelSorted();
} }
void
ConversationModelPimpl::slotCallAddedToConference(const std::string& callId, const std::string& confId)
{
for (auto& conversation: conversations) {
if (conversation.callId == callId) {
conversation.callId = confId;
dirtyConversations = true;
emit linked.selectConversation(conversation.uid);
}
}
}
} // namespace lrc } // namespace lrc
#include "api/moc_conversationmodel.cpp" #include "api/moc_conversationmodel.cpp"
......
...@@ -91,6 +91,16 @@ public Q_SLOTS: ...@@ -91,6 +91,16 @@ public Q_SLOTS:
* @param payload * @param payload
*/ */
void slotincomingVCardChunk(const std::string& callId, const std::string& from, int part, int numberOfParts, const std::string& payload); void slotincomingVCardChunk(const std::string& callId, const std::string& from, int part, int numberOfParts, const std::string& payload);
/**
* Listen from CallbacksHandler when a conference is created.
* @param callId
*/
void slotConferenceCreated(const std::string& callId);
/**
* Listen from CallbacksHandler when a conference is deleted.
* @param callId
*/
void slotConferenceRemoved(const std::string& callId);
}; };
NewCallModel::NewCallModel(const account::Info& owner, const CallbacksHandler& callbacksHandler) NewCallModel::NewCallModel(const account::Info& owner, const CallbacksHandler& callbacksHandler)
...@@ -134,6 +144,7 @@ NewCallModel::createCall(const std::string& url) ...@@ -134,6 +144,7 @@ NewCallModel::createCall(const std::string& url)
callInfo->id = callId.toStdString(); callInfo->id = callId.toStdString();
callInfo->peer = url; callInfo->peer = url;
callInfo->status = call::Status::SEARCHING; callInfo->status = call::Status::SEARCHING;
callInfo->type = call::Type::DIALOG;
pimpl_->calls.emplace(callId.toStdString(), std::move(callInfo)); pimpl_->calls.emplace(callId.toStdString(), std::move(callInfo));
return callId.toStdString(); return callId.toStdString();
...@@ -148,7 +159,21 @@ NewCallModel::accept(const std::string& callId) const ...@@ -148,7 +159,21 @@ NewCallModel::accept(const std::string& callId) const
void void
NewCallModel::hangUp(const std::string& callId) const NewCallModel::hangUp(const std::string& callId) const
{ {
CallManager::instance().hangUp(callId.c_str()); auto it = pimpl_->calls.find(callId);
if (it == pimpl_->calls.end()) return;
auto& call = it->second;
switch(call->type)
{
case call::Type::DIALOG:
CallManager::instance().hangUp(callId.c_str());
break;
case call::Type::CONFERENCE:
CallManager::instance().hangUpConference(callId.c_str());
break;
case call::Type::INVALID:
default:
break;
}
} }
void void
...@@ -169,15 +194,28 @@ NewCallModel::playDTMF(const std::string& callId, const std::string& value) cons ...@@ -169,15 +194,28 @@ NewCallModel::playDTMF(const std::string& callId, const std::string& value) cons
void void
NewCallModel::togglePause(const std::string& callId) const NewCallModel::togglePause(const std::string& callId) const
{ {
auto call = pimpl_->calls.find(callId); auto it = pimpl_->calls.find(callId);
if (call == pimpl_->calls.end()) return; if (it == pimpl_->calls.end()) return;
switch(pimpl_->calls[callId]->status) auto& call = it->second;
switch(call->status)
{ {
case call::Status::PAUSED: case call::Status::PAUSED:
CallManager::instance().unhold(callId.c_str()); if (call->type == call::Type::DIALOG)
CallManager::instance().unhold(callId.c_str());
else {
CallManager::instance().unholdConference(callId.c_str());
call->status = call::Status::IN_PROGRESS;
emit callStatusChanged(callId);
}
break; break;
case call::Status::IN_PROGRESS: case call::Status::IN_PROGRESS:
CallManager::instance().hold(callId.c_str()); if (call->type == call::Type::DIALOG)
CallManager::instance().hold(callId.c_str());
else {
CallManager::instance().holdConference(callId.c_str());
call->status = call::Status::PAUSED;
emit callStatusChanged(callId);
}
break; break;
case call::Status::INVALID: case call::Status::INVALID:
case call::Status::OUTGOING_REQUESTED: case call::Status::OUTGOING_REQUESTED:
...@@ -231,9 +269,20 @@ NewCallModel::transfer(const std::string& callId, const std::string& to) const ...@@ -231,9 +269,20 @@ NewCallModel::transfer(const std::string& callId, const std::string& to) const
} }
void void
NewCallModel::addParticipant(const std::string& callId, const std::string& participant) const NewCallModel::joinCalls(const std::string& callIdA, const std::string& callIdB) const
{ {
if (pimpl_->calls.find(callIdA) == pimpl_->calls.end()) return;
if (pimpl_->calls.find(callIdB) == pimpl_->calls.end()) return;
auto& call1 = pimpl_->calls[callIdA];
auto& call2 = pimpl_->calls[callIdB];
if (call1->type == call::Type::CONFERENCE)
CallManager::instance().addParticipant(callIdB.c_str(), callIdA.c_str());
else if (call2->type == call::Type::CONFERENCE)
CallManager::instance().addParticipant(callIdA.c_str(), callIdB.c_str());
else if (call1->type == call::Type::CONFERENCE && call2->type == call::Type::CONFERENCE)
CallManager::instance().joinConference(callIdA.c_str(), callIdB.c_str());
else
CallManager::instance().joinParticipant(callIdA.c_str(), callIdB.c_str());
} }
void void
...@@ -288,6 +337,8 @@ NewCallModelPimpl::NewCallModelPimpl(const NewCallModel& linked, const Callbacks ...@@ -288,6 +337,8 @@ NewCallModelPimpl::NewCallModelPimpl(const NewCallModel& linked, const Callbacks
connect(&callbacksHandler, &CallbacksHandler::callStateChanged, this, &NewCallModelPimpl::slotCallStateChanged); connect(&callbacksHandler, &CallbacksHandler::callStateChanged, this, &NewCallModelPimpl::slotCallStateChanged);
connect(&VideoRendererManager::instance(), &VideoRendererManager::remotePreviewStarted, this, &NewCallModelPimpl::slotRemotePreviewStarted); connect(&VideoRendererManager::instance(), &VideoRendererManager::remotePreviewStarted, this, &NewCallModelPimpl::slotRemotePreviewStarted);
connect(&callbacksHandler, &CallbacksHandler::incomingVCardChunk, this, &NewCallModelPimpl::slotincomingVCardChunk); connect(&callbacksHandler, &CallbacksHandler::incomingVCardChunk, this, &NewCallModelPimpl::slotincomingVCardChunk);
connect(&callbacksHandler, &CallbacksHandler::conferenceCreated, this , &NewCallModelPimpl::slotConferenceCreated);
connect(&callbacksHandler, &CallbacksHandler::conferenceRemoved, this , &NewCallModelPimpl::slotConferenceRemoved);
} }