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)
return Status::INVALID;
}
enum class Type {
INVALID,
DIALOG,
CONFERENCE
};
struct Info
{
std::string id;
std::chrono::steady_clock::time_point startTime;
Status status = Status::INVALID;
Type type = Type::INVALID;
std::string peer;
bool audioMuted = false;
bool videoMuted = false;
......
......@@ -116,11 +116,12 @@ public:
*/
void setFilter(const profile::Type& filter = profile::Type::INVALID);
/**
* Add a new participant to a conversation
* @param uid conversation linked
* @param uri peer to add
* Join participants from A to B and vice-versa.
* @note conversations must be in a call.
* @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
* @param uid of the conversation
......
......@@ -141,20 +141,20 @@ public:
*/
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
*/
void removeParticipant(const std::string& callId, const std::string& participant) const;
/**
* @param callId
* @return the renderer linked to a call
*/
Video::Renderer* getRenderer(const std::string& callId) const;
/**
* @param callId
* @return a human readable call duration (M:ss)
......@@ -189,6 +189,12 @@ Q_SIGNALS:
* @param renderer
*/
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:
std::unique_ptr<NewCallModelPimpl> pimpl_;
......
......@@ -84,6 +84,21 @@ CallbacksHandler::CallbacksHandler(const Lrc& parent)
this,
&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(),
&CallManagerInterface::incomingMessage,
this,
......@@ -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
......@@ -137,7 +137,18 @@ Q_SIGNALS:
*/
void incomingCallMessage(const std::string& callId,
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:
/**
......@@ -230,6 +241,22 @@ private Q_SLOTS:
void slotIncomingMessage(const QString& callId,
const QString& from,
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:
const api::Lrc& parent;
......
......@@ -99,8 +99,10 @@ public:
* Add a new message from a peer in the database
* @param from the peer uri
* @param body the content of the message
*/
void addIncomingMessage(const std::string& from, const std::string& body);
* @param authorProfileId override the author of the message (if empty it's from)*/
void addIncomingMessage(const std::string& from,
const std::string& body,
const std::string& authorProfileId="");
const ConversationModel& linked;
Database& db;
......@@ -166,6 +168,12 @@ public Q_SLOTS:
* @param body of the message
*/
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)
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 accountId = pimpl_->accountProfileId;
......@@ -444,11 +479,18 @@ ConversationModel::setFilter(const profile::Type& filter)
}
void
ConversationModel::addParticipant(const std::string& uid, const::std::string& uri)
ConversationModel::joinConversations(const std::string& uidA, const std::string& uidB)
{
Q_UNUSED(uid)
Q_UNUSED(uri)
// TODO when conferences.will be implemented
auto conversationAIdx = pimpl_->indexOf(uidA);
auto conversationBIdx = pimpl_->indexOf(uidB);
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
......@@ -514,6 +556,10 @@ ConversationModelPimpl::ConversationModelPimpl(const ConversationModel& linked,
&lrc::api::NewCallModel::callEnded,
this,
&ConversationModelPimpl::slotCallEnded);
connect(&*linked.owner.callModel,
&lrc::api::NewCallModel::callAddedToConference,
this,
&ConversationModelPimpl::slotCallAddedToConference);
}
ConversationModelPimpl::~ConversationModelPimpl()
......@@ -760,40 +806,31 @@ ConversationModelPimpl::slotCallEnded(const std::string& callId)
addCallMessage(callId, "🕽 Call ended");
// reset the callId stored in the conversation
auto it = std::find_if(conversations.begin(), conversations.end(),
[&](const conversation::Info& conversation) {
return conversation.callId == callId;
});
if (it != conversations.end()) {
it->callId = "";
dirtyConversations = true;
}
for (auto& conversation: conversations)
if (conversation.callId == callId) {
conversation.callId = "";
dirtyConversations = true;
}
}
void
ConversationModelPimpl::addCallMessage(const std::string& callId, const std::string& body)
{
// Get conversation
auto i = std::find_if(
conversations.begin(), conversations.end(),
[callId](const conversation::Info& conversation) {
return conversation.callId == callId;
});
if (i == conversations.end()) return;
auto& conversation = *i;
auto uid = conversation.uid;
auto msg = interaction::Info {accountProfileId, body, std::time(nullptr),
interaction::Type::CALL, interaction::Status::SUCCEED};
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();
for (auto& conversation: conversations) {
if (conversation.callId == callId) {
auto uid = conversation.uid;
auto msg = interaction::Info {accountProfileId, body, std::time(nullptr),
interaction::Type::CALL, interaction::Status::SUCCEED};
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
......@@ -813,11 +850,26 @@ ConversationModelPimpl::slotIncomingCallMessage(const std::string& callId, const
if (not linked.owner.callModel->hasCall(callId))
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
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 accountProfileId = database::getProfileId(db, linked.owner.profileInfo.uri);
......@@ -825,7 +877,8 @@ ConversationModelPimpl::addIncomingMessage(const std::string& from, const std::s
if (conv.empty()) {
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};
int msgId = database::addMessageToConversation(db, accountProfileId, conv[0], msg);
auto conversationIdx = indexOf(conv[0]);
......@@ -843,6 +896,19 @@ ConversationModelPimpl::addIncomingMessage(const std::string& from, const std::s
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
#include "api/moc_conversationmodel.cpp"
......
......@@ -91,6 +91,16 @@ public Q_SLOTS:
* @param 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)
......@@ -134,6 +144,7 @@ NewCallModel::createCall(const std::string& url)
callInfo->id = callId.toStdString();
callInfo->peer = url;
callInfo->status = call::Status::SEARCHING;
callInfo->type = call::Type::DIALOG;
pimpl_->calls.emplace(callId.toStdString(), std::move(callInfo));
return callId.toStdString();
......@@ -148,7 +159,21 @@ NewCallModel::accept(const std::string& callId) const
void
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
......@@ -169,15 +194,28 @@ NewCallModel::playDTMF(const std::string& callId, const std::string& value) cons
void
NewCallModel::togglePause(const std::string& callId) const
{
auto call = pimpl_->calls.find(callId);
if (call == pimpl_->calls.end()) return;
switch(pimpl_->calls[callId]->status)
auto it = pimpl_->calls.find(callId);
if (it == pimpl_->calls.end()) return;
auto& call = it->second;
switch(call->status)
{
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;
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;
case call::Status::INVALID:
case call::Status::OUTGOING_REQUESTED:
......@@ -231,9 +269,20 @@ NewCallModel::transfer(const std::string& callId, const std::string& to) const
}
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
......@@ -288,6 +337,8 @@ NewCallModelPimpl::NewCallModelPimpl(const NewCallModel& linked, const Callbacks
connect(&callbacksHandler, &CallbacksHandler::callStateChanged, this, &NewCallModelPimpl::slotCallStateChanged);
connect(&VideoRendererManager::instance(), &VideoRendererManager::remotePreviewStarted, this, &NewCallModelPimpl::slotRemotePreviewStarted);
connect(&callbacksHandler, &CallbacksHandler::incomingVCardChunk, this, &NewCallModelPimpl::slotincomingVCardChunk);
connect(&callbacksHandler, &CallbacksHandler::conferenceCreated, this , &NewCallModelPimpl::slotConferenceCreated);
connect(&callbacksHandler, &CallbacksHandler::conferenceRemoved, this , &NewCallModelPimpl::slotConferenceRemoved);
}
NewCallModelPimpl::~NewCallModelPimpl()
......@@ -315,6 +366,7 @@ NewCallModelPimpl::slotIncomingCall(const std::string& accountId, const std::str
callInfo->id = callId;
callInfo->peer = fromId;
callInfo->status = call::Status::INCOMING_RINGING;
callInfo->type = call::Type::DIALOG;
calls.emplace(callId, std::move(callInfo));
emit linked.newIncomingCall(fromId, callId);
......@@ -410,6 +462,33 @@ NewCallModel::hasCall(const std::string& callId)
return pimpl_->calls.find(callId) != pimpl_->calls.end();
}
void
NewCallModelPimpl::slotConferenceCreated(const std::string& callId)
{
if (calls.find(callId) != calls.end()) return;
auto callInfo = std::make_shared<call::Info>();
callInfo->id = callId;
callInfo->status = call::Status::IN_PROGRESS;
callInfo->type = call::Type::CONFERENCE;
callInfo->startTime = std::chrono::steady_clock::now();
calls[callId] = callInfo;
QStringList callList = CallManager::instance().getParticipantList(callId.c_str());
foreach(const auto& call, callList) {
emit linked.callAddedToConference(call.toStdString(), callId);
}
}
void
NewCallModelPimpl::slotConferenceRemoved(const std::string& callId)
{
auto it = calls.find(callId);
if (it == calls.end()) return;
auto& call = it->second;
call->status = call::Status::ENDED;
emit linked.callEnded(callId);
emit linked.callStatusChanged(callId);
}
} // namespace lrc
#include "api/moc_newcallmodel.cpp"
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