From 532c64bfc48cfe23aca0f8cac13a13b0b64fb840 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 10 Apr 2018 15:32:57 +0200 Subject: [PATCH] Add API to get the imdn state of a chat message for each participant. --- include/CMakeLists.txt | 1 + include/linphone/api/c-api.h | 1 + include/linphone/api/c-chat-message.h | 21 +++- .../linphone/api/c-participant-imdn-state.h | 91 ++++++++++++++++ include/linphone/api/c-types.h | 6 + src/CMakeLists.txt | 4 + src/c-wrapper/api/c-chat-message.cpp | 14 ++- .../api/c-participant-imdn-state.cpp | 59 ++++++++++ src/c-wrapper/c-wrapper.h | 1 + src/chat/chat-message/chat-message-p.h | 3 +- src/chat/chat-message/chat-message.cpp | 66 +++++++---- src/chat/chat-message/chat-message.h | 5 + src/conference/participant-imdn-state-p.h | 42 +++++++ src/conference/participant-imdn-state.cpp | 61 +++++++++++ src/conference/participant-imdn-state.h | 48 ++++++++ src/core/core.h | 1 + src/db/main-db.cpp | 103 ++++++++++++++---- src/db/main-db.h | 22 +++- tester/group_chat_tester.c | 34 +++++- 19 files changed, 530 insertions(+), 53 deletions(-) create mode 100644 include/linphone/api/c-participant-imdn-state.h create mode 100644 src/c-wrapper/api/c-participant-imdn-state.cpp create mode 100644 src/conference/participant-imdn-state-p.h create mode 100644 src/conference/participant-imdn-state.cpp create mode 100644 src/conference/participant-imdn-state.h diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt index 2e09af5eb..a55154d99 100644 --- a/include/CMakeLists.txt +++ b/include/CMakeLists.txt @@ -89,6 +89,7 @@ set(C_API_HEADER_FILES c-event-log.h c-magic-search.h c-participant.h + c-participant-imdn-state.h c-search-result.h c-types.h ) diff --git a/include/linphone/api/c-api.h b/include/linphone/api/c-api.h index 039b55246..54a47fe56 100644 --- a/include/linphone/api/c-api.h +++ b/include/linphone/api/c-api.h @@ -34,6 +34,7 @@ #include "linphone/api/c-dial-plan.h" #include "linphone/api/c-event-log.h" #include "linphone/api/c-participant.h" +#include "linphone/api/c-participant-imdn-state.h" #include "linphone/api/c-magic-search.h" #include "linphone/api/c-search-result.h" #include "linphone/api/c-types.h" diff --git a/include/linphone/api/c-chat-message.h b/include/linphone/api/c-chat-message.h index 1cd3dbd21..da24fa8f5 100644 --- a/include/linphone/api/c-chat-message.h +++ b/include/linphone/api/c-chat-message.h @@ -369,7 +369,26 @@ LINPHONE_PUBLIC const char* linphone_chat_message_get_text_content(const Linphon */ LINPHONE_PUBLIC bool_t linphone_chat_message_is_file_transfer_in_progress(LinphoneChatMessage *msg); -LINPHONE_PUBLIC bctbx_list_t *linphone_chat_message_get_participants_in_state (const LinphoneChatMessage *msg, const LinphoneChatMessageState state); +/** + * Gets the list of participants that displayed this message and the time at which they did. + * @param[in] msg LinphoneChatMessage object + * @return \bctbx_list{LinphoneParticipantImdnState} + */ +LINPHONE_PUBLIC bctbx_list_t *linphone_chat_message_get_participants_that_have_displayed (const LinphoneChatMessage *msg); + +/** + * Gets the list of participants that did not receive this message. + * @param[in] msg LinphoneChatMessage object + * @return \bctbx_list{LinphoneParticipantImdnState} + */ +LINPHONE_PUBLIC bctbx_list_t *linphone_chat_message_get_participants_that_have_not_received (const LinphoneChatMessage *msg); + +/** + * Gets the list of participants that received this message and the time at which they did. + * @param[in] msg LinphoneChatMessage object + * @return \bctbx_list{LinphoneParticipantImdnState} + */ +LINPHONE_PUBLIC bctbx_list_t *linphone_chat_message_get_participants_that_have_received (const LinphoneChatMessage *msg); /** * @} diff --git a/include/linphone/api/c-participant-imdn-state.h b/include/linphone/api/c-participant-imdn-state.h new file mode 100644 index 000000000..041dd2efc --- /dev/null +++ b/include/linphone/api/c-participant-imdn-state.h @@ -0,0 +1,91 @@ +/* + * c-participant-imdn-state.h + * Copyright (C) 2010-2018 Belledonne Communications SARL + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _L_C_PARTICIPANT_IMDN_STATE_H_ +#define _L_C_PARTICIPANT_IMDN_STATE_H_ + +#include "linphone/api/c-types.h" + +// ============================================================================= + +#ifdef __cplusplus + extern "C" { +#endif // ifdef __cplusplus + +/** + * @addtogroup misc + * @{ + */ + +/** + * Increment reference count of LinphoneParticipantImdnState object. + **/ +LINPHONE_PUBLIC LinphoneParticipantImdnState *linphone_participant_imdn_state_ref (LinphoneParticipantImdnState *state); + +/** + * Decrement reference count of LinphoneParticipantImdnState object. + **/ +LINPHONE_PUBLIC void linphone_participant_imdn_state_unref (LinphoneParticipantImdnState *state); + +/** + * Retrieve the user pointer associated with a LinphoneParticipantImdnState. + * @param[in] state A LinphoneParticipantImdnState object + * @return The user pointer associated with the LinphoneParticipantImdnState. +**/ +LINPHONE_PUBLIC void *linphone_participant_imdn_state_get_user_data(const LinphoneParticipantImdnState *state); + +/** + * Assign a user pointer to a LinphoneParticipantImdnState. + * @param[in] state A LinphoneParticipantImdnState object + * @param[in] ud The user pointer to associate with the LinphoneParticipantImdnState +**/ +LINPHONE_PUBLIC void linphone_participant_imdn_state_set_user_data(LinphoneParticipantImdnState *state, void *ud); + +/** + * Get the participant concerned by a LinphoneParticipantImdnState. + * @param[in] state A LinphoneParticipantImdnState object + * @return The participant concerned by the LinphoneParticipantImdnState + */ +LINPHONE_PUBLIC const LinphoneParticipant *linphone_participant_imdn_state_get_participant ( + const LinphoneParticipantImdnState *state +); + +/** + * Get the chat message state the participant is in. + * @param state A LinphoneParticipantImdnState object + * @return The chat message state the participant is in + */ +LINPHONE_PUBLIC LinphoneChatMessageState linphone_participant_imdn_state_get_state (const LinphoneParticipantImdnState *state); + +/** + * Get the timestamp at which a participant has reached the state described by a LinphoneParticipantImdnState. + * @param[in] state A LinphoneParticipantImdnState object + * @return The timestamp at which the participant has reached the state described in the LinphoneParticipantImdnState + */ +LINPHONE_PUBLIC time_t linphone_participant_imdn_state_get_state_change_time (const LinphoneParticipantImdnState *state); + +/** + * @} + */ + +#ifdef __cplusplus + } +#endif // ifdef __cplusplus + +#endif // ifndef _L_C_PARTICIPANT_IMDN_STATE_H_ diff --git a/include/linphone/api/c-types.h b/include/linphone/api/c-types.h index f587b77f8..bb1193001 100644 --- a/include/linphone/api/c-types.h +++ b/include/linphone/api/c-types.h @@ -162,6 +162,12 @@ typedef struct _LinphoneMagicSearch LinphoneMagicSearch; **/ typedef struct _LinphoneParticipant LinphoneParticipant; +/** + * The LinphoneParticipantImdnState object represents the state of chat message for a participant of a conference chat room. + * @ingroup misc +**/ +typedef struct _LinphoneParticipantImdnState LinphoneParticipantImdnState; + /** * The LinphoneSearchResult object represents a result of a search * @ingroup misc diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ca182139f..d9a4e79a6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -82,6 +82,8 @@ set(LINPHONE_CXX_OBJECTS_PRIVATE_HEADER_FILES conference/params/media-session-params-p.h conference/params/media-session-params.h conference/participant-device.h + conference/participant-imdn-state.h + conference/participant-imdn-state-p.h conference/participant-p.h conference/participant.h conference/remote-conference-p.h @@ -177,6 +179,7 @@ set(LINPHONE_CXX_OBJECTS_SOURCE_FILES c-wrapper/api/c-event-log.cpp c-wrapper/api/c-magic-search.cpp c-wrapper/api/c-participant.cpp + c-wrapper/api/c-participant-imdn-state.cpp c-wrapper/api/c-search-result.cpp c-wrapper/internal/c-sal.cpp c-wrapper/internal/c-tools.cpp @@ -212,6 +215,7 @@ set(LINPHONE_CXX_OBJECTS_SOURCE_FILES conference/params/call-session-params.cpp conference/params/media-session-params.cpp conference/participant-device.cpp + conference/participant-imdn-state.cpp conference/participant.cpp conference/remote-conference.cpp conference/session/call-session.cpp diff --git a/src/c-wrapper/api/c-chat-message.cpp b/src/c-wrapper/api/c-chat-message.cpp index a616a9659..4eaafb3ae 100644 --- a/src/c-wrapper/api/c-chat-message.cpp +++ b/src/c-wrapper/api/c-chat-message.cpp @@ -32,6 +32,7 @@ #include "content/content-type.h" #include "content/content.h" #include "conference/participant.h" +#include "conference/participant-imdn-state.h" // ============================================================================= @@ -247,10 +248,19 @@ bool_t linphone_chat_message_is_file_transfer_in_progress(LinphoneChatMessage *m return L_GET_CPP_PTR_FROM_C_OBJECT(msg)->isFileTransferInProgress(); } -bctbx_list_t *linphone_chat_message_get_participants_in_state (const LinphoneChatMessage *msg, const LinphoneChatMessageState state) { - return L_GET_RESOLVED_C_LIST_FROM_CPP_LIST(L_GET_PRIVATE_FROM_C_OBJECT(msg)->getParticipantsInState((LinphonePrivate::ChatMessage::State) state)); +bctbx_list_t *linphone_chat_message_get_participants_that_have_displayed (const LinphoneChatMessage *msg) { + return L_GET_RESOLVED_C_LIST_FROM_CPP_LIST(L_GET_CPP_PTR_FROM_C_OBJECT(msg)->getParticipantsThatHaveDisplayed()); } +bctbx_list_t *linphone_chat_message_get_participants_that_have_not_received (const LinphoneChatMessage *msg) { + return L_GET_RESOLVED_C_LIST_FROM_CPP_LIST(L_GET_CPP_PTR_FROM_C_OBJECT(msg)->getParticipantsThatHaveNotReceived()); +} + +bctbx_list_t *linphone_chat_message_get_participants_that_have_received (const LinphoneChatMessage *msg) { + return L_GET_RESOLVED_C_LIST_FROM_CPP_LIST(L_GET_CPP_PTR_FROM_C_OBJECT(msg)->getParticipantsThatHaveReceived()); +} + + // ============================================================================= // Old listener // ============================================================================= diff --git a/src/c-wrapper/api/c-participant-imdn-state.cpp b/src/c-wrapper/api/c-participant-imdn-state.cpp new file mode 100644 index 000000000..2f3a906d2 --- /dev/null +++ b/src/c-wrapper/api/c-participant-imdn-state.cpp @@ -0,0 +1,59 @@ +/* + * c-participant-imdn-state.cpp + * Copyright (C) 2010-2018 Belledonne Communications SARL + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "linphone/api/c-participant-imdn-state.h" + +#include "c-wrapper/c-wrapper.h" +#include "conference/participant.h" +#include "conference/participant-imdn-state.h" + +// ============================================================================= + +using namespace std; + +L_DECLARE_C_CLONABLE_OBJECT_IMPL(ParticipantImdnState); + +LinphoneParticipantImdnState *linphone_participant_imdn_state_ref (LinphoneParticipantImdnState *state) { + belle_sip_object_ref(state); + return state; +} + +void linphone_participant_imdn_state_unref (LinphoneParticipantImdnState *state) { + belle_sip_object_unref(state); +} + +void *linphone_participant_imdn_state_get_user_data(const LinphoneParticipantImdnState *state) { + return L_GET_USER_DATA_FROM_C_OBJECT(state); +} + +void linphone_participant_imdn_state_set_user_data(LinphoneParticipantImdnState *state, void *ud) { + L_SET_USER_DATA_FROM_C_OBJECT(state, ud); +} + +const LinphoneParticipant *linphone_participant_imdn_state_get_participant (const LinphoneParticipantImdnState *state) { + return L_GET_C_BACK_PTR(L_GET_CPP_PTR_FROM_C_OBJECT(state)->getParticipant()); +} + +LinphoneChatMessageState linphone_participant_imdn_state_get_state (const LinphoneParticipantImdnState *state) { + return (LinphoneChatMessageState)L_GET_CPP_PTR_FROM_C_OBJECT(state)->getState(); +} + +time_t linphone_participant_imdn_state_get_state_change_time (const LinphoneParticipantImdnState *state) { + return L_GET_CPP_PTR_FROM_C_OBJECT(state)->getStateChangeTime(); +} diff --git a/src/c-wrapper/c-wrapper.h b/src/c-wrapper/c-wrapper.h index e0109ce7c..9f59405ef 100644 --- a/src/c-wrapper/c-wrapper.h +++ b/src/c-wrapper/c-wrapper.h @@ -42,6 +42,7 @@ F(MagicSearch, MagicSearch) \ F(MediaSessionParams, CallParams) \ F(Participant, Participant) \ + F(ParticipantImdnState, ParticipantImdnState) \ F(SearchResult, SearchResult) #define L_REGISTER_SUBTYPES(F) \ diff --git a/src/chat/chat-message/chat-message-p.h b/src/chat/chat-message/chat-message-p.h index 1c7e8d854..c3350d887 100644 --- a/src/chat/chat-message/chat-message-p.h +++ b/src/chat/chat-message/chat-message-p.h @@ -30,6 +30,7 @@ #include "content/content.h" #include "content/file-content.h" #include "content/file-transfer-content.h" +#include "db/main-db.h" #include "db/main-db-chat-message-key.h" #include "event-log/conference/conference-chat-message-event.h" #include "object/object-p.h" @@ -58,8 +59,8 @@ public: void setDirection (ChatMessage::Direction dir); + std::list getParticipantsByImdnState (MainDb::ParticipantStateRetrievalFunc func) const; void setParticipantState (const IdentityAddress &participantAddress, ChatMessage::State newState, time_t stateChangeTime); - std::list> getParticipantsInState (const ChatMessage::State state) const; void setState (ChatMessage::State newState, bool force = false); void setTime (time_t time); diff --git a/src/chat/chat-message/chat-message.cpp b/src/chat/chat-message/chat-message.cpp index ae75b0525..5627ed922 100644 --- a/src/chat/chat-message/chat-message.cpp +++ b/src/chat/chat-message/chat-message.cpp @@ -35,6 +35,7 @@ #include "chat/modifier/file-transfer-chat-message-modifier.h" #include "chat/modifier/multipart-chat-message-modifier.h" #include "conference/participant.h" +#include "conference/participant-imdn-state.h" #include "content/file-content.h" #include "content/content.h" #include "core/core.h" @@ -70,6 +71,25 @@ void ChatMessagePrivate::setIsReadOnly (bool readOnly) { isReadOnly = readOnly; } +list ChatMessagePrivate::getParticipantsByImdnState (MainDb::ParticipantStateRetrievalFunc func) const { + L_Q(); + + list result; + if (!(q->getChatRoom()->getCapabilities() & AbstractChatRoom::Capabilities::Conference) || !dbKey.isValid()) + return result; + + unique_ptr &mainDb = q->getChatRoom()->getCore()->getPrivate()->mainDb; + shared_ptr eventLog = mainDb->getEventFromKey(dbKey); + list dbResults = func(eventLog); + for (const auto &dbResult : dbResults) { + auto participant = q->getChatRoom()->findParticipant(dbResult.address); + if (participant) + result.emplace_back(participant, dbResult.state, dbResult.timestamp); + } + + return result; +} + void ChatMessagePrivate::setParticipantState (const IdentityAddress &participantAddress, ChatMessage::State newState, time_t stateChangeTime) { L_Q(); @@ -123,29 +143,6 @@ void ChatMessagePrivate::setParticipantState (const IdentityAddress &participant setState(ChatMessage::State::DeliveredToUser); } -list> ChatMessagePrivate::getParticipantsInState (const ChatMessage::State state) const { - L_Q(); - - list> participantsInState; - if (!(q->getChatRoom()->getCapabilities() & AbstractChatRoom::Capabilities::Conference) || !dbKey.isValid()) { - return participantsInState; - } - - unique_ptr &mainDb = q->getChatRoom()->getCore()->getPrivate()->mainDb; - shared_ptr eventLog = mainDb->getEventFromKey(dbKey); - list addressesInState = mainDb->getChatMessageParticipantsInState(eventLog, state); - const list> &participants = q->getChatRoom()->getParticipants(); - for (IdentityAddress addr : addressesInState) { - for (const auto &participant : participants) { - if (participant->getAddress() == addr) { - participantsInState.push_back(participant); - } - } - } - - return participantsInState; -} - void ChatMessagePrivate::setState (ChatMessage::State newState, bool force) { L_Q(); @@ -997,6 +994,29 @@ void ChatMessage::setToBeStored (bool value) { // ----------------------------------------------------------------------------- +list ChatMessage::getParticipantsThatHaveDisplayed () const { + L_D(); + unique_ptr &mainDb = getChatRoom()->getCore()->getPrivate()->mainDb; + auto func = bind(&MainDb::getChatMessageParticipantsThatHaveDisplayed, mainDb.get(), std::placeholders::_1); + return d->getParticipantsByImdnState(func); +} + +list ChatMessage::getParticipantsThatHaveNotReceived () const { + L_D(); + unique_ptr &mainDb = getChatRoom()->getCore()->getPrivate()->mainDb; + auto func = bind(&MainDb::getChatMessageParticipantsThatHaveNotReceived, mainDb.get(), std::placeholders::_1); + return d->getParticipantsByImdnState(func); +} + +list ChatMessage::getParticipantsThatHaveReceived () const { + L_D(); + unique_ptr &mainDb = getChatRoom()->getCore()->getPrivate()->mainDb; + auto func = bind(&MainDb::getChatMessageParticipantsThatHaveReceived, mainDb.get(), std::placeholders::_1); + return d->getParticipantsByImdnState(func); +} + +// ----------------------------------------------------------------------------- + const LinphoneErrorInfo *ChatMessage::getErrorInfo () const { L_D(); if (!d->errorInfo) d->errorInfo = linphone_error_info_new(); // let's do it mutable diff --git a/src/chat/chat-message/chat-message.h b/src/chat/chat-message/chat-message.h index 2b53bda7d..86691880c 100644 --- a/src/chat/chat-message/chat-message.h +++ b/src/chat/chat-message/chat-message.h @@ -40,6 +40,7 @@ class Content; class FileTransferContent; class ChatMessagePrivate; class Participant; +class ParticipantImdnState; class LINPHONE_PUBLIC ChatMessage : public Object, public CoreAccessor { friend class BasicToClientGroupChatRoom; @@ -94,6 +95,10 @@ public: bool getToBeStored () const; void setToBeStored (bool value); + std::list getParticipantsThatHaveDisplayed () const; + std::list getParticipantsThatHaveReceived () const; + std::list getParticipantsThatHaveNotReceived () const; + const std::list &getContents () const; void addContent (Content &content); void removeContent (const Content &content); diff --git a/src/conference/participant-imdn-state-p.h b/src/conference/participant-imdn-state-p.h new file mode 100644 index 000000000..981bfefb1 --- /dev/null +++ b/src/conference/participant-imdn-state-p.h @@ -0,0 +1,42 @@ +/* + * participant-imdn-state-p.h + * Copyright (C) 2010-2018 Belledonne Communications SARL + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _L_PARTICIPANT_IMDN_STATE_P_H_ +#define _L_PARTICIPANT_IMDN_STATE_P_H_ + +#include "object/clonable-object-p.h" + +#include "conference/participant-imdn-state.h" + +// ============================================================================= + +LINPHONE_BEGIN_NAMESPACE + +class ParticipantImdnStatePrivate : public ClonableObjectPrivate { +public: + std::shared_ptr participant; + ChatMessage::State state = ChatMessage::State::Idle; + time_t stateChangeTime = 0; + + L_DECLARE_PUBLIC(ParticipantImdnState); +}; + +LINPHONE_END_NAMESPACE + +#endif // ifndef _L_PARTICIPANT_IMDN_STATE_P_H_ diff --git a/src/conference/participant-imdn-state.cpp b/src/conference/participant-imdn-state.cpp new file mode 100644 index 000000000..cd3772bdb --- /dev/null +++ b/src/conference/participant-imdn-state.cpp @@ -0,0 +1,61 @@ +/* + * participant-imdn-state.cpp + * Copyright (C) 2010-2018 Belledonne Communications SARL + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "participant-imdn-state-p.h" + +using namespace std; + +LINPHONE_BEGIN_NAMESPACE + +// ============================================================================= + +ParticipantImdnState::ParticipantImdnState (const shared_ptr &participant, ChatMessage::State state, time_t stateChangeTime) + : ClonableObject(*new ParticipantImdnStatePrivate) +{ + L_D(); + d->participant = participant; + d->state = state; + d->stateChangeTime = stateChangeTime; +} + +ParticipantImdnState::ParticipantImdnState(const ParticipantImdnState &other) : ClonableObject(*new ParticipantImdnStatePrivate) { + L_D(); + d->participant = other.getParticipant(); + d->state = other.getState(); + d->stateChangeTime = other.getStateChangeTime(); +} + +// ----------------------------------------------------------------------------- + +shared_ptr ParticipantImdnState::getParticipant () const { + L_D(); + return d->participant; +} + +ChatMessage::State ParticipantImdnState::getState () const { + L_D(); + return d->state; +} + +time_t ParticipantImdnState::getStateChangeTime () const { + L_D(); + return d->stateChangeTime; +} + +LINPHONE_END_NAMESPACE diff --git a/src/conference/participant-imdn-state.h b/src/conference/participant-imdn-state.h new file mode 100644 index 000000000..0a9c7ded4 --- /dev/null +++ b/src/conference/participant-imdn-state.h @@ -0,0 +1,48 @@ +/* + * participant-imdn-state.h + * Copyright (C) 2010-2018 Belledonne Communications SARL + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _L_PARTICIPANT_IMDN_STATE_H_ +#define _L_PARTICIPANT_IMDN_STATE_H_ + +#include "chat/chat-message/chat-message.h" +#include "object/clonable-object.h" + +// ============================================================================= + +LINPHONE_BEGIN_NAMESPACE + +class Participant; +class ParticipantImdnStatePrivate; + +class ParticipantImdnState : public ClonableObject { +public: + ParticipantImdnState (const std::shared_ptr &participant, ChatMessage::State state, time_t stateChangeTime); + ParticipantImdnState (const ParticipantImdnState &other); + + std::shared_ptr getParticipant () const; + ChatMessage::State getState () const; + time_t getStateChangeTime () const; + +private: + L_DECLARE_PRIVATE(ParticipantImdnState); +}; + +LINPHONE_END_NAMESPACE + +#endif // ifndef _L_PARTICIPANT_IMDN_STATE_H_ diff --git a/src/core/core.h b/src/core/core.h index 72b037003..bcb458a87 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -44,6 +44,7 @@ class LINPHONE_PUBLIC Core : public Object { friend class BasicToClientGroupChatRoomPrivate; friend class CallPrivate; friend class CallSession; + friend class ChatMessage; friend class ChatMessagePrivate; friend class ChatRoom; friend class ChatRoomPrivate; diff --git a/src/db/main-db.cpp b/src/db/main-db.cpp index a3d4b9edd..afd1b42ff 100644 --- a/src/db/main-db.cpp +++ b/src/db/main-db.cpp @@ -1930,6 +1930,85 @@ list> MainDb::getUnreadChatMessages (const ChatRoomId &c }; } +list MainDb::getChatMessageParticipantsThatHaveDisplayed ( + const shared_ptr &eventLog +) const { + return L_DB_TRANSACTION { + L_D(); + + const EventLogPrivate *dEventLog = eventLog->getPrivate(); + MainDbKeyPrivate *dEventKey = static_cast(dEventLog->dbKey).getPrivate(); + const long long &eventId = dEventKey->storageId; + int stateInt = static_cast(ChatMessage::State::Displayed); + + static const string query = "SELECT sip_address.value, chat_message_participant.state_change_time" + " FROM sip_address, chat_message_participant" + " WHERE event_id = :eventId AND state = :state" + " AND sip_address.id = chat_message_participant.participant_sip_address_id"; + soci::rowset rows = (d->dbSession.getBackendSession()->prepare << query, + soci::use(eventId), soci::use(stateInt) + ); + + list result; + for (const auto &row : rows) + result.emplace_back(IdentityAddress(row.get(0)), ChatMessage::State::Displayed, Utils::getTmAsTimeT(row.get(1))); + return result; + }; +} + +list MainDb::getChatMessageParticipantsThatHaveNotReceived ( + const shared_ptr &eventLog +) const { + return L_DB_TRANSACTION { + L_D(); + + const EventLogPrivate *dEventLog = eventLog->getPrivate(); + MainDbKeyPrivate *dEventKey = static_cast(dEventLog->dbKey).getPrivate(); + const long long &eventId = dEventKey->storageId; + int deliveredStateInt = static_cast(ChatMessage::State::DeliveredToUser); + int displayedStateInt = static_cast(ChatMessage::State::Displayed); + + static const string query = "SELECT sip_address.value, chat_message_participant.state_change_time" + " FROM sip_address, chat_message_participant" + " WHERE event_id = :eventId AND state <> :deliveredState AND state <> :displayedState" + " AND sip_address.id = chat_message_participant.participant_sip_address_id"; + soci::rowset rows = (d->dbSession.getBackendSession()->prepare << query, + soci::use(eventId), soci::use(deliveredStateInt), soci::use(displayedStateInt) + ); + + list result; + for (const auto &row : rows) + result.emplace_back(IdentityAddress(row.get(0)), ChatMessage::State::Idle, 0); + return result; + }; +} + +list MainDb::getChatMessageParticipantsThatHaveReceived ( + const shared_ptr &eventLog +) const { + return L_DB_TRANSACTION { + L_D(); + + const EventLogPrivate *dEventLog = eventLog->getPrivate(); + MainDbKeyPrivate *dEventKey = static_cast(dEventLog->dbKey).getPrivate(); + const long long &eventId = dEventKey->storageId; + int stateInt = static_cast(ChatMessage::State::DeliveredToUser); + + static const string query = "SELECT sip_address.value, chat_message_participant.state_change_time" + " FROM sip_address, chat_message_participant" + " WHERE event_id = :eventId AND state = :state" + " AND sip_address.id = chat_message_participant.participant_sip_address_id"; + soci::rowset rows = (d->dbSession.getBackendSession()->prepare << query, + soci::use(eventId), soci::use(stateInt) + ); + + list result; + for (const auto &row : rows) + result.emplace_back(IdentityAddress(row.get(0)), ChatMessage::State::DeliveredToUser, Utils::getTmAsTimeT(row.get(1))); + return result; + }; +} + list MainDb::getChatMessageParticipantStates (const shared_ptr &eventLog) const { return L_DB_TRANSACTION { L_D(); @@ -1953,30 +2032,6 @@ list MainDb::getChatMessageParticipantStates (const shared_p }; } -list MainDb::getChatMessageParticipantsInState (const shared_ptr &eventLog, const ChatMessage::State state) const { - return L_DB_TRANSACTION { - L_D(); - - const EventLogPrivate *dEventLog = eventLog->getPrivate(); - MainDbKeyPrivate *dEventKey = static_cast(dEventLog->dbKey).getPrivate(); - const long long &eventId = dEventKey->storageId; - - int stateInt = static_cast(state); - list participantsAddresses; - - static const string query = "SELECT sip_address.value" - " FROM sip_address, chat_message_participant" - " WHERE event_id = :eventId AND state = :state" - " AND sip_address.id = chat_message_participant.participant_sip_address_id"; - soci::rowset rows = (d->dbSession.getBackendSession()->prepare << query, soci::use(eventId), soci::use(stateInt)); - for (const auto &row : rows) { - participantsAddresses.push_back(IdentityAddress(row.get(0))); - } - - return participantsAddresses; - }; -} - ChatMessage::State MainDb::getChatMessageParticipantState ( const shared_ptr &eventLog, const IdentityAddress &participantAddress diff --git a/src/db/main-db.h b/src/db/main-db.h index d7ce028e0..bb134ab50 100644 --- a/src/db/main-db.h +++ b/src/db/main-db.h @@ -20,6 +20,7 @@ #ifndef _L_MAIN_DB_H_ #define _L_MAIN_DB_H_ +#include #include #include "linphone/utils/enum-mask.h" @@ -59,6 +60,15 @@ public: typedef EnumMask FilterMask; + struct ParticipantState { + ParticipantState (const IdentityAddress &address, ChatMessage::State state, time_t timestamp) + : address(address), state(state), timestamp(timestamp) {} + + IdentityAddress address; + ChatMessage::State state = ChatMessage::State::Idle; + time_t timestamp = 0; + }; + MainDb (const std::shared_ptr &core); // --------------------------------------------------------------------------- @@ -85,13 +95,23 @@ public: // Conference chat message events. // --------------------------------------------------------------------------- + using ParticipantStateRetrievalFunc = std::function(const std::shared_ptr &eventLog)>; + int getChatMessageCount (const ChatRoomId &chatRoomId = ChatRoomId()) const; int getUnreadChatMessageCount (const ChatRoomId &chatRoomId = ChatRoomId()) const; void markChatMessagesAsRead (const ChatRoomId &chatRoomId) const; std::list> getUnreadChatMessages (const ChatRoomId &chatRoomId) const; + std::list getChatMessageParticipantsThatHaveDisplayed ( + const std::shared_ptr &eventLog + ) const; + std::list getChatMessageParticipantsThatHaveNotReceived ( + const std::shared_ptr &eventLog + ) const; + std::list getChatMessageParticipantsThatHaveReceived ( + const std::shared_ptr &eventLog + ) const; std::list getChatMessageParticipantStates (const std::shared_ptr &eventLog) const; - std::list getChatMessageParticipantsInState (const std::shared_ptr &eventLog, const ChatMessage::State state) const; ChatMessage::State getChatMessageParticipantState ( const std::shared_ptr &eventLog, const IdentityAddress &participantAddress diff --git a/tester/group_chat_tester.c b/tester/group_chat_tester.c index fd6372d0f..a8d29303c 100644 --- a/tester/group_chat_tester.c +++ b/tester/group_chat_tester.c @@ -3056,6 +3056,7 @@ static void imdn_for_group_chat_room (void) { stats initialMarieStats = marie->stat; stats initialPaulineStats = pauline->stat; stats initialChloeStats = chloe->stat; + time_t initialTime = ms_time(NULL); // Enable IMDN linphone_im_notif_policy_enable_all(linphone_core_get_im_notif_policy(marie->lc)); @@ -3090,15 +3091,46 @@ static void imdn_for_group_chat_room (void) { 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)); + BC_ASSERT_TRUE(wait_for_list(coresList, &chloe->stat.number_of_LinphoneMessageDeliveredToUser, initialChloeStats.number_of_LinphoneMessageDeliveredToUser + 1, 1000)); + BC_ASSERT_PTR_NULL(linphone_chat_message_get_participants_that_have_displayed(chloeMessage)); + bctbx_list_t *participantsThatReceivedChloeMessage = linphone_chat_message_get_participants_that_have_received(chloeMessage); + if (BC_ASSERT_PTR_NOT_NULL(participantsThatReceivedChloeMessage)) { + BC_ASSERT_EQUAL((int)bctbx_list_size(participantsThatReceivedChloeMessage), 2, int, "%d"); + for (bctbx_list_t *item = participantsThatReceivedChloeMessage; item; item = bctbx_list_next(item)) { + LinphoneParticipantImdnState *state = (LinphoneParticipantImdnState *)bctbx_list_get_data(item); + BC_ASSERT_GREATER(linphone_participant_imdn_state_get_state_change_time(state), initialTime, int, "%d"); + BC_ASSERT_EQUAL(linphone_participant_imdn_state_get_state(state), LinphoneChatMessageStateDeliveredToUser, int, "%d"); + BC_ASSERT_PTR_NOT_NULL(linphone_participant_imdn_state_get_participant(state)); + } + bctbx_list_free_with_data(participantsThatReceivedChloeMessage, (bctbx_list_free_func)linphone_participant_imdn_state_unref); + } + BC_ASSERT_PTR_NULL(linphone_chat_message_get_participants_that_have_not_received(chloeMessage)); // 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)); + bctbx_list_t *participantsThatDisplayedChloeMessage = linphone_chat_message_get_participants_that_have_displayed(chloeMessage); + if (BC_ASSERT_PTR_NOT_NULL(participantsThatDisplayedChloeMessage)) { + BC_ASSERT_EQUAL((int)bctbx_list_size(participantsThatDisplayedChloeMessage), 1, int, "%d"); + bctbx_list_free_with_data(participantsThatDisplayedChloeMessage, (bctbx_list_free_func)linphone_participant_imdn_state_unref); + } + participantsThatReceivedChloeMessage = linphone_chat_message_get_participants_that_have_received(chloeMessage); + if (BC_ASSERT_PTR_NOT_NULL(participantsThatReceivedChloeMessage)) { + BC_ASSERT_EQUAL((int)bctbx_list_size(participantsThatReceivedChloeMessage), 1, int, "%d"); + bctbx_list_free_with_data(participantsThatReceivedChloeMessage, (bctbx_list_free_func)linphone_participant_imdn_state_unref); + } + BC_ASSERT_PTR_NULL(linphone_chat_message_get_participants_that_have_not_received(chloeMessage)); // 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)); + participantsThatDisplayedChloeMessage = linphone_chat_message_get_participants_that_have_displayed(chloeMessage); + if (BC_ASSERT_PTR_NOT_NULL(participantsThatDisplayedChloeMessage)) { + BC_ASSERT_EQUAL((int)bctbx_list_size(participantsThatDisplayedChloeMessage), 2, int, "%d"); + bctbx_list_free_with_data(participantsThatDisplayedChloeMessage, (bctbx_list_free_func)linphone_participant_imdn_state_unref); + } + BC_ASSERT_PTR_NULL(linphone_chat_message_get_participants_that_have_received(chloeMessage)); + BC_ASSERT_PTR_NULL(linphone_chat_message_get_participants_that_have_not_received(chloeMessage)); linphone_chat_message_unref(chloeMessage);