Source

Target

Commits (1)
  • Andrea Gianarda's avatar
    Assign to every participant a sequence number. The first time a participant is... · 3a8b1b37
    Andrea Gianarda authored
    Assign to every participant a sequence number. The first time a participant is added to a conference, its sequence number is 0 and it is incremented by one every time the conference is updated. The sequence number is sent to every participant as a parameter of the ics field ATTENDEE
    Add conference scheduler cancel conference
    Add test to verify edition of conference while it is active
    3a8b1b37
Showing with 370 additions and 96 deletions
......@@ -681,7 +681,7 @@ LocalConference::LocalConference (const std::shared_ptr<Core> &core, const std::
const auto & participants = info->getParticipants();
for (const auto & p : participants) {
invitedAddresses.push_back(p);
invitedAddresses.push_back(p.first);
}
getMe()->setAdmin(true);
......
......@@ -74,6 +74,13 @@ LINPHONE_PUBLIC LinphoneAccount *linphone_conference_scheduler_get_account(const
*/
LINPHONE_PUBLIC const LinphoneConferenceInfo* linphone_conference_scheduler_get_info(const LinphoneConferenceScheduler *conference_scheduler);
/**
* Cancel the conference linked to the #LinphoneConferenceInfo provided as argument
* @param conference_scheduler the #LinphoneConferenceScheduler object. @notnil
* @param conference_info the #LinphoneConferenceInfo object to linked to the conference to cancel. @maybenil
*/
LINPHONE_PUBLIC void linphone_conference_scheduler_cancel_conference(LinphoneConferenceScheduler *conference_scheduler, LinphoneConferenceInfo* conference_info);
/**
* Sets the #LinphoneConferenceInfo to use to create/update the conference, which will be done right away.
* @param conference_scheduler the #LinphoneConferenceScheduler object. @notnil
......
......@@ -57,7 +57,7 @@ const bctbx_list_t *linphone_conference_info_get_participants(const LinphoneConf
const auto & participants = ConferenceInfo::toCpp(conference_info)->getParticipants();
bctbx_list_t * participant_addresses = NULL;
for (const auto & participant : participants) {
const auto & address = participant.asAddress();
const auto & address = participant.first.asAddress();
participant_addresses = bctbx_list_append(participant_addresses, L_GET_C_BACK_PTR(&address));
}
return participant_addresses;
......@@ -67,7 +67,13 @@ void linphone_conference_info_set_participants(LinphoneConferenceInfo *conferenc
const std::list<LinphonePrivate::IdentityAddress> participantsList = L_GET_CPP_LIST_FROM_C_LIST_2(participants, LinphoneAddress *, LinphonePrivate::IdentityAddress, [] (LinphoneAddress *addr) {
return addr ? LinphonePrivate::IdentityAddress(*L_GET_CPP_PTR_FROM_C_OBJECT(addr)) : LinphonePrivate::IdentityAddress();
});
ConferenceInfo::toCpp(conference_info)->setParticipants(participantsList);
ConferenceInfo::participant_list_t participantsMap;
ConferenceInfo::participant_params_t participantsParams;
for (const auto & p : participantsList) {
participantsMap[p] = participantsParams;
}
ConferenceInfo::toCpp(conference_info)->setParticipants(participantsMap);
}
void linphone_conference_info_add_participant(LinphoneConferenceInfo *conference_info, LinphoneAddress *participant) {
......
......@@ -58,6 +58,10 @@ void linphone_conference_scheduler_set_info(LinphoneConferenceScheduler *confere
ConferenceScheduler::toCpp(conference_scheduler)->setInfo(ConferenceInfo::toCpp(conference_info)->getSharedFromThis());
}
void linphone_conference_scheduler_cancel_conference(LinphoneConferenceScheduler *conference_scheduler, LinphoneConferenceInfo* conference_info) {
ConferenceScheduler::toCpp(conference_scheduler)->cancelConference(ConferenceInfo::toCpp(conference_info)->getSharedFromThis());
}
void linphone_conference_scheduler_send_invitations(LinphoneConferenceScheduler *conference_scheduler, LinphoneChatRoomParams* chat_room_params) {
ConferenceScheduler::toCpp(conference_scheduler)->sendInvitations(ChatRoomParams::toCpp(chat_room_params)->getSharedFromThis());
}
......
......@@ -535,7 +535,7 @@ void Call::createRemoteConference(const shared_ptr<CallSession> &session) {
}
std::list<IdentityAddress> invitees {conferenceInfo->getOrganizer()};
for (const auto & participant : conferenceInfo->getParticipants()) {
invitees.push_back(participant);
invitees.push_back(participant.first);
}
const ConferenceAddress confAddr(conferenceInfo->getUri());
......
......@@ -39,12 +39,17 @@ void Ics::Event::setOrganizer (const std::string &organizer) {
mOrganizer = organizer;
}
const std::list<std::string> &Ics::Event::getAttendees () const {
const Ics::Event::attendee_list_t &Ics::Event::getAttendees () const {
return mAttendees;
}
void Ics::Event::addAttendee (const std::string &attendee) {
mAttendees.push_back(attendee);
Ics::Event::attendee_params_t params;
addAttendee(attendee, params);
}
void Ics::Event::addAttendee (const std::string &attendee, const attendee_params_t & params) {
mAttendees.insert(std::make_pair(attendee, params));
}
tm Ics::Event::getDateTimeStart () const {
......@@ -145,7 +150,14 @@ std::string Ics::Event::asString () const {
if (!mOrganizer.empty()) output << "ORGANIZER:" << mOrganizer << "\r\n";
if (!mAttendees.empty()) {
for (const auto &attendee : mAttendees) {
output << "ATTENDEE:" << attendee << "\r\n";
output << "ATTENDEE";
const auto & params = attendee.second;
for (const auto &param : params) {
output << ";" << param.first << "=" << param.second;
}
const auto & address = attendee.first;
output << ":" << address;
output << "\r\n";
}
}
if (!mXConfUri.empty()) output << "X-CONFURI:" << mXConfUri << "\r\n";
......@@ -172,12 +184,12 @@ std::string Ics::Event::asString () const {
if (mUid.empty()) {
ostringstream uid;
uid << setw(4) << (stamp.tm_year + 1900)
<< setw(2) << (stamp.tm_mon + 1)
<< setw(2) << stamp.tm_mday
<< (stamp.tm_mon + 1)
<< stamp.tm_mday
<< "T"
<< setw(2) << stamp.tm_hour
<< setw(2) << stamp.tm_min
<< setw(2) << stamp.tm_sec
<< stamp.tm_hour
<< stamp.tm_min
<< stamp.tm_sec
<< "Z";
size_t p;
......@@ -251,12 +263,14 @@ std::shared_ptr<ConferenceInfo> Ics::Icalendar::toConferenceInfo () const {
}
for (const auto &attendee : event->getAttendees()) {
if (!attendee.empty()) {
const auto & addr = IdentityAddress(attendee);
auto address = attendee.first;
auto params = attendee.second;
if (!address.empty()) {
const auto & addr = IdentityAddress(address);
if (addr.isValid()) {
confInfo->addParticipant(addr);
confInfo->addParticipant(addr, params);
} else {
lWarning() << "Could not parse attendee's address:" << attendee << " because it is not a valid address";
lWarning() << "Could not parse attendee's address:" << address << " because it is not a valid address";
}
}
}
......
......@@ -21,7 +21,7 @@
#define _L_ICS_H_
#include <ctime>
#include <list>
#include <map>
#include <string>
#include "conference/conference-info.h"
......@@ -35,6 +35,8 @@ namespace Ics {
class LINPHONE_PUBLIC Event {
friend class Icalendar;
public:
using attendee_params_t = std::map<std::string, std::string>;
using attendee_list_t = std::map<std::string, attendee_params_t>;
Event () = default;
~Event () = default;
......@@ -42,8 +44,9 @@ namespace Ics {
const std::string &getOrganizer () const;
void setOrganizer (const std::string &organizer);
const std::list<std::string> &getAttendees () const;
const attendee_list_t &getAttendees () const;
void addAttendee (const std::string &attendee);
void addAttendee (const std::string &attendee, const attendee_params_t & params);
tm getDateTimeStart () const;
void setDateTimeStart (tm dateTimeStart);
......@@ -70,7 +73,7 @@ namespace Ics {
private:
std::string mOrganizer;
std::list<std::string> mAttendees;
attendee_list_t mAttendees;
tm mDateTimeStart;
tm mDuration;
unsigned int mSequence = 0;
......
......@@ -205,7 +205,29 @@ namespace Ics {
}
void addAttendee (const string &attendee) {
mAttendees.push_back(attendee);
if (!attendee.empty()) {
Ics::Event::attendee_params_t params;
size_t paramStart = attendee.find("ATTENDEE");
// Chop off ATTENDEE
const auto & paramAddress = attendee.substr(paramStart+strlen("ATTENDEE"));
size_t addressStart = paramAddress.find(":");
// Split parameters and address.
// Parameters end at the first : sign
const auto & paramsStr = paramAddress.substr(0, addressStart);
const auto & address = paramAddress.substr(addressStart + 1, attendee.size());
if (!paramsStr.empty()) {
const auto &splittedValue = bctoolbox::Utils::split(Utils::trim(paramsStr), ";");
for (const auto & param : splittedValue) {
if (!param.empty()) {
auto equal = param.find("=");
string name = param.substr(0, equal);
string value = param.substr(equal + 1, param.size());
params.insert(std::make_pair(name, value));
}
}
}
mAttendees.insert(std::make_pair(address, params));
}
}
void setUid (const string &xUid) {
......@@ -263,7 +285,7 @@ namespace Ics {
event->setXConfUri(mXConfUri);
for (const auto &attendee : mAttendees) {
event->addAttendee(attendee);
event->addAttendee(attendee.first, attendee.second);
}
event->setUid(mUid);
......@@ -279,7 +301,7 @@ namespace Ics {
string mOrganizer;
string mUid;
unsigned int mSequence = 0;
list<string> mAttendees;
Ics::Event::attendee_list_t mAttendees;
shared_ptr<DateTimeNode> mDateStart;
shared_ptr<DurationNode> mDuration;
};
......@@ -337,10 +359,11 @@ public:
Ics::Parser::Parser () : Singleton(*new ParserPrivate) {
L_D();
shared_ptr<belr::Grammar> grammar = belr::GrammarLoader::get().load(IcsGrammar);
if (!grammar)
lFatal() << "Unable to load CPIM grammar.";
d->parser = make_shared<belr::Parser<shared_ptr<Node>>>(grammar);
d->parser->setHandler("icalobject", belr::make_fn(make_shared<IcalendarNode>))
......@@ -353,7 +376,7 @@ Ics::Parser::Parser () : Singleton(*new ParserPrivate) {
->setCollector("dtstval", belr::make_sfn(&EventNode::setDateStart))
->setCollector("dur-value", belr::make_sfn(&EventNode::setDuration))
->setCollector("orgvalue", belr::make_sfn(&EventNode::setOrganizer))
->setCollector("attvalue", belr::make_sfn(&EventNode::addAttendee))
->setCollector("attendee", belr::make_sfn(&EventNode::addAttendee))
->setCollector("uid", belr::make_sfn(&EventNode::setUid))
->setCollector("seq", belr::make_sfn(&EventNode::setSequence))
->setCollector("x-prop", belr::make_sfn(&EventNode::setXProp));
......
......@@ -45,7 +45,7 @@ icalparameter = altrepparam ; Alternate text representation
/ valuetypeparam ; Property value data type
/ other-param
other-param = x-param ;/ iana-param
other-param = (iana-param / x-param)
iana-param = iana-token "=" param-value *("," param-value)
......
......@@ -32,6 +32,8 @@ using namespace std;
LINPHONE_BEGIN_NAMESPACE
const std::string ConferenceInfo::sequenceParam = "X-SEQ";
ConferenceInfo::ConferenceInfo () {
}
......@@ -43,20 +45,28 @@ void ConferenceInfo::setOrganizer (const IdentityAddress organizer) {
mOrganizer = organizer;
}
const std::list<IdentityAddress> & ConferenceInfo::getParticipants () const {
const ConferenceInfo::participant_list_t & ConferenceInfo::getParticipants () const {
return mParticipants;
}
void ConferenceInfo::setParticipants (const std::list<IdentityAddress> participants) {
void ConferenceInfo::setParticipants (const participant_list_t & participants) {
mParticipants = participants;
}
void ConferenceInfo::addParticipant (const IdentityAddress participant) {
mParticipants.push_back(participant);
void ConferenceInfo::addParticipant (const IdentityAddress & participant) {
ConferenceInfo::participant_params_t params;
params.insert(std::make_pair(ConferenceInfo::sequenceParam, "0"));
addParticipant(participant, params);
}
void ConferenceInfo::addParticipant (const IdentityAddress & participant, const participant_params_t & params) {
mParticipants.insert(std::make_pair(participant, params));
}
void ConferenceInfo::removeParticipant (const IdentityAddress participant) {
const auto it = std::find(mParticipants.cbegin(), mParticipants.cend(), participant);
void ConferenceInfo::removeParticipant (const IdentityAddress & participant) {
const auto it = std::find_if(mParticipants.cbegin(), mParticipants.cend(), [&participant] ( const auto & p) {
return (p.first == participant);
});
if (it == mParticipants.cend()) {
lInfo() << "Unable to find participant with address " << participant << " in conference info " << this << " (address " << getUri() << ")";
} else {
......@@ -128,6 +138,24 @@ void ConferenceInfo::setState (const ConferenceInfo::State &state) {
mState = state;
}
void ConferenceInfo::updateFrom (const std::shared_ptr<ConferenceInfo> & info) {
setUri(info->getUri());
setIcsUid(info->getIcsUid());
setIcsSequence(info->getIcsSequence() + 1);
const auto & participants = info->getParticipants();
for (auto & participant : mParticipants) {
const auto & otherParticipant = std::find_if(participants.cbegin(), participants.cend(), [&participant] (const auto & p) {
return (p.first == participant.first);
});
if (otherParticipant != participants.cend()) {
participant.second = otherParticipant->second;
}
}
}
const string ConferenceInfo::toIcsString (bool cancel) const {
Ics::Icalendar cal;
......@@ -162,9 +190,10 @@ const string ConferenceInfo::toIcsString (bool cancel) const {
}
for (const auto & participant : mParticipants) {
if (participant.isValid()) {
const auto uri = participant.getAddressWithoutGruu().asString();
event->addAttendee(uri);
const auto & address = participant.first;
if (address.isValid()) {
const auto uri = address.getAddressWithoutGruu().asString();
event->addAttendee(uri, participant.second);
}
}
......@@ -202,4 +231,14 @@ void ConferenceInfo::setCreationTime(time_t time) {
mCreationTime = time;
}
const std::string ConferenceInfo::paramsToString(const ConferenceInfo::participant_params_t & params) {
std::string str;
for (const auto & param : params) {
if (!str.empty()) {
str.append(";");
}
str.append(param.first + "=" + param.second);
}
return str;
}
LINPHONE_END_NAMESPACE
......@@ -21,6 +21,7 @@
#define _L_CONFERENCE_INFO_H_
#include <ctime>
#include <map>
#include <string>
#include "address/address.h"
......@@ -36,6 +37,10 @@ LINPHONE_BEGIN_NAMESPACE
class LINPHONE_PUBLIC ConferenceInfo : public bellesip::HybridObject<LinphoneConferenceInfo, ConferenceInfo> {
public:
static const std::string sequenceParam;
using participant_params_t = std::map<std::string, std::string>;
using participant_list_t = std::map<IdentityAddress, participant_params_t>;
enum class State {
New = LinphoneConferenceInfoStateNew,
Updated = LinphoneConferenceInfoStateUpdated,
......@@ -48,13 +53,16 @@ public:
return new ConferenceInfo(*this);
}
static const std::string paramsToString(const participant_params_t & params);
const IdentityAddress &getOrganizer () const;
void setOrganizer (IdentityAddress organizer);
const std::list<IdentityAddress> &getParticipants () const;
void setParticipants (const std::list<IdentityAddress> participants);
void addParticipant (const IdentityAddress participant);
void removeParticipant (const IdentityAddress participant);
const participant_list_t &getParticipants () const;
void setParticipants (const participant_list_t & participants);
void addParticipant (const IdentityAddress & participant);
void addParticipant (const IdentityAddress & participant, const participant_params_t & params);
void removeParticipant (const IdentityAddress & participant);
const ConferenceAddress &getUri () const;
void setUri (const ConferenceAddress uri);
......@@ -82,11 +90,13 @@ public:
const std::string toIcsString (bool cancel = false) const;
void updateFrom (const std::shared_ptr<ConferenceInfo> & info);
// Used only by the tester
void setCreationTime(time_t time);
private:
IdentityAddress mOrganizer;
std::list<IdentityAddress> mParticipants;
participant_list_t mParticipants;
ConferenceAddress mUri;
time_t mDateTime = (time_t) -1;
unsigned int mDuration = 0;
......
......@@ -81,16 +81,27 @@ const std::shared_ptr<ConferenceInfo> ConferenceScheduler::getInfo () const {
return mConferenceInfo;
}
void ConferenceScheduler::fillCancelList(const std::list<IdentityAddress> &oldList, const std::list<IdentityAddress> &newList) {
void ConferenceScheduler::fillCancelList(const ConferenceInfo::participant_list_t &oldList, const ConferenceInfo::participant_list_t &newList) {
mCancelToSend.clear();
for (const auto & address : oldList) {
const bool participantFound = (std::find(newList.cbegin(), newList.cend(), address) != newList.cend());
const bool participantFound = (std::find_if(newList.cbegin(), newList.cend(), [&address] (const auto &e) {
return (e.first == address.first);
}) != newList.cend());
if (!participantFound) {
mCancelToSend.push_back(address);
mCancelToSend.push_back(address.first);
}
}
}
void ConferenceScheduler::cancelConference (std::shared_ptr<ConferenceInfo> info) {
while (!info->getParticipants().empty()) {
const auto & participants = info->getParticipants();
const auto & participant = *(participants.begin());
info->removeParticipant(participant.first);
}
setInfo(info);
}
void ConferenceScheduler::setInfo (std::shared_ptr<ConferenceInfo> info) {
if (!info) {
lWarning() << "[Conference Scheduler] [" << this << "] Trying to set null conference info to the conference scheduler. Aborting conference creation!";
......@@ -114,7 +125,9 @@ void ConferenceScheduler::setInfo (std::shared_ptr<ConferenceInfo> info) {
const auto & organizer = info->getOrganizer();
const auto & participants = info->getParticipants();
const auto participantListEmpty = participants.empty();
const bool participantFound = (std::find(participants.cbegin(), participants.cend(), creator) != participants.cend());
const bool participantFound = (std::find_if(participants.cbegin(), participants.cend(), [&creator] (const auto &p) {
return (p.first == creator);
}) != participants.cend());
if ((creator != organizer) && !participantFound) {
lWarning() << "[Conference Scheduler] [" << this << "] Unable to find the address " << creator << " setting the conference information among the list of participants or the organizer (" << info->getOrganizer() << ") of conference " << info->getUri();
setState(State::Error);
......@@ -122,18 +135,15 @@ void ConferenceScheduler::setInfo (std::shared_ptr<ConferenceInfo> info) {
}
bool isUpdate = false;
ConferenceAddress conferenceAddress;
#ifdef HAVE_DB_STORAGE
if (info->getUri().isValid()) {
auto &mainDb = getCore()->getPrivate()->mainDb;
auto confInfo = mainDb->getConferenceInfoFromURI(info->getUri());
if (confInfo) {
lInfo() << "[Conference Scheduler] [" << this << "] Found matching conference info in database for address [" << info->getUri() << "]";
conferenceAddress = info->getUri();
isUpdate = true;
setState(State::Updating);
info->setIcsUid(confInfo->getIcsUid());
info->setIcsSequence(confInfo->getIcsSequence() + 1);
info->updateFrom(confInfo);
fillCancelList(confInfo->getParticipants(), info->getParticipants());
}
}
......@@ -155,9 +165,8 @@ void ConferenceScheduler::setInfo (std::shared_ptr<ConferenceInfo> info) {
return;
}
} else if (mConferenceInfo != nullptr) {
conferenceAddress = mConferenceInfo->getUri();
info->setUri(conferenceAddress);
setState(State::Updating);
info->updateFrom(mConferenceInfo);
fillCancelList(mConferenceInfo->getParticipants(), info->getParticipants());
}
......@@ -193,12 +202,17 @@ void ConferenceScheduler::setInfo (std::shared_ptr<ConferenceInfo> info) {
}
}
std::list<IdentityAddress> invitees;
for (const auto &p : mConferenceInfo->getParticipants()) {
invitees.push_back(p.first);
}
if (isUpdate) {
// Updating an existing conference
mSession = getCore()->createOrUpdateConferenceOnServer(conferenceParams, creator, mConferenceInfo->getParticipants(), conferenceAddress);
mSession = getCore()->createOrUpdateConferenceOnServer(conferenceParams, creator, invitees, info->getUri());
} else {
// Creating conference
mSession = getCore()->createConferenceOnServer(conferenceParams, identityAddress, mConferenceInfo->getParticipants());
mSession = getCore()->createConferenceOnServer(conferenceParams, identityAddress, invitees);
}
if (mSession == nullptr) {
lError() << "[Conference Scheduler] [" << this << "] createConferenceOnServer returned a null session!";
......@@ -291,7 +305,10 @@ void ConferenceScheduler::onCallSessionSetTerminated (const shared_ptr<CallSessi
auto new_params = linphone_core_create_call_params(getCore()->getCCore(), nullptr);
// Participant with the focus call is admin
L_GET_CPP_PTR_FROM_C_OBJECT(new_params)->addCustomContactParameter("admin", Utils::toString(true));
auto addressesList(mConferenceInfo->getParticipants());
std::list<IdentityAddress> addressesList;
for (const auto &p : mConferenceInfo->getParticipants()) {
addressesList.push_back(p.first);
}
addressesList.sort();
addressesList.unique();
......@@ -371,7 +388,9 @@ void ConferenceScheduler::sendInvitations (shared_ptr<ChatRoomParams> chatRoomPa
}
const auto & participants = mConferenceInfo->getParticipants();
const bool participantFound = (std::find(participants.cbegin(), participants.cend(), sender) != participants.cend());
const bool participantFound = (std::find_if(participants.cbegin(), participants.cend(), [&sender] (const auto &p) {
return (p.first == sender);
}) != participants.cend());
if ((sender != mConferenceInfo->getOrganizer()) && !participantFound) {
lWarning() << "[Conference Scheduler] [" << this << "] Unable to find the address " << sender << " sending invitations among the list of participants or the organizer (" << mConferenceInfo->getOrganizer() << ") of conference " << mConferenceInfo->getUri();
return;
......@@ -386,7 +405,10 @@ void ConferenceScheduler::sendInvitations (shared_ptr<ChatRoomParams> chatRoomPa
return;
}
auto invitees = participants;
std::list<IdentityAddress> invitees;
for (const auto &p :participants) {
invitees.push_back(p.first);
}
invitees.insert(invitees.begin(), mCancelToSend.begin(), mCancelToSend.end());
mInvitationsToSend.clear();
......
......@@ -64,6 +64,7 @@ public:
State getState () const;
const std::shared_ptr<ConferenceInfo> getInfo () const;
void cancelConference (std::shared_ptr<ConferenceInfo> info);
void setInfo(const std::shared_ptr<ConferenceInfo> info);
void setConferenceAddress(const ConferenceAddress& conferenceAddress);
......@@ -78,7 +79,7 @@ private:
std::string stateToString (State state);
std::shared_ptr<ChatMessage> createInvitationChatMessage(std::shared_ptr<AbstractChatRoom> chatRoom, bool cancel);
void fillCancelList(const std::list<IdentityAddress> &oldList, const std::list<IdentityAddress> &newList);
void fillCancelList(const ConferenceInfo::participant_list_t &oldList, const ConferenceInfo::participant_list_t &newList);
ConferenceScheduler::State mState;
std::shared_ptr<ConferenceInfo> mConferenceInfo = nullptr;
......
......@@ -674,7 +674,9 @@ std::shared_ptr<ConferenceInfo> Conference::createOrGetConferenceInfo() const {
std::shared_ptr<ConferenceInfo> Conference::createConferenceInfo(const IdentityAddress & organizer, const std::list<IdentityAddress> invitedParticipants) const {
std::shared_ptr<ConferenceInfo> info = ConferenceInfo::create();
info->setOrganizer(organizer);
info->setParticipants(invitedParticipants);
for (const auto & participant : invitedParticipants) {
info->addParticipant(participant);
}
const auto & conferenceAddress = getConferenceAddress();
if (conferenceAddress.isValid()) {
......@@ -716,12 +718,12 @@ void Conference::updateParticipantsInConferenceInfo(const IdentityAddress & part
if ((getState() == ConferenceInterface::State::CreationPending) || (getState() == ConferenceInterface::State::Created)) {
auto info = createOrGetConferenceInfo();
if (info) {
std::list<IdentityAddress> currentParticipants = info->getParticipants();
const auto participantAddressIt = std::find(currentParticipants.begin(), currentParticipants.end(), participantAddress);
const auto & currentParticipants = info->getParticipants();
const auto participantAddressIt = std::find_if(currentParticipants.begin(), currentParticipants.end(), [&participantAddress] (const auto & p) {
return (p.first == participantAddress);
});
if (participantAddressIt == currentParticipants.end()) {
currentParticipants.push_back(participantAddress);
info->setParticipants(currentParticipants);
info->addParticipant(participantAddress);
// Store into DB after the start incoming notification in order to have a valid conference address being the contact address of the call
auto &mainDb = getCore()->getPrivate()->mainDb;
......
......@@ -642,7 +642,11 @@ void Core::setEncryptionEngine (EncryptionEngine *imee) {
EncryptionEngine *Core::getEncryptionEngine () const {
L_D();
return d->imee.get();
const auto & imee = d->imee;
if (imee) {
return imee.get();
}
return nullptr;
}
void Core::enableLimeX3dh (bool enable) {
......
......@@ -70,8 +70,7 @@ private:
void insertChatRoomParticipantDevice (long long participantId, long long participantDeviceSipAddressId, const std::string &deviceName);
void insertChatMessageParticipant (long long chatMessageId, long long sipAddressId, int state, time_t stateChangeTime);
long long insertConferenceInfo (const std::shared_ptr<ConferenceInfo> &conferenceInfo, const std::shared_ptr<ConferenceInfo> &oldConferenceInfo);
long long insertConferenceInfoParticipant (long long conferenceInfoId, long long participantSipAddressId);
void deleteConferenceInfoParticipant (long long conferenceInfoId, long long participantSipAddressId);
long long insertOrUpdateConferenceInfoParticipant (long long conferenceInfoId, long long participantSipAddressId, bool deleted, const ConferenceInfo::participant_params_t params);
long long insertOrUpdateConferenceCall (const std::shared_ptr<CallLog> &callLog, const std::shared_ptr<ConferenceInfo> &conferenceInfo = nullptr);
long long selectSipAddressId (const std::string &sipAddress) const;
......
......@@ -63,7 +63,7 @@ LINPHONE_BEGIN_NAMESPACE
#ifdef HAVE_DB_STORAGE
namespace {
constexpr unsigned int ModuleVersionEvents = makeVersion(1, 0, 19);
constexpr unsigned int ModuleVersionEvents = makeVersion(1, 0, 20);
constexpr unsigned int ModuleVersionFriends = makeVersion(1, 0, 0);
constexpr unsigned int ModuleVersionLegacyFriendsImport = makeVersion(1, 0, 0);
constexpr unsigned int ModuleVersionLegacyHistoryImport = makeVersion(1, 0, 0);
......@@ -569,7 +569,7 @@ long long MainDbPrivate::insertConferenceInfo (const std::shared_ptr<ConferenceI
const string& uid = conferenceInfo->getIcsUid();
long long conferenceInfoId = selectConferenceInfoId(uriSipAddressid);
std::list<IdentityAddress> dbParticipantList;
ConferenceInfo::participant_list_t dbParticipantList;
if (conferenceInfoId >= 0) {
// The conference info is already stored in DB, but still update it some information might have changed
lInfo() << "Update conferenceInfo in database: " << conferenceInfoId << ".";
......@@ -606,18 +606,24 @@ long long MainDbPrivate::insertConferenceInfo (const std::shared_ptr<ConferenceI
const auto & participantList = conferenceInfo->getParticipants();
for (const auto & participantAddress : participantList) {
insertConferenceInfoParticipant(
insertOrUpdateConferenceInfoParticipant(
conferenceInfoId,
insertSipAddress(participantAddress.asString())
insertSipAddress(participantAddress.first.asString()),
false,
participantAddress.second
);
}
for (const auto & oldParticipantAddress : dbParticipantList) {
const bool deleted = (std::find(participantList.cbegin(), participantList.cend(), oldParticipantAddress) == participantList.cend());
const bool deleted = (std::find_if(participantList.cbegin(), participantList.cend(), [&oldParticipantAddress] (const auto & p) {
return (p.first == oldParticipantAddress.first);
}) == participantList.cend());
if (deleted) {
deleteConferenceInfoParticipant(
insertOrUpdateConferenceInfoParticipant(
conferenceInfoId,
insertSipAddress(oldParticipantAddress.asString())
insertSipAddress(oldParticipantAddress.first.asString()),
true,
oldParticipantAddress.second
);
}
}
......@@ -630,16 +636,25 @@ long long MainDbPrivate::insertConferenceInfo (const std::shared_ptr<ConferenceI
#endif
}
long long MainDbPrivate::insertConferenceInfoParticipant (long long conferenceInfoId, long long participantSipAddressId) {
long long MainDbPrivate::insertOrUpdateConferenceInfoParticipant (long long conferenceInfoId, long long participantSipAddressId, bool deleted, const ConferenceInfo::participant_params_t params) {
#ifdef HAVE_DB_STORAGE
long long conferenceInfoParticipantId = selectConferenceInfoParticipantId(conferenceInfoId, participantSipAddressId);
auto paramsStr = ConferenceInfo::paramsToString(params);
int participantDeleted = deleted ? 1 : 0;
if (conferenceInfoParticipantId >= 0) {
*dbSession.getBackendSession() << "UPDATE conference_info_participant SET"
" deleted = :deleted, params = :paramsStr"
" WHERE conference_info_id = :conferenceInfoId AND participant_sip_address_id = :participantSipAddressId",
soci::use(participantDeleted), soci::use(paramsStr),
soci::use(conferenceInfoId), soci::use(participantSipAddressId);
return conferenceInfoParticipantId;
}
*dbSession.getBackendSession() << "INSERT INTO conference_info_participant (conference_info_id, participant_sip_address_id)"
" VALUES (:conferenceInfoId, :participantSipAddressId)",
soci::use(conferenceInfoId), soci::use(participantSipAddressId);
*dbSession.getBackendSession() << "INSERT INTO conference_info_participant (conference_info_id, participant_sip_address_id, deleted, params)"
" VALUES (:conferenceInfoId, :participantSipAddressId, :deleted, :paramsStr)",
soci::use(conferenceInfoId), soci::use(participantSipAddressId),
soci::use(participantDeleted), soci::use(paramsStr);
return dbSession.getLastInsertId();
#else
......@@ -647,14 +662,6 @@ long long MainDbPrivate::insertConferenceInfoParticipant (long long conferenceIn
#endif
}
void MainDbPrivate::deleteConferenceInfoParticipant (long long conferenceInfoId, long long participantSipAddressId) {
#ifdef HAVE_DB_STORAGE
*dbSession.getBackendSession() << "DELETE FROM conference_info_participant"
" WHERE conference_info_id = :conferenceInfoId AND participant_sip_address_id = :participantSipAddressId",
soci::use(conferenceInfoId), soci::use(participantSipAddressId);
#endif
}
long long MainDbPrivate::insertOrUpdateConferenceCall (const std::shared_ptr<CallLog> &callLog, const std::shared_ptr<ConferenceInfo> &conferenceInfo) {
#ifdef HAVE_DB_STORAGE
long long conferenceInfoId = -1;
......@@ -1059,7 +1066,8 @@ shared_ptr<EventLog> MainDbPrivate::selectConferenceCallEvent (
soci::rowset<soci::row> participantRows = (session->prepare << query, soci::use(conferenceInfoId));
for (const auto &participantRow : participantRows) {
IdentityAddress participant(participantRow.get<string>(0));
conferenceInfo->addParticipant(participant);
ConferenceInfo::participant_params_t participantParams;
conferenceInfo->addParticipant(participant, participantParams);
}
cache(conferenceInfo, conferenceInfoId);
......@@ -1796,7 +1804,7 @@ shared_ptr<ConferenceInfo> MainDbPrivate::selectConferenceInfo (const soci::row
conferenceInfo->setIcsSequence(dbSession.getUnsignedInt(row,8,0));
conferenceInfo->setIcsUid(row.get<string>(9));
static const string query = "SELECT sip_address.value"
static const string query = "SELECT sip_address.value, conference_info_participant.deleted, conference_info_participant.params"
" FROM sip_address, conference_info, conference_info_participant"
" WHERE conference_info.id = :conferenceInfoId"
" AND sip_address.id = conference_info_participant.participant_sip_address_id"
......@@ -1805,8 +1813,22 @@ shared_ptr<ConferenceInfo> MainDbPrivate::selectConferenceInfo (const soci::row
soci::session *session = dbSession.getBackendSession();
soci::rowset<soci::row> participantRows = (session->prepare << query, soci::use(dbConferenceInfoId));
for (const auto &participantRow : participantRows) {
IdentityAddress participant(participantRow.get<string>(0));
conferenceInfo->addParticipant(participant);
int deleted = participantRow.get<int>(1);
if (deleted == 0) {
IdentityAddress participant(participantRow.get<string>(0));
ConferenceInfo::participant_params_t participantParams;
const string params = participantRow.get<string>(2);
if (!params.empty()) {
const auto &splittedValue = bctoolbox::Utils::split(Utils::trim(params), ";");
for (const auto & param : splittedValue) {
auto equal = param.find("=");
string name = param.substr(0, equal);
string value = param.substr(equal + 1, param.size());
participantParams.insert(std::make_pair(name, value));
}
}
conferenceInfo->addParticipant(participant, participantParams);
}
}
cache(conferenceInfo, dbConferenceInfoId);
......@@ -2168,6 +2190,11 @@ void MainDbPrivate::updateSchema () {
*session << "ALTER TABLE conference_info ADD COLUMN ics_uid VARCHAR(2048) DEFAULT ''";
}
if (version < makeVersion(1, 0, 20)) {
*session << "ALTER TABLE conference_info_participant ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT 0";
*session << "ALTER TABLE conference_info_participant ADD COLUMN params VARCHAR(2048) DEFAULT ''";
}
// /!\ Warning : if varchar columns < 255 were to be indexed, their size must be set back to 191 = max indexable (KEY or UNIQUE) varchar size for mysql < 5.7 with charset utf8mb4 (both here and in column creation)
#endif
......@@ -3427,13 +3454,12 @@ void MainDb::updateChatRoomEphemeralEnabled (const ConferenceId &conferenceId, b
" SET ephemeral_enabled = :ephemeralEnabled"
" WHERE id = :chatRoomId";
int ephemeralEnabledInt = ephemeralEnabled ? 1 : 0;
int isEphemeralEnabled = ephemeralEnabled ? 1 : 0;
L_DB_TRANSACTION {
L_D();
const long long &dbChatRoomId = d->selectChatRoomId(conferenceId);
*d->dbSession.getBackendSession() << query, soci::use(ephemeralEnabledInt), soci::use(dbChatRoomId);
*d->dbSession.getBackendSession() << query, soci::use(isEphemeralEnabled), soci::use(dbChatRoomId);
tr.commit();
};
#endif
......
......@@ -389,7 +389,9 @@ std::shared_ptr<ConferenceInfo> Utils::createConferenceInfoFromOp (SalCallOp *op
}
if (!resourceList.isEmpty()) {
auto invitees = Utils::parseResourceLists(resourceList);
info->setParticipants(invitees);
for (const auto &i : invitees) {
info->addParticipant(i);
}
}
char * remoteContactAddressStr = sal_address_as_string(remote ? op->getRemoteContactAddress() : op->getContactAddress());
......
......@@ -9764,7 +9764,7 @@ static void simple_conference_with_participant_with_no_event_log(void) {
destroy_mgr_in_conference(marie);
}
 
void simple_remote_conference(void) {
void simple_remote_conference_base(bool_t enable_ice) {
LinphoneCoreManager *marie = create_mgr_for_conference("marie_rc", TRUE, NULL);
LinphoneCoreManager *pauline = create_mgr_for_conference("pauline_tcp_rc", TRUE, NULL);
LinphoneCoreManager* laure = create_mgr_for_conference( liblinphone_tester_ipv6_available() ? "laure_tcp_rc" : "laure_rc_udp", TRUE, NULL);
......@@ -9777,6 +9777,13 @@ void simple_remote_conference(void) {
const char *laure_proxy_uri = linphone_proxy_config_get_server_addr(laure_proxy_config);
const char *focus_uri = linphone_proxy_config_get_identity(focus_proxy_config);
 
if (enable_ice) {
linphone_core_set_firewall_policy(focus_mgr->lc,LinphonePolicyUseIce);
linphone_core_set_firewall_policy(marie->lc,LinphonePolicyUseIce);
linphone_core_set_firewall_policy(pauline->lc,LinphonePolicyUseIce);
linphone_core_set_firewall_policy(laure->lc,LinphonePolicyUseIce);
}
linphone_config_set_string(marie_config, "misc", "conference_type", "remote");
LinphoneProxyConfig *marie_proxy = linphone_core_get_default_proxy_config(marie->lc);
linphone_proxy_config_edit(marie_proxy);
......@@ -9795,6 +9802,14 @@ void simple_remote_conference(void) {
linphone_conference_server_destroy(focus);
}
 
void simple_remote_conference(void) {
simple_remote_conference_base(FALSE);
}
void simple_ice_remote_conference(void) {
simple_remote_conference_base(TRUE);
}
void simple_remote_conference_shut_down_focus(void) {
LinphoneCoreManager *marie = create_mgr_for_conference("marie_rc", TRUE, NULL);
LinphoneCoreManager *pauline = create_mgr_for_conference("pauline_tcp_rc", TRUE, NULL);
......@@ -10808,7 +10823,7 @@ static void multiple_conferences_in_server_mode(void) {
destroy_mgr_in_conference(chloe);
}
 
static void conference_mix_created_by_merging_video_calls_base (LinphoneConferenceLayout layout) {
static void conference_mix_created_by_merging_video_calls_base (LinphoneConferenceLayout layout, bool_t enable_ice) {
LinphoneConferenceServer *focus = linphone_conference_server_new("conference_focus_rc", TRUE);
LinphoneCoreManager* focus_mgr = (LinphoneCoreManager*)focus;
linphone_core_enable_conference_server(focus_mgr->lc,TRUE);
......@@ -10857,6 +10872,10 @@ static void conference_mix_created_by_merging_video_calls_base (LinphoneConferen
LinphoneVideoActivationPolicy * cpol = linphone_core_get_video_activation_policy(c);
BC_ASSERT_TRUE(linphone_video_activation_policy_get_automatically_accept(cpol) == TRUE);
linphone_video_activation_policy_unref(cpol);
if (enable_ice) {
linphone_core_set_firewall_policy(c,LinphonePolicyUseIce);
}
}
 
linphone_video_activation_policy_unref(pol);
......@@ -10999,11 +11018,11 @@ static void conference_mix_created_by_merging_video_calls_base (LinphoneConferen
}
 
static void video_conference_created_by_merging_video_calls_with_grid_layout_2(void) {
conference_mix_created_by_merging_video_calls_base(LinphoneConferenceLayoutGrid);
conference_mix_created_by_merging_video_calls_base(LinphoneConferenceLayoutGrid, TRUE);
}
 
static void video_conference_created_by_merging_video_calls_with_active_speaker_layout_2(void) {
conference_mix_created_by_merging_video_calls_base(LinphoneConferenceLayoutActiveSpeaker);
conference_mix_created_by_merging_video_calls_base(LinphoneConferenceLayoutActiveSpeaker, FALSE);
}
 
test_t audio_video_conference_basic_tests[] = {
......@@ -11049,6 +11068,7 @@ test_t audio_video_conference_basic_tests[] = {
TEST_NO_TAG("Conference with back to back call invite and accept without ICE", conference_with_back_to_back_call_invite_accept_without_ice),
// TEST_ONE_TAG("Conference with back to back call invite and accept with ICE", conference_with_back_to_back_call_invite_accept_with_ice, "ICE"),
TEST_NO_TAG("Simple remote conference", simple_remote_conference),
TEST_NO_TAG("Simple ICE remote conference", simple_ice_remote_conference),
TEST_NO_TAG("Simple remote conference with shut down focus", simple_remote_conference_shut_down_focus),
TEST_NO_TAG("Eject from 3 participants in remote conference", eject_from_3_participants_remote_conference)
};
......@@ -11105,7 +11125,7 @@ test_t video_conference_tests[] = {
TEST_NO_TAG("Video conference by merging calls", video_conference_by_merging_calls),
TEST_NO_TAG("Video conference by merging video calls without conference event package", video_conference_created_by_merging_video_calls_without_conference_event_package),
TEST_NO_TAG("Video conference by merging video calls with grid layout", video_conference_created_by_merging_video_calls_with_grid_layout),
TEST_NO_TAG("Video conference by merging video calls with grid layout 2", video_conference_created_by_merging_video_calls_with_grid_layout_2),
TEST_ONE_TAG("Video conference by merging video calls with grid layout 2", video_conference_created_by_merging_video_calls_with_grid_layout_2, "ICE"),
TEST_ONE_TAG("ICE video conference by merging video calls with grid layout", ice_video_conference_created_by_merging_video_calls_with_grid_layout, "ICE"),
TEST_ONE_TAG("One participant ICE video conference with grid layout", ice_video_conference_one_participant_grid_layout, "ICE"),
TEST_ONE_TAG("One participant ICE video conference with active speaker layout", ice_video_conference_one_participant_active_speaker_layout, "ICE"),
......
......@@ -6231,6 +6231,97 @@ static void call_with_early_media_accepted_state_changed_callback(void) {
linphone_core_manager_destroy(pauline);
}
static void call_with_audio_stream_added_later_on(void){
LinphoneCoreManager *marie = linphone_core_manager_new("marie_rc");
LinphoneCoreManager* pauline = linphone_core_manager_new(transport_supported(LinphoneTransportTls) ? "pauline_rc" : "pauline_tcp_rc");
linphone_core_enable_video_display(marie->lc, TRUE);
linphone_core_enable_video_display(pauline->lc, TRUE);
LinphoneVideoActivationPolicy *vpol = linphone_factory_create_video_activation_policy(linphone_factory_get());
linphone_video_activation_policy_set_automatically_initiate(vpol, TRUE);
linphone_video_activation_policy_set_automatically_accept(vpol, TRUE);
linphone_core_set_video_activation_policy(marie->lc, vpol);
linphone_core_set_video_activation_policy(pauline->lc, vpol);
linphone_video_activation_policy_unref(vpol);
bool_t call_ok;
LinphoneCallParams *marie_params = linphone_core_create_call_params(marie->lc, NULL);
linphone_call_params_enable_audio(marie_params,TRUE);
linphone_call_params_enable_video(marie_params,TRUE);
LinphoneCallParams *pauline_params = linphone_core_create_call_params(pauline->lc, NULL);
linphone_call_params_enable_audio(pauline_params,FALSE);
linphone_call_params_enable_video(pauline_params,TRUE);
BC_ASSERT_TRUE((call_ok=call_with_params(pauline,marie,pauline_params,marie_params)));
linphone_call_params_unref(marie_params);
linphone_call_params_unref(pauline_params);
if (!call_ok) goto end;
LinphoneCall * pauline_call = linphone_core_get_current_call(pauline->lc);
LinphoneCall * marie_call = linphone_core_get_current_call(marie->lc);
if (marie_call) {
const LinphoneCallParams* call_rparams = linphone_call_get_remote_params(marie_call);
BC_ASSERT_FALSE(linphone_call_params_audio_enabled(call_rparams));
BC_ASSERT_TRUE(linphone_call_params_video_enabled(call_rparams));
const LinphoneCallParams* call_params = linphone_call_get_current_params(marie_call);
BC_ASSERT_FALSE(linphone_call_params_audio_enabled(call_params));
BC_ASSERT_TRUE(linphone_call_params_video_enabled(call_rparams));
}
if (pauline_call) {
const LinphoneCallParams* call_lparams = linphone_call_get_params(pauline_call);
BC_ASSERT_FALSE(linphone_call_params_audio_enabled(call_lparams));
BC_ASSERT_TRUE(linphone_call_params_video_enabled(call_lparams));
const LinphoneCallParams* call_rparams = linphone_call_get_remote_params(pauline_call);
BC_ASSERT_FALSE(linphone_call_params_audio_enabled(call_rparams));
BC_ASSERT_TRUE(linphone_call_params_video_enabled(call_rparams));
const LinphoneCallParams* call_cparams = linphone_call_get_current_params(pauline_call);
BC_ASSERT_FALSE(linphone_call_params_audio_enabled(call_cparams));
BC_ASSERT_TRUE(linphone_call_params_video_enabled(call_cparams));
}
stats initial_pauline_stat = pauline->stat;
stats initial_marie_stat = marie->stat;
LinphoneCallParams * pauline_new_params = linphone_core_create_call_params(pauline->lc, pauline_call);
linphone_call_params_enable_audio (pauline_new_params, TRUE);
linphone_call_update(pauline_call, pauline_new_params);
linphone_call_params_unref (pauline_new_params);
BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallUpdatedByRemote, initial_marie_stat.number_of_LinphoneCallUpdatedByRemote + 1));
BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallUpdating, initial_pauline_stat.number_of_LinphoneCallUpdating + 1));
BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallStreamsRunning, initial_marie_stat.number_of_LinphoneCallStreamsRunning + 1));
BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallStreamsRunning, initial_pauline_stat.number_of_LinphoneCallStreamsRunning + 1));
if (marie_call) {
const LinphoneCallParams* call_rparams = linphone_call_get_remote_params(marie_call);
BC_ASSERT_TRUE(linphone_call_params_audio_enabled(call_rparams));
BC_ASSERT_TRUE(linphone_call_params_video_enabled(call_rparams));
const LinphoneCallParams* call_params = linphone_call_get_current_params(marie_call);
BC_ASSERT_TRUE(linphone_call_params_audio_enabled(call_params));
}
if (pauline_call) {
const LinphoneCallParams* call_lparams = linphone_call_get_params(pauline_call);
BC_ASSERT_TRUE(linphone_call_params_audio_enabled(call_lparams));
BC_ASSERT_TRUE(linphone_call_params_video_enabled(call_lparams));
const LinphoneCallParams* call_rparams = linphone_call_get_remote_params(pauline_call);
BC_ASSERT_TRUE(linphone_call_params_audio_enabled(call_rparams));
BC_ASSERT_TRUE(linphone_call_params_video_enabled(call_rparams));
const LinphoneCallParams* call_cparams = linphone_call_get_current_params(pauline_call);
BC_ASSERT_TRUE(linphone_call_params_audio_enabled(call_cparams));
BC_ASSERT_TRUE(linphone_call_params_video_enabled(call_cparams));
}
end_call(pauline, marie);
end:
linphone_core_manager_destroy(marie);
linphone_core_manager_destroy(pauline);
}
static void call_with_two_audio_streams(void){
const char * crashing_invite =
"INVITE sip:631453@212.55.48.36:51230;transport=udp SIP/2.0\r\n"
......@@ -6620,6 +6711,7 @@ test_t call_tests[] = {
TEST_NO_TAG("Call without automatic 180 ringing but early media", call_without_automatic_180_ringing_but_early_media),
TEST_NO_TAG("Call with early media accepted in state changed callback", call_with_early_media_accepted_state_changed_callback),
TEST_NO_TAG("Call with same codecs ordered differently", call_with_same_codecs_ordered_differently),
TEST_NO_TAG("Call with audio stream added later on", call_with_audio_stream_added_later_on),
TEST_NO_TAG("Call with 2 audio streams", call_with_two_audio_streams),
TEST_NO_TAG("Call with unknown stream, accepted", call_with_unknown_stream_accepted),
TEST_NO_TAG("Simple call with display name", simple_call_with_display_name),
......