diff --git a/coreapi/callbacks.c b/coreapi/callbacks.c index f3554763d..535883882 100644 --- a/coreapi/callbacks.c +++ b/coreapi/callbacks.c @@ -39,6 +39,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "c-wrapper/c-wrapper.h" #include "call/call-p.h" +#include "chat/chat-message/chat-message-p.h" #include "chat/chat-room/chat-room.h" #include "chat/chat-room/server-group-chat-room-p.h" #include "conference/participant.h" @@ -565,7 +566,7 @@ static void message_delivery_update(SalOp *op, SalMessageDeliveryStatus status) // Check that the message does not belong to an already destroyed chat room - if so, do not invoke callbacks if (msg->getChatRoom()) - msg->updateState((LinphonePrivate::ChatMessage::State)chatStatusSal2Linphone(status)); + L_GET_PRIVATE(msg)->setState((LinphonePrivate::ChatMessage::State)chatStatusSal2Linphone(status)); } static void info_received(SalOp *op, SalBodyHandler *body_handler) { diff --git a/coreapi/private_functions.h b/coreapi/private_functions.h index e0e48308f..0908f70ea 100644 --- a/coreapi/private_functions.h +++ b/coreapi/private_functions.h @@ -355,7 +355,6 @@ LinphoneChatMessageStateChangedCb linphone_chat_message_get_message_state_change void linphone_chat_message_set_message_state_changed_cb(LinphoneChatMessage* msg, LinphoneChatMessageStateChangedCb cb); void linphone_chat_message_set_message_state_changed_cb_user_data(LinphoneChatMessage* msg, void *user_data); void * linphone_chat_message_get_message_state_changed_cb_user_data(LinphoneChatMessage* msg); -void linphone_chat_message_update_state(LinphoneChatMessage *msg, LinphoneChatMessageState new_state); LinphoneChatRoom *_linphone_core_create_chat_room_from_call(LinphoneCall *call); void linphone_core_play_named_tone(LinphoneCore *lc, LinphoneToneID id); diff --git a/src/c-wrapper/api/c-chat-message.cpp b/src/c-wrapper/api/c-chat-message.cpp index da453b39c..04218bfeb 100644 --- a/src/c-wrapper/api/c-chat-message.cpp +++ b/src/c-wrapper/api/c-chat-message.cpp @@ -209,10 +209,6 @@ void linphone_chat_message_resend_2(LinphoneChatMessage *msg) { L_GET_CPP_PTR_FROM_C_OBJECT(msg)->send(); } -void linphone_chat_message_update_state(LinphoneChatMessage *msg, LinphoneChatMessageState new_state) { - L_GET_CPP_PTR_FROM_C_OBJECT(msg)->updateState((LinphonePrivate::ChatMessage::State) new_state); -} - LinphoneStatus linphone_chat_message_put_char(LinphoneChatMessage *msg, uint32_t character) { return ((LinphoneStatus)L_GET_CPP_PTR_FROM_C_OBJECT(msg)->putCharacter(character)); } diff --git a/src/chat/chat-message/chat-message-p.h b/src/chat/chat-message/chat-message-p.h index 0eae6c24e..fd374a39e 100644 --- a/src/chat/chat-message/chat-message-p.h +++ b/src/chat/chat-message/chat-message-p.h @@ -60,7 +60,8 @@ public: void setDirection (ChatMessage::Direction dir); - void setState (ChatMessage::State state, bool force = false); + void setParticipantState (const IdentityAddress &participantAddress, ChatMessage::State newState); + void setState (ChatMessage::State newState, bool force = false); void setTime (time_t time); @@ -134,6 +135,8 @@ public: void updateInDb (); private: + static bool validStateTransition (ChatMessage::State currentState, ChatMessage::State newState); + // TODO: Clean attributes. time_t time = ::ms_time(0); // TODO: Change me in all files. std::string imdnId; diff --git a/src/chat/chat-message/chat-message.cpp b/src/chat/chat-message/chat-message.cpp index 40a80054e..a9f0e5ccc 100644 --- a/src/chat/chat-message/chat-message.cpp +++ b/src/chat/chat-message/chat-message.cpp @@ -63,28 +63,65 @@ void ChatMessagePrivate::setIsReadOnly (bool readOnly) { isReadOnly = readOnly; } -void ChatMessagePrivate::setState (ChatMessage::State s, bool force) { +void ChatMessagePrivate::setParticipantState (const IdentityAddress &participantAddress, ChatMessage::State newState) { + L_Q(); + + if (!(q->getChatRoom()->getCapabilities() & AbstractChatRoom::Capabilities::Conference)) { + setState(newState); + return; + } + + if (!dbKey.isValid()) + return; + + unique_ptr &mainDb = q->getChatRoom()->getCore()->getPrivate()->mainDb; + shared_ptr eventLog = mainDb->getEventFromKey(dbKey); + ChatMessage::State currentState = mainDb->getChatMessageParticipantState(eventLog, participantAddress); + if (!validStateTransition(currentState, newState)) + return; + + mainDb->setChatMessageParticipantState(eventLog, participantAddress, newState); + + list states = mainDb->getChatMessageParticipantStates(eventLog); + size_t nbDisplayedStates = 0; + size_t nbDeliveredToUserStates = 0; + size_t nbNotDeliveredStates = 0; + for (const auto &state : states) { + switch (state) { + case ChatMessage::State::Displayed: + nbDisplayedStates++; + break; + case ChatMessage::State::DeliveredToUser: + nbDeliveredToUserStates++; + break; + case ChatMessage::State::NotDelivered: + nbNotDeliveredStates++; + break; + default: + break; + } + } + + if (nbNotDeliveredStates > 0) + setState(ChatMessage::State::NotDelivered); + else if (nbDisplayedStates == states.size()) + setState(ChatMessage::State::Displayed); + else if ((nbDisplayedStates + nbDeliveredToUserStates) == states.size()) + setState(ChatMessage::State::DeliveredToUser); +} + +void ChatMessagePrivate::setState (ChatMessage::State newState, bool force) { L_Q(); if (force) - state = s; + state = newState; - if (s == state) - return; - - if ( - (state == ChatMessage::State::Displayed || state == ChatMessage::State::DeliveredToUser) && - ( - s == ChatMessage::State::DeliveredToUser || - s == ChatMessage::State::Delivered || - s == ChatMessage::State::NotDelivered - ) - ) + if (!validStateTransition(state, newState)) return; lInfo() << "Chat message " << this << ": moving from " << Utils::toString(state) << - " to " << Utils::toString(s); - state = s; + " to " << Utils::toString(newState); + state = newState; LinphoneChatMessage *msg = L_GET_C_BACK_PTR(q); if (linphone_chat_message_get_message_state_changed_cb(msg)) @@ -756,6 +793,24 @@ void ChatMessagePrivate::updateInDb () { // ----------------------------------------------------------------------------- +bool ChatMessagePrivate::validStateTransition (ChatMessage::State currentState, ChatMessage::State newState) { + if (newState == currentState) + return false; + + if ( + (currentState == ChatMessage::State::Displayed || currentState == ChatMessage::State::DeliveredToUser) && + ( + newState == ChatMessage::State::DeliveredToUser || + newState == ChatMessage::State::Delivered || + newState == ChatMessage::State::NotDelivered + ) + ) + return false; + return true; +} + +// ----------------------------------------------------------------------------- + ChatMessage::ChatMessage (const shared_ptr &chatRoom, ChatMessage::Direction direction) : Object(*new ChatMessagePrivate), CoreAccessor(chatRoom->getCore()) { L_D(); @@ -931,12 +986,6 @@ void ChatMessage::removeCustomHeader (const string &headerName) { d->customHeaders.erase(headerName); } -void ChatMessage::updateState (State state) { - L_D(); - - d->setState(state); -} - void ChatMessage::send () { L_D(); diff --git a/src/chat/chat-message/chat-message.h b/src/chat/chat-message/chat-message.h index af7623b77..1108bcb70 100644 --- a/src/chat/chat-message/chat-message.h +++ b/src/chat/chat-message/chat-message.h @@ -47,6 +47,7 @@ class LINPHONE_PUBLIC ChatMessage : public Object, public CoreAccessor { friend class ChatRoomPrivate; friend class CpimChatMessageModifier; friend class FileTransferChatMessageModifier; + friend class Imdn; friend class MainDb; friend class MainDbPrivate; friend class RealTimeTextChatRoomPrivate; @@ -63,7 +64,6 @@ public: // ----- TODO: Remove me. void cancelFileTransfer (); int putCharacter (uint32_t character); - void updateState (State state); void sendDeliveryNotification (LinphoneReason reason); void sendDisplayNotification (); void setIsSecured (bool isSecured); diff --git a/src/chat/chat-room/chat-room-p.h b/src/chat/chat-room/chat-room-p.h index 7b45cc6c0..2deeec842 100644 --- a/src/chat/chat-room/chat-room-p.h +++ b/src/chat/chat-room/chat-room-p.h @@ -61,7 +61,7 @@ public: LinphoneReason onSipMessageReceived (SalOp *op, const SalMessage *message) override; void onChatMessageReceived (const std::shared_ptr &chatMessage) override; - void onImdnReceived (const std::string &text); + void onImdnReceived (const std::shared_ptr &chatMessage); void onIsComposingReceived (const Address &remoteAddress, const std::string &text); void onIsComposingRefreshNeeded () override; void onIsComposingStateChanged (bool isComposing) override; diff --git a/src/chat/chat-room/chat-room.cpp b/src/chat/chat-room/chat-room.cpp index 6a9fa970c..b3c1f6dce 100644 --- a/src/chat/chat-room/chat-room.cpp +++ b/src/chat/chat-room/chat-room.cpp @@ -227,7 +227,7 @@ LinphoneReason ChatRoomPrivate::onSipMessageReceived (SalOp *op, const SalMessag goto end; } } else if (msg->getPrivate()->getContentType() == ContentType::Imdn) { - onImdnReceived(msg->getPrivate()->getText()); + onImdnReceived(msg); if (lp_config_get_int(linphone_core_get_config(cCore), "sip", "deliver_imdn", 0) != 1) { goto end; } @@ -246,9 +246,8 @@ void ChatRoomPrivate::onChatMessageReceived (const shared_ptr &chat chatMessage->getPrivate()->notifyReceiving(); } -void ChatRoomPrivate::onImdnReceived (const string &text) { - L_Q(); - Imdn::parse(*q, text); +void ChatRoomPrivate::onImdnReceived (const shared_ptr &chatMessage) { + Imdn::parse(chatMessage); } void ChatRoomPrivate::onIsComposingReceived (const Address &remoteAddress, const string &text) { diff --git a/src/chat/modifier/file-transfer-chat-message-modifier.cpp b/src/chat/modifier/file-transfer-chat-message-modifier.cpp index 951efa582..41ffa66c8 100644 --- a/src/chat/modifier/file-transfer-chat-message-modifier.cpp +++ b/src/chat/modifier/file-transfer-chat-message-modifier.cpp @@ -364,24 +364,24 @@ void FileTransferChatMessageModifier::processResponseFromPostFile (const belle_h message->removeContent(*fileContent); message->addContent(*fileTransferContent); - message->updateState(ChatMessage::State::FileTransferDone); + message->getPrivate()->setState(ChatMessage::State::FileTransferDone); releaseHttpRequest(); message->getPrivate()->send(); fileUploadEndBackgroundTask(); } else { lWarning() << "Received empty response from server, file transfer failed"; - message->updateState(ChatMessage::State::NotDelivered); + message->getPrivate()->setState(ChatMessage::State::NotDelivered); releaseHttpRequest(); fileUploadEndBackgroundTask(); } } else if (code == 400) { lWarning() << "Received HTTP code response " << code << " for file transfer, probably meaning file is too large"; - message->updateState(ChatMessage::State::FileTransferError); + message->getPrivate()->setState(ChatMessage::State::FileTransferError); releaseHttpRequest(); fileUploadEndBackgroundTask(); } else { lWarning() << "Unhandled HTTP code response " << code << " for file transfer"; - message->updateState(ChatMessage::State::NotDelivered); + message->getPrivate()->setState(ChatMessage::State::NotDelivered); releaseHttpRequest(); fileUploadEndBackgroundTask(); } @@ -398,7 +398,7 @@ void FileTransferChatMessageModifier::processIoErrorUpload (const belle_sip_io_e shared_ptr message = chatMessage.lock(); if (!message) return; - message->updateState(ChatMessage::State::NotDelivered); + message->getPrivate()->setState(ChatMessage::State::NotDelivered); releaseHttpRequest(); } @@ -412,7 +412,7 @@ void FileTransferChatMessageModifier::processAuthRequestedUpload (const belle_si shared_ptr message = chatMessage.lock(); if (!message) return; - message->updateState(ChatMessage::State::NotDelivered); + message->getPrivate()->setState(ChatMessage::State::NotDelivered); releaseHttpRequest(); } @@ -844,7 +844,7 @@ void FileTransferChatMessageModifier::processAuthRequestedDownload (const belle_ shared_ptr message = chatMessage.lock(); if (!message) return; - message->updateState(ChatMessage::State::FileTransferError); + message->getPrivate()->setState(ChatMessage::State::FileTransferError); releaseHttpRequest(); } @@ -858,7 +858,7 @@ void FileTransferChatMessageModifier::processIoErrorDownload (const belle_sip_io shared_ptr message = chatMessage.lock(); if (!message) return; - message->updateState(ChatMessage::State::FileTransferError); + message->getPrivate()->setState(ChatMessage::State::FileTransferError); releaseHttpRequest(); } diff --git a/src/chat/notification/imdn.cpp b/src/chat/notification/imdn.cpp index eceda4c11..817ae3a2a 100644 --- a/src/chat/notification/imdn.cpp +++ b/src/chat/notification/imdn.cpp @@ -17,7 +17,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "chat/chat-message/chat-message.h" +#include "chat/chat-message/chat-message-p.h" #include "chat/chat-room/chat-room.h" #include "core/core.h" #include "logger/logger.h" @@ -133,18 +133,18 @@ string Imdn::createXml (const string &id, time_t time, Imdn::Type imdnType, Linp return content; } -void Imdn::parse (ChatRoom &cr, const string &text) { +void Imdn::parse (const shared_ptr &chatMessage) { xmlparsing_context_t *xmlCtx = linphone_xmlparsing_context_new(); xmlSetGenericErrorFunc(xmlCtx, linphone_xmlparsing_genericxml_error); - xmlCtx->doc = xmlReadDoc((const unsigned char *)text.c_str(), 0, nullptr, 0); + xmlCtx->doc = xmlReadDoc((const unsigned char *)chatMessage->getPrivate()->getText().c_str(), 0, nullptr, 0); if (xmlCtx->doc) - parse(cr, xmlCtx); + parse(chatMessage, xmlCtx); else lWarning() << "Wrongly formatted IMDN XML: " << xmlCtx->errorBuffer; linphone_xmlparsing_context_destroy(xmlCtx); } -void Imdn::parse (ChatRoom &cr, xmlparsing_context_t *xmlCtx) { +void Imdn::parse (const shared_ptr &imdnMessage, xmlparsing_context_t *xmlCtx) { char xpathStr[MAX_XPATH_LENGTH]; char *messageIdStr = nullptr; char *datetimeStr = nullptr; @@ -164,11 +164,13 @@ void Imdn::parse (ChatRoom &cr, xmlparsing_context_t *xmlCtx) { } if (messageIdStr && datetimeStr) { - shared_ptr cm = cr.findChatMessage(messageIdStr, ChatMessage::Direction::Outgoing); + shared_ptr cr = imdnMessage->getChatRoom(); + shared_ptr cm = cr->findChatMessage(messageIdStr, ChatMessage::Direction::Outgoing); + const IdentityAddress &participantAddress = imdnMessage->getFromAddress().getAddressWithoutGruu(); if (!cm) { lWarning() << "Received IMDN for unknown message " << messageIdStr; } else { - LinphoneImNotifPolicy *policy = linphone_core_get_im_notif_policy(cr.getCore()->getCCore()); + LinphoneImNotifPolicy *policy = linphone_core_get_im_notif_policy(cr->getCore()->getCCore()); snprintf(xpathStr, sizeof(xpathStr), "%s[1]/imdn:delivery-notification/imdn:status", imdnPrefix.c_str()); xmlXPathObjectPtr deliveryStatusObject = linphone_get_xml_xpath_object_for_node_list(xmlCtx, xpathStr); snprintf(xpathStr, sizeof(xpathStr), "%s[1]/imdn:display-notification/imdn:status", imdnPrefix.c_str()); @@ -178,9 +180,9 @@ void Imdn::parse (ChatRoom &cr, xmlparsing_context_t *xmlCtx) { xmlNodePtr node = deliveryStatusObject->nodesetval->nodeTab[0]; if (node->children && node->children->name) { if (strcmp((const char *)node->children->name, "delivered") == 0) { - cm->updateState(ChatMessage::State::DeliveredToUser); + cm->getPrivate()->setParticipantState(participantAddress, ChatMessage::State::DeliveredToUser); } else if (strcmp((const char *)node->children->name, "error") == 0) { - cm->updateState(ChatMessage::State::NotDelivered); + cm->getPrivate()->setParticipantState(participantAddress, ChatMessage::State::NotDelivered); } } } @@ -191,7 +193,7 @@ void Imdn::parse (ChatRoom &cr, xmlparsing_context_t *xmlCtx) { xmlNodePtr node = displayStatusObject->nodesetval->nodeTab[0]; if (node->children && node->children->name) { if (strcmp((const char *)node->children->name, "displayed") == 0) { - cm->updateState(ChatMessage::State::Displayed); + cm->getPrivate()->setParticipantState(participantAddress, ChatMessage::State::Displayed); } } } diff --git a/src/chat/notification/imdn.h b/src/chat/notification/imdn.h index ed51696b0..e6eea4766 100644 --- a/src/chat/notification/imdn.h +++ b/src/chat/notification/imdn.h @@ -38,10 +38,10 @@ public: }; static std::string createXml (const std::string &id, time_t time, Imdn::Type imdnType, LinphoneReason reason); - static void parse (ChatRoom &cr, const std::string &content); + static void parse (const std::shared_ptr &chatMessage); private: - static void parse (ChatRoom &cr, xmlparsing_context_t *xmlCtx); + static void parse (const std::shared_ptr &chatMessage, xmlparsing_context_t *xmlCtx); private: static const std::string imdnPrefix; diff --git a/src/db/main-db.cpp b/src/db/main-db.cpp index 83b903aaf..248c681a9 100644 --- a/src/db/main-db.cpp +++ b/src/db/main-db.cpp @@ -516,19 +516,12 @@ void MainDbPrivate::insertChatRoomParticipantDevice ( } void MainDbPrivate::insertChatMessageParticipant (long long eventId, long long sipAddressId, int state) { - // TODO: Deal with read messages. - // Remove if displayed? Think a good alorithm for mark as read. - soci::session *session = dbSession.getBackendSession(); - soci::statement statement = ( - session->prepare << "UPDATE chat_message_participant SET state = :state" - " WHERE event_id = :eventId AND participant_sip_address_id = :sipAddressId", - soci::use(state), soci::use(eventId), soci::use(sipAddressId) - ); - statement.execute(); - if (statement.get_affected_rows() == 0 && state != int(ChatMessage::State::Displayed)) + if (state != static_cast(ChatMessage::State::Displayed)) { + soci::session *session = dbSession.getBackendSession(); *session << "INSERT INTO chat_message_participant (event_id, participant_sip_address_id, state)" " VALUES (:eventId, :sipAddressId, :state)", soci::use(eventId), soci::use(sipAddressId), soci::use(state); + } } // ----------------------------------------------------------------------------- @@ -992,6 +985,11 @@ long long MainDbPrivate::insertConferenceChatMessageEvent (const shared_ptrgetContents()) insertContent(eventId, *content); + for (const auto &participant : chatRoom->getParticipants()) { + const long long &participantSipAddressId = selectSipAddressId(participant->getAddress().asString()); + insertChatMessageParticipant(eventId, participantSipAddressId, state); + } + return eventId; } @@ -2216,6 +2214,75 @@ list> MainDb::getUnreadChatMessages (const ChatRoomId &c }; } +list MainDb::getChatMessageParticipantStates (const shared_ptr &eventLog) const { + return L_SAFE_TRANSACTION { + L_D(); + + soci::session *session = d->dbSession.getBackendSession(); + + const EventLogPrivate *dEventLog = eventLog->getPrivate(); + MainDbKeyPrivate *dEventKey = static_cast(dEventLog->dbKey).getPrivate(); + const long long &eventId = dEventKey->storageId; + list states; + unsigned int state; + + soci::statement statement = (session->prepare + << "SELECT state FROM chat_message_participant WHERE event_id = :eventId", + soci::into(state), soci::use(eventId) + ); + statement.execute(); + while (statement.fetch()) + states.push_back(static_cast(state)); + + return states; + }; +} + +ChatMessage::State MainDb::getChatMessageParticipantState ( + const shared_ptr &eventLog, + const IdentityAddress &participantAddress +) const { + return L_SAFE_TRANSACTION { + L_D(); + + soci::session *session = d->dbSession.getBackendSession(); + + const EventLogPrivate *dEventLog = eventLog->getPrivate(); + MainDbKeyPrivate *dEventKey = static_cast(dEventLog->dbKey).getPrivate(); + const long long &eventId = dEventKey->storageId; + const long long &participantSipAddressId = d->selectSipAddressId(participantAddress.asString()); + unsigned int state; + + *session << "SELECT state FROM chat_message_participant" + " WHERE event_id = :eventId AND participant_sip_address_id = :participantSipAddressId", + soci::into(state), soci::use(eventId), soci::use(participantSipAddressId); + + return static_cast(state); + }; +} + +void MainDb::setChatMessageParticipantState ( + const shared_ptr &eventLog, + const IdentityAddress &participantAddress, + ChatMessage::State state +) { + L_SAFE_TRANSACTION { + L_D(); + + soci::session *session = d->dbSession.getBackendSession(); + + const EventLogPrivate *dEventLog = eventLog->getPrivate(); + MainDbKeyPrivate *dEventKey = static_cast(dEventLog->dbKey).getPrivate(); + const long long &eventId = dEventKey->storageId; + const long long &participantSipAddressId = d->selectSipAddressId(participantAddress.asString()); + int stateInt = static_cast(state); + + *session << "UPDATE chat_message_participant SET state = :state" + " WHERE event_id = :eventId AND participant_sip_address_id = :participantSipAddressId", + soci::use(stateInt), soci::use(eventId), soci::use(participantSipAddressId); + }; +} + shared_ptr MainDb::getLastChatMessage (const ChatRoomId &chatRoomId) const { list> chatList = getHistory(chatRoomId, 1, Filter::ConferenceChatMessageFilter); if (chatList.empty()) diff --git a/src/db/main-db.h b/src/db/main-db.h index c22041597..ee8145962 100644 --- a/src/db/main-db.h +++ b/src/db/main-db.h @@ -25,6 +25,7 @@ #include "linphone/utils/enum-mask.h" #include "abstract/abstract-db.h" +#include "chat/chat-message/chat-message.h" #include "chat/chat-room/chat-room-id.h" #include "core/core-accessor.h" @@ -85,6 +86,17 @@ public: void markChatMessagesAsRead (const ChatRoomId &chatRoomId = ChatRoomId()) const; std::list> getUnreadChatMessages (const ChatRoomId &chatRoomId = ChatRoomId()) const; + std::list getChatMessageParticipantStates (const std::shared_ptr &eventLog) const; + ChatMessage::State getChatMessageParticipantState ( + const std::shared_ptr &eventLog, + const IdentityAddress &participantAddress + ) const; + void setChatMessageParticipantState ( + const std::shared_ptr &eventLog, + const IdentityAddress &participantAddress, + ChatMessage::State state + ); + std::shared_ptr getLastChatMessage (const ChatRoomId &chatRoomId) const; std::list> findChatMessages ( diff --git a/tester/group_chat_tester.c b/tester/group_chat_tester.c index 56f5f22fd..6c6371e8d 100644 --- a/tester/group_chat_tester.c +++ b/tester/group_chat_tester.c @@ -2614,6 +2614,76 @@ static void group_chat_room_new_unique_one_to_one_chat_room_after_both_participa linphone_core_manager_destroy(pauline); } +static void imdn_for_group_chat_room (void) { + LinphoneCoreManager *marie = linphone_core_manager_create("marie_rc"); + LinphoneCoreManager *pauline = linphone_core_manager_create("pauline_rc"); + LinphoneCoreManager *chloe = linphone_core_manager_create("chloe_rc"); + bctbx_list_t *coresManagerList = NULL; + bctbx_list_t *participantsAddresses = NULL; + coresManagerList = bctbx_list_append(coresManagerList, marie); + coresManagerList = bctbx_list_append(coresManagerList, pauline); + coresManagerList = bctbx_list_append(coresManagerList, chloe); + bctbx_list_t *coresList = init_core_for_conference(coresManagerList); + start_core_for_conference(coresManagerList); + participantsAddresses = bctbx_list_append(participantsAddresses, linphone_address_new(linphone_core_get_identity(pauline->lc))); + participantsAddresses = bctbx_list_append(participantsAddresses, linphone_address_new(linphone_core_get_identity(chloe->lc))); + stats initialMarieStats = marie->stat; + stats initialPaulineStats = pauline->stat; + stats initialChloeStats = chloe->stat; + + // Enable IMDN + linphone_im_notif_policy_enable_all(linphone_core_get_im_notif_policy(marie->lc)); + linphone_im_notif_policy_enable_all(linphone_core_get_im_notif_policy(pauline->lc)); + linphone_im_notif_policy_enable_all(linphone_core_get_im_notif_policy(chloe->lc)); + + // Marie creates a new group chat room + const char *initialSubject = "Colleagues"; + LinphoneChatRoom *marieCr = create_chat_room_client_side(coresList, marie, &initialMarieStats, participantsAddresses, initialSubject, -1); + const LinphoneAddress *confAddr = linphone_chat_room_get_conference_address(marieCr); + + // Check that the chat room is correctly created on Pauline's side and that the participants are added + LinphoneChatRoom *paulineCr = check_creation_chat_room_client_side(coresList, pauline, &initialPaulineStats, confAddr, initialSubject, 2, 0); + + // Check that the chat room is correctly created on Chloe's side and that the participants are added + LinphoneChatRoom *chloeCr = check_creation_chat_room_client_side(coresList, chloe, &initialChloeStats, confAddr, initialSubject, 2, 0); + + // Chloe begins composing a message + const char *chloeMessage = "Hello"; + _send_message(chloeCr, chloeMessage); + BC_ASSERT_TRUE(wait_for_list(coresList, &marie->stat.number_of_LinphoneMessageReceived, initialMarieStats.number_of_LinphoneMessageReceived + 1, 10000)); + BC_ASSERT_TRUE(wait_for_list(coresList, &pauline->stat.number_of_LinphoneMessageReceived, initialPaulineStats.number_of_LinphoneMessageReceived + 1, 10000)); + LinphoneChatMessage *marieLastMsg = marie->stat.last_received_chat_message; + if (!BC_ASSERT_PTR_NOT_NULL(marieLastMsg)) + goto end; + BC_ASSERT_STRING_EQUAL(linphone_chat_message_get_text(marieLastMsg), chloeMessage); + LinphoneAddress *chloeAddr = linphone_address_new(linphone_core_get_identity(chloe->lc)); + BC_ASSERT_TRUE(linphone_address_weak_equal(chloeAddr, linphone_chat_message_get_from_address(marieLastMsg))); + linphone_address_unref(chloeAddr); + + // Check that the message has been delivered to Marie and Pauline + BC_ASSERT_TRUE(wait_for_list(coresList, &chloe->stat.number_of_LinphoneMessageDeliveredToUser, initialChloeStats.number_of_LinphoneMessageDeliveredToUser + 1, 10000)); + + // Marie marks the message as read, check that the state is not yet displayed on Chloe's side + linphone_chat_room_mark_as_read(marieCr); + BC_ASSERT_FALSE(wait_for_list(coresList, &chloe->stat.number_of_LinphoneMessageDisplayed, initialChloeStats.number_of_LinphoneMessageDisplayed + 1, 1000)); + + // Pauline also marks the message as read, check that the state is now displayed on Chloe's side + linphone_chat_room_mark_as_read(paulineCr); + BC_ASSERT_TRUE(wait_for_list(coresList, &chloe->stat.number_of_LinphoneMessageDisplayed, initialChloeStats.number_of_LinphoneMessageDisplayed + 1, 1000)); + +end: + // Clean db from chat room + linphone_core_manager_delete_chat_room(marie, marieCr, coresList); + linphone_core_manager_delete_chat_room(chloe, chloeCr, coresList); + linphone_core_manager_delete_chat_room(pauline, paulineCr, coresList); + + bctbx_list_free(coresList); + bctbx_list_free(coresManagerList); + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); + linphone_core_manager_destroy(chloe); +} + test_t group_chat_tests[] = { TEST_TWO_TAGS("Group chat room creation server", group_chat_room_creation_server, "Server", "LeaksMemory"), TEST_TWO_TAGS("Send message", group_chat_room_send_message, "Server", "LeaksMemory"), @@ -2651,7 +2721,8 @@ test_t group_chat_tests[] = { TEST_TWO_TAGS("Unique one-to-one chatroom", group_chat_room_unique_one_to_one_chat_room, "Server", "LeaksMemory"), TEST_TWO_TAGS("Unique one-to-one chatroom recreated from message", group_chat_room_unique_one_to_one_chat_room_recreated_from_message, "Server", "LeaksMemory"), TEST_TWO_TAGS("Unique one-to-one chatroom recreated from message with app restart", group_chat_room_unique_one_to_one_chat_room_recreated_from_message_with_app_restart, "Server", "LeaksMemory"), - TEST_TWO_TAGS("New unique one-to-one chatroom after both participants left", group_chat_room_new_unique_one_to_one_chat_room_after_both_participants_left, "Server", "LeaksMemory") + TEST_TWO_TAGS("New unique one-to-one chatroom after both participants left", group_chat_room_new_unique_one_to_one_chat_room_after_both_participants_left, "Server", "LeaksMemory"), + TEST_TWO_TAGS("IMDN for group chat room", imdn_for_group_chat_room, "Server", "LeaksMemory") }; test_suite_t group_chat_test_suite = {