diff --git a/.gitignore b/.gitignore index 594850b6d..69b985389 100644 --- a/.gitignore +++ b/.gitignore @@ -106,3 +106,4 @@ tools/lp-test-ecc tools/lp-sendmsg *.pyc +liblinphone.spec diff --git a/CMakeLists.txt b/CMakeLists.txt index 1fb65a4de..b5a4cbe6a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,6 +68,7 @@ cmake_dependent_option(ENABLE_NOTIFY "Enable libnotify support." YES "ENABLE_GTK cmake_dependent_option(ENABLE_ASSISTANT "Turn on assistant compiling." YES "ENABLE_GTK_UI" NO) set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_EXTENSIONS NO) if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Release") @@ -272,6 +273,7 @@ else() "-Wunused" ) list(APPEND STRICT_OPTIONS_CXX + "-Wnon-virtual-dtor" "-Woverloaded-virtual" ) CHECK_CXX_COMPILER_FLAG("-Wsuggest-override" SUGGEST_OVERRIDE) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 1f1b88af7..ac389909d 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -1,6 +1,6 @@ ############################################################################ # CMakeLists.txt -# Copyright (C) 2017 Belledonne Communications, Grenoble France +# Copyright (C) 2017-2018 Belledonne Communications, Grenoble France # ############################################################################ # @@ -20,7 +20,7 @@ # ############################################################################ -if (NOT CPACK_PACKAGE_NAME) +if(NOT CPACK_PACKAGE_NAME) set(CPACK_PACKAGE_NAME "liblinphone") ENDIF() @@ -40,13 +40,14 @@ set(CPACK_SOURCE_IGNORE_FILES bc_project_build_version(${PROJECT_VERSION} PROJECT_VERSION_BUILD) if(PROJECT_VERSION_BUILD) - set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_FILE_NAME}-${PROJECT_VERSION_BUILD}") + set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_FILE_NAME}-${PROJECT_VERSION_BUILD}") endif() message("-- Package file name is ${CPACK_PACKAGE_FILE_NAME}" ) set(CPACK_SOURCE_PACKAGE_FILE_NAME ${CPACK_PACKAGE_FILE_NAME}) -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/rpm/liblinphone.spec.in ${CMAKE_CURRENT_SOURCE_DIR}/../liblinphone.spec) + +bc_generate_rpm_specfile("rpm/liblinphone.spec.cmake" "${PROJECT_SOURCE_DIR}/liblinphone.spec") include(CPack) diff --git a/build/rpm/liblinphone.spec.in b/build/rpm/liblinphone.spec.cmake similarity index 66% rename from build/rpm/liblinphone.spec.in rename to build/rpm/liblinphone.spec.cmake index 1ea7e5aa3..b0f030334 100755 --- a/build/rpm/liblinphone.spec.in +++ b/build/rpm/liblinphone.spec.cmake @@ -1,12 +1,7 @@ # -*- rpm-spec -*- -## rpmbuild options -# These 2 lines are here because we can build the RPM for flexisip, in which -# case we prefix the entire installation so that we don't break compatibility -# with the user's libs. -# To compile with bc prefix, use rpmbuild -ba --with bc [SPEC] -%define pkg_name %{?_with_bc:bc-liblinphone}%{!?_with_bc:liblinphone} -%{?_with_bc: %define _prefix /opt/belledonne-communications} +%define _prefix @CMAKE_INSTALL_PREFIX@ +%define pkg_prefix @BC_PACKAGE_NAME_PREFIX@ # re-define some directories for older RPMBuild versions which don't. This messes up the doc/ dir # taken from https://fedoraproject.org/wiki/Packaging:RPMMacros?rd=Packaging/RPMMacros @@ -15,18 +10,20 @@ %define _docdir %{_datadir}/doc %define build_number @PROJECT_VERSION_BUILD@ +%if %{build_number} +%define build_number_ext -%{build_number} +%endif - -Name: %{pkg_name} +Name: @CPACK_PACKAGE_NAME@ Version: @PROJECT_VERSION@ -Release: %build_number%{?dist} +Release: %{build_number}%{?dist} Summary: Phone anywhere in the whole world by using the Internet Group: Applications/Communications License: GPL URL: http://www.linphone.org -Source0: %{name}-%{version}-%{build_number}.tar.gz +Source0: %{name}-%{version}%{?build_number_ext}.tar.gz BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot Requires: %{pkg_prefix}bctoolbox @@ -34,15 +31,13 @@ Requires: %{pkg_prefix}ortp Requires: %{pkg_prefix}mediastreamer Requires: %{pkg_prefix}belle-sip Requires: %{pkg_prefix}belr -%if %{?_with_soci:1}%{!?_with_soci:0} +%if @ENABLE_SOCI_STORAGE@ Requires: %{pkg_prefix}soci %endif %description liblinphone is the voip sdk used by Linphone -%define video %{?_without_video:0}%{!?_without_video:1} - %package devel Summary: Development libraries for liblinphone @@ -61,11 +56,15 @@ develop programs using the liblinphone library. %define ctest_name ctest %endif +# This is for debian builds where debug_package has to be manually specified, whereas in centos it does not +%define custom_debug_package %{!?_enable_debug_packages:%debug_package}%{?_enable_debug_package:%{nil}} +%custom_debug_package + %prep -%setup -n %{name}-%{version}-%build_number +%setup -n %{name}-%{version}%{?build_number_ext} %build -%{expand:%%%cmake_name} . -DCMAKE_INSTALL_LIBDIR:PATH=%{_libdir} -DCMAKE_PREFIX_PATH:PATH=%{_prefix} -DENABLE_VIDEO=%{video} +%{expand:%%%cmake_name} . -DCMAKE_BUILD_TYPE=@CMAKE_BUILD_TYPE@ -DCMAKE_INSTALL_LIBDIR:PATH=%{_libdir} -DCMAKE_PREFIX_PATH:PATH=%{_prefix} @RPM_ALL_CMAKE_OPTIONS@ make %{?_smp_mflags} %install @@ -84,25 +83,32 @@ rm -rf $RPM_BUILD_ROOT %files %defattr(-,root,root) %doc AUTHORS ChangeLog COPYING NEWS README.md TODO -%{_bindir}/linphonec -%{_bindir}/linphonecsh -%{_bindir}/lp-auto-answer -%{_bindir}/lp-test-ecc +%if @ENABLE_DAEMON@ || @ENABLE_CONSOLE_UI@ +%{_bindir}/* +%endif %{_libdir}/*.so.* #%{_mandir}/* %{_datadir}/linphone %{_datadir}/sounds/linphone -%{_datadir}/Linphone/rootca.pem %files devel %defattr(-,root,root) -%{_bindir}/* %{_includedir}/linphone +%if @ENABLE_CXX_WRAPPER@ +%{_includedir}/linphone++ +%endif +%if @ENABLE_STATIC@ %{_libdir}/*.a +%endif +%if @ENABLE_SHARED@ %{_libdir}/*.so -#%{_docdir} +%endif +%if @ENABLE_DOC@ +%{_docdir}/linphone*/html +%{_docdir}/linphone*/xml +%endif %{_datadir}/Linphone/cmake/*.cmake -%{_datadir}/liblinphone_tester +%{_datadir}/LinphoneCxx/cmake/*.cmake %{_datadir}/belr/grammars/cpim_grammar diff --git a/console/commands.c b/console/commands.c index cd178daed..e7cd39bb4 100644 --- a/console/commands.c +++ b/console/commands.c @@ -930,6 +930,7 @@ lpc_cmd_firewall(LinphoneCore *lc, char *args) return 1; } linphone_core_set_firewall_policy(lc,LinphonePolicyUseNatAddress); + setting = linphone_core_get_nat_address(lc); } } diff --git a/console/linphonec.c b/console/linphonec.c index ca556536b..b786288b4 100644 --- a/console/linphonec.c +++ b/console/linphonec.c @@ -975,7 +975,7 @@ linphonec_idle_call () linphone_core_iterate(opm); if (answer_call){ fprintf (stdout, "-------auto answering to call-------\n" ); - linphone_core_accept_call(opm,NULL); + linphone_core_accept_call(opm, linphone_core_get_current_call(opm)); answer_call=FALSE; } /* auto call handling */ diff --git a/console/shell.c b/console/shell.c index f80a15378..8cf1d6c09 100644 --- a/console/shell.c +++ b/console/shell.c @@ -156,11 +156,11 @@ static char *argv_to_line(int argc, char *argv[]) { } #endif -#define MAX_ARGS 10 +#define MAX_ARGS 20 #ifndef _WIN32 static void spawn_linphonec(int argc, char *argv[]){ - char * args[MAX_ARGS]; + char * args[MAX_ARGS+1]; int i,j; pid_t pid; j=0; @@ -168,10 +168,10 @@ static void spawn_linphonec(int argc, char *argv[]){ args[j++]="--pipe"; args[j++]="-c"; args[j++]="/dev/null"; - for(i=0;iusername ? creator->username : creator->phone_number); - if (addr == NULL) goto end; + if (addr == NULL) { + if (creator->username && creator->domain) { + char *url = ms_strdup_printf("sip:%s@%s", creator->username, creator->domain); + addr = linphone_address_new(url); + ms_free(url); + if (addr == NULL) { + goto end; + } + } else { + goto end; + } + } identity = linphone_address_as_string(addr); linphone_address_unref(addr); @@ -139,6 +150,9 @@ LinphoneProxyConfig * linphone_account_creator_create_proxy_config(const Linphon snprintf(buff, sizeof(buff), "%d", dial_prefix_number); linphone_proxy_config_set_dial_prefix(cfg, buff); } + if (linphone_proxy_config_get_server_addr(cfg) == NULL && creator->domain != NULL) { + linphone_proxy_config_set_server_addr(cfg, creator->domain); + } linphone_proxy_config_enable_register(cfg, TRUE); diff --git a/coreapi/call_log.c b/coreapi/call_log.c index cc1874e41..a4f52c67f 100644 --- a/coreapi/call_log.c +++ b/coreapi/call_log.c @@ -170,6 +170,23 @@ bctbx_list_t * linphone_core_read_call_logs_from_config_file(LinphoneCore *lc){ * Public functions * ******************************************************************************/ +LinphoneCallLog *linphone_core_create_call_log(LinphoneCore *lc, LinphoneAddress *from, LinphoneAddress *to, LinphoneCallDir dir, + int duration, time_t start_time, time_t connected_time, LinphoneCallStatus status, bool_t video_enabled, float quality) { + LinphoneCallLog *log = linphone_call_log_new(dir, linphone_address_ref(from), linphone_address_ref(to)); + + log->duration = duration; + log->start_date_time = start_time; + set_call_log_date(log,log->start_date_time); + log->connected_date_time = connected_time; + log->status = status; + log->video_enabled = video_enabled; + log->quality = quality; + + linphone_core_store_call_log(lc, log); + + return log; +} + const char *linphone_call_log_get_call_id(const LinphoneCallLog *cl){ return cl->call_id; } @@ -198,7 +215,11 @@ const char *linphone_call_log_get_ref_key(const LinphoneCallLog *cl){ return cl->refkey; } -LinphoneAddress *linphone_call_log_get_remote_address(const LinphoneCallLog *cl){ +const LinphoneAddress *linphone_call_log_get_local_address(const LinphoneCallLog *cl) { + return (cl->dir == LinphoneCallIncoming) ? cl->to : cl->from; +} + +const LinphoneAddress *linphone_call_log_get_remote_address(const LinphoneCallLog *cl){ return (cl->dir == LinphoneCallIncoming) ? cl->from : cl->to; } @@ -610,7 +631,10 @@ int linphone_core_get_call_history_size(LinphoneCore *lc) { sqlite3_stmt *selectStatement; int returnValue; - if (!lc || lc->logs_db == NULL) return 0; + if (!lc) + return 0; + if (!lc->logs_db) + return (int)bctbx_list_size(lc->call_logs); buf = sqlite3_mprintf("SELECT count(*) FROM call_history"); returnValue = sqlite3_prepare_v2(lc->logs_db, buf, -1, &selectStatement, NULL); diff --git a/coreapi/callbacks.c b/coreapi/callbacks.c index 25967bcb8..da809bc05 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" @@ -255,7 +256,9 @@ static void call_terminated(SalOp *op, const char *from) { } static void call_failure(SalOp *op) { - LinphonePrivate::CallSession *session = reinterpret_cast(op->get_user_pointer()); + shared_ptr session; + if (op->get_user_pointer()) + session = reinterpret_cast(op->get_user_pointer())->getSharedFromThis(); if (!session) { ms_warning("Failure reported on already terminated CallSession"); return; @@ -565,7 +568,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) { @@ -668,20 +671,25 @@ static void on_expire(SalOp *op){ static void on_notify_response(SalOp *op){ LinphoneEvent *lev=(LinphoneEvent*)op->get_user_pointer(); + if (!lev) + return; - if (lev==NULL) return; - /*this is actually handling out of dialogs notify - for the moment*/ - if (!lev->is_out_of_dialog_op) return; - switch (linphone_event_get_subscription_state(lev)){ - case LinphoneSubscriptionIncomingReceived: - if (op->get_error_info()->reason == SalReasonNone){ - linphone_event_set_state(lev, LinphoneSubscriptionTerminated); - }else{ - linphone_event_set_state(lev, LinphoneSubscriptionError); - } - break; - default: - ms_warning("Unhandled on_notify_response() case %s", linphone_subscription_state_to_string(linphone_event_get_subscription_state(lev))); + if (lev->is_out_of_dialog_op) { + switch (linphone_event_get_subscription_state(lev)) { + case LinphoneSubscriptionIncomingReceived: + if (op->get_error_info()->reason == SalReasonNone) + linphone_event_set_state(lev, LinphoneSubscriptionTerminated); + else + linphone_event_set_state(lev, LinphoneSubscriptionError); + break; + default: + ms_warning("Unhandled on_notify_response() case %s", + linphone_subscription_state_to_string(linphone_event_get_subscription_state(lev))); + break; + } + } else { + ms_warning("on_notify_response in dialog"); + _linphone_event_notify_notify_response(lev); } } @@ -742,10 +750,12 @@ static void refer_received(SalOp *op, const SalAddress *refer_to){ return; } } else { - LinphoneChatRoom *cr = L_GET_C_BACK_PTR(L_GET_CPP_PTR_FROM_C_OBJECT(lc)->findChatRoom(ChatRoomId(addr, IdentityAddress(op->get_to())))); - if (!cr) - cr = _linphone_client_group_chat_room_new(lc, addr.asString().c_str(), nullptr, FALSE); - L_GET_CPP_PTR_FROM_C_OBJECT(cr)->join(); + shared_ptr chatRoom = L_GET_CPP_PTR_FROM_C_OBJECT(lc)->findChatRoom( + ChatRoomId(addr, IdentityAddress(op->get_to())) + ); + if (!chatRoom) + chatRoom = L_GET_PRIVATE_FROM_C_OBJECT(lc)->createClientGroupChatRoom("", addr.asString(), false); + chatRoom->join(); static_cast(op)->reply(SalReasonNone); return; } diff --git a/coreapi/chat.c b/coreapi/chat.c index 19a3d040a..ac9c8dcf0 100644 --- a/coreapi/chat.c +++ b/coreapi/chat.c @@ -36,6 +36,7 @@ #include "linphone/wrapper_utils.h" #include "c-wrapper/c-wrapper.h" +#include "call/call.h" #include "chat/chat-room/basic-chat-room.h" #include "chat/chat-room/client-group-chat-room.h" #include "chat/chat-room/client-group-to-basic-chat-room.h" @@ -149,12 +150,9 @@ int linphone_core_message_received(LinphoneCore *lc, LinphonePrivate::SalOp *op, } void linphone_core_real_time_text_received(LinphoneCore *lc, LinphoneChatRoom *cr, uint32_t character, LinphoneCall *call) { - if (linphone_core_realtime_text_enabled(lc)) { - shared_ptr rttcr = - static_pointer_cast(L_GET_CPP_PTR_FROM_C_OBJECT(cr)); - L_GET_PRIVATE(rttcr)->realtimeTextReceived(character, call); - //L_GET_PRIVATE(static_pointer_cast(L_GET_CPP_PTR_FROM_C_OBJECT(cr)))->realtimeTextReceived(character, call); - } + if (!(L_GET_CPP_PTR_FROM_C_OBJECT(cr)->getCapabilities() & LinphonePrivate::ChatRoom::Capabilities::RealTimeText)) + return; + L_GET_PRIVATE_FROM_C_OBJECT(cr, RealTimeTextChatRoom)->realtimeTextReceived(character, L_GET_CPP_PTR_FROM_C_OBJECT(call)); } unsigned int linphone_chat_message_store(LinphoneChatMessage *msg) { diff --git a/coreapi/event.c b/coreapi/event.c index 950c4ca2c..582774843 100644 --- a/coreapi/event.c +++ b/coreapi/event.c @@ -76,6 +76,46 @@ LINPHONE_PUBLIC const char *linphone_publish_state_to_string(LinphonePublishStat return NULL; } + +BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(LinphoneEventCbs); + +BELLE_SIP_INSTANCIATE_VPTR(LinphoneEventCbs, belle_sip_object_t, + NULL, // destroy + NULL, // clone + NULL, // marshal + FALSE +); + +static LinphoneEventCbs *linphone_event_cbs_new(void) { + return belle_sip_object_new(LinphoneEventCbs); +} + +LinphoneEventCbs *linphone_event_cbs_ref(LinphoneEventCbs *cbs) { + belle_sip_object_ref(cbs); + return cbs; +} + +void linphone_event_cbs_unref(LinphoneEventCbs *cbs) { + belle_sip_object_unref(cbs); +} + +void *linphone_event_cbs_get_user_data(const LinphoneEventCbs *cbs) { + return cbs->user_data; +} + +void linphone_event_cbs_set_user_data(LinphoneEventCbs *cbs, void *ud) { + cbs->user_data = ud; +} + +LinphoneEventCbsNotifyResponseCb linphone_event_cbs_get_notify_response(const LinphoneEventCbs *cbs) { + return cbs->notify_response_cb; +} + +void linphone_event_cbs_set_notify_response(LinphoneEventCbs *cbs, LinphoneEventCbsNotifyResponseCb cb) { + cbs->notify_response_cb = cb; +} + + static void linphone_event_release(LinphoneEvent *lev){ if (lev->op) { /*this will stop the refresher*/ @@ -86,6 +126,7 @@ static void linphone_event_release(LinphoneEvent *lev){ static LinphoneEvent * linphone_event_new_base(LinphoneCore *lc, LinphoneSubscriptionDir dir, const char *name, LinphonePrivate::SalEventOp *op){ LinphoneEvent *lev=belle_sip_object_new(LinphoneEvent); + lev->callbacks = linphone_event_cbs_new(); lev->lc=lc; lev->dir=dir; lev->op=op; @@ -446,6 +487,7 @@ static void linphone_event_destroy(LinphoneEvent *lev){ if (lev->to_address) linphone_address_unref(lev->to_address); if (lev->from_address) linphone_address_unref(lev->from_address); if (lev->remote_contact_address) linphone_address_unref(lev->remote_contact_address); + linphone_event_cbs_unref(lev->callbacks); ms_free(lev->name); } @@ -523,6 +565,16 @@ static belle_sip_error_code _linphone_event_marshall(belle_sip_object_t *obj, ch return err; } +void _linphone_event_notify_notify_response(const LinphoneEvent *lev) { + LinphoneEventCbsNotifyResponseCb cb = linphone_event_cbs_get_notify_response(lev->callbacks); + if (cb) + cb(lev); +} + +LinphoneEventCbs *linphone_event_get_callbacks(const LinphoneEvent *ev) { + return ev->callbacks; +} + BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(LinphoneEvent); BELLE_SIP_INSTANCIATE_VPTR(LinphoneEvent, belle_sip_object_t, diff --git a/coreapi/factory.c b/coreapi/factory.c index 0e34dd3d0..e316a93ba 100644 --- a/coreapi/factory.c +++ b/coreapi/factory.c @@ -21,6 +21,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "c-wrapper/c-wrapper.h" +#include "address/address-p.h" + // TODO: From coreapi. Remove me later. #include "private.h" @@ -145,6 +147,7 @@ LinphoneFactory *linphone_factory_get(void) { } void linphone_factory_clean(void){ + LinphonePrivate::AddressPrivate::clearSipAddressesCache(); if (_factory){ belle_sip_object_unref(_factory); _factory = NULL; @@ -238,6 +241,10 @@ LinphoneCallCbs * linphone_factory_create_call_cbs(const LinphoneFactory *factor return _linphone_call_cbs_new(); } +LinphoneChatRoomCbs * linphone_factory_create_chat_room_cbs(const LinphoneFactory *factory) { + return _linphone_chat_room_cbs_new(); +} + LinphoneVcard *linphone_factory_create_vcard(LinphoneFactory *factory) { return _linphone_vcard_new(); } diff --git a/coreapi/friend.c b/coreapi/friend.c index f5d915a4a..9c929e225 100644 --- a/coreapi/friend.c +++ b/coreapi/friend.c @@ -413,7 +413,7 @@ void linphone_friend_add_phone_number(LinphoneFriend *lf, const char *phone) { } } -bctbx_list_t* linphone_friend_get_phone_numbers(LinphoneFriend *lf) { +bctbx_list_t* linphone_friend_get_phone_numbers(const LinphoneFriend *lf) { if (!lf || !lf->vcard) return NULL; if (linphone_core_vcard_supported()) { @@ -1134,7 +1134,7 @@ void linphone_friend_destroy(LinphoneFriend *lf) { linphone_friend_unref(lf); } -LinphoneVcard* linphone_friend_get_vcard(LinphoneFriend *fr) { +LinphoneVcard* linphone_friend_get_vcard(const LinphoneFriend *fr) { if (fr && linphone_core_vcard_supported()) return fr->vcard; return NULL; } diff --git a/coreapi/friendlist.c b/coreapi/friendlist.c index 159409449..5e9ca2abf 100644 --- a/coreapi/friendlist.c +++ b/coreapi/friendlist.c @@ -634,10 +634,13 @@ static LinphoneFriendListStatus _linphone_friend_list_remove_friend(LinphoneFrie list->friends = bctbx_list_erase_link(list->friends, elem); if(lf->refkey) { bctbx_iterator_t * it = bctbx_map_cchar_find_key(list->friends_map, lf->refkey); - if (!bctbx_iterator_cchar_equals(it, bctbx_map_cchar_end(list->friends_map))){ + bctbx_iterator_t * end = bctbx_map_cchar_end(list->friends_map); + if (!bctbx_iterator_cchar_equals(it, end)){ linphone_friend_unref((LinphoneFriend*)bctbx_pair_cchar_get_second(bctbx_iterator_cchar_get_pair(it))); bctbx_map_cchar_erase(list->friends_map, it); } + if (it) bctbx_iterator_cchar_delete(it); + if (end) bctbx_iterator_cchar_delete(end); } phone_numbers = linphone_friend_get_phone_numbers(lf); @@ -647,14 +650,17 @@ static LinphoneFriendListStatus _linphone_friend_list_remove_friend(LinphoneFrie const char *uri = linphone_friend_phone_number_to_sip_uri(lf, number); if(uri) { bctbx_iterator_t * it = bctbx_map_cchar_find_key(list->friends_map_uri, uri); - if (!bctbx_iterator_cchar_equals(it, bctbx_map_cchar_end(list->friends_map_uri))){ + bctbx_iterator_t * end = bctbx_map_cchar_end(list->friends_map_uri); + if (!bctbx_iterator_cchar_equals(it, end)){ linphone_friend_unref((LinphoneFriend*)bctbx_pair_cchar_get_second(bctbx_iterator_cchar_get_pair(it))); bctbx_map_cchar_erase(list->friends_map_uri, it); } - bctbx_iterator_cchar_delete(it); + if (it) bctbx_iterator_cchar_delete(it); + if (end) bctbx_iterator_cchar_delete(end); } iterator = bctbx_list_next(iterator); } + if (phone_numbers) bctbx_list_free(phone_numbers); addresses = linphone_friend_get_addresses(lf); iterator = (bctbx_list_t *)addresses; @@ -663,13 +669,16 @@ static LinphoneFriendListStatus _linphone_friend_list_remove_friend(LinphoneFrie char *uri = linphone_address_as_string_uri_only(lfaddr); if(uri) { bctbx_iterator_t * it = bctbx_map_cchar_find_key(list->friends_map_uri, uri); - if (!bctbx_iterator_cchar_equals(it, bctbx_map_cchar_end(list->friends_map_uri))){ + bctbx_iterator_t * end = bctbx_map_cchar_end(list->friends_map_uri); + if (!bctbx_iterator_cchar_equals(it, end)){ linphone_friend_unref((LinphoneFriend*)bctbx_pair_cchar_get_second(bctbx_iterator_cchar_get_pair(it))); bctbx_map_cchar_erase(list->friends_map_uri, it); } - bctbx_iterator_cchar_delete(it); + if (it) bctbx_iterator_cchar_delete(it); + if (end) bctbx_iterator_cchar_delete(end); ms_free(uri); } + iterator = bctbx_list_next(iterator); } @@ -766,7 +775,7 @@ void linphone_friend_list_synchronize_friends_from_server(LinphoneFriendList *li LinphoneFriend * linphone_friend_list_find_friend_by_address(const LinphoneFriendList *list, const LinphoneAddress *address) { LinphoneAddress *clean_addr = linphone_address_clone(address); LinphoneFriend *lf; - if (linphone_address_has_param(clean_addr, "gr")) { + if (linphone_address_has_uri_param(clean_addr, "gr")) { linphone_address_remove_uri_param(clean_addr, "gr"); } char *uri = linphone_address_as_string_uri_only(clean_addr); diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index 114749d3a..c6af072cf 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -1395,6 +1395,9 @@ static void sip_config_read(LinphoneCore *lc) { /*this is to filter out unsupported encryption schemes*/ linphone_core_set_media_encryption(lc,linphone_core_get_media_encryption(lc)); + /*enable the reconnection to the primary server when it is up again asap*/ + lc->sal->enable_reconnect_to_primary_asap(!!lp_config_get_int(lc->config,"sip","reconnect_to_primary_asap",0)); + /*for tuning or test*/ lc->sip_conf.sdp_200_ack = !!lp_config_get_int(lc->config,"sip","sdp_200_ack",0); lc->sip_conf.register_only_when_network_is_up= @@ -2142,6 +2145,7 @@ static void linphone_core_internal_notify_received(LinphoneCore *lc, LinphoneEve while ((part = linphone_content_get_part(body, i))) { i++; L_GET_PRIVATE(cgcr)->notifyReceived(linphone_content_get_string_buffer(part)); + linphone_content_unref(part); } } else L_GET_PRIVATE(cgcr)->notifyReceived(linphone_content_get_string_buffer(body)); @@ -2320,7 +2324,7 @@ void linphone_core_start (LinphoneCore *lc) { lp_config_set_string(lc->config,"misc","uuid",tmp); }else if (strcmp(uuid,"0")!=0) /*to allow to disable sip.instance*/ lc->sal->set_uuid(uuid); - + if (lc->sal->get_root_ca()) { belle_tls_crypto_config_set_root_ca(lc->http_crypto_config, lc->sal->get_root_ca()); belle_http_provider_set_tls_crypto_config(lc->http_provider, lc->http_crypto_config); @@ -2384,7 +2388,8 @@ LinphoneCore *linphone_core_new(const LinphoneCoreVTable *vtable, } LinphoneCore *linphone_core_ref(LinphoneCore *lc) { - return reinterpret_cast(belle_sip_object_ref(BELLE_SIP_OBJECT(lc))); + belle_sip_object_ref(BELLE_SIP_OBJECT(lc)); + return lc; } void linphone_core_unref(LinphoneCore *lc) { @@ -3417,11 +3422,16 @@ LinphoneCall * linphone_core_start_refered_call(LinphoneCore *lc, LinphoneCall * system. */ static bctbx_list_t *make_routes_for_proxy(LinphoneProxyConfig *proxy, const LinphoneAddress *dest){ - bctbx_list_t *ret=NULL; - const char *local_route=linphone_proxy_config_get_route(proxy); - const LinphoneAddress *srv_route=linphone_proxy_config_get_service_route(proxy); - if (local_route){ - ret=bctbx_list_append(ret,sal_address_new(local_route)); + bctbx_list_t *ret = NULL; + const bctbx_list_t *proxy_routes = linphone_proxy_config_get_routes(proxy); + bctbx_list_t *proxy_routes_iterator = (bctbx_list_t *)proxy_routes; + const LinphoneAddress *srv_route = linphone_proxy_config_get_service_route(proxy); + while (proxy_routes_iterator) { + const char *local_route = (const char *)bctbx_list_get_data(proxy_routes_iterator); + if (local_route) { + ret = bctbx_list_append(ret, sal_address_new(local_route)); + } + proxy_routes_iterator = bctbx_list_next(proxy_routes_iterator); } if (srv_route){ ret=bctbx_list_append(ret,sal_address_clone(L_GET_PRIVATE_FROM_C_OBJECT(srv_route)->getInternalAddress())); @@ -3445,7 +3455,7 @@ LinphoneProxyConfig * linphone_core_lookup_known_proxy(LinphoneCore *lc, const L LinphoneProxyConfig *default_cfg=lc->default_proxy; if (linphone_address_get_domain(uri) == NULL) { - ms_message("cannot seach for proxy for uri [%p] if no domain set. returning default",uri); + ms_message("Cannot look for proxy for uri [%p] that has no domain set, returning default", uri); return default_cfg; } /*return default proxy if it is matching the destination uri*/ @@ -5812,15 +5822,17 @@ void sip_config_uninit(LinphoneCore *lc) } if (i>=20) ms_warning("Cannot complete unregistration, giving up"); } + elem = config->proxies; - config->proxies=NULL; /*to make sure proxies cannot be refferenced during deletion*/ + config->proxies=NULL; /*to make sure proxies cannot be referenced during deletion*/ bctbx_list_free_with_data(elem,(void (*)(void*)) _linphone_proxy_config_release); config->deleted_proxies=bctbx_list_free_with_data(config->deleted_proxies,(void (*)(void*)) _linphone_proxy_config_release); - /*no longuer need to write proxy config if not changedlinphone_proxy_config_write_to_config_file(lc->config,NULL,i);*/ /*mark the end */ + /*no longuer need to write proxy config if not changed linphone_proxy_config_write_to_config_file(lc->config,NULL,i);*/ /*mark the end */ lc->auth_info=bctbx_list_free_with_data(lc->auth_info,(void (*)(void*))linphone_auth_info_unref); + lc->default_proxy = NULL; if (lc->vcard_context) { linphone_vcard_context_destroy(lc->vcard_context); @@ -5850,6 +5862,7 @@ void sip_config_uninit(LinphoneCore *lc) delete lc->sal; lc->sal=NULL; + if (lc->sip_conf.guessed_contact) ms_free(lc->sip_conf.guessed_contact); if (config->contact) @@ -6709,11 +6722,13 @@ int linphone_core_get_video_dscp(const LinphoneCore *lc){ void linphone_core_set_chat_database_path (LinphoneCore *lc, const char *path) { if (!linphone_core_conference_server_enabled(lc)) { - auto &mainDb = L_GET_PRIVATE(lc->cppPtr)->mainDb; - if (mainDb) + auto &mainDb = L_GET_PRIVATE_FROM_C_OBJECT(lc)->mainDb; + if (mainDb) { mainDb->import(LinphonePrivate::MainDb::Sqlite3, path); - else + L_GET_PRIVATE_FROM_C_OBJECT(lc)->loadChatRooms(); + } else { ms_warning("linphone_core_set_chat_database_path() needs to be called once linphone_core_start() has been called"); + } } } @@ -7040,20 +7055,15 @@ LinphoneConference *linphone_core_get_conference(LinphoneCore *lc) { return lc->conf_ctx; } -void linphone_core_set_conference_factory_uri(LinphoneCore *lc, const char *uri) { - lp_config_set_string(linphone_core_get_config(lc), "misc", "conference_factory_uri", uri); -} - -const char * linphone_core_get_conference_factory_uri(const LinphoneCore *lc) { - return lp_config_get_string(linphone_core_get_config(lc), "misc", "conference_factory_uri", nullptr); -} - void linphone_core_enable_conference_server (LinphoneCore *lc, bool_t enable) { lp_config_set_int(linphone_core_get_config(lc), "misc", "conference_server_enabled", enable); } bool_t _linphone_core_is_conference_creation (const LinphoneCore *lc, const LinphoneAddress *addr) { - const char *uri = linphone_core_get_conference_factory_uri(lc); + LinphoneProxyConfig *proxy = linphone_core_get_default_proxy_config(lc); + if (!proxy) + return FALSE; + const char *uri = linphone_proxy_config_get_conference_factory_uri(proxy); if (!uri) return FALSE; diff --git a/coreapi/linphonecore_jni.cc b/coreapi/linphonecore_jni.cc index 473407d2e..707d229f3 100644 --- a/coreapi/linphonecore_jni.cc +++ b/coreapi/linphonecore_jni.cc @@ -8913,3 +8913,15 @@ extern "C" jstring Java_org_linphone_core_LinphoneCoreImpl_getTlsKeyPath(JNIEnv return NULL; } } + +extern "C" jlong Java_org_linphone_core_LinphoneCoreImpl_createCallLog(JNIEnv *env, jobject, jlong jcore, jlong jfrom, jlong jto, jint jdir, jint duration, jlong start_time, jlong connected_time, jint jstatus, jboolean video_enabled, jfloat quality) { + LinphoneCore *core = (LinphoneCore*)jcore; + LinphoneAddress *from = (LinphoneAddress *)jfrom; + LinphoneAddress *to = (LinphoneAddress *)jto; + LinphoneCallDir dir = (LinphoneCallDir)jdir; + time_t start = (time_t) start_time; + time_t connected = (time_t) connected_time; + LinphoneCallStatus status = (LinphoneCallStatus)jstatus; + LinphoneCallLog *log = linphone_core_create_call_log(core, from, to, dir, duration, start, connected, status, video_enabled, quality); + return (jlong) log; +} diff --git a/coreapi/misc.c b/coreapi/misc.c index 169cc6b88..3e255a354 100644 --- a/coreapi/misc.c +++ b/coreapi/misc.c @@ -907,17 +907,21 @@ void linphone_core_report_call_log(LinphoneCore *lc, LinphoneCallLog *call_log){ // TODO: This is a workaround that has to be removed ASAP // Do not add calls made to the conference factory in the history - char *to = linphone_address_as_string(call_log->to); - const char *conference_factory_uri = linphone_core_get_conference_factory_uri(lc); - if (conference_factory_uri && (strcmp(conference_factory_uri, to) == 0)) { - bctbx_free(to); - return; + const char *conference_factory_uri = nullptr; + LinphoneProxyConfig *proxy = linphone_core_lookup_known_proxy(lc, call_log->to); + if (proxy) + conference_factory_uri = linphone_proxy_config_get_conference_factory_uri(proxy); + if (conference_factory_uri) { + LinphoneAddress *conference_factory_addr = linphone_address_new(conference_factory_uri); + if (linphone_address_weak_equal(call_log->to, conference_factory_addr)) { + linphone_address_unref(conference_factory_addr); + return; + } + linphone_address_unref(conference_factory_addr); } - if (strstr(to, "chatroom-") == to) { - bctbx_free(to); + const char *username = linphone_address_get_username(call_log->to); + if (username && (strstr(username, "chatroom-") == username)) return; - } - bctbx_free(to); // End of workaround #ifdef SQLITE_STORAGE_ENABLED diff --git a/coreapi/nat_policy.c b/coreapi/nat_policy.c index 8edfe3bd2..ed072ca7f 100644 --- a/coreapi/nat_policy.c +++ b/coreapi/nat_policy.c @@ -185,6 +185,17 @@ void linphone_nat_policy_set_stun_server(LinphoneNatPolicy *policy, const char * if (new_stun_server != NULL) { policy->stun_server = new_stun_server; } + if (policy->stun_addrinfo) { + bctbx_freeaddrinfo(policy->stun_addrinfo); + policy->stun_addrinfo = NULL; + } + if (policy->stun_resolver_context){ + belle_sip_resolver_context_cancel(policy->stun_resolver_context); + belle_sip_object_unref(policy->stun_resolver_context); + policy->stun_resolver_context = NULL; + + } + linphone_nat_policy_resolve_stun_server(policy); } const char * linphone_nat_policy_get_stun_server_username(const LinphoneNatPolicy *policy) { diff --git a/coreapi/private_functions.h b/coreapi/private_functions.h index 4bd4d5fd8..e6a27fbe0 100644 --- a/coreapi/private_functions.h +++ b/coreapi/private_functions.h @@ -47,6 +47,7 @@ void linphone_call_notify_transfer_state_changed(LinphoneCall *call, LinphoneCal void linphone_call_notify_stats_updated(LinphoneCall *call, const LinphoneCallStats *stats); void linphone_call_notify_info_message_received(LinphoneCall *call, const LinphoneInfoMessage *msg); void linphone_call_notify_ack_processing(LinphoneCall *call, LinphoneHeaders *msg, bool_t is_received); +void linphone_call_notify_tmmbr_received(LinphoneCall *call, int stream_index, int tmmbr); LinphoneCall * linphone_call_new_outgoing(struct _LinphoneCore *lc, const LinphoneAddress *from, const LinphoneAddress *to, const LinphoneCallParams *params, LinphoneProxyConfig *cfg); LinphoneCall * linphone_call_new_incoming(struct _LinphoneCore *lc, const LinphoneAddress *from, const LinphoneAddress *to, LinphonePrivate::SalCallOp *op); @@ -275,11 +276,28 @@ void _linphone_proxy_config_unregister(LinphoneProxyConfig *obj); void _linphone_proxy_config_release_ops(LinphoneProxyConfig *obj); /*chat*/ -LinphoneChatRoom *_linphone_client_group_chat_room_new (LinphoneCore *core, const char *uri, const char *subject, bool_t fallback); LinphoneChatRoom *_linphone_server_group_chat_room_new (LinphoneCore *core, LinphonePrivate::SalCallOp *op); void linphone_chat_room_set_call(LinphoneChatRoom *cr, LinphoneCall *call); -LinphoneChatRoomCbs * linphone_chat_room_cbs_new (void); -/**/ +LinphoneChatRoomCbs * _linphone_chat_room_cbs_new (void); +void _linphone_chat_room_notify_is_composing_received(LinphoneChatRoom *cr, const LinphoneAddress *remoteAddr, bool_t isComposing); +void _linphone_chat_room_notify_message_received(LinphoneChatRoom *cr, LinphoneChatMessage *msg); +void _linphone_chat_room_notify_participant_added(LinphoneChatRoom *cr, const LinphoneEventLog *event_log); +void _linphone_chat_room_notify_participant_removed(LinphoneChatRoom *cr, const LinphoneEventLog *event_log); +void _linphone_chat_room_notify_participant_device_added(LinphoneChatRoom *cr, const LinphoneEventLog *event_log); +void _linphone_chat_room_notify_participant_device_removed(LinphoneChatRoom *cr, const LinphoneEventLog *event_log); +void _linphone_chat_room_notify_participant_admin_status_changed(LinphoneChatRoom *cr, const LinphoneEventLog *event_log); +void _linphone_chat_room_notify_state_changed(LinphoneChatRoom *cr, LinphoneChatRoomState newState); +void _linphone_chat_room_notify_subject_changed(LinphoneChatRoom *cr, const LinphoneEventLog *event_log); +void _linphone_chat_room_notify_undecryptable_message_received(LinphoneChatRoom *cr, LinphoneChatMessage *msg); +void _linphone_chat_room_notify_chat_message_received(LinphoneChatRoom *cr, const LinphoneEventLog *event_log); +void _linphone_chat_room_notify_chat_message_sent(LinphoneChatRoom *cr, const LinphoneEventLog *event_log); +void _linphone_chat_room_notify_conference_address_generation(LinphoneChatRoom *cr); +void _linphone_chat_room_notify_participant_device_fetch_requested(LinphoneChatRoom *cr, const LinphoneAddress *participantAddr); +void _linphone_chat_room_notify_participants_capabilities_checked(LinphoneChatRoom *cr, const LinphoneAddress *deviceAddr, const bctbx_list_t *participantsAddr); +void _linphone_chat_room_notify_participant_registration_subscription_requested(LinphoneChatRoom *cr, const LinphoneAddress *participantAddr); +void _linphone_chat_room_notify_participant_registration_unsubscription_requested(LinphoneChatRoom *cr, const LinphoneAddress *participantAddr); +void _linphone_chat_room_notify_chat_message_should_be_stored(LinphoneChatRoom *cr, LinphoneChatMessage *msg); +void _linphone_chat_room_clear_callbacks (LinphoneChatRoom *cr); LinphoneToneDescription * linphone_tone_description_new(LinphoneReason reason, LinphoneToneID id, const char *audiofile); void linphone_tone_description_destroy(LinphoneToneDescription *obj); @@ -355,7 +373,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); @@ -380,6 +397,7 @@ void linphone_event_set_internal(LinphoneEvent *lev, bool_t internal); bool_t linphone_event_is_internal(LinphoneEvent *lev); void linphone_event_set_state(LinphoneEvent *lev, LinphoneSubscriptionState state); void linphone_event_set_publish_state(LinphoneEvent *lev, LinphonePublishState state); +void _linphone_event_notify_notify_response(const LinphoneEvent *lev); LinphoneSubscriptionState linphone_subscription_state_from_sal(SalSubscribeStatus ss); LinphoneContent *linphone_content_from_sal_body_handler(SalBodyHandler *ref); void linphone_core_invalidate_friend_subscriptions(LinphoneCore *lc); diff --git a/coreapi/private_structs.h b/coreapi/private_structs.h index 449609ba9..3bcfc4d78 100644 --- a/coreapi/private_structs.h +++ b/coreapi/private_structs.h @@ -90,7 +90,7 @@ struct _LinphoneProxyConfig LinphoneAddress* identity_address; LinphoneAddress *contact_address; LinphoneAddress *contact_address_without_params; - char *reg_route; + bctbx_list_t *reg_routes; char *quality_reporting_collector; char *realm; char *contact_params; @@ -106,6 +106,7 @@ struct _LinphoneProxyConfig LinphoneRegistrationState state; LinphoneAVPFMode avpf_mode; LinphoneNatPolicy *nat_policy; + int quality_reporting_interval; bool_t commit; bool_t reg_sendregister; @@ -115,15 +116,14 @@ struct _LinphoneProxyConfig bool_t send_publish; bool_t quality_reporting_enabled; uint8_t avpf_rr_interval; - uint8_t quality_reporting_interval; + bool_t register_changed; time_t deletion_date; LinphonePrivacyMask privacy; /*use to check if server config has changed between edit() and done()*/ LinphoneAddress *saved_proxy; LinphoneAddress *saved_identity; - bool_t register_changed; - bool_t unused[3]; + /*---*/ LinphoneAddress *pending_contact; /*use to store previous contact in case of network failure*/ LinphoneEvent *presence_publish_event; @@ -131,6 +131,7 @@ struct _LinphoneProxyConfig char *refkey; char *sip_etag; /*publish context*/ + char *conference_factory_uri; }; BELLE_SIP_DECLARE_VPTR_NO_EXPORT(LinphoneProxyConfig); @@ -151,11 +152,6 @@ struct _LinphoneAuthInfo char *algorithm; }; -struct _LinphoneChatMessageCharacter { - uint32_t value; - bool_t has_been_read; -}; - struct _LinphoneFriendPresence { char *uri_or_tel; LinphonePresenceModel *presence; @@ -378,6 +374,14 @@ struct _LCCallbackObj { void *_user_data; }; +struct _LinphoneEventCbs { + belle_sip_object_t base; + void *user_data; + LinphoneEventCbsNotifyResponseCb notify_response_cb; +}; + +BELLE_SIP_DECLARE_VPTR_NO_EXPORT(LinphoneEventCbs); + struct _LinphoneEvent{ belle_sip_object_t base; LinphoneErrorInfo *ei; @@ -389,6 +393,7 @@ struct _LinphoneEvent{ LinphonePublishState publish_state; void *userdata; char *name; + LinphoneEventCbs *callbacks; int expires; bool_t terminating; bool_t is_out_of_dialog_op; /*used for out of dialog notify*/ diff --git a/coreapi/private_types.h b/coreapi/private_types.h index f646ce3ce..5e7ca8029 100644 --- a/coreapi/private_types.h +++ b/coreapi/private_types.h @@ -27,8 +27,6 @@ typedef struct StunCandidate StunCandidate; typedef struct _PortConfig PortConfig; -typedef struct _LinphoneChatMessageCharacter LinphoneChatMessageCharacter; - typedef struct _LinphoneFriendPresence LinphoneFriendPresence; typedef struct _LinphoneFriendPhoneNumberSipUri LinphoneFriendPhoneNumberSipUri; diff --git a/coreapi/proxy.c b/coreapi/proxy.c index d5f3ea5ad..03213da1e 100644 --- a/coreapi/proxy.c +++ b/coreapi/proxy.c @@ -20,6 +20,7 @@ Copyright (C) 2000 Simon MORLAT (simon.morlat@linphone.org) #include +#include #include "linphone/core_utils.h" #include "linphone/core.h" #include "linphone/lpconfig.h" @@ -113,13 +114,14 @@ static void linphone_proxy_config_init(LinphoneCore* lc, LinphoneProxyConfig *cf const char *dial_prefix = lc ? lp_config_get_default_string(lc->config,"proxy","dial_prefix",NULL) : NULL; const char *identity = lc ? lp_config_get_default_string(lc->config, "proxy", "reg_identity", NULL) : NULL; const char *proxy = lc ? lp_config_get_default_string(lc->config, "proxy", "reg_proxy", NULL) : NULL; - const char *route = lc ? lp_config_get_default_string(lc->config, "proxy", "reg_route", NULL) : NULL; + const char *route = lc ? lp_config_get_default_string(lc->config, "proxy", "reg_route", NULL) : NULL; //TODO return list instead of string const char *realm = lc ? lp_config_get_default_string(lc->config, "proxy", "realm", NULL) : NULL; const char *quality_reporting_collector = lc ? lp_config_get_default_string(lc->config, "proxy", "quality_reporting_collector", NULL) : NULL; const char *contact_params = lc ? lp_config_get_default_string(lc->config, "proxy", "contact_parameters", NULL) : NULL; const char *contact_uri_params = lc ? lp_config_get_default_string(lc->config, "proxy", "contact_uri_parameters", NULL) : NULL; const char *refkey = lc ? lp_config_get_default_string(lc->config, "proxy", "refkey", NULL) : NULL; const char *nat_policy_ref = lc ? lp_config_get_default_string(lc->config, "proxy", "nat_policy_ref", NULL):NULL; + const char *conference_factory_uri = lc ? lp_config_get_default_string(lc->config, "proxy", "conference_factory_uri", NULL):NULL; cfg->lc = lc; cfg->expires = lc ? lp_config_get_default_int(lc->config, "proxy", "reg_expires", 3600) : 3600; cfg->reg_sendregister = lc ? !!lp_config_get_default_int(lc->config, "proxy", "reg_sendregister", 1) : 1; @@ -129,11 +131,11 @@ static void linphone_proxy_config_init(LinphoneCore* lc, LinphoneProxyConfig *cf cfg->identity_address = identity ? linphone_address_new(identity) : NULL; cfg->reg_identity = cfg->identity_address ? linphone_address_as_string(cfg->identity_address) : NULL; cfg->reg_proxy = proxy ? ms_strdup(proxy) : NULL; - cfg->reg_route = route ? ms_strdup(route) : NULL; + cfg->reg_routes = route ? bctbx_list_append(cfg->reg_routes, ms_strdup(route)) : NULL; //TODO get list directly cfg->realm = realm ? ms_strdup(realm) : NULL; cfg->quality_reporting_enabled = lc ? !!lp_config_get_default_int(lc->config, "proxy", "quality_reporting_enabled", 0) : 0; cfg->quality_reporting_collector = quality_reporting_collector ? ms_strdup(quality_reporting_collector) : NULL; - cfg->quality_reporting_interval = lc ? !!lp_config_get_default_int(lc->config, "proxy", "quality_reporting_interval", 0) : 0; + cfg->quality_reporting_interval = lc ? lp_config_get_default_int(lc->config, "proxy", "quality_reporting_interval", 0) : 0; cfg->contact_params = contact_params ? ms_strdup(contact_params) : NULL; cfg->contact_uri_params = contact_uri_params ? ms_strdup(contact_uri_params) : NULL; cfg->avpf_mode = lc ? static_cast(lp_config_get_default_int(lc->config, "proxy", "avpf", LinphoneAVPFDefault)) : LinphoneAVPFDefault; @@ -150,6 +152,7 @@ static void linphone_proxy_config_init(LinphoneCore* lc, LinphoneProxyConfig *cf ms_error("Cannot create default nat policy with ref [%s] for proxy config [%p]",nat_policy_ref,cfg); } } + cfg->conference_factory_uri = conference_factory_uri ? ms_strdup(conference_factory_uri) : NULL; } LinphoneProxyConfig *linphone_proxy_config_new() { @@ -181,13 +184,18 @@ bool_t linphone_proxy_config_compute_publish_params_hash(LinphoneProxyConfig * c char hash[33]; char saved; unsigned long long previous_hash[2]; + bctbx_list_t *routes_iterator = cfg->reg_routes; previous_hash[0] = cfg->previous_publish_config_hash[0]; previous_hash[1] = cfg->previous_publish_config_hash[1]; source = ms_strcat_printf(source, "%i",cfg->privacy); source=append_linphone_address(cfg->identity_address, source); source=append_string(cfg->reg_proxy,source); - source=append_string(cfg->reg_route,source); + while (routes_iterator) { + const char *route = (const char *)bctbx_list_get_data(routes_iterator); + source=append_string(route,source); + routes_iterator = bctbx_list_next(routes_iterator); + } source=append_string(cfg->realm,source); source = ms_strcat_printf(source, "%i",cfg->publish_expires); source = ms_strcat_printf(source, "%i",cfg->publish); @@ -233,7 +241,7 @@ void _linphone_proxy_config_destroy(LinphoneProxyConfig *cfg){ if (cfg->reg_proxy!=NULL) ms_free(cfg->reg_proxy); if (cfg->reg_identity!=NULL) ms_free(cfg->reg_identity); if (cfg->identity_address!=NULL) linphone_address_unref(cfg->identity_address); - if (cfg->reg_route!=NULL) ms_free(cfg->reg_route); + if (cfg->reg_routes!=NULL) bctbx_list_free_with_data(cfg->reg_routes, ms_free); if (cfg->quality_reporting_collector!=NULL) ms_free(cfg->quality_reporting_collector); if (cfg->ssctx!=NULL) sip_setup_context_free(cfg->ssctx); if (cfg->realm!=NULL) ms_free(cfg->realm); @@ -249,6 +257,8 @@ void _linphone_proxy_config_destroy(LinphoneProxyConfig *cfg){ if (cfg->nat_policy != NULL) { linphone_nat_policy_unref(cfg->nat_policy); } + if (cfg->conference_factory_uri) + bctbx_free(cfg->conference_factory_uri); if (cfg->ei){ linphone_error_info_unref(cfg->ei); } @@ -344,9 +354,9 @@ const char *linphone_proxy_config_get_domain(const LinphoneProxyConfig *cfg){ LinphoneStatus linphone_proxy_config_set_route(LinphoneProxyConfig *cfg, const char *route) { - if (cfg->reg_route!=NULL){ - ms_free(cfg->reg_route); - cfg->reg_route=NULL; + if (cfg->reg_routes != NULL) { + bctbx_list_free_with_data(cfg->reg_routes, ms_free); + cfg->reg_routes = NULL; } if (route!=NULL && route[0] !='\0'){ SalAddress *addr; @@ -358,7 +368,7 @@ LinphoneStatus linphone_proxy_config_set_route(LinphoneProxyConfig *cfg, const c addr=sal_address_new(tmp); if (addr!=NULL){ sal_address_destroy(addr); - cfg->reg_route=tmp; + cfg->reg_routes = bctbx_list_append(cfg->reg_routes, tmp); return 0; }else{ ms_free(tmp); @@ -369,6 +379,37 @@ LinphoneStatus linphone_proxy_config_set_route(LinphoneProxyConfig *cfg, const c } } +LinphoneStatus linphone_proxy_config_set_routes(LinphoneProxyConfig *cfg, const bctbx_list_t *routes) { + if (cfg->reg_routes != NULL) { + bctbx_list_free_with_data(cfg->reg_routes, ms_free); + cfg->reg_routes = NULL; + } + bctbx_list_t *iterator = (bctbx_list_t *)routes; + while (iterator != NULL) { + char *route = (char *)bctbx_list_get_data(iterator); + if (route != NULL && route[0] !='\0') { + SalAddress *addr; + char *tmp; + /*try to prepend 'sip:' */ + if (strstr(route,"sip:") == NULL && strstr(route,"sips:") == NULL) { + tmp = ms_strdup_printf("sip:%s",route); + } else { + tmp = ms_strdup(route); + } + addr = sal_address_new(tmp); + if (addr != NULL) { + sal_address_destroy(addr); + cfg->reg_routes = bctbx_list_append(cfg->reg_routes, tmp); + } else { + ms_free(tmp); + return -1; + } + } + iterator = bctbx_list_next(iterator); + } + return 0; +} + bool_t linphone_proxy_config_check(LinphoneCore *lc, LinphoneProxyConfig *cfg){ if (cfg->reg_proxy==NULL) return FALSE; @@ -565,7 +606,7 @@ bool_t linphone_proxy_config_quality_reporting_enabled(LinphoneProxyConfig *cfg) } void linphone_proxy_config_set_quality_reporting_interval(LinphoneProxyConfig *cfg, int interval) { - cfg->quality_reporting_interval = !!interval; + cfg->quality_reporting_interval = interval; } int linphone_proxy_config_get_quality_reporting_interval(LinphoneProxyConfig *cfg) { @@ -786,6 +827,13 @@ LinphoneAddress* linphone_proxy_config_normalize_sip_uri(LinphoneProxyConfig *pr return NULL; } +void linphone_proxy_config_set_etag(LinphoneProxyConfig *cfg,const char* sip_etag) { + if (cfg->sip_etag) ms_free(cfg->sip_etag); + if (sip_etag) + cfg->sip_etag = ms_strdup(sip_etag); + else + cfg->sip_etag = NULL; +} /** * Commits modification made to the proxy configuration. **/ @@ -827,11 +875,7 @@ LinphoneStatus linphone_proxy_config_done(LinphoneProxyConfig *cfg) ms_message("Publish params have changed on proxy config [%p]",cfg); if (cfg->presence_publish_event) { if (cfg->publish) { - const char * sip_etag = linphone_event_get_custom_header(cfg->presence_publish_event, "SIP-ETag"); - if (sip_etag) { - if (cfg->sip_etag) ms_free(cfg->sip_etag); - cfg->sip_etag = ms_strdup(sip_etag); - } + linphone_proxy_config_set_etag(cfg, linphone_event_get_custom_header(cfg->presence_publish_event, "SIP-ETag")); } /*publish is terminated*/ linphone_event_terminate(cfg->presence_publish_event); @@ -930,8 +974,13 @@ void _linphone_proxy_config_unpublish(LinphoneProxyConfig *obj) { } } -const char *linphone_proxy_config_get_route(const LinphoneProxyConfig *cfg){ - return cfg->reg_route; +const char *linphone_proxy_config_get_route(const LinphoneProxyConfig *cfg) { + if (cfg->reg_routes) return (const char *)bctbx_list_get_data(cfg->reg_routes); + return NULL; +} + +const bctbx_list_t* linphone_proxy_config_get_routes(const LinphoneProxyConfig *cfg) { + return cfg->reg_routes; } const LinphoneAddress *linphone_proxy_config_get_identity_address(const LinphoneProxyConfig *cfg){ @@ -1089,7 +1138,7 @@ int linphone_core_get_default_proxy(LinphoneCore *lc, LinphoneProxyConfig **conf return linphone_core_get_default_proxy_config_index(lc); } -LinphoneProxyConfig * linphone_core_get_default_proxy_config(LinphoneCore *lc) { +LinphoneProxyConfig * linphone_core_get_default_proxy_config(const LinphoneCore *lc) { return lc->default_proxy; } @@ -1112,8 +1161,8 @@ void linphone_proxy_config_write_to_config_file(LpConfig *config, LinphoneProxyC if (cfg->reg_proxy!=NULL){ lp_config_set_string(config,key,"reg_proxy",cfg->reg_proxy); } - if (cfg->reg_route!=NULL){ - lp_config_set_string(config,key,"reg_route",cfg->reg_route); + if (cfg->reg_routes != NULL) { + lp_config_set_string_list(config, key, "reg_route", cfg->reg_routes); } if (cfg->reg_identity!=NULL){ lp_config_set_string(config,key,"reg_identity",cfg->reg_identity); @@ -1147,6 +1196,8 @@ void linphone_proxy_config_write_to_config_file(LpConfig *config, LinphoneProxyC lp_config_set_string(config, key, "nat_policy_ref", cfg->nat_policy->ref); linphone_nat_policy_save_to_config(cfg->nat_policy); } + + lp_config_set_string(config, key, "conference_factory_uri", cfg->conference_factory_uri); } @@ -1181,7 +1232,10 @@ LinphoneProxyConfig *linphone_proxy_config_new_from_config_file(LinphoneCore* lc CONFIGURE_STRING_VALUE(cfg,config,key,identity,"reg_identity") CONFIGURE_STRING_VALUE(cfg,config,key,server_addr,"reg_proxy") - CONFIGURE_STRING_VALUE(cfg,config,key,route,"reg_route") + bctbx_list_t *routes = linphone_config_get_string_list(config, key, "reg_route", NULL); + linphone_proxy_config_set_routes(cfg, routes); + if (routes) + bctbx_list_free_with_data(routes, (bctbx_list_free_func)bctbx_free); CONFIGURE_STRING_VALUE(cfg,config,key,realm,"realm") @@ -1213,6 +1267,8 @@ LinphoneProxyConfig *linphone_proxy_config_new_from_config_file(LinphoneCore* lc cfg->nat_policy = linphone_core_create_nat_policy_from_config(lc, nat_policy_ref); } + CONFIGURE_STRING_VALUE(cfg, config, key, conference_factory_uri, "conference_factory_uri"); + return cfg; } @@ -1490,8 +1546,35 @@ void linphone_proxy_config_set_nat_policy(LinphoneProxyConfig *cfg, LinphoneNatP } void linphone_proxy_config_notify_publish_state_changed(LinphoneProxyConfig *cfg, LinphonePublishState state) { - if ((cfg->presence_publish_event != NULL) && ((state == LinphonePublishCleared) || (state == LinphonePublishError))) { - linphone_event_unref(cfg->presence_publish_event); - cfg->presence_publish_event = NULL; + + if (cfg->presence_publish_event != NULL) { + switch (state) { + case LinphonePublishCleared: + linphone_proxy_config_set_etag(cfg,NULL); + BCTBX_NO_BREAK; + case LinphonePublishError: + linphone_event_unref(cfg->presence_publish_event); + cfg->presence_publish_event = NULL; + break; + case LinphonePublishOk: + linphone_proxy_config_set_etag(cfg,linphone_event_get_custom_header(cfg->presence_publish_event, "SIP-ETag")); + break; + default: + break; + + } } } + +void linphone_proxy_config_set_conference_factory_uri(LinphoneProxyConfig *cfg, const char *uri) { + if (cfg->conference_factory_uri) { + bctbx_free(cfg->conference_factory_uri); + cfg->conference_factory_uri = nullptr; + } + if (uri) + cfg->conference_factory_uri = bctbx_strdup(uri); +} + +const char * linphone_proxy_config_get_conference_factory_uri(const LinphoneProxyConfig *cfg) { + return cfg->conference_factory_uri; +} diff --git a/coreapi/ringtoneplayer_ios.h b/coreapi/ringtoneplayer_ios.h index 37aa2b22e..fce0a16dc 100644 --- a/coreapi/ringtoneplayer_ios.h +++ b/coreapi/ringtoneplayer_ios.h @@ -24,7 +24,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. extern "C" { #endif -LinphoneRingtonePlayer* linphone_ringtoneplayer_ios_new(); +LinphoneRingtonePlayer* linphone_ringtoneplayer_ios_new(void); void linphone_ringtoneplayer_ios_destroy(LinphoneRingtonePlayer* rp); int linphone_ringtoneplayer_ios_start_with_cb(LinphoneRingtonePlayer* rp, const char* ringtone, int loop_pause_ms, LinphoneRingtonePlayerFunc end_of_ringtone, void * user_data); bool_t linphone_ringtoneplayer_ios_is_started(LinphoneRingtonePlayer* rp); diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt index 055f1840d..92593391a 100644 --- a/include/CMakeLists.txt +++ b/include/CMakeLists.txt @@ -86,7 +86,9 @@ set(C_API_HEADER_FILES c-chat-room.h c-dial-plan.h c-event-log.h + c-magic-search.h c-participant.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 0b671d6ee..039b55246 100644 --- a/include/linphone/api/c-api.h +++ b/include/linphone/api/c-api.h @@ -34,6 +34,8 @@ #include "linphone/api/c-dial-plan.h" #include "linphone/api/c-event-log.h" #include "linphone/api/c-participant.h" +#include "linphone/api/c-magic-search.h" +#include "linphone/api/c-search-result.h" #include "linphone/api/c-types.h" #endif // ifndef _L_C_API_H_ diff --git a/include/linphone/api/c-call-cbs.h b/include/linphone/api/c-call-cbs.h index 5bd6f7c52..3901702fc 100644 --- a/include/linphone/api/c-call-cbs.h +++ b/include/linphone/api/c-call-cbs.h @@ -159,6 +159,20 @@ LINPHONE_PUBLIC LinphoneCallCbsAckProcessingCb linphone_call_cbs_get_ack_process */ LINPHONE_PUBLIC void linphone_call_cbs_set_ack_processing (LinphoneCallCbs *cbs, LinphoneCallCbsAckProcessingCb cb); +/** + * Get the TMMBR received callback. + * @param[in] cbs LinphoneCallCbs object. + * @return The current TMMBR received callback. + */ +LINPHONE_PUBLIC LinphoneCallCbsTmmbrReceivedCb linphone_call_cbs_get_tmmbr_received(LinphoneCallCbs *cbs); + +/** + * Set the TMMBR received callback. + * @param[in] cbs LinphoneCallCbs object. + * @param[in] cb The TMMBR received callback to be used. + */ +LINPHONE_PUBLIC void linphone_call_cbs_set_tmmbr_received(LinphoneCallCbs *cbs, LinphoneCallCbsTmmbrReceivedCb cb); + /** * @} */ diff --git a/include/linphone/api/c-callbacks.h b/include/linphone/api/c-callbacks.h index dc36e2b48..c7e147c2a 100644 --- a/include/linphone/api/c-callbacks.h +++ b/include/linphone/api/c-callbacks.h @@ -87,6 +87,14 @@ typedef void (*LinphoneCallCbsTransferStateChangedCb)(LinphoneCall *call, Linpho */ typedef void (*LinphoneCallCbsAckProcessingCb)(LinphoneCall *call, LinphoneHeaders *ack, bool_t is_received); +/** + * Callback for notifying a received TMMBR. + * @param call LinphoneCall for which the TMMBR has changed + * @param stream_index the index of the current stream + * @param tmmbr the value of the received TMMBR + */ +typedef void (*LinphoneCallCbsTmmbrReceivedCb)(LinphoneCall *call, int stream_index, int tmmbr); + /** * @} **/ @@ -105,7 +113,7 @@ typedef void (*LinphoneCallCbsAckProcessingCb)(LinphoneCall *call, LinphoneHeade * @deprecated Use #LinphoneChatMessageCbsMsgStateChangedCb instead. * @donotwrap */ -typedef void (*LinphoneChatMessageStateChangedCb)(LinphoneChatMessage* msg,LinphoneChatMessageState state,void* ud); +typedef void (*LinphoneChatMessageStateChangedCb)(LinphoneChatMessage* msg, LinphoneChatMessageState state, void* ud); /** * Call back used to notify message delivery status @@ -239,7 +247,7 @@ typedef void (*LinphoneChatRoomCbsConferenceAddressGenerationCb) (LinphoneChatRo * @param[in] cr #LinphoneChatRoom object * @param[in] participantAddr #LinphoneAddress object */ -typedef void (*LinphoneChatRoomCbsParticipantDeviceFetchedCb) (LinphoneChatRoom *cr, const LinphoneAddress *participantAddr); +typedef void (*LinphoneChatRoomCbsParticipantDeviceFetchRequestedCb) (LinphoneChatRoom *cr, const LinphoneAddress *participantAddr); /** * Callback used when a group chat room server is checking participants capabilities. @@ -249,6 +257,27 @@ typedef void (*LinphoneChatRoomCbsParticipantDeviceFetchedCb) (LinphoneChatRoom */ typedef void (*LinphoneChatRoomCbsParticipantsCapabilitiesCheckedCb) (LinphoneChatRoom *cr, const LinphoneAddress *deviceAddr, const bctbx_list_t *participantsAddr); +/** + * Callback used when a group chat room server is subscribing to registration state of a participant. + * @param[in] cr #LinphoneChatRoom object + * @param[in] participantAddr #LinphoneAddress object + */ +typedef void (*LinphoneChatRoomCbsParticipantRegistrationSubscriptionRequestedCb) (LinphoneChatRoom *cr, const LinphoneAddress *participantAddr); + +/** + * Callback used when a group chat room server is unsubscribing to registration state of a participant. + * @param[in] cr #LinphoneChatRoom object + * @param[in] participantAddr #LinphoneAddress object + */ +typedef void (*LinphoneChatRoomCbsParticipantRegistrationUnsubscriptionRequestedCb) (LinphoneChatRoom *cr, const LinphoneAddress *participantAddr); + +/** + * Callback used to tell the core whether or not to store the incoming message in db or not using linphone_chat_message_set_to_be_stored(). + * @param[in] cr #LinphoneChatRoom object + * @param[in] msg The #LinphoneChatMessage that is being received + */ +typedef void (*LinphoneChatRoomCbsShouldChatMessageBeStoredCb) (LinphoneChatRoom *cr, LinphoneChatMessage *msg); + /** * @} **/ diff --git a/include/linphone/api/c-chat-message.h b/include/linphone/api/c-chat-message.h index 71e98a0ba..c77ddfc87 100644 --- a/include/linphone/api/c-chat-message.h +++ b/include/linphone/api/c-chat-message.h @@ -362,6 +362,13 @@ LINPHONE_PUBLIC bool_t linphone_chat_message_has_text_content(const LinphoneChat */ LINPHONE_PUBLIC const char* linphone_chat_message_get_text_content(const LinphoneChatMessage *msg); +/** + * Gets whether or not a file is currently being downloaded or uploaded + * @param[in] msg LinphoneChatMessage object + * @return true if download or upload is in progress, false otherwise + */ +LINPHONE_PUBLIC bool_t linphone_chat_message_is_file_transfer_in_progress(LinphoneChatMessage *msg); + /** * @} */ diff --git a/include/linphone/api/c-chat-room-cbs.h b/include/linphone/api/c-chat-room-cbs.h index 1be433b15..e0f772904 100644 --- a/include/linphone/api/c-chat-room-cbs.h +++ b/include/linphone/api/c-chat-room-cbs.h @@ -244,18 +244,18 @@ LINPHONE_PUBLIC LinphoneChatRoomCbsConferenceAddressGenerationCb linphone_chat_r LINPHONE_PUBLIC void linphone_chat_room_cbs_set_conference_address_generation (LinphoneChatRoomCbs *cbs, LinphoneChatRoomCbsConferenceAddressGenerationCb cb); /** - * Get the participant device getting callback. + * Get the participant device fetching callback. * @param[in] cbs #LinphoneChatRoomCbs object - * @return The participant device getting callback + * @return The participant device fetching callback */ -LINPHONE_PUBLIC LinphoneChatRoomCbsParticipantDeviceFetchedCb linphone_chat_room_cbs_get_participant_device_fetched (const LinphoneChatRoomCbs *cbs); +LINPHONE_PUBLIC LinphoneChatRoomCbsParticipantDeviceFetchRequestedCb linphone_chat_room_cbs_get_participant_device_fetch_requested (const LinphoneChatRoomCbs *cbs); /** - * Set the participant device getting callback. + * Set the participant device fetching callback. * @param[in] cbs #LinphoneChatRoomCbs object - * @param[in] cb The participant device getting callback to be used + * @param[in] cb The participant device fetching callback to be used */ -LINPHONE_PUBLIC void linphone_chat_room_cbs_set_participant_device_fetched (LinphoneChatRoomCbs *cbs, LinphoneChatRoomCbsParticipantDeviceFetchedCb cb); +LINPHONE_PUBLIC void linphone_chat_room_cbs_set_participant_device_fetch_requested (LinphoneChatRoomCbs *cbs, LinphoneChatRoomCbsParticipantDeviceFetchRequestedCb cb); /** * Get the participants capabilities callback. @@ -271,6 +271,47 @@ LINPHONE_PUBLIC LinphoneChatRoomCbsParticipantsCapabilitiesCheckedCb linphone_ch */ LINPHONE_PUBLIC void linphone_chat_room_cbs_set_participants_capabilities_checked (LinphoneChatRoomCbs *cbs, LinphoneChatRoomCbsParticipantsCapabilitiesCheckedCb cb); +/** + * Get the participant registration subscription callback. + * @param[in] cbs LinphoneChatRoomCbs object + * @return The participant registration subscription callback + */ +LINPHONE_PUBLIC LinphoneChatRoomCbsParticipantRegistrationSubscriptionRequestedCb linphone_chat_room_cbs_get_participant_registration_subscription_requested (const LinphoneChatRoomCbs *cbs); + +/** + * Set the participant registration subscription callback. + * @param[in] cbs LinphoneChatRoomCbs object + * @param[in] cb The participant registration subscription callback to be used + */ +LINPHONE_PUBLIC void linphone_chat_room_cbs_set_participant_registration_subscription_requested (LinphoneChatRoomCbs *cbs, LinphoneChatRoomCbsParticipantRegistrationSubscriptionRequestedCb cb); + +/** + * Get the participant registration unsubscription callback. + * @param[in] cbs LinphoneChatRoomCbs object + * @return The participant registration unsubscription callback + */ +LINPHONE_PUBLIC LinphoneChatRoomCbsParticipantRegistrationUnsubscriptionRequestedCb linphone_chat_room_cbs_get_participant_registration_unsubscription_requested (const LinphoneChatRoomCbs *cbs); + +/** + * Set the participant registration unsubscription callback. + * @param[in] cbs LinphoneChatRoomCbs object + * @param[in] cb The participant registration unsubscription callback to be used + */ +LINPHONE_PUBLIC void linphone_chat_room_cbs_set_participant_registration_unsubscription_requested (LinphoneChatRoomCbs *cbs, LinphoneChatRoomCbsParticipantRegistrationUnsubscriptionRequestedCb cb); + +/** + * Get the message should be stored callback. + * @param[in] cbs LinphoneChatRoomCbs object + * @return The message should be stored getting callback + */ +LINPHONE_PUBLIC LinphoneChatRoomCbsShouldChatMessageBeStoredCb linphone_chat_room_cbs_get_chat_message_should_be_stored( LinphoneChatRoomCbs *cbs); +/** + * Set the message should be stored callback. + * @param[in] cbs LinphoneChatRoomCbs object + * @param[in] cb The message should be stored callback to be used + */ +LINPHONE_PUBLIC void linphone_chat_room_cbs_set_chat_message_should_be_stored( LinphoneChatRoomCbs *cbs, LinphoneChatRoomCbsShouldChatMessageBeStoredCb cb); + /** * @} */ diff --git a/include/linphone/api/c-chat-room.h b/include/linphone/api/c-chat-room.h index c83ec6328..1e4d41155 100644 --- a/include/linphone/api/c-chat-room.h +++ b/include/linphone/api/c-chat-room.h @@ -179,6 +179,23 @@ LINPHONE_PUBLIC bctbx_list_t *linphone_chat_room_get_history (LinphoneChatRoom * */ LINPHONE_PUBLIC bctbx_list_t *linphone_chat_room_get_history_range(LinphoneChatRoom *cr, int begin, int end); +/** + * Gets nb_events most recent chat message events from cr chat room, sorted from oldest to most recent. + * @param[in] cr The #LinphoneChatRoom object corresponding to the conversation for which events should be retrieved + * @param[in] nb_events Number of events to retrieve. 0 means everything. + * @return \bctbx_list{LinphoneEventLog} + */ +LINPHONE_PUBLIC bctbx_list_t *linphone_chat_room_get_history_message_events (LinphoneChatRoom *cr, int nb_events); + +/** + * Gets the partial list of chat message events in the given range, sorted from oldest to most recent. + * @param[in] cr The #LinphoneChatRoom object corresponding to the conversation for which events should be retrieved + * @param[in] begin The first event of the range to be retrieved. History most recent event has index 0. + * @param[in] end The last event of the range to be retrieved. History oldest event has index of history size - 1 + * @return \bctbx_list{LinphoneEventLog} + */ +LINPHONE_PUBLIC bctbx_list_t *linphone_chat_room_get_history_range_message_events (LinphoneChatRoom *cr, int begin, int end); + /** * Gets nb_events most recent events from cr chat room, sorted from oldest to most recent. * @param[in] cr The #LinphoneChatRoom object corresponding to the conversation for which events should be retrieved @@ -261,11 +278,27 @@ LINPHONE_PUBLIC bool_t linphone_chat_room_lime_available(LinphoneChatRoom *cr); LINPHONE_PUBLIC LinphoneCall *linphone_chat_room_get_call(const LinphoneChatRoom *room); /** - * Get the #LinphoneChatRoomCbs object associated with the LinphoneChatRoom. - * @param[in] cr #LinphoneChatRoom object - * @return The #LinphoneChatRoomCbs object associated with the #LinphoneChatRoom + * Add a listener in order to be notified of #LinphoneChatRoom events. Once an event is received, registred #LinphoneChatRoomCbs are + * invoked sequencially. + * @param[in] call #LinphoneChatRoom object to monitor. + * @param[in] cbs A #LinphoneChatRoomCbs object holding the callbacks you need. A reference is taken by the #LinphoneChatRoom until you invoke linphone_call_remove_callbacks(). */ -LINPHONE_PUBLIC LinphoneChatRoomCbs * linphone_chat_room_get_callbacks (const LinphoneChatRoom *cr); +LINPHONE_PUBLIC void linphone_chat_room_add_callbacks(LinphoneChatRoom *cr, LinphoneChatRoomCbs *cbs); + +/** + * Remove a listener from a LinphoneChatRoom + * @param[in] call LinphoneChatRoom object + * @param[in] cbs LinphoneChatRoomCbs object to remove. + */ +LINPHONE_PUBLIC void linphone_chat_room_remove_callbacks(LinphoneChatRoom *cr, LinphoneChatRoomCbs *cbs); + +/** + * Gets the current LinphoneChatRoomCbs. + * This is meant only to be called from a callback to be able to get the user_data associated with the LinphoneChatRoomCbs that is calling the callback. + * @param[in] call LinphoneChatRoom object + * @return The LinphoneChatRoomCbs that has called the last callback + */ +LINPHONE_PUBLIC LinphoneChatRoomCbs *linphone_chat_room_get_current_callbacks(const LinphoneChatRoom *cr); /** * Get the state of the chat room. @@ -281,6 +314,13 @@ LINPHONE_PUBLIC LinphoneChatRoomState linphone_chat_room_get_state (const Linpho */ LINPHONE_PUBLIC bool_t linphone_chat_room_has_been_left (const LinphoneChatRoom *cr); +/** + * Return the last updated time for the chat room + * @param[in] cr LinphoneChatRoom object + * @return the last updated time + */ +LINPHONE_PUBLIC time_t linphone_chat_room_get_last_update_time(const LinphoneChatRoom *cr); + /** * Add a participant to a chat room. This may fail if this type of chat room does not handle participants. * Use linphone_chat_room_can_handle_participants() to know if this chat room handles participants. @@ -414,13 +454,22 @@ LINPHONE_PUBLIC void linphone_chat_room_set_conference_address (LinphoneChatRoom /** * Set the participant device. This function needs to be called from the - * #LinphoneChatRoomCbsParticipantDeviceFetchedCb callback and only there. + * #LinphoneChatRoomCbsParticipantDeviceFetchRequestedCb callback and only there. * @param[in] cr A #LinphoneChatRoom object * @param[in] partAddr The participant address * @param[in] partDevices \bctbx_list{LinphoneAddress} list of the participant devices to be used by the group chat room */ LINPHONE_PUBLIC void linphone_chat_room_set_participant_devices (LinphoneChatRoom *cr, const LinphoneAddress *partAddr, const bctbx_list_t *partDevices); +/** + * Add a participant device. + * This is to used if a new device registers itself after the chat room creation. + * @param[in] cr A #LinphoneChatRoom object + * @param[in] participantAddress The address of the participant for which a new device is to be added + * @param[in] deviceAddress The address of the new device to be added + */ +LINPHONE_PUBLIC void linphone_chat_room_add_participant_device (LinphoneChatRoom *cr, const LinphoneAddress *participantAddress, const LinphoneAddress *deviceAddress); + /** * Set the participant device. This function needs to be called from the * #LinphoneChatRoomCbsParticipantsCapabilitiesCheckedCb callback and only there. @@ -437,14 +486,6 @@ LINPHONE_PUBLIC void linphone_chat_room_add_compatible_participants (LinphoneCha **/ LINPHONE_PUBLIC LINPHONE_DEPRECATED LinphoneCore* linphone_chat_room_get_lc(const LinphoneChatRoom *cr); -/** - * Destroy a LinphoneChatRoom. - * @param cr #LinphoneChatRoom object - * @deprecated Use linphone_chat_room_unref() instead. - * @donotwrap - */ -LINPHONE_PUBLIC LINPHONE_DEPRECATED void linphone_chat_room_destroy(LinphoneChatRoom *cr); - /** * @} */ diff --git a/include/linphone/api/c-magic-search.h b/include/linphone/api/c-magic-search.h new file mode 100644 index 000000000..e45bc2605 --- /dev/null +++ b/include/linphone/api/c-magic-search.h @@ -0,0 +1,143 @@ +/* + * c-magic-search.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_MAGIC_SEARCH_H_ +#define _L_C_MAGIC_SEARCH_H_ + +#include "linphone/api/c-types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @addtogroup misc + * @{ + */ + +/** + * Constructs a LinphoneMagicSearch object + **/ +LINPHONE_PUBLIC LinphoneMagicSearch *linphone_magic_search_new(LinphoneCore *lc); + +/** + * Increment reference count of LinphoneMagicSearch object. + **/ +LINPHONE_PUBLIC LinphoneMagicSearch *linphone_magic_search_ref(LinphoneMagicSearch *magicSearch); + +/** + * Decrement reference count of LinphoneMagicSearch object. When dropped to zero, memory is freed. + **/ +LINPHONE_PUBLIC void linphone_magic_search_unref(LinphoneMagicSearch *magicSearch); + +/** + * Set the minimum value used to calculate the weight in search + * @param[in] weight minimum weight + **/ +LINPHONE_PUBLIC void linphone_magic_search_set_min_weight(LinphoneMagicSearch *magicSearch, const unsigned int weight); + +/** + * @return the minimum value used to calculate the weight in search + **/ +LINPHONE_PUBLIC unsigned int linphone_magic_search_get_min_weight(const LinphoneMagicSearch *magicSearch); + +/** + * Set the maximum value used to calculate the weight in search + * @param[in] weight maximum weight + **/ +LINPHONE_PUBLIC void linphone_magic_search_set_max_weight(LinphoneMagicSearch *magicSearch, const unsigned int weight); + +/** + * @return the maximum value used to calculate the weight in search + **/ +LINPHONE_PUBLIC unsigned int linphone_magic_search_get_max_weight(const LinphoneMagicSearch *magicSearch); + +/** + * @return the delimiter used to find matched filter word + **/ +LINPHONE_PUBLIC const char *linphone_magic_search_get_delimiter(const LinphoneMagicSearch *magicSearch); + +/** + * Set the delimiter used to find matched filter word + * @param[in] delimiter delimiter (example "-_.,") + **/ +LINPHONE_PUBLIC void linphone_magic_search_set_delimiter(LinphoneMagicSearch *magicSearch, const char *delimiter); + +/** + * @return if the delimiter search is used + **/ +LINPHONE_PUBLIC bool_t linphone_magic_search_get_use_delimiter(LinphoneMagicSearch *magicSearch); + +/** + * Enable or disable the delimiter in search + * @param[in] enable + **/ +LINPHONE_PUBLIC void linphone_magic_search_set_use_delimiter(LinphoneMagicSearch *magicSearch, bool_t enable); + +/** + * @return the number of the maximum SearchResult which will be return + **/ +LINPHONE_PUBLIC unsigned int linphone_magic_search_get_search_limit(const LinphoneMagicSearch *magicSearch); + +/** + * Set the number of the maximum SearchResult which will be return + * @param[in] limit + **/ +LINPHONE_PUBLIC void linphone_magic_search_set_search_limit(LinphoneMagicSearch *magicSearch, const unsigned int limit); + +/** + * @return if the search is limited + **/ +LINPHONE_PUBLIC bool_t linphone_magic_search_get_limited_search(const LinphoneMagicSearch *magicSearch); + +/** + * Enable or disable the limited search + * @param[in] limited + **/ +LINPHONE_PUBLIC void linphone_magic_search_set_limited_search(LinphoneMagicSearch *magicSearch, const bool_t limited); + +/** + * Reset the cache to begin a new search + **/ +LINPHONE_PUBLIC void linphone_magic_search_reset_search_cache(LinphoneMagicSearch *magicSearch); + +/** + * Create a sorted list of SearchResult from SipUri, Contact name, + * Contact displayname, Contact phone number, which match with a filter word + * The last item list will be an address formed with "filter" if a proxy config exist + * During the first search, a cache is created and used for the next search + * Use linphone_magic_search_reset_search_cache() to begin a new search + * @param[in] filter word we search + * @param[in] withDomain domain which we want to search only + * - "" for searching in all contact + * - "*" for searching in contact with sip SipUri + * - "yourdomain" for searching in contact from "yourdomain" domain + * @return sorted list of \bctbx_list{LinphoneSearchResult} + **/ +LINPHONE_PUBLIC bctbx_list_t* linphone_magic_search_get_contact_list_from_filter(LinphoneMagicSearch *magicSearch, const char *filter, const char *withDomain); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif // _L_C_MAGIC_SEARCH_H_ diff --git a/include/linphone/api/c-search-result.h b/include/linphone/api/c-search-result.h new file mode 100644 index 000000000..7a4a62485 --- /dev/null +++ b/include/linphone/api/c-search-result.h @@ -0,0 +1,67 @@ +/* + * c-search-result.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_SEARCH_RESULT_H_ +#define _L_C_SEARCH_RESULT_H_ + +#include "linphone/api/c-types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @addtogroup misc + * @{ + */ + +/** + * Increment reference count of LinphoneSearchResult object. + **/ +LINPHONE_PUBLIC LinphoneSearchResult *linphone_search_result_ref(LinphoneSearchResult *searchResult); + +/** + * Decrement reference count of LinphoneSearchResult object. When dropped to zero, memory is freed. + **/ +LINPHONE_PUBLIC void linphone_search_result_unref(LinphoneSearchResult *searchResult); + +/** + * @return LinphoneFriend associed + **/ +LINPHONE_PUBLIC const LinphoneFriend* linphone_search_result_get_friend(const LinphoneSearchResult *searchResult); + +/** + * @return LinphoneAddress associed + **/ +LINPHONE_PUBLIC const LinphoneAddress* linphone_search_result_get_address(const LinphoneSearchResult *searchResult); + +/** + * @return the result weight + **/ +LINPHONE_PUBLIC unsigned int linphone_search_result_get_weight(const LinphoneSearchResult *searchResult); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif // _L_C_SEARCH_RESULT_H_ diff --git a/include/linphone/api/c-types.h b/include/linphone/api/c-types.h index f3024abc7..c28e017f6 100644 --- a/include/linphone/api/c-types.h +++ b/include/linphone/api/c-types.h @@ -151,11 +151,22 @@ typedef struct _LinphoneEventLog LinphoneEventLog; typedef struct _LinphoneDialPlan LinphoneDialPlan; /** - * The #LinphoneParticipant object represents a participant of a conference. + * A #LinphoneMagicSearch is used to do specifics searchs + * @ingroup misc +**/ +typedef struct _LinphoneMagicSearch LinphoneMagicSearch; + +/** * @ingroup misc **/ typedef struct _LinphoneParticipant LinphoneParticipant; +/** + * The LinphoneSearchResult object represents a result of a search + * @ingroup misc + **/ +typedef struct _LinphoneSearchResult LinphoneSearchResult; + // ============================================================================= // C Enums. // ============================================================================= diff --git a/include/linphone/call_log.h b/include/linphone/call_log.h index 43a2a3e7c..5e0d84e52 100644 --- a/include/linphone/call_log.h +++ b/include/linphone/call_log.h @@ -90,12 +90,19 @@ LINPHONE_PUBLIC float linphone_call_log_get_quality(const LinphoneCallLog *cl); **/ LINPHONE_PUBLIC const char * linphone_call_log_get_ref_key(const LinphoneCallLog *cl); +/** + * Get the local address (that is from or to depending on call direction) + * @param[in] cl LinphoneCallLog object + * @return The local address of the call + */ +LINPHONE_PUBLIC const LinphoneAddress *linphone_call_log_get_local_address(const LinphoneCallLog *cl); + /** * Get the remote address (that is from or to depending on call direction). * @param[in] cl #LinphoneCallLog object * @return The remote address of the call. **/ -LINPHONE_PUBLIC LinphoneAddress * linphone_call_log_get_remote_address(const LinphoneCallLog *cl); +LINPHONE_PUBLIC const LinphoneAddress * linphone_call_log_get_remote_address(const LinphoneCallLog *cl); /** * Get the RTP statistics computed by the remote end and sent back via RTCP. @@ -199,6 +206,23 @@ LINPHONE_PUBLIC LinphoneCallLog * linphone_call_log_ref(LinphoneCallLog *cl); **/ LINPHONE_PUBLIC void linphone_call_log_unref(LinphoneCallLog *cl); +/** + * Creates a fake LinphoneCallLog. + * @param[in] lc LinphoneCore object + * @param[in] from LinphoneAddress of caller + * @param[in] to LinphoneAddress of callee + * @param[in] dir LinphoneCallDir of call + * @param[in] duration call length in seconds + * @param[in] start_time timestamp of call start time + * @param[in] connected_time timestamp of call connection + * @param[in] status LinphoneCallStatus of call + * @param[in] video_enabled whether video was enabled or not for this call + * @param[in] quality call quality + * @return LinphoneCallLog object +**/ +LINPHONE_PUBLIC LinphoneCallLog *linphone_core_create_call_log(LinphoneCore *lc, LinphoneAddress *from, LinphoneAddress *to, LinphoneCallDir dir, + int duration, time_t start_time, time_t connected_time, LinphoneCallStatus status, bool_t video_enabled, float quality); + /******************************************************************************* * DEPRECATED * diff --git a/include/linphone/callbacks.h b/include/linphone/callbacks.h index f6833baa1..319a9a4a9 100644 --- a/include/linphone/callbacks.h +++ b/include/linphone/callbacks.h @@ -448,6 +448,21 @@ typedef LinphoneCoreCbsPublishStateChangedCb LinphoneCorePublishStateChangedCb; * @} **/ +/** + * @addtogroup event_api + * @{ + */ + +/** + * Callback used to notify the response to a sent NOTIFY + * @param ev The LinphoneEvent object that has sent the NOTIFY and for which we received a response +**/ +typedef void (*LinphoneEventCbsNotifyResponseCb)(const LinphoneEvent *ev); + +/** + * @} + */ + /** * @addtogroup buddy_list * @{ diff --git a/include/linphone/core.h b/include/linphone/core.h index 82ed102e3..50f627403 100644 --- a/include/linphone/core.h +++ b/include/linphone/core.h @@ -115,6 +115,14 @@ LINPHONE_PUBLIC LinphonePlayer *linphone_core_create_local_player(LinphoneCore * **/ LINPHONE_PUBLIC LinphoneInfoMessage *linphone_core_create_info_message(LinphoneCore*lc); +/** + * Create a #LinphoneMagicSearch object. + * @param[in] lc #LinphoneCore object + * @return The create #LinphoneMagicSearch object + * @ingroup misc + */ +LINPHONE_PUBLIC LinphoneMagicSearch *linphone_core_create_magic_search(LinphoneCore *lc); + /** * Checks if a new version of the application is available. * @param lc #LinphoneCore object @@ -2009,7 +2017,7 @@ LINPHONE_PUBLIC LINPHONE_DEPRECATED int linphone_core_get_default_proxy(Linphone * @param[in] lc #LinphoneCore object * @return The default proxy configuration. **/ -LINPHONE_PUBLIC LinphoneProxyConfig * linphone_core_get_default_proxy_config(LinphoneCore *lc); +LINPHONE_PUBLIC LinphoneProxyConfig * linphone_core_get_default_proxy_config(const LinphoneCore *lc); /** * Sets the default proxy. @@ -4244,20 +4252,6 @@ LINPHONE_PUBLIC LinphoneStatus linphone_core_stop_conference_recording(LinphoneC */ LINPHONE_PUBLIC LinphoneConference *linphone_core_get_conference(LinphoneCore *lc); -/** - * Set the conference factory uri. - * @param[in] lc A #LinphoneCore object - * @param[in] uri The uri of the conference factory - */ -void linphone_core_set_conference_factory_uri(LinphoneCore *lc, const char *uri); - -/** - * Get the conference factory uri. - * @param[in] lc A #LinphoneCore object - * @return The uri of the conference factory - */ -const char * linphone_core_get_conference_factory_uri(const LinphoneCore *lc); - /** * Enable the conference server feature. This has the effect to listen of the conference factory uri * to create new conferences when receiving INVITE messages there. diff --git a/include/linphone/event.h b/include/linphone/event.h index a379f8de1..a2306edbc 100644 --- a/include/linphone/event.h +++ b/include/linphone/event.h @@ -20,6 +20,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #ifndef LINPHONE_EVENT_H_ #define LINPHONE_EVENT_H_ +#include "linphone/callbacks.h" #include "linphone/types.h" #ifdef __cplusplus @@ -203,6 +204,55 @@ LINPHONE_PUBLIC const LinphoneAddress *linphone_event_get_remote_contact (const **/ LINPHONE_PUBLIC LinphoneCore *linphone_event_get_core(const LinphoneEvent *lev); +/** + * Get the LinphoneEventCbs object associated with a LinphoneEvent. + * @param[in] ev LinphoneEvent object + * @return The LinphoneEventCbs object associated with the LinphoneEvent. +**/ + +LINPHONE_PUBLIC LinphoneEventCbs *linphone_event_get_callbacks(const LinphoneEvent *ev); + +/** + * Acquire a reference to a LinphoneEventCbs object. + * @param[in] cbs LinphoneEventCbs object. + * @return The same LinphoneEventCbs object. +**/ +LINPHONE_PUBLIC LinphoneEventCbs *linphone_event_cbs_ref(LinphoneEventCbs *cbs); + +/** + * Release a reference to a LinphoneEventCbs object. + * @param[in] cbs LinphoneEventCbs object. +**/ +LINPHONE_PUBLIC void linphone_event_cbs_unref(LinphoneEventCbs *cbs); + +/** + * Retrieve the user pointer associated with a LinphoneEventCbs object. + * @param[in] cbs LinphoneEventCbs object. + * @return The user pointer associated with the LinphoneEventCbs object. +**/ +LINPHONE_PUBLIC void *linphone_event_cbs_get_user_data(const LinphoneEventCbs *cbs); + +/** + * Assign a user pointer to a LinphoneEventCbs object. + * @param[in] cbs LinphoneEventCbs object. + * @param[in] ud The user pointer to associate with the LinphoneEventCbs object. +**/ +LINPHONE_PUBLIC void linphone_event_cbs_set_user_data(LinphoneEventCbs *cbs, void *ud); + +/** + * Get the notify response callback. + * @param[in] cbs LinphoneEventCbs object. + * @return The current notify response callback. +**/ +LINPHONE_PUBLIC LinphoneEventCbsNotifyResponseCb linphone_event_cbs_get_notify_response(const LinphoneEventCbs *cbs); + +/** + * Set the notify response callback. + * @param[in] cbs LinphoneEventCbs object. + * @param[in] cb The notify response callback to be used. +**/ +LINPHONE_PUBLIC void linphone_event_cbs_set_notify_response(LinphoneEventCbs *cbs, LinphoneEventCbsNotifyResponseCb cb); + /** * @} **/ diff --git a/include/linphone/factory.h b/include/linphone/factory.h index 9686f96ea..7722ea612 100644 --- a/include/linphone/factory.h +++ b/include/linphone/factory.h @@ -224,6 +224,13 @@ LINPHONE_PUBLIC LinphoneAuthInfo *linphone_factory_create_auth_info(const Linpho */ LINPHONE_PUBLIC LinphoneCallCbs * linphone_factory_create_call_cbs(const LinphoneFactory *factory); +/** + * Create a LinphoneChatRoomCbs object that holds callbacks for events happening on a chat room. + * @param[in] factory LinphoneFactory singletion object + * @return A new LinphoneChatRoomCbs object + */ +LINPHONE_PUBLIC LinphoneChatRoomCbs * linphone_factory_create_chat_room_cbs(const LinphoneFactory *factory); + /** * Create an empty #LinphoneVcard. * @return a new #LinphoneVcard. diff --git a/include/linphone/friend.h b/include/linphone/friend.h index b87636260..4717ef16f 100644 --- a/include/linphone/friend.h +++ b/include/linphone/friend.h @@ -116,7 +116,7 @@ LINPHONE_PUBLIC void linphone_friend_add_phone_number(LinphoneFriend *lf, const * @param lf #LinphoneFriend object * @return \bctbx_list{const char *} */ -LINPHONE_PUBLIC bctbx_list_t* linphone_friend_get_phone_numbers(LinphoneFriend *lf); +LINPHONE_PUBLIC bctbx_list_t* linphone_friend_get_phone_numbers(const LinphoneFriend *lf); /** * Removes a phone number in this friend @@ -306,7 +306,7 @@ LINPHONE_PUBLIC LinphoneCore *linphone_friend_get_core(const LinphoneFriend *fr) * Returns the vCard object associated to this friend, if any * @param[in] fr #LinphoneFriend object */ -LINPHONE_PUBLIC LinphoneVcard* linphone_friend_get_vcard(LinphoneFriend *fr); +LINPHONE_PUBLIC LinphoneVcard* linphone_friend_get_vcard(const LinphoneFriend *fr); /** * Binds a vCard object to a friend diff --git a/include/linphone/proxy_config.h b/include/linphone/proxy_config.h index 6f5861f0b..127b4b0d4 100644 --- a/include/linphone/proxy_config.h +++ b/include/linphone/proxy_config.h @@ -99,6 +99,16 @@ LINPHONE_PUBLIC LinphoneStatus linphone_proxy_config_set_identity_address(Linpho **/ LINPHONE_PUBLIC LinphoneStatus linphone_proxy_config_set_route(LinphoneProxyConfig *cfg, const char *route); +/** + * Sets a list of SIP route. + * When a route is set, all outgoing calls will go to the route's destination if this proxy + * is the default one (see linphone_core_set_default_proxy() ). + * @param[in] cfg the #LinphoneProxyConfig + * @param[in] routes A \bctbx_list{const char *} of routes + * @return -1 if routes are invalid, 0 otherwise. +**/ +LINPHONE_PUBLIC LinphoneStatus linphone_proxy_config_set_routes(LinphoneProxyConfig *cfg, const bctbx_list_t *routes); + /** * Sets the registration expiration time in seconds. **/ @@ -257,9 +267,17 @@ LINPHONE_PUBLIC void linphone_proxy_config_set_realm(LinphoneProxyConfig *cfg, c /** * @return the route set for this proxy configuration. + * @deprecated Use linphone_proxy_config_get_routes() instead. **/ LINPHONE_PUBLIC const char *linphone_proxy_config_get_route(const LinphoneProxyConfig *cfg); +/** + * Gets the list of the routes set for this proxy config. + * @param[in] cfg #LinphoneProxyConfig object. + * @return \bctbx_list{const char *} the list of routes. + */ +LINPHONE_PUBLIC const bctbx_list_t* linphone_proxy_config_get_routes(const LinphoneProxyConfig *cfg); + /** * @return the SIP identity that belongs to this proxy configuration. **/ @@ -585,6 +603,20 @@ LINPHONE_PUBLIC LinphoneNatPolicy * linphone_proxy_config_get_nat_policy(const L */ LINPHONE_PUBLIC void linphone_proxy_config_set_nat_policy(LinphoneProxyConfig *cfg, LinphoneNatPolicy *policy); +/** + * Set the conference factory uri. + * @param[in] cfg A #LinphoneProxyConfig object + * @param[in] uri The uri of the conference factory + */ +void linphone_proxy_config_set_conference_factory_uri(LinphoneProxyConfig *cfg, const char *uri); + +/** + * Get the conference factory uri. + * @param[in] cfg A #LinphoneProxyConfig object + * @return The uri of the conference factory + */ +const char * linphone_proxy_config_get_conference_factory_uri(const LinphoneProxyConfig *cfg); + /** * @} */ diff --git a/include/linphone/types.h b/include/linphone/types.h index defbeb454..bd408358b 100644 --- a/include/linphone/types.h +++ b/include/linphone/types.h @@ -420,6 +420,12 @@ typedef struct _LinphoneErrorInfo LinphoneErrorInfo; **/ typedef struct _LinphoneEvent LinphoneEvent; +/** + * An object to handle the callbacks for handling the LinphoneEvent operations. + * @ingroup event_api +**/ +typedef struct _LinphoneEventCbs LinphoneEventCbs; + /** * #LinphoneFactory is a singleton object devoted to the creation of all the object * of Liblinphone that cannot created by #LinphoneCore or #LinphoneCore itself. diff --git a/include/linphone/utils/enum-mask.h b/include/linphone/utils/enum-mask.h index a385401f3..53eac2c5f 100644 --- a/include/linphone/utils/enum-mask.h +++ b/include/linphone/utils/enum-mask.h @@ -138,10 +138,10 @@ private: return (mMask & value) == value && (value || mMask == 0); } -// On CentOs 7 GCC 4.8.5 have issue with array-bounds +// On CentOs 7 GCC 4.8.5 have issue with array-bounds. #if __GNUC__ == 4 && __GNUC_MINOR__ == 8 && __GNUC_PATCHLEVEL__ == 5 -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Warray-bounds" + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Warray-bounds" #endif static constexpr StorageType init ( @@ -152,7 +152,7 @@ private: } #if __GNUC__ == 4 && __GNUC_MINOR__ == 8 && __GNUC_PATCHLEVEL__ == 5 -#pragma GCC diagnostic pop + #pragma GCC diagnostic pop #endif StorageType mMask; diff --git a/include/linphone/utils/general.h b/include/linphone/utils/general.h index c1970637d..26c99603e 100644 --- a/include/linphone/utils/general.h +++ b/include/linphone/utils/general.h @@ -27,6 +27,10 @@ // ============================================================================= +// ----------------------------------------------------------------------------- +// Namespace. +// ----------------------------------------------------------------------------- + #ifdef __cplusplus #define LINPHONE_BEGIN_NAMESPACE namespace LinphonePrivate { #define LINPHONE_END_NAMESPACE } @@ -75,10 +79,10 @@ LINPHONE_BEGIN_NAMESPACE // Debug. // ----------------------------------------------------------------------------- -void l_assert (const char *condition, const char *file, int line); +void lAssert (const char *condition, const char *file, int line); #ifdef DEBUG - #define L_ASSERT(CONDITION) ((CONDITION) ? static_cast(0) : LinphonePrivate::l_assert(#CONDITION, __FILE__, __LINE__)) + #define L_ASSERT(CONDITION) ((CONDITION) ? static_cast(0) : LinphonePrivate::lAssert(#CONDITION, __FILE__, __LINE__)) #else #define L_ASSERT(CONDITION) static_cast(false && (CONDITION)) #endif @@ -100,16 +104,28 @@ void l_assert (const char *condition, const char *file, int line); // ----------------------------------------------------------------------------- // Define an integer version like: 0xXXYYZZ, XX=MAJOR, YY=MINOR, and ZZ=PATCH. -#define L_VERSION(MAJOR, MINOR, PATCH) (((MAJOR) << 16) | ((MINOR) << 8) | (PATCH)) +constexpr unsigned int makeVersion (unsigned int major, unsigned int minor, unsigned int patch) { + return ((major << 16) | (minor << 8) | patch); +} // Not available in C++11... template -std::unique_ptr make_unique(Args && ...args) { +std::unique_ptr makeUnique(Args && ...args) { return std::unique_ptr(new T(std::forward(args)...)); } +#define L_AUTO_RETURN(VALUE) -> decltype(VALUE) { return VALUE; } + // ----------------------------------------------------------------------------- -// Data access. +// Class tools. +// ----------------------------------------------------------------------------- + +#define L_DISABLE_COPY(CLASS) \ + CLASS (const CLASS &) = delete; \ + CLASS &operator= (const CLASS &) = delete; + +// ----------------------------------------------------------------------------- +// PImpl tools. // ----------------------------------------------------------------------------- class BaseObject; @@ -119,34 +135,68 @@ class ClonableObjectPrivate; class Object; class ObjectPrivate; +namespace Private { + template + using BetterPrivateAncestor = typename std::conditional< + std::is_base_of::value, + BaseObject, + typename std::conditional< + std::is_base_of::value, + ClonableObject, + T + >::type + >::type; + + // Generic public helper. + template< + typename R, + typename P, + typename C + > + constexpr R *getPublicHelper (P *object, const C *) { + return static_cast(object); + } + + // Generic public helper. Deal with shared data. + template< + typename R, + typename P, + typename C + > + inline R *getPublicHelper (const P &objectSet, const C *) { + auto it = objectSet.cbegin(); + L_ASSERT(it != objectSet.cend()); + return static_cast(*it); + } + + template + struct AddConstMirror { + typedef U type; + }; + + template + struct AddConstMirror { + typedef typename std::add_const::type type; + }; +} + #define L_INTERNAL_CHECK_OBJECT_INHERITANCE(CLASS) \ static_assert( \ !(std::is_base_of::value && std::is_base_of::value), \ "Multiple inheritance between BaseObject and ClonableObject is not allowed." \ ); -#define L_INTERNAL_GET_BETTER_PRIVATE_ANCESTOR(CLASS) \ - std::conditional< \ - std::is_base_of::value, \ - BaseObject, \ - std::conditional< \ - std::is_base_of::value, \ - ClonableObject, \ - CLASS \ - >::type \ - >::type - #define L_INTERNAL_DECLARE_PRIVATE(CLASS) \ inline CLASS ## Private *getPrivate () { \ L_INTERNAL_CHECK_OBJECT_INHERITANCE(CLASS); \ return reinterpret_cast( \ - L_INTERNAL_GET_BETTER_PRIVATE_ANCESTOR(CLASS)::mPrivate \ + LinphonePrivate::Private::BetterPrivateAncestor::mPrivate \ ); \ } \ inline const CLASS ## Private *getPrivate () const { \ L_INTERNAL_CHECK_OBJECT_INHERITANCE(CLASS); \ return reinterpret_cast( \ - L_INTERNAL_GET_BETTER_PRIVATE_ANCESTOR(CLASS)::mPrivate \ + LinphonePrivate::Private::BetterPrivateAncestor::mPrivate \ ); \ } \ friend class CLASS ## Private; \ @@ -162,61 +212,25 @@ class ObjectPrivate; friend class Tester; #endif -// Generic public helper. -template< - typename R, - typename P, - typename C -> -constexpr R *getPublicHelper (P *object, const C *) { - return static_cast(object); -} - -// Generic public helper. Deal with shared data. -template< - typename R, - typename P, - typename C -> -inline R *getPublicHelper (const P &objectSet, const C *) { - auto it = objectSet.cbegin(); - L_ASSERT(it != objectSet.cend()); - return static_cast(*it); -} - #define L_DECLARE_PUBLIC(CLASS) \ - inline CLASS *getPublic () { \ - return getPublicHelper(mPublic, this); \ + CLASS *getPublic () { \ + return LinphonePrivate::Private::getPublicHelper(mPublic, this); \ } \ - inline const CLASS *getPublic () const { \ - return getPublicHelper(mPublic, this); \ + const CLASS *getPublic () const { \ + return LinphonePrivate::Private::getPublicHelper(mPublic, this); \ } \ friend class CLASS; -#define L_DISABLE_COPY(CLASS) \ - CLASS (const CLASS &) = delete; \ - CLASS &operator= (const CLASS &) = delete; - // Get Private data. #define L_D() decltype(getPrivate()) const d = getPrivate(); // Get Public data. #define L_Q() decltype(getPublic()) const q = getPublic(); -template -struct AddConstMirror { - typedef U type; -}; - -template -struct AddConstMirror { - typedef typename std::add_const::type type; -}; - // Get Private data of class in a multiple inheritance case. #define L_D_T(CLASS, NAME) \ auto const NAME = static_cast< \ - AddConstMirror< \ + LinphonePrivate::Private::AddConstMirror< \ std::remove_reference::type, \ CLASS ## Private \ >::type * \ @@ -225,12 +239,16 @@ struct AddConstMirror { // Get Private data of class in a multiple inheritance case. #define L_Q_T(CLASS, NAME) \ auto const NAME = static_cast< \ - AddConstMirror< \ + LinphonePrivate::Private::AddConstMirror< \ std::remove_reference::type, \ CLASS \ >::type * \ >(getPublic()); +// ----------------------------------------------------------------------------- +// Overload. +// ----------------------------------------------------------------------------- + #define L_OVERRIDE_SHARED_FROM_THIS(CLASS) \ inline std::shared_ptr getSharedFromThis () { \ return std::static_pointer_cast(Object::getSharedFromThis()); \ @@ -239,25 +257,17 @@ struct AddConstMirror { return std::static_pointer_cast(Object::getSharedFromThis()); \ } -// ----------------------------------------------------------------------------- -// Overload. -// ----------------------------------------------------------------------------- - namespace Private { template struct ResolveMemberFunctionOverload { template - constexpr auto operator() (Ret (Obj::*func)(Args...)) const -> decltype(func) { - return func; - } + constexpr auto operator() (Ret (Obj::*func)(Args...)) const L_AUTO_RETURN(func); }; template struct ResolveConstMemberFunctionOverload { template - constexpr auto operator() (Ret (Obj::*func)(Args...) const) const -> decltype(func) { - return func; - } + constexpr auto operator() (Ret (Obj::*func)(Args...) const) const L_AUTO_RETURN(func); }; template @@ -266,14 +276,47 @@ namespace Private { using ResolveConstMemberFunctionOverload::operator(); template - constexpr auto operator() (Ret (*func)(Args...)) const -> decltype(func) { - return func; - } + constexpr auto operator() (Ret (*func)(Args...)) const L_AUTO_RETURN(func); }; } // Useful to select a specific overloaded function. (Avoid usage of static_cast.) -#define L_RESOLVE_OVERLOAD(ARGS) LinphonePrivate::Private::ResolveOverload +template +using resolveOverload = Private::ResolveOverload; + +// ----------------------------------------------------------------------------- +// Math. +// ----------------------------------------------------------------------------- + +// Get the length of one integer. +constexpr int getIntLength (int n) { + return n < 0 ? 1 + getIntLength(-n) : (n < 10 ? 1 : 1 + getIntLength(n / 10)); +} + +namespace Private { + constexpr int pow10Impl (int n, int acc) { + return n == 0 ? acc : pow10Impl(n - 1, acc * 10); + } +} + +template +constexpr T abs (const T &value) { + return value < 0 ? -value : value; +} + +constexpr int pow10 (int n) { + return (n < 0 ? -1 : +1) * Private::pow10Impl(abs(n), 1); +} + +// Returns the sum of n elements. +constexpr int sums () { + return 0; +} + +template +constexpr int sums (T i, Args... args) { + return i + sums(args...); +} // ----------------------------------------------------------------------------- // Wrapper public. diff --git a/include/linphone/utils/static-string.h b/include/linphone/utils/static-string.h new file mode 100644 index 000000000..ae65f6668 --- /dev/null +++ b/include/linphone/utils/static-string.h @@ -0,0 +1,166 @@ +/* + * static-string.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_STATIC_STRING_H_ +#define _L_STATIC_STRING_H_ + +#include "linphone/utils/general.h" + +// ============================================================================= +// Compile time strings. Useful to build const char * at compilation. +// ============================================================================= + +LINPHONE_BEGIN_NAMESPACE + +template +struct IndexSequence { + using Type = IndexSequence; +}; + +namespace Private { + template + struct ConcatIndexSequenceImpl; + + template + struct ConcatIndexSequenceImpl, IndexSequence> : + IndexSequence {}; + + template + struct MakeIndexSequenceImpl : ConcatIndexSequenceImpl< + typename MakeIndexSequenceImpl::Type, + typename MakeIndexSequenceImpl::Type + > {}; + + template<> + struct MakeIndexSequenceImpl<0> : IndexSequence<> {}; + + template<> + struct MakeIndexSequenceImpl<1> : IndexSequence<0> {}; +} + +template +using MakeIndexSequence = typename Private::MakeIndexSequenceImpl::Type; + +// ============================================================================= + +// See: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4121.pdf +template +using RawStaticString = const char [N]; + +namespace Private { + template> + struct StaticString; + + template + struct StaticString> { + constexpr StaticString (const RawStaticString &inRaw) : raw{ (inRaw[Index])... } {} + + constexpr char operator[] (std::size_t i) const { + return raw[i]; + } + + constexpr operator const char * () const { + return raw; + } + + RawStaticString raw; + }; + + template + class StaticStringConcatHelper { + public: + template + constexpr StaticStringConcatHelper( + const Private::StaticString &s1, + const Private::StaticString &s2 + ) : StaticStringConcatHelper(s1, s2, MakeIndexSequence{}, MakeIndexSequence{}) {} + + RawStaticString raw; + + private: + template + constexpr StaticStringConcatHelper( + const Private::StaticString &s1, + const Private::StaticString &s2, + IndexSequence, + IndexSequence + ) : raw{ s1[Index1]..., s2[Index2]... } {} + }; + + template + class StaticIntStringHelper { + public: + constexpr StaticIntStringHelper () : StaticIntStringHelper(MakeIndexSequence= 0 ? N - 1 : N - 2>{}) {} + + RawStaticString raw; + + private: + template= 0, int>::type* = nullptr> + constexpr StaticIntStringHelper (const IndexSequence &) : + raw{ char('0' + Value / pow10(N - Index - 2) % 10 )..., '\0' } {} + + template::type* = nullptr> + constexpr StaticIntStringHelper (const IndexSequence &) : + raw{ '-', char('0' + abs(Value) / pow10(N - Index - 3) % 10 )..., '\0' } {} + }; +}; + +// ----------------------------------------------------------------------------- + +template +constexpr Private::StaticString StaticString (const RawStaticString &raw) { + return Private::StaticString(raw); +} + +template +constexpr Private::StaticString operator+ ( + const Private::StaticString &s1, + const Private::StaticString &s2 +) { + return StaticString(Private::StaticStringConcatHelper(s1, s2).raw); +} + +template +constexpr Private::StaticString operator+ ( + const Private::StaticString &s1, + const RawStaticString &s2 +) { + return StaticString(Private::StaticStringConcatHelper(s1, StaticString(s2)).raw); +} + +template +constexpr Private::StaticString operator+ ( + const RawStaticString &s1, + const Private::StaticString &s2 +) { + return StaticString(Private::StaticStringConcatHelper(StaticString(s1), s2).raw); +} + +// ----------------------------------------------------------------------------- + +template +constexpr Private::StaticString StaticIntString () { + return StaticString(Private::StaticIntStringHelper().raw); +} + +// ----------------------------------------------------------------------------- + +LINPHONE_END_NAMESPACE + +#endif // ifndef _L_STATIC_STRING_H_ diff --git a/include/linphone/utils/utils.h b/include/linphone/utils/utils.h index 9e853e25a..e13c89889 100644 --- a/include/linphone/utils/utils.h +++ b/include/linphone/utils/utils.h @@ -21,7 +21,9 @@ #define _L_UTILS_H_ #include +#include #include +#include #include #include "linphone/utils/enum-generator.h" @@ -105,6 +107,8 @@ namespace Utils { return str ? str : ""; } + LINPHONE_PUBLIC std::string trim (const std::string &str); + template LINPHONE_PUBLIC const T &getEmptyConstRefObject () { static const T object; diff --git a/include/linphone/wrapper_utils.h b/include/linphone/wrapper_utils.h index 2e19ad9a3..686f8f16f 100644 --- a/include/linphone/wrapper_utils.h +++ b/include/linphone/wrapper_utils.h @@ -54,6 +54,21 @@ LINPHONE_PUBLIC bctbx_list_t *linphone_core_get_callbacks_list(const LinphoneCor */ LINPHONE_PUBLIC const bctbx_list_t *linphone_call_get_callbacks_list(const LinphoneCall *call); +/** + * @brief Gets the list of listener in the chat room. + * @param[in] cr #LinphoneChatRoom object. + * @return The list of #LinphoneChatRoomCbs. + * @donotwrap + */ +LINPHONE_PUBLIC const bctbx_list_t *linphone_chat_room_get_callbacks_list(const LinphoneChatRoom *cr); + +/** + * Sets the current LinphoneChatRoomCbs. + * @param[in] cr LinphoneChatRoom object + * @param[in] cbs LinphoneChatRoomCbs object + */ +LINPHONE_PUBLIC void linphone_chat_room_set_current_callbacks(LinphoneChatRoom *cr, LinphoneChatRoomCbs *cbs); + /** * Send a message to peer member of this chat room. * diff --git a/java/common/org/linphone/core/LinphoneCore.java b/java/common/org/linphone/core/LinphoneCore.java index c7abd5303..a8c56dcfd 100644 --- a/java/common/org/linphone/core/LinphoneCore.java +++ b/java/common/org/linphone/core/LinphoneCore.java @@ -2557,4 +2557,10 @@ public interface LinphoneCore { * @return a new LinphoneFriend with the given address link with this LinphoneCore */ public LinphoneFriend createFriendWithAddress(String address); + + /** + * Creates and stores a fake LinphoneCallLog + * @return the LinphoneCallLog created and stored + */ + public LinphoneCallLog createCallLog(LinphoneAddress from, LinphoneAddress to, CallDirection dir, int duration, long start, long connected, LinphoneCallLog.CallStatus status, boolean videoEnabled, float quality); } diff --git a/java/impl/org/linphone/core/LinphoneCoreImpl.java b/java/impl/org/linphone/core/LinphoneCoreImpl.java index b300a7fc0..6ec98b7e7 100644 --- a/java/impl/org/linphone/core/LinphoneCoreImpl.java +++ b/java/impl/org/linphone/core/LinphoneCoreImpl.java @@ -1828,4 +1828,12 @@ class LinphoneCoreImpl implements LinphoneCore { public LinphoneFriend createFriendWithAddress(String address) { return (LinphoneFriend) createFriendWithAddress(nativePtr, address); } + + private native long createCallLog(long ptr, long from, long to, int dir, int duration, long start, long connected, int status, boolean videoEnabled, float quality); + @Override + public LinphoneCallLog createCallLog(LinphoneAddress from, LinphoneAddress to, CallDirection dir, int duration, long start, long connected, LinphoneCallLog.CallStatus status, boolean videoEnabled, float quality) { + long logPtr = createCallLog(nativePtr, ((LinphoneAddressImpl) from).nativePtr, ((LinphoneAddressImpl) to).nativePtr, dir == CallDirection.Incoming ? 1 : 0, duration, start, connected, status.toInt(), videoEnabled, quality); + LinphoneCallLog log = new LinphoneCallLogImpl(logPtr); + return log; + } } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6246f1d3e..ca182139f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -23,7 +23,6 @@ set(LINPHONE_CXX_OBJECTS_PRIVATE_HEADER_FILES address/address-p.h address/address.h - address/identity-address-p.h address/identity-address.h c-wrapper/c-wrapper.h c-wrapper/internal/c-sal.h @@ -92,10 +91,12 @@ set(LINPHONE_CXX_OBJECTS_PRIVATE_HEADER_FILES conference/session/call-session.h conference/session/media-session.h conference/session/port-config.h + containers/lru-cache.h + content/content-disposition.h content/content-manager.h + content/content-p.h content/content-type.h content/content.h - content/content-p.h content/file-content.h content/file-transfer-content.h core/core-accessor.h @@ -106,13 +107,15 @@ set(LINPHONE_CXX_OBJECTS_PRIVATE_HEADER_FILES core/platform-helpers/platform-helpers.h db/abstract/abstract-db-p.h db/abstract/abstract-db.h + db/internal/db-transaction.h + db/internal/statements.h db/main-db-chat-message-key.h db/main-db-event-key.h db/main-db-key-p.h db/main-db-key.h db/main-db-p.h db/main-db.h - db/session/db-session-p.h + db/session/db-session.h dial-plan/dial-plan-p.h dial-plan/dial-plan.h enums.h @@ -137,6 +140,7 @@ set(LINPHONE_CXX_OBJECTS_PRIVATE_HEADER_FILES object/base-object.h object/clonable-object-p.h object/clonable-object.h + object/clonable-shared-pointer.h object/object-head-p.h object/object-head.h object/object-p.h @@ -144,6 +148,10 @@ set(LINPHONE_CXX_OBJECTS_PRIVATE_HEADER_FILES object/property-container.h object/singleton.h sal/sal.h + search/magic-search-p.h + search/magic-search.h + search/search-result-p.h + search/search-result.h utils/background-task.h utils/payload-type-handler.h variant/variant.h @@ -167,7 +175,9 @@ set(LINPHONE_CXX_OBJECTS_SOURCE_FILES c-wrapper/api/c-core.cpp c-wrapper/api/c-dial-plan.cpp c-wrapper/api/c-event-log.cpp + c-wrapper/api/c-magic-search.cpp c-wrapper/api/c-participant.cpp + c-wrapper/api/c-search-result.cpp c-wrapper/internal/c-sal.cpp c-wrapper/internal/c-tools.cpp call/call.cpp @@ -206,6 +216,7 @@ set(LINPHONE_CXX_OBJECTS_SOURCE_FILES conference/remote-conference.cpp conference/session/call-session.cpp conference/session/media-session.cpp + content/content-disposition.cpp content/content-manager.cpp content/content-type.cpp content/content.cpp @@ -218,6 +229,7 @@ set(LINPHONE_CXX_OBJECTS_SOURCE_FILES core/paths/paths.cpp core/platform-helpers/platform-helpers.cpp db/abstract/abstract-db.cpp + db/internal/statements.cpp db/main-db-chat-message-key.cpp db/main-db-event-key.cpp db/main-db-key.cpp @@ -249,6 +261,8 @@ set(LINPHONE_CXX_OBJECTS_SOURCE_FILES sal/refer-op.cpp sal/register-op.cpp sal/sal.cpp + search/magic-search.cpp + search/search-result.cpp utils/background-task.cpp utils/fs.cpp utils/general.cpp @@ -264,29 +278,26 @@ set(LINPHONE_OBJC_SOURCE_FILES) if (APPLE) list(APPEND LINPHONE_OBJC_SOURCE_FILES core/paths/paths-apple.mm) list(APPEND LINPHONE_CXX_OBJECTS_PRIVATE_HEADER_FILES core/paths/paths-apple.h) - elseif (ANDROID) list(APPEND LINPHONE_CXX_OBJECTS_SOURCE_FILES core/paths/paths-android.cpp core/platform-helpers/android-platform-helpers.cpp) list(APPEND LINPHONE_CXX_OBJECTS_PRIVATE_HEADER_FILES core/paths/paths-android.h) - elseif (WIN32) list(APPEND LINPHONE_CXX_OBJECTS_SOURCE_FILES core/paths/paths-windows.cpp) list(APPEND LINPHONE_CXX_OBJECTS_PRIVATE_HEADER_FILES core/paths/paths-windows.h) - elseif (UNIX) list(APPEND LINPHONE_CXX_OBJECTS_SOURCE_FILES core/paths/paths-linux.cpp) list(APPEND LINPHONE_CXX_OBJECTS_PRIVATE_HEADER_FILES core/paths/paths-linux.h) endif () -set(LINPHONE_CXX_OBJECTS_INCLUDE_DIRS ${BELR_INCLUDE_DIRS} ${LIBXSD_INCLUDE_DIRS}) +set(LINPHONE_CXX_OBJECTS_INCLUDE_DIRS + ${BELR_INCLUDE_DIRS} + ${LIBXSD_INCLUDE_DIRS} + ${SOCI_INCLUDE_DIRS} + ${SOCI_MYSQL_INCLUDES} +) set(LINPHONE_CXX_OBJECTS_DEFINITIONS "-DLIBLINPHONE_EXPORTS") set(LINPHONE_CXX_OBJECTS_INCLUDE_DIRS ${BELR_INCLUDE_DIRS}) -if (SOCI_FOUND) - list(APPEND LINPHONE_CXX_OBJECTS_INCLUDE_DIRS ${SOCI_INCLUDE_DIRS} ${SOCI_MYSQL_INCLUDES}) - add_definitions(-DSOCI_ENABLED) -endif () - set(LINPHONE_PRIVATE_HEADER_FILES) foreach (header ${LINPHONE_CXX_OBJECTS_PRIVATE_HEADER_FILES}) list(APPEND LINPHONE_PRIVATE_HEADER_FILES "${CMAKE_CURRENT_SOURCE_DIR}/${header}") diff --git a/src/address/address-p.h b/src/address/address-p.h index a0c10d9a5..64d7d1ccf 100644 --- a/src/address/address-p.h +++ b/src/address/address-p.h @@ -38,6 +38,8 @@ public: } void setInternalAddress (const SalAddress *value); + static void clearSipAddressesCache (); + private: struct AddressCache { std::string scheme; diff --git a/src/address/address.cpp b/src/address/address.cpp index d7e276123..bd875f7a2 100644 --- a/src/address/address.cpp +++ b/src/address/address.cpp @@ -17,12 +17,10 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "linphone/utils/utils.h" - #include "address-p.h" #include "address/identity-address.h" #include "c-wrapper/c-wrapper.h" -#include "c-wrapper/internal/c-sal.h" +#include "containers/lru-cache.h" #include "logger/logger.h" // ============================================================================= @@ -31,6 +29,49 @@ using namespace std; LINPHONE_BEGIN_NAMESPACE +namespace { + class SalAddressWrap { + public: + explicit SalAddressWrap (SalAddress *salAddress = nullptr) : mSalAddress(salAddress) {} + + SalAddressWrap (const SalAddressWrap &other) : mSalAddress(other.mSalAddress) { + if (mSalAddress) + sal_address_ref(mSalAddress); + } + + SalAddressWrap (SalAddressWrap &&other) : mSalAddress(other.mSalAddress) { + other.mSalAddress = nullptr; + } + + ~SalAddressWrap () { + if (mSalAddress) + sal_address_unref(mSalAddress); + } + + const SalAddress *get () { + return mSalAddress; + } + + private: + SalAddress *mSalAddress; + }; + LruCache addressesCache; +} + +static SalAddress *getSalAddressFromCache (const string &uri) { + SalAddressWrap *wrap = addressesCache[uri]; + if (wrap) + return sal_address_clone(wrap->get()); + + SalAddress *address = sal_address_new(L_STRING_TO_C(uri)); + if (address) { + addressesCache.insert(uri, SalAddressWrap(address)); + return sal_address_clone(address); + } + + return nullptr; +} + // ----------------------------------------------------------------------------- void AddressPrivate::setInternalAddress (const SalAddress *addr) { @@ -39,11 +80,16 @@ void AddressPrivate::setInternalAddress (const SalAddress *addr) { internalAddress = sal_address_clone(addr); } +void AddressPrivate::clearSipAddressesCache () { + addressesCache.clear(); +} + // ----------------------------------------------------------------------------- Address::Address (const string &address) : ClonableObject(*new AddressPrivate) { L_D(); - if (!(d->internalAddress = sal_address_new(L_STRING_TO_C(address)))) { + + if (!(d->internalAddress = getSalAddressFromCache(address))) { lWarning() << "Cannot create Address, bad uri [" << address << "]"; } } @@ -65,12 +111,12 @@ Address::Address (const IdentityAddress &identityAddress) : ClonableObject(*new if (identityAddress.hasGruu()) uri += ";gr=" + identityAddress.getGruu(); - d->internalAddress = sal_address_new(L_STRING_TO_C(uri)); + d->internalAddress = getSalAddressFromCache(uri); } -Address::Address (const Address &src) : ClonableObject(*new AddressPrivate) { +Address::Address (const Address &other) : ClonableObject(*new AddressPrivate) { L_D(); - SalAddress *salAddress = src.getPrivate()->internalAddress; + SalAddress *salAddress = other.getPrivate()->internalAddress; if (salAddress) d->internalAddress = sal_address_clone(salAddress); } @@ -81,28 +127,28 @@ Address::~Address () { sal_address_destroy(d->internalAddress); } -Address &Address::operator= (const Address &src) { +Address &Address::operator= (const Address &other) { L_D(); - if (this != &src) { + if (this != &other) { if (d->internalAddress) sal_address_destroy(d->internalAddress); - SalAddress *salAddress = src.getPrivate()->internalAddress; + SalAddress *salAddress = other.getPrivate()->internalAddress; d->internalAddress = salAddress ? sal_address_clone(salAddress) : nullptr; } return *this; } -bool Address::operator== (const Address &address) const { - return asString() == address.asString(); +bool Address::operator== (const Address &other) const { + return asString() == other.asString(); } -bool Address::operator!= (const Address &address) const { - return !(*this == address); +bool Address::operator!= (const Address &other) const { + return !(*this == other); } -bool Address::operator< (const Address &address) const { - return asString() < address.asString(); +bool Address::operator< (const Address &other) const { + return asString() < other.asString(); } bool Address::isValid () const { diff --git a/src/address/address.h b/src/address/address.h index 875bc2ee9..f92300277 100644 --- a/src/address/address.h +++ b/src/address/address.h @@ -20,6 +20,8 @@ #ifndef _L_ADDRESS_H_ #define _L_ADDRESS_H_ +#include + #include "enums.h" #include "object/clonable-object.h" @@ -42,15 +44,15 @@ class LINPHONE_PUBLIC Address : public ClonableObject { public: explicit Address (const std::string &address = ""); Address (const IdentityAddress &identityAddress); - Address (const Address &src); + Address (const Address &other); ~Address (); - Address &operator= (const Address &src); + Address &operator= (const Address &other); - bool operator== (const Address &address) const; - bool operator!= (const Address &address) const; + bool operator== (const Address &other) const; + bool operator!= (const Address &other) const; - bool operator< (const Address &address) const; + bool operator< (const Address &other) const; bool isValid () const; @@ -107,6 +109,11 @@ private: L_DECLARE_PRIVATE(Address); }; +inline std::ostream &operator<< (std::ostream &os, const Address &address) { + os << "Address(" << address.asString() << ")"; + return os; +} + LINPHONE_END_NAMESPACE #endif // ifndef _L_ADDRESS_H_ diff --git a/src/address/identity-address.cpp b/src/address/identity-address.cpp index 40c6d5511..f20590e16 100644 --- a/src/address/identity-address.cpp +++ b/src/address/identity-address.cpp @@ -20,10 +20,10 @@ #include "linphone/utils/utils.h" #include "address.h" -#include "identity-address-p.h" #include "c-wrapper/c-wrapper.h" -#include "identity-address-p.h" +#include "identity-address.h" #include "logger/logger.h" +#include "object/clonable-object-p.h" // ============================================================================= @@ -33,6 +33,16 @@ LINPHONE_BEGIN_NAMESPACE // ----------------------------------------------------------------------------- +class IdentityAddressPrivate : public ClonableObjectPrivate { +public: + std::string scheme; + std::string username; + std::string domain; + std::string gruu; +}; + +// ----------------------------------------------------------------------------- + IdentityAddress::IdentityAddress (const string &address) : ClonableObject(*new IdentityAddressPrivate) { L_D(); Address tmpAddress(address); @@ -53,35 +63,35 @@ IdentityAddress::IdentityAddress (const Address &address) : ClonableObject(*new d->gruu = address.getUriParamValue("gr"); } -IdentityAddress::IdentityAddress (const IdentityAddress &src) : ClonableObject(*new IdentityAddressPrivate) { +IdentityAddress::IdentityAddress (const IdentityAddress &other) : ClonableObject(*new IdentityAddressPrivate) { L_D(); - d->scheme = src.getScheme(); - d->username = src.getUsername(); - d->domain = src.getDomain(); - d->gruu = src.getGruu(); + d->scheme = other.getScheme(); + d->username = other.getUsername(); + d->domain = other.getDomain(); + d->gruu = other.getGruu(); } -IdentityAddress &IdentityAddress::operator= (const IdentityAddress &src) { +IdentityAddress &IdentityAddress::operator= (const IdentityAddress &other) { L_D(); - if (this != &src) { - d->scheme = src.getScheme(); - d->username = src.getUsername(); - d->domain = src.getDomain(); - d->gruu = src.getGruu(); + if (this != &other) { + d->scheme = other.getScheme(); + d->username = other.getUsername(); + d->domain = other.getDomain(); + d->gruu = other.getGruu(); } return *this; } -bool IdentityAddress::operator== (const IdentityAddress &address) const { - return asString() == address.asString(); +bool IdentityAddress::operator== (const IdentityAddress &other) const { + return asString() == other.asString(); } -bool IdentityAddress::operator!= (const IdentityAddress &address) const { - return !(*this == address); +bool IdentityAddress::operator!= (const IdentityAddress &other) const { + return !(*this == other); } -bool IdentityAddress::operator< (const IdentityAddress &address) const { - return asString() < address.asString(); +bool IdentityAddress::operator< (const IdentityAddress &other) const { + return asString() < other.asString(); } bool IdentityAddress::isValid () const { diff --git a/src/address/identity-address.h b/src/address/identity-address.h index aefa87c58..2e39b959b 100644 --- a/src/address/identity-address.h +++ b/src/address/identity-address.h @@ -20,6 +20,8 @@ #ifndef _L_IDENTITY_ADDRESS_H_ #define _L_IDENTITY_ADDRESS_H_ +#include + #include "object/clonable-object.h" // ============================================================================= @@ -33,15 +35,15 @@ class LINPHONE_PUBLIC IdentityAddress : public ClonableObject { public: explicit IdentityAddress (const std::string &address = ""); IdentityAddress (const Address &address); - IdentityAddress (const IdentityAddress &src); + IdentityAddress (const IdentityAddress &other); ~IdentityAddress () = default; - IdentityAddress &operator= (const IdentityAddress &src); + IdentityAddress &operator= (const IdentityAddress &other); - bool operator== (const IdentityAddress &address) const; - bool operator!= (const IdentityAddress &address) const; + bool operator== (const IdentityAddress &other) const; + bool operator!= (const IdentityAddress &other) const; - bool operator< (const IdentityAddress &address) const; + bool operator< (const IdentityAddress &other) const; bool isValid () const; @@ -65,6 +67,11 @@ private: L_DECLARE_PRIVATE(IdentityAddress); }; +inline std::ostream &operator<< (std::ostream &os, const IdentityAddress &identityAddress) { + os << "IdentityAddress(" << identityAddress.asString() << ")"; + return os; +} + LINPHONE_END_NAMESPACE #endif // ifndef _L_IDENTITY_ADDRESS_H_ diff --git a/src/c-wrapper/api/c-call-cbs.cpp b/src/c-wrapper/api/c-call-cbs.cpp index 17b521e40..903dfa26a 100644 --- a/src/c-wrapper/api/c-call-cbs.cpp +++ b/src/c-wrapper/api/c-call-cbs.cpp @@ -33,6 +33,7 @@ struct _LinphoneCallCbs { LinphoneCallCbsStatsUpdatedCb statsUpdatedCb; LinphoneCallCbsTransferStateChangedCb transferStateChangedCb; LinphoneCallCbsAckProcessingCb ackProcessing; + LinphoneCallCbsTmmbrReceivedCb tmmbrReceivedCb; }; BELLE_SIP_DECLARE_VPTR_NO_EXPORT(LinphoneCallCbs); @@ -124,3 +125,11 @@ LinphoneCallCbsAckProcessingCb linphone_call_cbs_get_ack_processing (LinphoneCal void linphone_call_cbs_set_ack_processing (LinphoneCallCbs *cbs, LinphoneCallCbsAckProcessingCb cb){ cbs->ackProcessing = cb; } + +LinphoneCallCbsTmmbrReceivedCb linphone_call_cbs_get_tmmbr_received (LinphoneCallCbs *cbs) { + return cbs->tmmbrReceivedCb; +} + +void linphone_call_cbs_set_tmmbr_received (LinphoneCallCbs *cbs, LinphoneCallCbsTmmbrReceivedCb cb) { + cbs->tmmbrReceivedCb = cb; +} diff --git a/src/c-wrapper/api/c-call.cpp b/src/c-wrapper/api/c-call.cpp index 3f0b4f376..0fa450fa5 100644 --- a/src/c-wrapper/api/c-call.cpp +++ b/src/c-wrapper/api/c-call.cpp @@ -27,6 +27,7 @@ #include "call/call.h" #include "call/local-conference-call.h" #include "call/remote-conference-call.h" +#include "chat/chat-room/real-time-text-chat-room.h" #include "conference/params/media-session-params-p.h" #include "core/core-p.h" @@ -205,6 +206,10 @@ void linphone_call_notify_ack_processing (LinphoneCall *call, LinphoneHeaders *m NOTIFY_IF_EXIST(AckProcessing, ack_processing, call, msg, is_received) } +void linphone_call_notify_tmmbr_received (LinphoneCall *call, int stream_index, int tmmbr) { + NOTIFY_IF_EXIST(TmmbrReceived, tmmbr_received, call, stream_index, tmmbr) +} + // ============================================================================= // Public functions. @@ -246,7 +251,7 @@ const char *linphone_call_get_to_header (const LinphoneCall *call, const char *n } char *linphone_call_get_remote_address_as_string (const LinphoneCall *call) { - return ms_strdup(L_GET_CPP_PTR_FROM_C_OBJECT(call)->getRemoteAddressAsString().c_str()); + return ms_strdup(L_GET_CPP_PTR_FROM_C_OBJECT(call)->getRemoteAddress().asString().c_str()); } const LinphoneAddress *linphone_call_get_diversion_address (const LinphoneCall *call) { @@ -538,16 +543,10 @@ bool_t linphone_call_echo_limiter_enabled (const LinphoneCall *call) { } LinphoneChatRoom *linphone_call_get_chat_room (LinphoneCall *call) { -#if 0 - if (!call->chat_room){ - if (call->state != LinphoneCallReleased && call->state != LinphoneCallEnd){ - call->chat_room = _linphone_core_create_chat_room_from_call(call); - } - } - return call->chat_room; -#else + shared_ptr acr = L_GET_PRIVATE_FROM_C_OBJECT(call)->getChatRoom(); + if (acr) + return L_GET_C_BACK_PTR(acr); return nullptr; -#endif } float linphone_call_get_play_volume (const LinphoneCall *call) { diff --git a/src/c-wrapper/api/c-chat-message.cpp b/src/c-wrapper/api/c-chat-message.cpp index da453b39c..902055f9a 100644 --- a/src/c-wrapper/api/c-chat-message.cpp +++ b/src/c-wrapper/api/c-chat-message.cpp @@ -23,14 +23,14 @@ #include "ortp/b64.h" -#include "c-wrapper/c-wrapper.h" #include "address/address.h" -#include "content/content.h" -#include "content/content-type.h" +#include "c-wrapper/c-wrapper.h" #include "chat/chat-message/chat-message-p.h" #include "chat/chat-room/chat-room-p.h" #include "chat/chat-room/real-time-text-chat-room-p.h" #include "chat/notification/imdn.h" +#include "content/content-type.h" +#include "content/content.h" // ============================================================================= @@ -45,13 +45,18 @@ L_DECLARE_C_OBJECT_IMPL_WITH_XTORS(ChatMessage, LinphoneAddress *from; // cache for shared_ptr
LinphoneAddress *to; // cache for shared_ptr
LinphoneChatMessageStateChangedCb message_state_changed_cb; - void* message_state_changed_user_data; - mutable char *contentTypeCache; - mutable std::string textContentBody; + void *message_state_changed_user_data; + + struct Cache { + string contentType; + string textContentBody; + string customHeaderValue; + } mutable cache; ) static void _linphone_chat_message_constructor (LinphoneChatMessage *msg) { msg->cbs = linphone_chat_message_cbs_new(); + new(&msg->cache) LinphoneChatMessage::Cache(); } static void _linphone_chat_message_destructor (LinphoneChatMessage *msg) { @@ -61,8 +66,8 @@ static void _linphone_chat_message_destructor (LinphoneChatMessage *msg) { linphone_address_unref(msg->from); if (msg->to) linphone_address_unref(msg->to); - if (msg->contentTypeCache) - ms_free(msg->contentTypeCache); + + msg->cache.~Cache(); } // ============================================================================= @@ -103,7 +108,7 @@ const char *linphone_chat_message_get_external_body_url(const LinphoneChatMessag } void linphone_chat_message_set_external_body_url(LinphoneChatMessage *msg, const char *url) { - + L_GET_PRIVATE_FROM_C_OBJECT(msg)->setExternalBodyUrl(L_C_TO_STRING(url)); } time_t linphone_chat_message_get_time(const LinphoneChatMessage *msg) { @@ -160,8 +165,11 @@ void linphone_chat_message_set_file_transfer_filepath(LinphoneChatMessage *msg, L_GET_PRIVATE_FROM_C_OBJECT(msg)->setFileTransferFilepath(L_C_TO_STRING(filepath)); } -void linphone_chat_message_add_custom_header(LinphoneChatMessage *msg, const char *header_name, - const char *header_value) { +void linphone_chat_message_add_custom_header( + LinphoneChatMessage *msg, + const char *header_name, + const char *header_value +) { L_GET_PRIVATE_FROM_C_OBJECT(msg)->addSalCustomHeader(L_C_TO_STRING(header_name), L_C_TO_STRING(header_value)); } @@ -170,7 +178,8 @@ void linphone_chat_message_remove_custom_header(LinphoneChatMessage *msg, const } const char *linphone_chat_message_get_custom_header(LinphoneChatMessage *msg, const char *header_name) { - return L_STRING_TO_C(L_GET_PRIVATE_FROM_C_OBJECT(msg)->getSalCustomHeaderValue(L_C_TO_STRING(header_name))); + msg->cache.customHeaderValue = L_GET_PRIVATE_FROM_C_OBJECT(msg)->getSalCustomHeaderValue(L_C_TO_STRING(header_name)); + return L_STRING_TO_C(msg->cache.customHeaderValue); } const LinphoneErrorInfo *linphone_chat_message_get_error_info(const LinphoneChatMessage *msg) { @@ -209,10 +218,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)); } @@ -233,8 +238,12 @@ const char *linphone_chat_message_get_text_content(const LinphoneChatMessage *ms const LinphonePrivate::Content *content = L_GET_PRIVATE_FROM_C_OBJECT(msg)->getTextContent(); if (content->isEmpty()) return nullptr; - msg->textContentBody = content->getBodyAsString(); - return L_STRING_TO_C(msg->textContentBody); + msg->cache.textContentBody = content->getBodyAsString(); + return L_STRING_TO_C(msg->cache.textContentBody); +} + +bool_t linphone_chat_message_is_file_transfer_in_progress(LinphoneChatMessage *msg) { + return L_GET_CPP_PTR_FROM_C_OBJECT(msg)->isFileTransferInProgress(); } // ============================================================================= @@ -261,12 +270,9 @@ void * linphone_chat_message_get_message_state_changed_cb_user_data(LinphoneChat // Structure has changed, hard to keep the behavior // ============================================================================= -const char * linphone_chat_message_get_content_type(LinphoneChatMessage *msg) { - if (msg->contentTypeCache) { - ms_free(msg->contentTypeCache); - } - msg->contentTypeCache = ms_strdup(L_STRING_TO_C(L_GET_PRIVATE_FROM_C_OBJECT(msg)->getContentType().asString())); - return msg->contentTypeCache; +const char *linphone_chat_message_get_content_type(LinphoneChatMessage *msg) { + msg->cache.contentType = L_GET_PRIVATE_FROM_C_OBJECT(msg)->getContentType().asString(); + return L_STRING_TO_C(msg->cache.contentType); } void linphone_chat_message_set_content_type(LinphoneChatMessage *msg, const char *content_type) { @@ -305,11 +311,11 @@ LinphoneReason linphone_chat_message_get_reason(LinphoneChatMessage *msg) { } bool_t linphone_chat_message_is_file_transfer(LinphoneChatMessage *msg) { - return LinphonePrivate::ContentType(linphone_chat_message_get_content_type(msg)) == LinphonePrivate::ContentType::FileTransfer; + return L_GET_PRIVATE_FROM_C_OBJECT(msg)->hasFileTransferContent(); } bool_t linphone_chat_message_is_text(LinphoneChatMessage *msg) { - return LinphonePrivate::ContentType(linphone_chat_message_get_content_type(msg)) == LinphonePrivate::ContentType::PlainText; + return L_GET_PRIVATE_FROM_C_OBJECT(msg)->hasTextContent(); } const char *linphone_chat_message_state_to_string(const LinphoneChatMessageState state) { diff --git a/src/c-wrapper/api/c-chat-room-cbs.cpp b/src/c-wrapper/api/c-chat-room-cbs.cpp index c8c8ca89d..1d3d4d8d6 100644 --- a/src/c-wrapper/api/c-chat-room-cbs.cpp +++ b/src/c-wrapper/api/c-chat-room-cbs.cpp @@ -39,8 +39,11 @@ struct _LinphoneChatRoomCbs { LinphoneChatRoomCbsChatMessageReceivedCb chatMessageReceivedCb; LinphoneChatRoomCbsChatMessageSentCb chatMessageSentCb; LinphoneChatRoomCbsConferenceAddressGenerationCb conferenceAddressGenerationCb; - LinphoneChatRoomCbsParticipantDeviceFetchedCb participantDeviceFetchedCb; + LinphoneChatRoomCbsParticipantDeviceFetchRequestedCb participantDeviceFetchRequestedCb; LinphoneChatRoomCbsParticipantsCapabilitiesCheckedCb participantsCapabilitiesChecked; + LinphoneChatRoomCbsParticipantRegistrationSubscriptionRequestedCb participantRegistrationSubscriptionRequestedCb; + LinphoneChatRoomCbsParticipantRegistrationUnsubscriptionRequestedCb participantRegistrationUnsubscriptionRequestedCb; + LinphoneChatRoomCbsShouldChatMessageBeStoredCb shouldMessageBeStoredCb; }; BELLE_SIP_DECLARE_VPTR_NO_EXPORT(LinphoneChatRoomCbs); @@ -56,7 +59,7 @@ BELLE_SIP_INSTANCIATE_VPTR(LinphoneChatRoomCbs, belle_sip_object_t, // ============================================================================= -LinphoneChatRoomCbs * linphone_chat_room_cbs_new (void) { +LinphoneChatRoomCbs * _linphone_chat_room_cbs_new (void) { return belle_sip_object_new(LinphoneChatRoomCbs); } @@ -181,12 +184,12 @@ void linphone_chat_room_cbs_set_conference_address_generation (LinphoneChatRoomC cbs->conferenceAddressGenerationCb = cb; } -LinphoneChatRoomCbsParticipantDeviceFetchedCb linphone_chat_room_cbs_get_participant_device_fetched (const LinphoneChatRoomCbs *cbs) { - return cbs->participantDeviceFetchedCb; +LinphoneChatRoomCbsParticipantDeviceFetchRequestedCb linphone_chat_room_cbs_get_participant_device_fetch_requested (const LinphoneChatRoomCbs *cbs) { + return cbs->participantDeviceFetchRequestedCb; } -void linphone_chat_room_cbs_set_participant_device_fetched (LinphoneChatRoomCbs *cbs, LinphoneChatRoomCbsParticipantDeviceFetchedCb cb) { - cbs->participantDeviceFetchedCb = cb; +void linphone_chat_room_cbs_set_participant_device_fetch_requested (LinphoneChatRoomCbs *cbs, LinphoneChatRoomCbsParticipantDeviceFetchRequestedCb cb) { + cbs->participantDeviceFetchRequestedCb = cb; } LinphoneChatRoomCbsParticipantsCapabilitiesCheckedCb linphone_chat_room_cbs_get_participants_capabilities_checked (const LinphoneChatRoomCbs *cbs) { @@ -196,3 +199,27 @@ LinphoneChatRoomCbsParticipantsCapabilitiesCheckedCb linphone_chat_room_cbs_get_ void linphone_chat_room_cbs_set_participants_capabilities_checked (LinphoneChatRoomCbs *cbs, LinphoneChatRoomCbsParticipantsCapabilitiesCheckedCb cb) { cbs->participantsCapabilitiesChecked = cb; } + +LinphoneChatRoomCbsParticipantRegistrationSubscriptionRequestedCb linphone_chat_room_cbs_get_participant_registration_subscription_requested (const LinphoneChatRoomCbs *cbs) { + return cbs->participantRegistrationSubscriptionRequestedCb; +} + +void linphone_chat_room_cbs_set_participant_registration_subscription_requested (LinphoneChatRoomCbs *cbs, LinphoneChatRoomCbsParticipantRegistrationSubscriptionRequestedCb cb) { + cbs->participantRegistrationSubscriptionRequestedCb = cb; +} + +LinphoneChatRoomCbsParticipantRegistrationUnsubscriptionRequestedCb linphone_chat_room_cbs_get_participant_registration_unsubscription_requested (const LinphoneChatRoomCbs *cbs) { + return cbs->participantRegistrationUnsubscriptionRequestedCb; +} + +void linphone_chat_room_cbs_set_participant_registration_unsubscription_requested (LinphoneChatRoomCbs *cbs, LinphoneChatRoomCbsParticipantRegistrationUnsubscriptionRequestedCb cb) { + cbs->participantRegistrationUnsubscriptionRequestedCb = cb; +} + +LinphoneChatRoomCbsShouldChatMessageBeStoredCb linphone_chat_room_cbs_get_chat_message_should_be_stored( LinphoneChatRoomCbs *cbs) { + return cbs->shouldMessageBeStoredCb; +} + +void linphone_chat_room_cbs_set_chat_message_should_be_stored( LinphoneChatRoomCbs *cbs, LinphoneChatRoomCbsShouldChatMessageBeStoredCb cb) { + cbs->shouldMessageBeStoredCb = cb; +} diff --git a/src/c-wrapper/api/c-chat-room.cpp b/src/c-wrapper/api/c-chat-room.cpp index 3288f1073..e549964e0 100644 --- a/src/c-wrapper/api/c-chat-room.cpp +++ b/src/c-wrapper/api/c-chat-room.cpp @@ -27,10 +27,8 @@ #include "address/identity-address.h" #include "c-wrapper/c-wrapper.h" +#include "call/call.h" #include "chat/chat-message/chat-message-p.h" -#include "chat/chat-room/basic-chat-room.h" -#include "chat/chat-room/client-group-chat-room-p.h" -#include "chat/chat-room/client-group-to-basic-chat-room.h" #include "chat/chat-room/real-time-text-chat-room-p.h" #include "chat/chat-room/server-group-chat-room-p.h" #include "conference/participant.h" @@ -47,19 +45,17 @@ static void _linphone_chat_room_destructor (LinphoneChatRoom *cr); L_DECLARE_C_OBJECT_IMPL_WITH_XTORS( ChatRoom, _linphone_chat_room_constructor, _linphone_chat_room_destructor, - LinphoneChatRoomCbs *cbs; + bctbx_list_t *callbacks; /* A list of LinphoneCallCbs object */ + LinphoneChatRoomCbs *currentCbs; /* The current LinphoneCallCbs object used to call a callback */ mutable LinphoneAddress *conferenceAddressCache; mutable LinphoneAddress *peerAddressCache; mutable LinphoneAddress *localAddressCache; mutable bctbx_list_t *composingAddresses; ) -static void _linphone_chat_room_constructor (LinphoneChatRoom *cr) { - cr->cbs = linphone_chat_room_cbs_new(); -} +static void _linphone_chat_room_constructor (LinphoneChatRoom *cr) {} static void _linphone_chat_room_destructor (LinphoneChatRoom *cr) { - linphone_chat_room_cbs_unref(cr->cbs); if (cr->conferenceAddressCache) linphone_address_unref(cr->conferenceAddressCache); if (cr->peerAddressCache) @@ -68,6 +64,7 @@ static void _linphone_chat_room_destructor (LinphoneChatRoom *cr) { linphone_address_unref(cr->localAddressCache); if (cr->composingAddresses) bctbx_list_free_with_data(cr->composingAddresses, (bctbx_list_free_func)linphone_address_unref); + _linphone_chat_room_clear_callbacks(cr); } static list _get_identity_address_list_from_address_list(list addressList) { @@ -156,9 +153,9 @@ void linphone_chat_room_receive_chat_message (LinphoneChatRoom *cr, LinphoneChat } uint32_t linphone_chat_room_get_char (const LinphoneChatRoom *cr) { - if (linphone_core_realtime_text_enabled(linphone_chat_room_get_core(cr))) - return L_GET_CPP_PTR_FROM_C_OBJECT(cr, RealTimeTextChatRoom)->getChar(); - return 0; + if (!(L_GET_CPP_PTR_FROM_C_OBJECT(cr)->getCapabilities() & LinphonePrivate::ChatRoom::Capabilities::RealTimeText)) + return 0; + return L_GET_CPP_PTR_FROM_C_OBJECT(cr, RealTimeTextChatRoom)->getChar(); } void linphone_chat_room_compose (LinphoneChatRoom *cr) { @@ -166,14 +163,15 @@ void linphone_chat_room_compose (LinphoneChatRoom *cr) { } LinphoneCall *linphone_chat_room_get_call (const LinphoneChatRoom *cr) { - if (linphone_core_realtime_text_enabled(linphone_chat_room_get_core(cr))) - return L_GET_CPP_PTR_FROM_C_OBJECT(cr, RealTimeTextChatRoom)->getCall(); - return nullptr; + if (!(L_GET_CPP_PTR_FROM_C_OBJECT(cr)->getCapabilities() & LinphonePrivate::ChatRoom::Capabilities::RealTimeText)) + return nullptr; + return L_GET_C_BACK_PTR(L_GET_CPP_PTR_FROM_C_OBJECT(cr, RealTimeTextChatRoom)->getCall()); } void linphone_chat_room_set_call (LinphoneChatRoom *cr, LinphoneCall *call) { - if (linphone_core_realtime_text_enabled(linphone_chat_room_get_core(cr))) - L_GET_PRIVATE_FROM_C_OBJECT(cr, RealTimeTextChatRoom)->call = call; + if (!(L_GET_CPP_PTR_FROM_C_OBJECT(cr)->getCapabilities() & LinphonePrivate::ChatRoom::Capabilities::RealTimeText)) + return; + L_GET_PRIVATE_FROM_C_OBJECT(cr, RealTimeTextChatRoom)->call = L_GET_CPP_PTR_FROM_C_OBJECT(call); } void linphone_chat_room_mark_as_read (LinphoneChatRoom *cr) { @@ -202,9 +200,8 @@ void linphone_chat_room_delete_history (LinphoneChatRoom *cr) { bctbx_list_t *linphone_chat_room_get_history_range (LinphoneChatRoom *cr, int startm, int endm) { list> chatMessages; - for (auto &event : L_GET_CPP_PTR_FROM_C_OBJECT(cr)->getHistoryRange(startm, endm)) - if (event->getType() == LinphonePrivate::EventLog::Type::ConferenceChatMessage) - chatMessages.push_back(static_pointer_cast(event)->getChatMessage()); + for (auto &event : L_GET_CPP_PTR_FROM_C_OBJECT(cr)->getMessageHistoryRange(startm, endm)) + chatMessages.push_back(static_pointer_cast(event)->getChatMessage()); return L_GET_RESOLVED_C_LIST_FROM_CPP_LIST(chatMessages); } @@ -213,6 +210,14 @@ bctbx_list_t *linphone_chat_room_get_history (LinphoneChatRoom *cr, int nb_messa return linphone_chat_room_get_history_range(cr, 0, nb_message); } +bctbx_list_t *linphone_chat_room_get_history_range_message_events (LinphoneChatRoom *cr, int startm, int endm) { + return L_GET_RESOLVED_C_LIST_FROM_CPP_LIST(L_GET_CPP_PTR_FROM_C_OBJECT(cr)->getMessageHistoryRange(startm, endm)); +} + +bctbx_list_t *linphone_chat_room_get_history_message_events (LinphoneChatRoom *cr, int nb_events) { + return L_GET_RESOLVED_C_LIST_FROM_CPP_LIST(L_GET_CPP_PTR_FROM_C_OBJECT(cr)->getMessageHistory(nb_events)); +} + bctbx_list_t *linphone_chat_room_get_history_events (LinphoneChatRoom *cr, int nb_events) { return L_GET_RESOLVED_C_LIST_FROM_CPP_LIST(L_GET_CPP_PTR_FROM_C_OBJECT(cr)->getHistory(nb_events)); } @@ -239,10 +244,6 @@ LinphoneChatMessage *linphone_chat_room_find_message (LinphoneChatRoom *cr, cons return L_GET_C_BACK_PTR(L_GET_CPP_PTR_FROM_C_OBJECT(cr)->findChatMessage(message_id)); } -LinphoneChatRoomCbs *linphone_chat_room_get_callbacks (const LinphoneChatRoom *cr) { - return cr->cbs; -} - LinphoneChatRoomState linphone_chat_room_get_state (const LinphoneChatRoom *cr) { return (LinphoneChatRoomState)L_GET_CPP_PTR_FROM_C_OBJECT(cr)->getState(); } @@ -251,6 +252,10 @@ bool_t linphone_chat_room_has_been_left (const LinphoneChatRoom *cr) { return (bool_t)L_GET_CPP_PTR_FROM_C_OBJECT(cr)->hasBeenLeft(); } +time_t linphone_chat_room_get_last_update_time(const LinphoneChatRoom *cr) { + return L_GET_CPP_PTR_FROM_C_OBJECT(cr)->getLastUpdateTime(); +} + void linphone_chat_room_add_participant (LinphoneChatRoom *cr, const LinphoneAddress *addr) { L_GET_CPP_PTR_FROM_C_OBJECT(cr)->addParticipant( LinphonePrivate::IdentityAddress(*L_GET_CPP_PTR_FROM_C_OBJECT(addr)), nullptr, false @@ -377,6 +382,16 @@ void linphone_chat_room_set_participant_devices (LinphoneChatRoom *cr, const Lin bctbx_free(addrStr); } +void linphone_chat_room_add_participant_device (LinphoneChatRoom *cr, const LinphoneAddress *participantAddress, const LinphoneAddress *deviceAddress) { + char *participantAddressStr = linphone_address_as_string(participantAddress); + char *deviceAddressStr = linphone_address_as_string(deviceAddress); + LinphonePrivate::ServerGroupChatRoomPrivate *sgcr = dynamic_cast(L_GET_PRIVATE_FROM_C_OBJECT(cr)); + if (sgcr) + sgcr->addParticipantDevice(LinphonePrivate::IdentityAddress(participantAddressStr), LinphonePrivate::IdentityAddress(deviceAddressStr)); + bctbx_free(participantAddressStr); + bctbx_free(deviceAddressStr); +} + void linphone_chat_room_add_compatible_participants (LinphoneChatRoom *cr, const LinphoneAddress *deviceAddr, const bctbx_list_t *participantsCompatible) { list lPartsComp = L_GET_RESOLVED_CPP_LIST_FROM_C_LIST(participantsCompatible, Address); LinphonePrivate::ServerGroupChatRoomPrivate *sgcr = dynamic_cast(L_GET_PRIVATE_FROM_C_OBJECT(cr)); @@ -389,6 +404,118 @@ void linphone_chat_room_add_compatible_participants (LinphoneChatRoom *cr, const } } +// ============================================================================= +// Callbacks +// ============================================================================= + +void _linphone_chat_room_clear_callbacks (LinphoneChatRoom *cr) { + bctbx_list_free_with_data(cr->callbacks, (bctbx_list_free_func)linphone_chat_room_cbs_unref); + cr->callbacks = nullptr; +} + +void linphone_chat_room_add_callbacks (LinphoneChatRoom *cr, LinphoneChatRoomCbs *cbs) { + cr->callbacks = bctbx_list_append(cr->callbacks, linphone_chat_room_cbs_ref(cbs)); +} + +void linphone_chat_room_remove_callbacks (LinphoneChatRoom *cr, LinphoneChatRoomCbs *cbs) { + cr->callbacks = bctbx_list_remove(cr->callbacks, cbs); + linphone_chat_room_cbs_unref(cbs); +} + +LinphoneChatRoomCbs *linphone_chat_room_get_current_callbacks (const LinphoneChatRoom *cr) { + return cr->currentCbs; +} + +void linphone_chat_room_set_current_callbacks(LinphoneChatRoom *cr, LinphoneChatRoomCbs *cbs) { + cr->currentCbs = cbs; +} + +const bctbx_list_t *linphone_chat_room_get_callbacks_list(const LinphoneChatRoom *cr) { + return cr->callbacks; +} + +#define NOTIFY_IF_EXIST(cbName, functionName, ...) \ + bctbx_list_t *callbacksCopy = bctbx_list_copy(linphone_chat_room_get_callbacks_list(cr)); \ + for (bctbx_list_t *it = callbacksCopy; it; it = bctbx_list_next(it)) { \ + linphone_chat_room_set_current_callbacks(cr, reinterpret_cast(bctbx_list_get_data(it))); \ + LinphoneChatRoomCbs ## cbName ## Cb cb = linphone_chat_room_cbs_get_ ## functionName (linphone_chat_room_get_current_callbacks(cr)); \ + if (cb) \ + cb(__VA_ARGS__); \ + } \ + bctbx_list_free(callbacksCopy); + +void _linphone_chat_room_notify_is_composing_received(LinphoneChatRoom *cr, const LinphoneAddress *remoteAddr, bool_t isComposing) { + NOTIFY_IF_EXIST(IsComposingReceived, is_composing_received, cr, remoteAddr, isComposing) +} + +void _linphone_chat_room_notify_message_received(LinphoneChatRoom *cr, LinphoneChatMessage *msg) { + NOTIFY_IF_EXIST(MessageReceived, message_received, cr, msg) +} + +void _linphone_chat_room_notify_participant_added(LinphoneChatRoom *cr, const LinphoneEventLog *event_log) { + NOTIFY_IF_EXIST(ParticipantAdded, participant_added, cr, event_log) +} + +void _linphone_chat_room_notify_participant_removed(LinphoneChatRoom *cr, const LinphoneEventLog *event_log) { + NOTIFY_IF_EXIST(ParticipantRemoved, participant_removed, cr, event_log) +} + +void _linphone_chat_room_notify_participant_device_added(LinphoneChatRoom *cr, const LinphoneEventLog *event_log) { + NOTIFY_IF_EXIST(ParticipantDeviceAdded, participant_device_added, cr, event_log) +} + +void _linphone_chat_room_notify_participant_device_removed(LinphoneChatRoom *cr, const LinphoneEventLog *event_log) { + NOTIFY_IF_EXIST(ParticipantDeviceRemoved, participant_device_removed, cr, event_log) +} + +void _linphone_chat_room_notify_participant_admin_status_changed(LinphoneChatRoom *cr, const LinphoneEventLog *event_log) { + NOTIFY_IF_EXIST(ParticipantAdminStatusChanged, participant_admin_status_changed, cr, event_log) +} + +void _linphone_chat_room_notify_state_changed(LinphoneChatRoom *cr, LinphoneChatRoomState newState) { + NOTIFY_IF_EXIST(StateChanged, state_changed, cr, newState) +} + +void _linphone_chat_room_notify_subject_changed(LinphoneChatRoom *cr, const LinphoneEventLog *event_log) { + NOTIFY_IF_EXIST(SubjectChanged, subject_changed, cr, event_log) +} + +void _linphone_chat_room_notify_undecryptable_message_received(LinphoneChatRoom *cr, LinphoneChatMessage *msg) { + NOTIFY_IF_EXIST(UndecryptableMessageReceived, undecryptable_message_received, cr, msg) +} + +void _linphone_chat_room_notify_chat_message_received(LinphoneChatRoom *cr, const LinphoneEventLog *event_log) { + NOTIFY_IF_EXIST(ChatMessageReceived, chat_message_received, cr, event_log) +} + +void _linphone_chat_room_notify_chat_message_sent(LinphoneChatRoom *cr, const LinphoneEventLog *event_log) { + NOTIFY_IF_EXIST(ChatMessageSent, chat_message_sent, cr, event_log) +} + +void _linphone_chat_room_notify_conference_address_generation(LinphoneChatRoom *cr) { + NOTIFY_IF_EXIST(ConferenceAddressGeneration, conference_address_generation, cr) +} + +void _linphone_chat_room_notify_participant_device_fetch_requested(LinphoneChatRoom *cr, const LinphoneAddress *participantAddr) { + NOTIFY_IF_EXIST(ParticipantDeviceFetchRequested, participant_device_fetch_requested, cr, participantAddr) +} + +void _linphone_chat_room_notify_participants_capabilities_checked(LinphoneChatRoom *cr, const LinphoneAddress *deviceAddr, const bctbx_list_t *participantsAddr) { + NOTIFY_IF_EXIST(ParticipantsCapabilitiesChecked, participants_capabilities_checked, cr, deviceAddr, participantsAddr) +} + +void _linphone_chat_room_notify_participant_registration_subscription_requested(LinphoneChatRoom *cr, const LinphoneAddress *participantAddr) { + NOTIFY_IF_EXIST(ParticipantRegistrationSubscriptionRequested, participant_registration_subscription_requested, cr, participantAddr) +} + +void _linphone_chat_room_notify_participant_registration_unsubscription_requested(LinphoneChatRoom *cr, const LinphoneAddress *participantAddr) { + NOTIFY_IF_EXIST(ParticipantRegistrationUnsubscriptionRequested, participant_registration_unsubscription_requested, cr, participantAddr) +} + +void _linphone_chat_room_notify_chat_message_should_be_stored(LinphoneChatRoom *cr, LinphoneChatMessage *msg) { + NOTIFY_IF_EXIST(ShouldChatMessageBeStored, chat_message_should_be_stored, cr, msg) +} + // ============================================================================= // Reference and user data handling functions. // ============================================================================= @@ -414,39 +541,6 @@ void linphone_chat_room_set_user_data (LinphoneChatRoom *cr, void *ud) { // Constructor and destructor functions. // ============================================================================= -LinphoneChatRoom *_linphone_client_group_chat_room_new (LinphoneCore *core, const char *uri, const char *subject, bool_t fallback) { - LinphoneAddress *addr = linphone_address_new(uri); - LinphoneProxyConfig *proxy = linphone_core_lookup_known_proxy(core, addr); - linphone_address_unref(addr); - string from; - if (proxy) { - const LinphoneAddress *contactAddr = linphone_proxy_config_get_contact(proxy); - if (contactAddr) { - char *cFrom = linphone_address_as_string(contactAddr); - from = string(cFrom); - bctbx_free(cFrom); - } else { - from = L_GET_CPP_PTR_FROM_C_OBJECT(linphone_proxy_config_get_identity_address(proxy))->asString(); - } - } - if (from.empty()) - from = linphone_core_get_primary_contact(core); - LinphonePrivate::IdentityAddress me(from); - shared_ptr cgcr = make_shared( - L_GET_CPP_PTR_FROM_C_OBJECT(core), L_C_TO_STRING(uri), me, L_C_TO_STRING(subject)); - LinphoneChatRoom *cr = L_INIT(ChatRoom); - if (fallback) { - // Create a ClientGroupToBasicChatRoom to handle fallback from ClientGroupChatRoom to BasicGroupChatRoom if - // only one participant is invited and that it does not support group chat - L_SET_CPP_PTR_FROM_C_OBJECT(cr, make_shared(cgcr)); - L_GET_PRIVATE(cgcr)->setCallSessionListener(L_GET_PRIVATE_FROM_C_OBJECT(cr)); - L_GET_PRIVATE(cgcr)->setChatRoomListener(L_GET_PRIVATE_FROM_C_OBJECT(cr)); - } else - L_SET_CPP_PTR_FROM_C_OBJECT(cr, cgcr); - L_GET_PRIVATE(cgcr)->setState(LinphonePrivate::ChatRoom::State::Instantiated); - return cr; -} - LinphoneChatRoom *_linphone_server_group_chat_room_new (LinphoneCore *core, LinphonePrivate::SalCallOp *op) { LinphoneChatRoom *cr = L_INIT(ChatRoom); L_SET_CPP_PTR_FROM_C_OBJECT(cr, make_shared( @@ -457,8 +551,3 @@ LinphoneChatRoom *_linphone_server_group_chat_room_new (LinphoneCore *core, Linp L_GET_PRIVATE_FROM_C_OBJECT(cr, ServerGroupChatRoom)->confirmCreation(); return cr; } - -/* DEPRECATED */ -void linphone_chat_room_destroy (LinphoneChatRoom *cr) { - linphone_chat_room_unref(cr); -} diff --git a/src/c-wrapper/api/c-magic-search.cpp b/src/c-wrapper/api/c-magic-search.cpp new file mode 100644 index 000000000..028a64ba1 --- /dev/null +++ b/src/c-wrapper/api/c-magic-search.cpp @@ -0,0 +1,102 @@ +/* + * c-magic-search.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 "search/magic-search.h" +#include "c-wrapper/c-wrapper.h" + +using namespace std; + +L_DECLARE_C_OBJECT_IMPL(MagicSearch); + +LinphoneMagicSearch *linphone_core_create_magic_search(LinphoneCore *lc) { + shared_ptr cppPtr = make_shared(L_GET_CPP_PTR_FROM_C_OBJECT(lc)); + + LinphoneMagicSearch *object = L_INIT(MagicSearch); + L_SET_CPP_PTR_FROM_C_OBJECT(object, cppPtr); + return object; +} + +LinphoneMagicSearch *linphone_magic_search_new(LinphoneCore *lc) { + return linphone_core_create_magic_search(lc); +} + +LinphoneMagicSearch *linphone_magic_search_ref(LinphoneMagicSearch *magicSearch) { + belle_sip_object_ref(magicSearch); + return magicSearch; +} + +void linphone_magic_search_unref(LinphoneMagicSearch *magicSearch) { + belle_sip_object_unref(magicSearch); +} + +void linphone_magic_search_set_min_weight(LinphoneMagicSearch *magicSearch, const unsigned int weight) { + L_GET_CPP_PTR_FROM_C_OBJECT(magicSearch)->setMinWeight(weight); +} + +unsigned int linphone_magic_search_get_min_weight(const LinphoneMagicSearch *magicSearch) { + return L_GET_CPP_PTR_FROM_C_OBJECT(magicSearch)->getMinWeight(); +} + +void linphone_magic_search_set_max_weight(LinphoneMagicSearch *magicSearch, const unsigned int weight) { + L_GET_CPP_PTR_FROM_C_OBJECT(magicSearch)->setMaxWeight(weight); +} + +unsigned int linphone_magic_search_get_max_weight(const LinphoneMagicSearch *magicSearch) { + return L_GET_CPP_PTR_FROM_C_OBJECT(magicSearch)->getMaxWeight(); +} + +const char *linphone_magic_search_get_delimiter(const LinphoneMagicSearch *magicSearch) { + return L_STRING_TO_C(L_GET_CPP_PTR_FROM_C_OBJECT(magicSearch)->getDelimiter()); +} + +void linphone_magic_search_set_delimiter(LinphoneMagicSearch *magicSearch, const char *delimiter) { + L_GET_CPP_PTR_FROM_C_OBJECT(magicSearch)->setDelimiter(L_C_TO_STRING(delimiter)); +} + +bool_t linphone_magic_search_get_use_delimiter(LinphoneMagicSearch *magicSearch) { + return L_GET_CPP_PTR_FROM_C_OBJECT(magicSearch)->getUseDelimiter(); +} + +void linphone_magic_search_set_use_delimiter(LinphoneMagicSearch *magicSearch, bool_t enable) { + L_GET_CPP_PTR_FROM_C_OBJECT(magicSearch)->setUseDelimiter(enable); +} + +unsigned int linphone_magic_search_get_search_limit(const LinphoneMagicSearch *magicSearch) { + return L_GET_CPP_PTR_FROM_C_OBJECT(magicSearch)->getSearchLimit(); +} + +void linphone_magic_search_set_search_limit(LinphoneMagicSearch *magicSearch, const unsigned int limit) { + L_GET_CPP_PTR_FROM_C_OBJECT(magicSearch)->setSearchLimit(limit); +} + +bool_t linphone_magic_search_get_limited_search(const LinphoneMagicSearch *magicSearch) { + return L_GET_CPP_PTR_FROM_C_OBJECT(magicSearch)->getLimitedSearch(); +} + +void linphone_magic_search_set_limited_search(LinphoneMagicSearch *magicSearch, const bool_t limited) { + L_GET_CPP_PTR_FROM_C_OBJECT(magicSearch)->setLimitedSearch(limited); +} + +void linphone_magic_search_reset_search_cache(LinphoneMagicSearch *magicSearch) { + L_GET_CPP_PTR_FROM_C_OBJECT(magicSearch)->resetSearchCache(); +} + +bctbx_list_t* linphone_magic_search_get_contact_list_from_filter(LinphoneMagicSearch *magicSearch, const char *filter, const char *withDomain) { + return L_GET_RESOLVED_C_LIST_FROM_CPP_LIST(L_GET_CPP_PTR_FROM_C_OBJECT(magicSearch)->getContactListFromFilter(L_C_TO_STRING(filter), L_C_TO_STRING(withDomain))); +} diff --git a/src/c-wrapper/api/c-search-result.cpp b/src/c-wrapper/api/c-search-result.cpp new file mode 100644 index 000000000..77cd38c13 --- /dev/null +++ b/src/c-wrapper/api/c-search-result.cpp @@ -0,0 +1,44 @@ +/* + * c-search-result.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 "search/search-result.h" +#include "c-wrapper/c-wrapper.h" + +L_DECLARE_C_CLONABLE_OBJECT_IMPL(SearchResult); + +LinphoneSearchResult *linphone_search_result_ref(LinphoneSearchResult *searchResult) { + belle_sip_object_ref(searchResult); + return searchResult; +} + +void linphone_search_result_unref(LinphoneSearchResult *searchResult) { + belle_sip_object_unref(searchResult); +} + +const LinphoneFriend* linphone_search_result_get_friend(const LinphoneSearchResult *searchResult) { + return L_GET_CPP_PTR_FROM_C_OBJECT(searchResult)->getFriend(); +} + +const LinphoneAddress* linphone_search_result_get_address(const LinphoneSearchResult *searchResult) { + return L_GET_CPP_PTR_FROM_C_OBJECT(searchResult)->getAddress(); +} + +unsigned int linphone_search_result_get_weight(const LinphoneSearchResult *searchResult) { + return L_GET_CPP_PTR_FROM_C_OBJECT(searchResult)->getWeight(); +} diff --git a/src/c-wrapper/c-wrapper.h b/src/c-wrapper/c-wrapper.h index 2e1260e5e..9ebb30f72 100644 --- a/src/c-wrapper/c-wrapper.h +++ b/src/c-wrapper/c-wrapper.h @@ -39,8 +39,10 @@ F(Core, Core) \ F(DialPlan, DialPlan) \ F(EventLog, EventLog) \ + F(MagicSearch, MagicSearch) \ F(MediaSessionParams, CallParams) \ - F(Participant, Participant) + F(Participant, Participant) \ + F(SearchResult, SearchResult) #define L_REGISTER_SUBTYPES(F) \ F(AbstractChatRoom, BasicChatRoom) \ @@ -87,6 +89,7 @@ BELLE_SIP_TYPE_ID(LinphoneContent), BELLE_SIP_TYPE_ID(LinphoneCoreCbs), BELLE_SIP_TYPE_ID(LinphoneErrorInfo), BELLE_SIP_TYPE_ID(LinphoneEvent), +BELLE_SIP_TYPE_ID(LinphoneEventCbs), BELLE_SIP_TYPE_ID(LinphoneFactory), BELLE_SIP_TYPE_ID(LinphoneFriend), BELLE_SIP_TYPE_ID(LinphoneFriendList), diff --git a/src/c-wrapper/internal/c-tools.h b/src/c-wrapper/internal/c-tools.h index 55d12a33b..17ec09ff5 100644 --- a/src/c-wrapper/internal/c-tools.h +++ b/src/c-wrapper/internal/c-tools.h @@ -155,7 +155,7 @@ private: // Runtime checker. // --------------------------------------------------------------------------- - static inline void abort (const char *message) { + [[noreturn]] static inline void abort (const char *message) { std::cerr << "[FATAL C-WRAPPER]" << message << std::endl; std::terminate(); } @@ -338,7 +338,7 @@ public: typename = typename std::enable_if::value, CppType>::type > static inline std::shared_ptr getCppPtrFromC (const CType *cObject) { - return getCppPtrFromC(const_cast(cObject)); + return getCppPtrFromC::type, CppType>(const_cast(cObject)); } template< @@ -562,7 +562,7 @@ public: static inline bctbx_list_t *getResolvedCListFromCppList (const std::list &cppList) { bctbx_list_t *result = nullptr; for (const auto &value : cppList) - result = bctbx_list_append(result, belle_sip_object_ref(getCBackPtr(&value))); + result = bctbx_list_append(result, getCBackPtr(&value)); return result; } diff --git a/src/call/call-p.h b/src/call/call-p.h index 94ec4cf07..0477198df 100644 --- a/src/call/call-p.h +++ b/src/call/call-p.h @@ -24,6 +24,7 @@ #include "conference/conference.h" #include "conference/session/call-session-listener.h" #include "object/object-p.h" +#include "utils/background-task.h" // TODO: Remove me later. #include "private.h" @@ -33,11 +34,10 @@ LINPHONE_BEGIN_NAMESPACE class CallSession; +class RealTimeTextChatRoom; class CallPrivate : public ObjectPrivate, public CallSessionListener { public: - CallPrivate () = default; - void initiateIncoming (); bool initiateOutgoing (); void iterate (time_t currentRealTime, bool oneSecondElapsed); @@ -49,6 +49,7 @@ public: virtual std::shared_ptr getActiveSession () const { return nullptr; } bool getAudioMuted () const; + std::shared_ptr getChatRoom (); LinphoneProxyConfig *getDestProxy () const; IceSession *getIceSession () const; @@ -71,53 +72,57 @@ private: void terminateBecauseOfLostMedia (); /* CallSessionListener */ - void onAckBeingSent (const std::shared_ptr &session, LinphoneHeaders *headers) override; - void onAckReceived (const std::shared_ptr &session, LinphoneHeaders *headers) override; - void onBackgroundTaskToBeStarted (const std::shared_ptr &session) override; - void onBackgroundTaskToBeStopped (const std::shared_ptr &session) override; - bool onCallSessionAccepted (const std::shared_ptr &session) override; - void onCallSessionConferenceStreamStarting (const std::shared_ptr &session, bool mute) override; - void onCallSessionConferenceStreamStopping (const std::shared_ptr &session) override; - void onCallSessionEarlyFailed (const std::shared_ptr &session, LinphoneErrorInfo *ei) override; - void onCallSessionSetReleased (const std::shared_ptr &session) override; - void onCallSessionSetTerminated (const std::shared_ptr &session) override; - void onCallSessionStartReferred (const std::shared_ptr &session) override; - void onCallSessionStateChanged (const std::shared_ptr &session, CallSession::State state, const std::string &message) override; - void onCallSessionTransferStateChanged (const std::shared_ptr &session, CallSession::State state) override; - void onCheckForAcceptation (const std::shared_ptr &session) override; - void onDtmfReceived (const std::shared_ptr &session, char dtmf) override; - void onIncomingCallSessionNotified (const std::shared_ptr &session) override; - void onIncomingCallSessionStarted (const std::shared_ptr &session) override; - void onIncomingCallSessionTimeoutCheck (const std::shared_ptr &session, int elapsed, bool oneSecondElapsed) override; - void onInfoReceived (const std::shared_ptr &session, const LinphoneInfoMessage *im) override; - void onNoMediaTimeoutCheck (const std::shared_ptr &session, bool oneSecondElapsed) override; - void onEncryptionChanged (const std::shared_ptr &session, bool activated, const std::string &authToken) override; - void onCallSessionStateChangedForReporting (const std::shared_ptr &session) override; - void onRtcpUpdateForReporting (const std::shared_ptr &session, SalStreamType type) override; - void onStatsUpdated (const std::shared_ptr &session, const LinphoneCallStats *stats) override; - void onUpdateMediaInfoForReporting (const std::shared_ptr &session, int statsType) override; - void onResetCurrentSession (const std::shared_ptr &session) override; - void onSetCurrentSession (const std::shared_ptr &session) override; - void onFirstVideoFrameDecoded (const std::shared_ptr &session) override; - void onResetFirstVideoFrameDecoded (const std::shared_ptr &session) override; - void onPlayErrorTone (const std::shared_ptr &session, LinphoneReason reason) override; - void onRingbackToneRequested (const std::shared_ptr &session, bool requested) override; - void onStartRinging (const std::shared_ptr &session) override; - void onStopRinging (const std::shared_ptr &session) override; - void onStopRingingIfInCall (const std::shared_ptr &session) override; - void onStopRingingIfNeeded (const std::shared_ptr &session) override; - bool areSoundResourcesAvailable (const std::shared_ptr &session) override; - bool isPlayingRingbackTone (const std::shared_ptr &session) override; + void onAckBeingSent (const std::shared_ptr &session, LinphoneHeaders *headers) override; + void onAckReceived (const std::shared_ptr &session, LinphoneHeaders *headers) override; + void onBackgroundTaskToBeStarted (const std::shared_ptr &session) override; + void onBackgroundTaskToBeStopped (const std::shared_ptr &session) override; + bool onCallSessionAccepted (const std::shared_ptr &session) override; + void onCallSessionConferenceStreamStarting (const std::shared_ptr &session, bool mute) override; + void onCallSessionConferenceStreamStopping (const std::shared_ptr &session) override; + void onCallSessionEarlyFailed (const std::shared_ptr &session, LinphoneErrorInfo *ei) override; + void onCallSessionSetReleased (const std::shared_ptr &session) override; + void onCallSessionSetTerminated (const std::shared_ptr &session) override; + void onCallSessionStartReferred (const std::shared_ptr &session) override; + void onCallSessionStateChanged (const std::shared_ptr &session, CallSession::State state, const std::string &message) override; + void onCallSessionTransferStateChanged (const std::shared_ptr &session, CallSession::State state) override; + void onCheckForAcceptation (const std::shared_ptr &session) override; + void onDtmfReceived (const std::shared_ptr &session, char dtmf) override; + void onIncomingCallSessionNotified (const std::shared_ptr &session) override; + void onIncomingCallSessionStarted (const std::shared_ptr &session) override; + void onIncomingCallSessionTimeoutCheck (const std::shared_ptr &session, int elapsed, bool oneSecondElapsed) override; + void onInfoReceived (const std::shared_ptr &session, const LinphoneInfoMessage *im) override; + void onNoMediaTimeoutCheck (const std::shared_ptr &session, bool oneSecondElapsed) override; + void onEncryptionChanged (const std::shared_ptr &session, bool activated, const std::string &authToken) override; + void onCallSessionStateChangedForReporting (const std::shared_ptr &session) override; + void onRtcpUpdateForReporting (const std::shared_ptr &session, SalStreamType type) override; + void onStatsUpdated (const std::shared_ptr &session, const LinphoneCallStats *stats) override; + void onUpdateMediaInfoForReporting (const std::shared_ptr &session, int statsType) override; + void onResetCurrentSession (const std::shared_ptr &session) override; + void onSetCurrentSession (const std::shared_ptr &session) override; + void onFirstVideoFrameDecoded (const std::shared_ptr &session) override; + void onResetFirstVideoFrameDecoded (const std::shared_ptr &session) override; + void onPlayErrorTone (const std::shared_ptr &session, LinphoneReason reason) override; + void onRingbackToneRequested (const std::shared_ptr &session, bool requested) override; + void onStartRinging (const std::shared_ptr &session) override; + void onStopRinging (const std::shared_ptr &session) override; + void onStopRingingIfInCall (const std::shared_ptr &session) override; + void onStopRingingIfNeeded (const std::shared_ptr &session) override; + bool areSoundResourcesAvailable (const std::shared_ptr &session) override; + bool isPlayingRingbackTone (const std::shared_ptr &session) override; + void onRealTimeTextCharacterReceived (const std::shared_ptr &session, RealtimeTextReceivedCharacter *character) override; + void onTmmbrReceived(const std::shared_ptr &session, int streamIndex, int tmmbr) override; mutable LinphonePlayer *player = nullptr; CallCallbackObj nextVideoFrameDecoded; - unsigned long backgroundTaskId = 0; - bool ringingBeep = false; bool playingRingbackTone = false; + BackgroundTask bgTask; + + mutable std::shared_ptr chatRoom; + L_DECLARE_PUBLIC(Call); }; diff --git a/src/call/call.cpp b/src/call/call.cpp index 4e00dcf2b..6d51ce781 100644 --- a/src/call/call.cpp +++ b/src/call/call.cpp @@ -19,6 +19,7 @@ #include "c-wrapper/c-wrapper.h" #include "call-p.h" +#include "chat/chat-room/real-time-text-chat-room-p.h" #include "conference/params/media-session-params-p.h" #include "conference/session/call-session-p.h" #include "conference/session/media-session-p.h" @@ -37,6 +38,17 @@ bool CallPrivate::getAudioMuted () const { return static_pointer_cast(getActiveSession())->getPrivate()->getAudioMuted(); } +shared_ptr CallPrivate::getChatRoom () { + L_Q(); + if (!chatRoom && (q->getState() != CallSession::State::End) && (q->getState() != CallSession::State::Released)) { + ChatRoomId chatRoomId(q->getRemoteAddress(), q->getLocalAddress()); + RealTimeTextChatRoom *rttcr = new RealTimeTextChatRoom(q->getCore(), chatRoomId); + chatRoom.reset(rttcr); + rttcr->getPrivate()->setCall(q->getSharedFromThis()); + } + return chatRoom; +} + LinphoneProxyConfig *CallPrivate::getDestProxy () const { return getActiveSession()->getPrivate()->getDestProxy(); } @@ -167,7 +179,7 @@ void CallPrivate::startRemoteRing () { void CallPrivate::terminateBecauseOfLostMedia () { L_Q(); - lInfo() << "Call [" << q << "]: Media connectivity with " << q->getRemoteAddressAsString() + lInfo() << "Call [" << q << "]: Media connectivity with " << q->getRemoteAddress().asString() << " is lost, call is going to be terminated"; static_pointer_cast(getActiveSession())->terminateBecauseOfLostMedia(); linphone_core_play_named_tone(q->getCore()->getCCore(), LinphoneToneCallLost); @@ -175,28 +187,26 @@ void CallPrivate::terminateBecauseOfLostMedia () { // ----------------------------------------------------------------------------- -void CallPrivate::onAckBeingSent (const shared_ptr &session, LinphoneHeaders *headers) { +void CallPrivate::onAckBeingSent (const shared_ptr &session, LinphoneHeaders *headers) { L_Q(); linphone_call_notify_ack_processing(L_GET_C_BACK_PTR(q), headers, false); } -void CallPrivate::onAckReceived (const shared_ptr &session, LinphoneHeaders *headers) { +void CallPrivate::onAckReceived (const shared_ptr &session, LinphoneHeaders *headers) { L_Q(); linphone_call_notify_ack_processing(L_GET_C_BACK_PTR(q), headers, true); } -void CallPrivate::onBackgroundTaskToBeStarted (const shared_ptr &session) { - backgroundTaskId = sal_begin_background_task("liblinphone call notification", nullptr, nullptr); +void CallPrivate::onBackgroundTaskToBeStarted (const shared_ptr &session) { + L_Q(); + bgTask.start(q->getCore(),30); } -void CallPrivate::onBackgroundTaskToBeStopped (const shared_ptr &session) { - if (backgroundTaskId != 0) { - sal_end_background_task(backgroundTaskId); - backgroundTaskId = 0; - } +void CallPrivate::onBackgroundTaskToBeStopped (const shared_ptr &session) { + bgTask.stop(); } -bool CallPrivate::onCallSessionAccepted (const shared_ptr &session) { +bool CallPrivate::onCallSessionAccepted (const shared_ptr &session) { L_Q(); LinphoneCore *lc = q->getCore()->getCCore(); bool wasRinging = false; @@ -217,21 +227,21 @@ bool CallPrivate::onCallSessionAccepted (const shared_ptr &se return wasRinging; } -void CallPrivate::onCallSessionConferenceStreamStarting (const shared_ptr &session, bool mute) { +void CallPrivate::onCallSessionConferenceStreamStarting (const shared_ptr &session, bool mute) { L_Q(); if (q->getCore()->getCCore()->conf_ctx) { linphone_conference_on_call_stream_starting(q->getCore()->getCCore()->conf_ctx, L_GET_C_BACK_PTR(q), mute); } } -void CallPrivate::onCallSessionConferenceStreamStopping (const shared_ptr &session) { +void CallPrivate::onCallSessionConferenceStreamStopping (const shared_ptr &session) { L_Q(); LinphoneCore *lc = q->getCore()->getCCore(); if (lc->conf_ctx && _linphone_call_get_endpoint(L_GET_C_BACK_PTR(q))) linphone_conference_on_call_stream_stopping(lc->conf_ctx, L_GET_C_BACK_PTR(q)); } -void CallPrivate::onCallSessionEarlyFailed (const shared_ptr &session, LinphoneErrorInfo *ei) { +void CallPrivate::onCallSessionEarlyFailed (const shared_ptr &session, LinphoneErrorInfo *ei) { L_Q(); LinphoneCallLog *log = session->getLog(); linphone_core_report_early_failed_call(q->getCore()->getCCore(), @@ -242,12 +252,12 @@ void CallPrivate::onCallSessionEarlyFailed (const shared_ptr linphone_call_unref(L_GET_C_BACK_PTR(q)); } -void CallPrivate::onCallSessionSetReleased (const shared_ptr &session) { +void CallPrivate::onCallSessionSetReleased (const shared_ptr &session) { L_Q(); linphone_call_unref(L_GET_C_BACK_PTR(q)); } -void CallPrivate::onCallSessionSetTerminated (const shared_ptr &session) { +void CallPrivate::onCallSessionSetTerminated (const shared_ptr &session) { L_Q(); LinphoneCore *core = q->getCore()->getCCore(); if (q->getSharedFromThis() == q->getCore()->getCurrentCall()) { @@ -270,60 +280,74 @@ void CallPrivate::onCallSessionSetTerminated (const shared_ptrbw_controller); } -void CallPrivate::onCallSessionStartReferred (const shared_ptr &session) { +void CallPrivate::onCallSessionStartReferred (const shared_ptr &session) { startReferredCall(nullptr); } -void CallPrivate::onCallSessionStateChanged (const shared_ptr &session, CallSession::State state, const string &message) { +void CallPrivate::onCallSessionStateChanged (const shared_ptr &session, CallSession::State state, const string &message) { L_Q(); + switch(state){ + case CallSession::State::OutgoingInit: + case CallSession::State::IncomingReceived: + getPlatformHelpers(q->getCore()->getCCore())->acquireWifiLock(); + getPlatformHelpers(q->getCore()->getCCore())->acquireMcastLock(); + getPlatformHelpers(q->getCore()->getCCore())->acquireCpuLock(); + break; + case CallSession::State::Released: + getPlatformHelpers(q->getCore()->getCCore())->releaseWifiLock(); + getPlatformHelpers(q->getCore()->getCCore())->releaseMcastLock(); + getPlatformHelpers(q->getCore()->getCCore())->releaseCpuLock(); + break; + default: + break; + } linphone_call_notify_state_changed(L_GET_C_BACK_PTR(q), static_cast(state), message.c_str()); } -void CallPrivate::onCallSessionTransferStateChanged (const shared_ptr &session, CallSession::State state) { +void CallPrivate::onCallSessionTransferStateChanged (const shared_ptr &session, CallSession::State state) { L_Q(); linphone_call_notify_transfer_state_changed(L_GET_C_BACK_PTR(q), static_cast(state)); } -void CallPrivate::onCheckForAcceptation (const shared_ptr &session) { +void CallPrivate::onCheckForAcceptation (const shared_ptr &session) { L_Q(); - LinphoneCall *lcall = L_GET_C_BACK_PTR(q); - bctbx_list_t *copy = bctbx_list_copy(linphone_core_get_calls(q->getCore()->getCCore())); - for (bctbx_list_t *it = copy; it != nullptr; it = bctbx_list_next(it)) { - LinphoneCall *call = reinterpret_cast(bctbx_list_get_data(it)); - if (call == lcall) continue; - switch (L_GET_CPP_PTR_FROM_C_OBJECT(call)->getState()) { + list> calls = q->getCore()->getCalls(); + shared_ptr currentCall = q->getSharedFromThis(); + for (const auto &call : calls) { + if (call == currentCall) + continue; + switch (call->getState()) { case CallSession::State::OutgoingInit: case CallSession::State::OutgoingProgress: case CallSession::State::OutgoingRinging: case CallSession::State::OutgoingEarlyMedia: - lInfo() << "Already existing call [" << call << "] in state [" << linphone_call_state_to_string(linphone_call_get_state(call)) << - "], canceling it before accepting new call [" << lcall << "]"; - linphone_call_terminate(call); + lInfo() << "Already existing call [" << call << "] in state [" << Utils::toString(call->getState()) + << "], canceling it before accepting new call [" << currentCall << "]"; + call->terminate(); break; default: - break; /* Nothing to do */ + break; // Nothing to do } } - bctbx_list_free(copy); } -void CallPrivate::onDtmfReceived (const shared_ptr &session, char dtmf) { +void CallPrivate::onDtmfReceived (const shared_ptr &session, char dtmf) { L_Q(); linphone_call_notify_dtmf_received(L_GET_C_BACK_PTR(q), dtmf); } -void CallPrivate::onIncomingCallSessionNotified (const shared_ptr &session) { +void CallPrivate::onIncomingCallSessionNotified (const shared_ptr &session) { L_Q(); /* The call is acceptable so we can now add it to our list */ q->getCore()->getPrivate()->addCall(q->getSharedFromThis()); } -void CallPrivate::onIncomingCallSessionStarted (const shared_ptr &session) { +void CallPrivate::onIncomingCallSessionStarted (const shared_ptr &session) { L_Q(); linphone_core_notify_incoming_call(q->getCore()->getCCore(), L_GET_C_BACK_PTR(q)); } -void CallPrivate::onIncomingCallSessionTimeoutCheck (const shared_ptr &session, int elapsed, bool oneSecondElapsed) { +void CallPrivate::onIncomingCallSessionTimeoutCheck (const shared_ptr &session, int elapsed, bool oneSecondElapsed) { L_Q(); if (oneSecondElapsed) lInfo() << "Incoming call ringing for " << elapsed << " seconds"; @@ -335,12 +359,12 @@ void CallPrivate::onIncomingCallSessionTimeoutCheck (const shared_ptr &session, const LinphoneInfoMessage *im) { +void CallPrivate::onInfoReceived (const shared_ptr &session, const LinphoneInfoMessage *im) { L_Q(); linphone_call_notify_info_message_received(L_GET_C_BACK_PTR(q), im); } -void CallPrivate::onNoMediaTimeoutCheck (const shared_ptr &session, bool oneSecondElapsed) { +void CallPrivate::onNoMediaTimeoutCheck (const shared_ptr &session, bool oneSecondElapsed) { L_Q(); int disconnectTimeout = linphone_core_get_nortp_timeout(q->getCore()->getCCore()); bool disconnected = false; @@ -352,42 +376,42 @@ void CallPrivate::onNoMediaTimeoutCheck (const shared_ptr &se terminateBecauseOfLostMedia(); } -void CallPrivate::onEncryptionChanged (const shared_ptr &session, bool activated, const string &authToken) { +void CallPrivate::onEncryptionChanged (const shared_ptr &session, bool activated, const string &authToken) { L_Q(); linphone_call_notify_encryption_changed(L_GET_C_BACK_PTR(q), activated, authToken.empty() ? nullptr : authToken.c_str()); } -void CallPrivate::onCallSessionStateChangedForReporting (const shared_ptr &session) { +void CallPrivate::onCallSessionStateChangedForReporting (const shared_ptr &session) { L_Q(); linphone_reporting_call_state_updated(L_GET_C_BACK_PTR(q)); } -void CallPrivate::onRtcpUpdateForReporting (const shared_ptr &session, SalStreamType type) { +void CallPrivate::onRtcpUpdateForReporting (const shared_ptr &session, SalStreamType type) { L_Q(); linphone_reporting_on_rtcp_update(L_GET_C_BACK_PTR(q), type); } -void CallPrivate::onStatsUpdated (const shared_ptr &session, const LinphoneCallStats *stats) { +void CallPrivate::onStatsUpdated (const shared_ptr &session, const LinphoneCallStats *stats) { L_Q(); linphone_call_notify_stats_updated(L_GET_C_BACK_PTR(q), stats); } -void CallPrivate::onUpdateMediaInfoForReporting (const std::shared_ptr &session, int statsType) { +void CallPrivate::onUpdateMediaInfoForReporting (const shared_ptr &session, int statsType) { L_Q(); linphone_reporting_update_media_info(L_GET_C_BACK_PTR(q), statsType); } -void CallPrivate::onResetCurrentSession (const shared_ptr &session) { +void CallPrivate::onResetCurrentSession (const shared_ptr &session) { L_Q(); q->getCore()->getPrivate()->setCurrentCall(nullptr); } -void CallPrivate::onSetCurrentSession (const shared_ptr &session) { +void CallPrivate::onSetCurrentSession (const shared_ptr &session) { L_Q(); q->getCore()->getPrivate()->setCurrentCall(q->getSharedFromThis()); } -void CallPrivate::onFirstVideoFrameDecoded (const shared_ptr &session) { +void CallPrivate::onFirstVideoFrameDecoded (const shared_ptr &session) { L_Q(); if (nextVideoFrameDecoded._func) { nextVideoFrameDecoded._func(L_GET_C_BACK_PTR(q), nextVideoFrameDecoded._user_data); @@ -396,16 +420,16 @@ void CallPrivate::onFirstVideoFrameDecoded (const shared_ptr } } -void CallPrivate::onResetFirstVideoFrameDecoded (const shared_ptr &session) { +void CallPrivate::onResetFirstVideoFrameDecoded (const shared_ptr &session) { resetFirstVideoFrameDecoded(); } -void CallPrivate::onPlayErrorTone (const shared_ptr &session, LinphoneReason reason) { +void CallPrivate::onPlayErrorTone (const shared_ptr &session, LinphoneReason reason) { L_Q(); linphone_core_play_call_error_tone(q->getCore()->getCCore(), reason); } -void CallPrivate::onRingbackToneRequested (const shared_ptr &session, bool requested) { +void CallPrivate::onRingbackToneRequested (const shared_ptr &session, bool requested) { L_Q(); if (requested && linphone_core_get_remote_ringback_tone(q->getCore()->getCCore())) playingRingbackTone = true; @@ -413,7 +437,7 @@ void CallPrivate::onRingbackToneRequested (const shared_ptr & playingRingbackTone = false; } -void CallPrivate::onStartRinging (const shared_ptr &session) { +void CallPrivate::onStartRinging (const shared_ptr &session) { L_Q(); LinphoneCore *lc = q->getCore()->getCCore(); if (lc->ringstream) @@ -421,12 +445,12 @@ void CallPrivate::onStartRinging (const shared_ptr &session) startRemoteRing(); } -void CallPrivate::onStopRinging (const shared_ptr &session) { +void CallPrivate::onStopRinging (const shared_ptr &session) { L_Q(); linphone_core_stop_ringing(q->getCore()->getCCore()); } -void CallPrivate::onStopRingingIfInCall (const shared_ptr &session) { +void CallPrivate::onStopRingingIfInCall (const shared_ptr &session) { L_Q(); LinphoneCore *lc = q->getCore()->getCCore(); // We stop the ring only if we have this current call or if we are in call @@ -435,7 +459,7 @@ void CallPrivate::onStopRingingIfInCall (const shared_ptr &se } } -void CallPrivate::onStopRingingIfNeeded (const shared_ptr &session) { +void CallPrivate::onStopRingingIfNeeded (const shared_ptr &session) { L_Q(); LinphoneCore *lc = q->getCore()->getCCore(); bool stopRinging = true; @@ -451,23 +475,35 @@ void CallPrivate::onStopRingingIfNeeded (const shared_ptr &se linphone_core_stop_ringing(lc); } -bool CallPrivate::areSoundResourcesAvailable (const shared_ptr &session) { +bool CallPrivate::areSoundResourcesAvailable (const shared_ptr &session) { L_Q(); LinphoneCore *lc = q->getCore()->getCCore(); shared_ptr currentCall = q->getCore()->getCurrentCall(); return !linphone_core_is_in_conference(lc) && (!currentCall || (currentCall == q->getSharedFromThis())); } -bool CallPrivate::isPlayingRingbackTone (const shared_ptr &session) { +bool CallPrivate::isPlayingRingbackTone (const shared_ptr &session) { return playingRingbackTone; } +void CallPrivate::onRealTimeTextCharacterReceived (const shared_ptr &session, RealtimeTextReceivedCharacter *data) { + L_Q(); + getChatRoom()->getPrivate()->realtimeTextReceived(data->character, q->getSharedFromThis()); +} + +void CallPrivate::onTmmbrReceived (const shared_ptr &session, int streamIndex, int tmmbr) { + L_Q(); + linphone_call_notify_tmmbr_received(L_GET_C_BACK_PTR(q), streamIndex, tmmbr); +} + // ============================================================================= Call::Call (CallPrivate &p, shared_ptr core) : Object(p), CoreAccessor(core) { L_D(); d->nextVideoFrameDecoded._func = nullptr; d->nextVideoFrameDecoded._user_data = nullptr; + + d->bgTask.setName("Liblinphone call notification"); } // ----------------------------------------------------------------------------- @@ -522,7 +558,7 @@ LinphoneStatus Call::pause () { return static_pointer_cast(d->getActiveSession())->pause(); } -LinphoneStatus Call::redirect (const std::string &redirectUri) { +LinphoneStatus Call::redirect (const string &redirectUri) { L_D(); return d->getActiveSession()->redirect(redirectUri); } @@ -537,7 +573,7 @@ LinphoneStatus Call::sendDtmf (char dtmf) { return static_pointer_cast(d->getActiveSession())->sendDtmf(dtmf); } -LinphoneStatus Call::sendDtmfs (const std::string &dtmfs) { +LinphoneStatus Call::sendDtmfs (const string &dtmfs) { L_D(); return static_pointer_cast(d->getActiveSession())->sendDtmfs(dtmfs); } @@ -683,6 +719,11 @@ const LinphoneErrorInfo *Call::getErrorInfo () const { return d->getActiveSession()->getErrorInfo(); } +const Address &Call::getLocalAddress () const { + L_D(); + return d->getActiveSession()->getLocalAddress(); +} + LinphoneCallLog *Call::getLog () const { L_D(); return d->getActiveSession()->getLog(); @@ -757,11 +798,6 @@ const Address &Call::getRemoteAddress () const { return d->getActiveSession()->getRemoteAddress(); } -string Call::getRemoteAddressAsString () const { - L_D(); - return d->getActiveSession()->getRemoteAddressAsString(); -} - string Call::getRemoteContact () const { L_D(); return d->getActiveSession()->getRemoteContact(); @@ -824,7 +860,7 @@ const Address &Call::getToAddress () const { return d->getActiveSession()->getToAddress(); } -string Call::getToHeader (const std::string &name) const { +string Call::getToHeader (const string &name) const { L_D(); return d->getActiveSession()->getToHeader(name); } diff --git a/src/call/call.h b/src/call/call.h index 6d1059127..4cfbf9a8a 100644 --- a/src/call/call.h +++ b/src/call/call.h @@ -36,6 +36,8 @@ class MediaSessionPrivate; class Call : public Object, public CoreAccessor { friend class CallSessionPrivate; + friend class ChatMessage; + friend class ChatMessagePrivate; friend class CorePrivate; friend class MediaSessionPrivate; @@ -85,6 +87,7 @@ public: const Address &getDiversionAddress () const; int getDuration () const; const LinphoneErrorInfo *getErrorInfo () const; + const Address &getLocalAddress () const; LinphoneCallLog *getLog () const; RtpTransport *getMetaRtcpTransport (int streamIndex) const; RtpTransport *getMetaRtpTransport (int streamIndex) const; @@ -98,7 +101,6 @@ public: std::shared_ptr getReferer () const; std::string getReferTo () const; const Address &getRemoteAddress () const; - std::string getRemoteAddressAsString () const; std::string getRemoteContact () const; const MediaSessionParams *getRemoteParams () const; std::string getRemoteUserAgent () const; diff --git a/src/call/local-conference-call-p.h b/src/call/local-conference-call-p.h index b352fa0b0..89e4a1971 100644 --- a/src/call/local-conference-call-p.h +++ b/src/call/local-conference-call-p.h @@ -29,8 +29,6 @@ LINPHONE_BEGIN_NAMESPACE class LocalConferenceCallPrivate : public CallPrivate { public: - LocalConferenceCallPrivate () = default; - std::shared_ptr getActiveSession () const override; private: diff --git a/src/call/remote-conference-call-p.h b/src/call/remote-conference-call-p.h index b627e2742..8f9cda90b 100644 --- a/src/call/remote-conference-call-p.h +++ b/src/call/remote-conference-call-p.h @@ -29,8 +29,6 @@ LINPHONE_BEGIN_NAMESPACE class RemoteConferenceCallPrivate : public CallPrivate { public: - RemoteConferenceCallPrivate () = default; - std::shared_ptr getActiveSession () const override; private: diff --git a/src/call/remote-conference-call.h b/src/call/remote-conference-call.h index f2a50fd00..9360b2c11 100644 --- a/src/call/remote-conference-call.h +++ b/src/call/remote-conference-call.h @@ -42,7 +42,7 @@ public: SalCallOp *op, const MediaSessionParams *msp ); - virtual ~RemoteConferenceCall (); + ~RemoteConferenceCall (); std::shared_ptr getCore () const; diff --git a/src/chat/chat-message/chat-message-p.h b/src/chat/chat-message/chat-message-p.h index 0eae6c24e..b665e0d1f 100644 --- a/src/chat/chat-message/chat-message-p.h +++ b/src/chat/chat-message/chat-message-p.h @@ -54,13 +54,12 @@ public: Started = 1 << 5, }; - ChatMessagePrivate () = default; - void setApplyModifiers (bool value) { applyModifiers = value; } 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); @@ -68,14 +67,30 @@ public: void setImdnMessageId (const std::string &imdnMessageId); - inline void forceFromAddress (const IdentityAddress &fromAddress) { + void forceFromAddress (const IdentityAddress &fromAddress) { this->fromAddress = fromAddress; } - inline void forceToAddress (const IdentityAddress &toAddress) { + void forceToAddress (const IdentityAddress &toAddress) { this->toAddress = toAddress; } + void markContentsAsNotLoaded () { + contentsNotLoadedFromDatabase = true; + } + + void loadContentsFromDatabase () const; + + std::list &getContents () { + loadContentsFromDatabase(); + return contents; + } + + const std::list &getContents () const { + loadContentsFromDatabase(); + return contents; + } + belle_http_request_t *getHttpRequest () const; void setHttpRequest (belle_http_request_t *request); @@ -85,11 +100,12 @@ public: SalCustomHeader *getSalCustomHeaders () const; void setSalCustomHeaders (SalCustomHeader *headers); - void addSalCustomHeader (const std::string& name, const std::string& value); - void removeSalCustomHeader (const std::string& name); - std::string getSalCustomHeaderValue (const std::string& name); + void addSalCustomHeader (const std::string &name, const std::string &value); + void removeSalCustomHeader (const std::string &name); + std::string getSalCustomHeaderValue (const std::string &name); void loadFileTransferUrlFromBodyToContent (); + std::string createFakeFileTransferFromUrl(const std::string &url); void setChatRoom (const std::shared_ptr &chatRoom); @@ -112,6 +128,7 @@ public: void setAppdata (const std::string &appData); const std::string &getExternalBodyUrl () const; + void setExternalBodyUrl (const std::string &url); bool hasTextContent () const; const Content* getTextContent () const; @@ -122,6 +139,9 @@ public: LinphoneContent *getFileTransferInformation () const; void setFileTransferInformation (const LinphoneContent *content); + void addContent (Content &content); + void removeContent (const Content &content); + bool downloadFile (); void sendImdn (Imdn::Type imdnType, LinphoneReason reason); @@ -134,15 +154,23 @@ public: void updateInDb (); private: + + ChatMessagePrivate(const std::shared_ptr &cr, ChatMessage::Direction dir); + + 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; std::string rttMessage; + std::string externalBodyUrl; bool isSecured = false; - bool isReadOnly = false; - std::list contents; + mutable bool isReadOnly = false; Content internalContent; + + // TODO: to replace salCustomheaders std::unordered_map customHeaders; + mutable LinphoneErrorInfo * errorInfo = nullptr; SalOp *salOp = nullptr; SalCustomHeader *salCustomHeaders = nullptr; @@ -171,9 +199,11 @@ private: ChatMessage::State state = ChatMessage::State::Idle; ChatMessage::Direction direction = ChatMessage::Direction::Incoming; + std::list contents; + bool encryptionPrevented = false; bool toBeStored = true; - + mutable bool contentsNotLoadedFromDatabase = false; L_DECLARE_PUBLIC(ChatMessage); }; diff --git a/src/chat/chat-message/chat-message.cpp b/src/chat/chat-message/chat-message.cpp index d6f7d3f95..eb2d1f029 100644 --- a/src/chat/chat-message/chat-message.cpp +++ b/src/chat/chat-message/chat-message.cpp @@ -25,7 +25,7 @@ #include "address/address.h" #include "c-wrapper/c-wrapper.h" -#include "call/call.h" +#include "call/call-p.h" #include "chat/chat-message/chat-message-p.h" #include "chat/chat-room/chat-room-p.h" #include "chat/chat-room/client-group-to-basic-chat-room.h" @@ -40,6 +40,7 @@ #include "core/core-p.h" #include "logger/logger.h" #include "chat/notification/imdn.h" +#include "sip-tools/sip-headers.h" #include "ortp/b64.h" @@ -51,6 +52,11 @@ using namespace B64_NAMESPACE; LINPHONE_BEGIN_NAMESPACE +ChatMessagePrivate::ChatMessagePrivate(const std::shared_ptr &cr, ChatMessage::Direction dir):fileTransferChatMessageModifier(cr->getCore()->getCCore()->http_provider) { + direction = dir; + setChatRoom(cr); +} + void ChatMessagePrivate::setDirection (ChatMessage::Direction dir) { direction = dir; } @@ -63,28 +69,71 @@ 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) + || (linphone_config_get_bool(linphone_core_get_config(q->getChatRoom()->getCore()->getCCore()), + "misc", "enable_simple_group_chat_message_state", TRUE + )) + ) { + 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; + + lInfo() << "Chat message " << this << ": moving participant '" << participantAddress.asString() << "' state to " + << Utils::toString(newState); + 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)) @@ -96,9 +145,15 @@ void ChatMessagePrivate::setState (ChatMessage::State s, bool force) { LinphoneChatMessageCbs *cbs = linphone_chat_message_get_callbacks(msg); if (cbs && linphone_chat_message_cbs_get_msg_state_changed(cbs)) - linphone_chat_message_cbs_get_msg_state_changed(cbs)(msg, linphone_chat_message_get_state(msg)); + linphone_chat_message_cbs_get_msg_state_changed(cbs)(msg, (LinphoneChatMessageState)state); - updateInDb(); + if (state == ChatMessage::State::FileTransferDone && !hasFileTransferContent()) { + // We wait until the file has been downloaded to send the displayed IMDN + q->sendDisplayNotification(); + setState(ChatMessage::State::Displayed); + } else { + updateInDb(); + } } belle_http_request_t *ChatMessagePrivate::getHttpRequest () const { @@ -142,7 +197,7 @@ string ChatMessagePrivate::getSalCustomHeaderValue (const string &name) { // ----------------------------------------------------------------------------- bool ChatMessagePrivate::hasTextContent() const { - for (const Content *c : contents) { + for (const Content *c : getContents()) { if (c->getContentType() == ContentType::PlainText) { return true; } @@ -151,7 +206,7 @@ bool ChatMessagePrivate::hasTextContent() const { } const Content* ChatMessagePrivate::getTextContent() const { - for (const Content *c : contents) { + for (const Content *c : getContents()) { if (c->getContentType() == ContentType::PlainText) { return c; } @@ -160,7 +215,7 @@ const Content* ChatMessagePrivate::getTextContent() const { } bool ChatMessagePrivate::hasFileTransferContent() const { - for (const Content *c : contents) { + for (const Content *c : getContents()) { if (c->getContentType() == ContentType::FileTransfer) { return true; } @@ -169,7 +224,7 @@ bool ChatMessagePrivate::hasFileTransferContent() const { } const Content* ChatMessagePrivate::getFileTransferContent() const { - for (const Content *c : contents) { + for (const Content *c : getContents()) { if (c->getContentType() == ContentType::FileTransfer) { return c; } @@ -186,27 +241,30 @@ void ChatMessagePrivate::setFileTransferFilepath (const string &path) { } const string &ChatMessagePrivate::getAppdata () const { - for (const Content *c : contents) { - if (c->isFile()) { - FileContent *fileContent = (FileContent *)c; - return fileContent->getAppData("legacy"); + for (const Content *c : getContents()) { + if (!c->getAppData("legacy").empty()) { + return c->getAppData("legacy"); } } return Utils::getEmptyConstRefObject(); } void ChatMessagePrivate::setAppdata (const string &data) { - for (const Content *c : contents) { - if (c->isFile()) { - FileContent *fileContent = (FileContent *)c; - fileContent->setAppData("legacy", data); - break; - } + bool contentFound = false; + for (Content *c : getContents()) { + c->setAppData("legacy", data); + contentFound = true; + break; + } + if (contentFound) { + updateInDb(); } - updateInDb(); } const string &ChatMessagePrivate::getExternalBodyUrl () const { + if (!externalBodyUrl.empty()) { + return externalBodyUrl; + } if (hasFileTransferContent()) { FileTransferContent *content = (FileTransferContent*) getFileTransferContent(); return content->getFileUrl(); @@ -214,7 +272,12 @@ const string &ChatMessagePrivate::getExternalBodyUrl () const { return Utils::getEmptyConstRefObject(); } +void ChatMessagePrivate::setExternalBodyUrl (const string &url) { + externalBodyUrl = url; +} + const ContentType &ChatMessagePrivate::getContentType () { + loadContentsFromDatabase(); if (direction == ChatMessage::Direction::Incoming) { if (contents.size() > 0) { Content *content = contents.front(); @@ -236,6 +299,7 @@ const ContentType &ChatMessagePrivate::getContentType () { } void ChatMessagePrivate::setContentType (const ContentType &contentType) { + loadContentsFromDatabase(); if (contents.size() > 0 && internalContent.getContentType().isEmpty() && internalContent.isEmpty()) { internalContent.setBody(contents.front()->getBody()); } @@ -249,6 +313,7 @@ void ChatMessagePrivate::setContentType (const ContentType &contentType) { } const string &ChatMessagePrivate::getText () { + loadContentsFromDatabase(); if (direction == ChatMessage::Direction::Incoming) { if (hasTextContent()) { cText = getTextContent()->getBodyAsString(); @@ -272,6 +337,7 @@ const string &ChatMessagePrivate::getText () { } void ChatMessagePrivate::setText (const string &text) { + loadContentsFromDatabase(); if (contents.size() > 0 && internalContent.getContentType().isEmpty() && internalContent.isEmpty()) { internalContent.setContentType(contents.front()->getContentType()); } @@ -288,13 +354,13 @@ LinphoneContent *ChatMessagePrivate::getFileTransferInformation () const { if (hasFileTransferContent()) { return getFileTransferContent()->toLinphoneContent(); } - for (const Content *c : contents) { + for (const Content *c : getContents()) { if (c->isFile()) { FileContent *fileContent = (FileContent *)c; return fileContent->toLinphoneContent(); } } - return NULL; + return nullptr; } void ChatMessagePrivate::setFileTransferInformation (const LinphoneContent *c_content) { @@ -306,7 +372,7 @@ void ChatMessagePrivate::setFileTransferInformation (const LinphoneContent *c_co fileContent->setContentType(contentType); fileContent->setFileSize(linphone_content_get_size(c_content)); fileContent->setFileName(linphone_content_get_name(c_content)); - if (linphone_content_get_string_buffer(c_content) != NULL) { + if (linphone_content_get_string_buffer(c_content)) { fileContent->setBody(linphone_content_get_string_buffer(c_content)); } @@ -316,19 +382,31 @@ void ChatMessagePrivate::setFileTransferInformation (const LinphoneContent *c_co bool ChatMessagePrivate::downloadFile () { L_Q(); - for (auto &content : contents) + for (auto &content : getContents()) if (content->getContentType() == ContentType::FileTransfer) return q->downloadFile(*static_cast(content)); return false; } +void ChatMessagePrivate::addContent (Content &content) { + getContents().push_back(&content); +} + +void ChatMessagePrivate::removeContent (const Content &content) { + getContents().remove(&const_cast(content)); +} + void ChatMessagePrivate::loadFileTransferUrlFromBodyToContent() { L_Q(); int errorCode = 0; fileTransferChatMessageModifier.decode(q->getSharedFromThis(), errorCode); } +std::string ChatMessagePrivate::createFakeFileTransferFromUrl(const std::string &url) { + return fileTransferChatMessageModifier.createFakeFileTransferFromUrl(url); +} + void ChatMessagePrivate::setChatRoom (const shared_ptr &cr) { chatRoom = cr; chatRoomId = cr->getChatRoomId(); @@ -349,13 +427,15 @@ void ChatMessagePrivate::sendImdn (Imdn::Type imdnType, LinphoneReason reason) { shared_ptr msg = q->getChatRoom()->createChatMessage(); Content *content = new Content(); - content->setContentType("message/imdn+xml"); + content->setContentType(ContentType::Imdn); content->setBody(Imdn::createXml(imdnId, time, imdnType, reason)); msg->addContent(*content); if (reason != LinphoneReasonNone) msg->getPrivate()->setEncryptionPrevented(true); + msg->setToBeStored(false); + msg->getPrivate()->addSalCustomHeader(PriorityHeader::HeaderName, PriorityHeader::NonUrgent); msg->getPrivate()->send(); } @@ -396,24 +476,22 @@ static void forceUtf8Content (Content &content) { void ChatMessagePrivate::notifyReceiving () { L_Q(); - if ((getContentType() == ContentType::Imdn) || (getContentType() == ContentType::ImIsComposing)) - return; - LinphoneChatRoom *chatRoom = L_GET_C_BACK_PTR(q->getChatRoom()); - LinphoneChatRoomCbs *cbs = linphone_chat_room_get_callbacks(chatRoom); - LinphoneChatRoomCbsParticipantAddedCb cb = linphone_chat_room_cbs_get_chat_message_received(cbs); + if ((getContentType() != ContentType::Imdn) && (getContentType() != ContentType::ImIsComposing)) { + _linphone_chat_room_notify_chat_message_should_be_stored(chatRoom, L_GET_C_BACK_PTR(q->getSharedFromThis())); + if (toBeStored) + storeInDb(); + } shared_ptr event = make_shared( ::time(nullptr), q->getSharedFromThis() ); - if (cb) - cb(chatRoom, L_GET_C_BACK_PTR(event)); + _linphone_chat_room_notify_chat_message_received(chatRoom, L_GET_C_BACK_PTR(event)); // Legacy q->getChatRoom()->getPrivate()->notifyChatMessageReceived(q->getSharedFromThis()); - if (toBeStored) - storeInDb(); - - q->sendDeliveryNotification(LinphoneReasonNone); + if ((getContentType() != ContentType::Imdn) && (getContentType() != ContentType::ImIsComposing)) { + q->sendDeliveryNotification(LinphoneReasonNone); + } } LinphoneReason ChatMessagePrivate::receive () { @@ -537,7 +615,7 @@ void ChatMessagePrivate::send () { currentSendStep |= ChatMessagePrivate::Step::Started; - if (toBeStored) + if (toBeStored && currentSendStep == (ChatMessagePrivate::Step::Started | ChatMessagePrivate::Step::None)) storeInDb(); if ((currentSendStep & ChatMessagePrivate::Step::FileUpload) == ChatMessagePrivate::Step::FileUpload) { @@ -565,8 +643,8 @@ void ChatMessagePrivate::send () { || (call->getState() == CallSession::State::Pausing) || (call->getState() == CallSession::State::PausedByRemote) ) { - lInfo() << "send SIP msg through the existing call."; - op = linphone_call_get_op(lcall); + lInfo() << "Send SIP msg through the existing call"; + op = call->getPrivate()->getOp(); string identity = linphone_core_find_best_identity(core->getCCore(), linphone_call_get_remote_address(lcall)); if (identity.empty()) { LinphoneAddress *addr = linphone_address_new(q->getToAddress().asString().c_str()); @@ -618,11 +696,8 @@ void ChatMessagePrivate::send () { if ((currentSendStep &ChatMessagePrivate::Step::Cpim) == ChatMessagePrivate::Step::Cpim) { lInfo() << "Cpim step already done, skipping"; } else { - int defaultValue = !!lp_config_get_string(core->getCCore()->config, "misc", "conference_factory_uri", nullptr); - if (lp_config_get_int(core->getCCore()->config, "sip", "use_cpim", defaultValue) == 1) { - CpimChatMessageModifier ccmm; - ccmm.encode(q->getSharedFromThis(), errorCode); - } + CpimChatMessageModifier ccmm; + ccmm.encode(q->getSharedFromThis(), errorCode); currentSendStep |= ChatMessagePrivate::Step::Cpim; } } @@ -653,19 +728,23 @@ void ChatMessagePrivate::send () { if (internalContent.isEmpty()) { if (contents.size() > 0) { internalContent = *(contents.front()); - } else { + } else if (externalBodyUrl.empty()) { // When using external body url, there is no content lError() << "Trying to send a message without any content !"; return; } } auto msgOp = dynamic_cast(op); - if (internalContent.getContentType().isValid()) { + if (!externalBodyUrl.empty()) { + char *content_type = ms_strdup_printf("message/external-body; access-type=URL; URL=\"%s\"", externalBodyUrl.c_str()); + msgOp->send_message(content_type, NULL); + ms_free(content_type); + } else if (internalContent.getContentType().isValid()) { msgOp->send_message(internalContent.getContentType().asString().c_str(), internalContent.getBodyAsUtf8String().c_str()); } else { msgOp->send_message(ContentType::PlainText.asString().c_str(), internalContent.getBodyAsUtf8String().c_str()); } - + // Restore FileContents and remove FileTransferContents list::iterator it = contents.begin(); while (it != contents.end()) { @@ -673,7 +752,7 @@ void ChatMessagePrivate::send () { if (content->getContentType() == ContentType::FileTransfer) { FileTransferContent *fileTransferContent = (FileTransferContent *)content; it = contents.erase(it); - q->addContent(*fileTransferContent->getFileContent()); + addContent(*fileTransferContent->getFileContent()); delete fileTransferContent; } else { it++; @@ -712,7 +791,10 @@ void ChatMessagePrivate::storeInDb () { updateInDb(); } else { shared_ptr eventLog = make_shared(time, q->getSharedFromThis()); - q->getChatRoom()->getCore()->getPrivate()->mainDb->addEvent(eventLog); + + // Avoid transaction in transaction if contents are not loaded. + loadContentsFromDatabase(); + q->getChatRoom()->getPrivate()->addEvent(eventLog); if (direction == ChatMessage::Direction::Incoming) { if (hasFileTransferContent()) { @@ -734,6 +816,9 @@ void ChatMessagePrivate::updateInDb () { unique_ptr &mainDb = q->getChatRoom()->getCore()->getPrivate()->mainDb; shared_ptr eventLog = mainDb->getEventFromKey(dbKey); + + // Avoid transaction in transaction if contents are not loaded. + loadContentsFromDatabase(); mainDb->updateEvent(eventLog); if (direction == ChatMessage::Direction::Incoming) { @@ -751,16 +836,31 @@ void ChatMessagePrivate::updateInDb () { // ----------------------------------------------------------------------------- -ChatMessage::ChatMessage (const shared_ptr &chatRoom, ChatMessage::Direction direction) : - Object(*new ChatMessagePrivate), CoreAccessor(chatRoom->getCore()) { - L_D(); +bool ChatMessagePrivate::validStateTransition (ChatMessage::State currentState, ChatMessage::State newState) { + if (newState == currentState) + return false; - d->direction = direction; - d->setChatRoom(chatRoom); + 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(chatRoom,direction)), CoreAccessor(chatRoom->getCore()) { } ChatMessage::~ChatMessage () { L_D(); + for (Content *content : d->contents) delete content; @@ -823,6 +923,18 @@ void ChatMessagePrivate::setImdnMessageId (const string &id) { imdnId = id; } +void ChatMessagePrivate::loadContentsFromDatabase () const { + L_Q(); + if (contentsNotLoadedFromDatabase) { + isReadOnly = false; + contentsNotLoadedFromDatabase = false; + q->getChatRoom()->getCore()->getPrivate()->mainDb->loadChatMessageContents( + const_pointer_cast(q->getSharedFromThis()) + ); + isReadOnly = true; + } +} + bool ChatMessage::isRead () const { L_D(); @@ -875,21 +987,19 @@ bool ChatMessage::isReadOnly () const { const list &ChatMessage::getContents () const { L_D(); - return d->contents; + return d->getContents(); } void ChatMessage::addContent (Content &content) { L_D(); - if (d->isReadOnly) return; - - d->contents.push_back(&content); + if (!d->isReadOnly) + d->addContent(content); } void ChatMessage::removeContent (const Content &content) { L_D(); - if (d->isReadOnly) return; - - d->contents.remove(&const_cast(content)); + if (!d->isReadOnly) + d->removeContent(content); } const Content &ChatMessage::getInternalContent () const { @@ -926,22 +1036,17 @@ 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(); // Do not allow sending a message that is already being sent or that has been correctly delivered/displayed if ((d->state == State::InProgress) || (d->state == State::Delivered) || (d->state == State::FileTransferDone) || (d->state == State::DeliveredToUser) || (d->state == State::Displayed)) { - lWarning() << "Cannot send chat message in state " << linphone_chat_message_state_to_string((LinphoneChatMessageState)d->state); + lWarning() << "Cannot send chat message in state " << Utils::toString(d->state); return; } + d->loadContentsFromDatabase(); getChatRoom()->getPrivate()->sendChatMessage(getSharedFromThis()); } @@ -966,6 +1071,11 @@ bool ChatMessage::downloadFile(FileTransferContent &fileTransferContent) { return d->fileTransferChatMessageModifier.downloadFile(getSharedFromThis(), &fileTransferContent); } +bool ChatMessage::isFileTransferInProgress() { + L_D(); + return d->fileTransferChatMessageModifier.isFileTransferInProgressAndValid(); +} + void ChatMessage::cancelFileTransfer () { L_D(); if (d->fileTransferChatMessageModifier.isFileTransferInProgressAndValid()) { @@ -981,48 +1091,50 @@ void ChatMessage::cancelFileTransfer () { int ChatMessage::putCharacter (uint32_t character) { L_D(); - shared_ptr core = getCore(); - if (linphone_core_realtime_text_enabled(core->getCCore())) { - static const uint32_t new_line = 0x2028; - static const uint32_t crlf = 0x0D0A; - static const uint32_t lf = 0x0A; + constexpr uint32_t newLine = 0x2028; + constexpr uint32_t crlf = 0x0D0A; + constexpr uint32_t lf = 0x0A; - shared_ptr chatRoom = getChatRoom(); + shared_ptr chatRoom = getChatRoom(); + if (!(chatRoom->getCapabilities() & LinphonePrivate::ChatRoom::Capabilities::RealTimeText)) + return -1; - shared_ptr rttcr = - static_pointer_cast(chatRoom); - LinphoneCall *call = rttcr->getCall(); + shared_ptr rttcr = + static_pointer_cast(chatRoom); + if (!rttcr) + return -1; - if (!call || !linphone_call_get_stream(call, LinphoneStreamTypeText)) - return -1; + shared_ptr call = rttcr->getCall(); + if (!call || !call->getPrivate()->getMediaStream(LinphoneStreamTypeText)) + return -1; - if (character == new_line || character == crlf || character == lf) { - if (lp_config_get_int(core->getCCore()->config, "misc", "store_rtt_messages", 1) == 1) { - // TODO: History. - lDebug() << "New line sent, forge a message with content " << d->rttMessage.c_str(); - d->setTime(ms_time(0)); - d->state = State::Displayed; - // d->direction = Direction::Outgoing; - // setFromAddress(Address( - // linphone_address_as_string(linphone_address_new(linphone_core_get_identity(core->getCCore()))) - // )); - // linphone_chat_message_store(L_GET_C_BACK_PTR(this)); - d->rttMessage = ""; - } - } else { - char *value = LinphonePrivate::Utils::utf8ToChar(character); - d->rttMessage = d->rttMessage + string(value); - lDebug() << "Sent RTT character: " << value << "(" << (unsigned long)character << - "), pending text is " << d->rttMessage.c_str(); - delete value; + if (character == newLine || character == crlf || character == lf) { + shared_ptr core = getCore(); + if (lp_config_get_int(core->getCCore()->config, "misc", "store_rtt_messages", 1) == 1) { + // TODO: History. + lDebug() << "New line sent, forge a message with content " << d->rttMessage.c_str(); + d->setTime(ms_time(0)); + d->state = State::Displayed; + // d->direction = Direction::Outgoing; + // setFromAddress(Address( + // linphone_address_as_string(linphone_address_new(linphone_core_get_identity(core->getCCore()))) + // )); + // linphone_chat_message_store(L_GET_C_BACK_PTR(this)); + d->rttMessage = ""; } - - text_stream_putchar32(reinterpret_cast( - linphone_call_get_stream(call, LinphoneStreamTypeText)), character - ); - return 0; + } else { + char *value = LinphonePrivate::Utils::utf8ToChar(character); + d->rttMessage = d->rttMessage + string(value); + lDebug() << "Sent RTT character: " << value << "(" << (unsigned long)character << + "), pending text is " << d->rttMessage.c_str(); + delete[] value; } - return -1; + + text_stream_putchar32( + reinterpret_cast(call->getPrivate()->getMediaStream(LinphoneStreamTypeText)), + character + ); + return 0; } LINPHONE_END_NAMESPACE diff --git a/src/chat/chat-message/chat-message.h b/src/chat/chat-message/chat-message.h index af7623b77..961455882 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); @@ -100,11 +100,13 @@ public: const Content &getInternalContent () const; void setInternalContent (const Content &content); + // TODO: to replace salCustomheaders std::string getCustomHeaderValue (const std::string &headerName) const; void addCustomHeader (const std::string &headerName, const std::string &headerValue); void removeCustomHeader (const std::string &headerName); bool downloadFile (FileTransferContent &content); + bool isFileTransferInProgress(); private: ChatMessage (const std::shared_ptr &chatRoom, ChatMessage::Direction direction); diff --git a/src/chat/chat-room/abstract-chat-room-p.h b/src/chat/chat-room/abstract-chat-room-p.h index 5577122ec..bd2ea703e 100644 --- a/src/chat/chat-room/abstract-chat-room-p.h +++ b/src/chat/chat-room/abstract-chat-room-p.h @@ -42,6 +42,8 @@ public: virtual void sendChatMessage (const std::shared_ptr &chatMessage) = 0; + virtual void addEvent (const std::shared_ptr &eventLog) = 0; + virtual void addTransientEvent (const std::shared_ptr &eventLog) = 0; virtual void removeTransientEvent (const std::shared_ptr &eventLog) = 0; diff --git a/src/chat/chat-room/abstract-chat-room.h b/src/chat/chat-room/abstract-chat-room.h index 46f5aec29..9662f89b9 100644 --- a/src/chat/chat-room/abstract-chat-room.h +++ b/src/chat/chat-room/abstract-chat-room.h @@ -68,6 +68,8 @@ public: virtual State getState () const = 0; virtual bool hasBeenLeft () const = 0; + virtual std::list> getMessageHistory (int nLast) const = 0; + virtual std::list> getMessageHistoryRange (int begin, int end) const = 0; virtual std::list> getHistory (int nLast) const = 0; virtual std::list> getHistoryRange (int begin, int end) const = 0; virtual int getHistorySize () const = 0; diff --git a/src/chat/chat-room/basic-chat-room-p.h b/src/chat/chat-room/basic-chat-room-p.h index 661a413a5..c281f940d 100644 --- a/src/chat/chat-room/basic-chat-room-p.h +++ b/src/chat/chat-room/basic-chat-room-p.h @@ -28,9 +28,6 @@ LINPHONE_BEGIN_NAMESPACE class BasicChatRoomPrivate : public ChatRoomPrivate { -public: - BasicChatRoomPrivate () = default; - private: std::string subject; std::list> participants; diff --git a/src/chat/chat-room/basic-to-client-group-chat-room.cpp b/src/chat/chat-room/basic-to-client-group-chat-room.cpp index 827d9b3d9..67f9ea2fc 100644 --- a/src/chat/chat-room/basic-to-client-group-chat-room.cpp +++ b/src/chat/chat-room/basic-to-client-group-chat-room.cpp @@ -53,10 +53,19 @@ public: } void sendChatMessage (const shared_ptr &chatMessage) override { + L_Q(); ProxyChatRoomPrivate::sendChatMessage(chatMessage); const char *specs = linphone_core_get_linphone_specs(chatMessage->getCore()->getCCore()); time_t currentRealTime = ms_time(nullptr); - if (!linphone_core_get_conference_factory_uri(chatMessage->getCore()->getCCore()) + LinphoneAddress *lAddr = linphone_address_new( + chatMessage->getChatRoom()->getChatRoomId().getLocalAddress().asString().c_str() + ); + LinphoneProxyConfig *proxy = linphone_core_lookup_known_proxy(q->getCore()->getCCore(), lAddr); + linphone_address_unref(lAddr); + const char *conferenceFactoryUri = nullptr; + if (proxy) + conferenceFactoryUri = linphone_proxy_config_get_conference_factory_uri(proxy); + if (!conferenceFactoryUri || (chatRoom->getCapabilities() & ChatRoom::Capabilities::Conference) || clientGroupChatRoom || !specs || !strstr(specs, "groupchat") @@ -69,7 +78,7 @@ public: } migrationRealTime = currentRealTime; clientGroupChatRoom = static_pointer_cast( - chatRoom->getCore()->getPrivate()->createClientGroupChatRoom(chatRoom->getSubject(), false) + chatRoom->getCore()->getPrivate()->createClientGroupChatRoom(chatRoom->getSubject(), "", false) ); clientGroupChatRoom->getPrivate()->setCallSessionListener(this); clientGroupChatRoom->getPrivate()->setChatRoomListener(this); @@ -77,7 +86,7 @@ public: } void onCallSessionStateChanged ( - const shared_ptr &session, + const shared_ptr &session, CallSession::State newState, const string &message ) override { diff --git a/src/chat/chat-room/chat-room-id.cpp b/src/chat/chat-room/chat-room-id.cpp index 5ffe44697..7b11fd77d 100644 --- a/src/chat/chat-room/chat-room-id.cpp +++ b/src/chat/chat-room/chat-room-id.cpp @@ -48,20 +48,21 @@ ChatRoomId::ChatRoomId ( L_USE_DEFAULT_CLONABLE_OBJECT_SHARED_IMPL(ChatRoomId); -bool ChatRoomId::operator== (const ChatRoomId &chatRoomId) const { +bool ChatRoomId::operator== (const ChatRoomId &other) const { L_D(); - const ChatRoomIdPrivate *dChatRoomId = chatRoomId.getPrivate(); + const ChatRoomIdPrivate *dChatRoomId = other.getPrivate(); return d->peerAddress == dChatRoomId->peerAddress && d->localAddress == dChatRoomId->localAddress; } -bool ChatRoomId::operator!= (const ChatRoomId &chatRoomId) const { - return !(*this == chatRoomId); +bool ChatRoomId::operator!= (const ChatRoomId &other) const { + return !(*this == other); } -bool ChatRoomId::operator< (const ChatRoomId &chatRoomId) const { +bool ChatRoomId::operator< (const ChatRoomId &other) const { L_D(); - const ChatRoomIdPrivate *dChatRoomId = chatRoomId.getPrivate(); - return d->peerAddress < dChatRoomId->peerAddress || d->localAddress < dChatRoomId->localAddress; + const ChatRoomIdPrivate *dChatRoomId = other.getPrivate(); + return d->peerAddress < dChatRoomId->peerAddress + || (d->peerAddress == dChatRoomId->peerAddress && d->localAddress < dChatRoomId->localAddress); } const IdentityAddress &ChatRoomId::getPeerAddress () const { diff --git a/src/chat/chat-room/chat-room-id.h b/src/chat/chat-room/chat-room-id.h index e8954b85f..1751a6820 100644 --- a/src/chat/chat-room/chat-room-id.h +++ b/src/chat/chat-room/chat-room-id.h @@ -32,14 +32,14 @@ class LINPHONE_PUBLIC ChatRoomId : public ClonableObject { public: ChatRoomId (); ChatRoomId (const IdentityAddress &peerAddress, const IdentityAddress &localAddress); - ChatRoomId (const ChatRoomId &src); + ChatRoomId (const ChatRoomId &other); - ChatRoomId &operator= (const ChatRoomId &src); + ChatRoomId &operator= (const ChatRoomId &other); - bool operator== (const ChatRoomId &chatRoomId) const; - bool operator!= (const ChatRoomId &chatRoomId) const; + bool operator== (const ChatRoomId &other) const; + bool operator!= (const ChatRoomId &other) const; - bool operator< (const ChatRoomId &chatRoomId) const; + bool operator< (const ChatRoomId &other) const; const IdentityAddress &getPeerAddress () const; const IdentityAddress &getLocalAddress () const; @@ -50,6 +50,11 @@ private: L_DECLARE_PRIVATE(ChatRoomId); }; +inline std::ostream &operator<< (std::ostream &os, const ChatRoomId &chatRoomId) { + os << "ChatRoomId(" << chatRoomId.getPeerAddress() << ", local=" << chatRoomId.getLocalAddress() << ")"; + return os; +} + LINPHONE_END_NAMESPACE // Add map key support. diff --git a/src/chat/chat-room/chat-room-p.h b/src/chat/chat-room/chat-room-p.h index dcb22bb92..4d177d845 100644 --- a/src/chat/chat-room/chat-room-p.h +++ b/src/chat/chat-room/chat-room-p.h @@ -33,6 +33,8 @@ LINPHONE_BEGIN_NAMESPACE class ChatRoomPrivate : public AbstractChatRoomPrivate, public IsComposingListener { public: + inline void setProxyChatRoom (AbstractChatRoom *value) { proxyChatRoom = value; } + inline void setCreationTime (time_t creationTime) override { this->creationTime = creationTime; } @@ -46,6 +48,8 @@ public: void sendChatMessage (const std::shared_ptr &chatMessage) override; void sendIsComposingNotification (); + void addEvent (const std::shared_ptr &eventLog) override; + void addTransientEvent (const std::shared_ptr &eventLog) override; void removeTransientEvent (const std::shared_ptr &eventLog) override; @@ -59,24 +63,27 @@ 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; void onIsRemoteComposingStateChanged (const Address &remoteAddress, bool isComposing) override; + LinphoneChatRoom *getCChatRoom () const; + std::list remoteIsComposing; std::list> transientEvents; ChatRoomId chatRoomId; private: + AbstractChatRoom *proxyChatRoom = nullptr; + ChatRoom::State state = ChatRoom::State::None; time_t creationTime = std::time(nullptr); time_t lastUpdateTime = std::time(nullptr); - std::shared_ptr pendingMessage; std::unique_ptr isComposingHandler; bool isComposing = false; diff --git a/src/chat/chat-room/chat-room.cpp b/src/chat/chat-room/chat-room.cpp index 14e92cf85..28106b50d 100644 --- a/src/chat/chat-room/chat-room.cpp +++ b/src/chat/chat-room/chat-room.cpp @@ -25,6 +25,8 @@ #include "chat/chat-message/chat-message-p.h" #include "chat/chat-room/chat-room-p.h" #include "core/core-p.h" +#include "sip-tools/sip-headers.h" +#include "logger/logger.h" // ============================================================================= @@ -50,19 +52,16 @@ void ChatRoomPrivate::sendChatMessage (const shared_ptr &chatMessag dChatMessage->setTime(ms_time(0)); dChatMessage->send(); - LinphoneChatRoom *cr = L_GET_C_BACK_PTR(q); - LinphoneChatRoomCbs *cbs = linphone_chat_room_get_callbacks(cr); - LinphoneChatRoomCbsChatMessageSentCb cb = linphone_chat_room_cbs_get_chat_message_sent(cbs); - + LinphoneChatRoom *cr = getCChatRoom(); // TODO: server currently don't stock message, remove condition in the future. - if (cb && !linphone_core_conference_server_enabled(q->getCore()->getCCore())) { + if (!linphone_core_conference_server_enabled(q->getCore()->getCCore())) { shared_ptr event = static_pointer_cast( q->getCore()->getPrivate()->mainDb->getEventFromKey(dChatMessage->dbKey) ); if (!event) event = make_shared(time(nullptr), chatMessage); - cb(cr, L_GET_C_BACK_PTR(event)); + _linphone_chat_room_notify_chat_message_sent(cr, L_GET_C_BACK_PTR(event)); } if (isComposing) @@ -74,22 +73,35 @@ void ChatRoomPrivate::sendChatMessage (const shared_ptr &chatMessag void ChatRoomPrivate::sendIsComposingNotification () { L_Q(); LinphoneImNotifPolicy *policy = linphone_core_get_im_notif_policy(q->getCore()->getCCore()); - if (linphone_im_notif_policy_get_send_is_composing(policy)) { - string payload = isComposingHandler->marshal(isComposing); - if (!payload.empty()) { - shared_ptr chatMessage = createChatMessage(ChatMessage::Direction::Outgoing); - chatMessage->setToBeStored(false); - Content *content = new Content(); - content->setContentType(ContentType::ImIsComposing); - content->setBody(payload); - chatMessage->addContent(*content); - chatMessage->getPrivate()->send(); - } - } + if (!linphone_im_notif_policy_get_send_is_composing(policy)) + return; + + string payload = isComposingHandler->marshal(isComposing); + if (payload.empty()) + return; + + Content *content = new Content(); + content->setContentType(ContentType::ImIsComposing); + content->setBody(payload); + + shared_ptr chatMessage = createChatMessage(ChatMessage::Direction::Outgoing); + chatMessage->setToBeStored(false); + chatMessage->addContent(*content); + chatMessage->getPrivate()->addSalCustomHeader(PriorityHeader::HeaderName, PriorityHeader::NonUrgent); + chatMessage->getPrivate()->addSalCustomHeader("Expires", "0"); + + chatMessage->getPrivate()->send(); } // ----------------------------------------------------------------------------- +void ChatRoomPrivate::addEvent (const shared_ptr &eventLog) { + L_Q(); + + q->getCore()->getPrivate()->mainDb->addEvent(eventLog); + setLastUpdateTime(eventLog->getCreationTime()); +} + void ChatRoomPrivate::addTransientEvent (const shared_ptr &eventLog) { auto it = find(transientEvents.begin(), transientEvents.end(), eventLog); if (it == transientEvents.end()) @@ -118,7 +130,7 @@ list> ChatRoomPrivate::findChatMessages (const string &m void ChatRoomPrivate::notifyChatMessageReceived (const shared_ptr &chatMessage) { L_Q(); - LinphoneChatRoom *cr = L_GET_C_BACK_PTR(q); + LinphoneChatRoom *cr = getCChatRoom(); if (!chatMessage->getPrivate()->getText().empty()) { /* Legacy API */ LinphoneAddress *fromAddress = linphone_address_new(chatMessage->getFromAddress().asString().c_str()); @@ -130,10 +142,7 @@ void ChatRoomPrivate::notifyChatMessageReceived (const shared_ptr & ); linphone_address_unref(fromAddress); } - LinphoneChatRoomCbs *cbs = linphone_chat_room_get_callbacks(cr); - LinphoneChatRoomCbsMessageReceivedCb cb = linphone_chat_room_cbs_get_message_received(cbs); - if (cb) - cb(cr, L_GET_C_BACK_PTR(chatMessage)); + _linphone_chat_room_notify_message_received(cr, L_GET_C_BACK_PTR(chatMessage)); linphone_core_notify_message_received(q->getCore()->getCCore(), cr, L_GET_C_BACK_PTR(chatMessage)); } @@ -145,35 +154,25 @@ void ChatRoomPrivate::notifyIsComposingReceived (const Address &remoteAddress, b else remoteIsComposing.remove(remoteAddress); - LinphoneChatRoom *cr = L_GET_C_BACK_PTR(q); - LinphoneChatRoomCbs *cbs = linphone_chat_room_get_callbacks(cr); - LinphoneChatRoomCbsIsComposingReceivedCb cb = linphone_chat_room_cbs_get_is_composing_received(cbs); - if (cb) { - LinphoneAddress *lAddr = linphone_address_new(remoteAddress.asString().c_str()); - cb(cr, lAddr, !!isComposing); - linphone_address_unref(lAddr); - } + LinphoneChatRoom *cr = getCChatRoom(); + LinphoneAddress *lAddr = linphone_address_new(remoteAddress.asString().c_str()); + _linphone_chat_room_notify_is_composing_received(cr, lAddr, !!isComposing); + linphone_address_unref(lAddr); // Legacy notification linphone_core_notify_is_composing_received(q->getCore()->getCCore(), cr); } void ChatRoomPrivate::notifyStateChanged () { L_Q(); - linphone_core_notify_chat_room_state_changed(q->getCore()->getCCore(), L_GET_C_BACK_PTR(q), (LinphoneChatRoomState)state); - LinphoneChatRoom *cr = L_GET_C_BACK_PTR(q); - LinphoneChatRoomCbs *cbs = linphone_chat_room_get_callbacks(cr); - LinphoneChatRoomCbsStateChangedCb cb = linphone_chat_room_cbs_get_state_changed(cbs); - if (cb) - cb(cr, (LinphoneChatRoomState)state); + LinphoneChatRoom *cr = getCChatRoom(); + linphone_core_notify_chat_room_state_changed(q->getCore()->getCCore(), cr, (LinphoneChatRoomState)state); + _linphone_chat_room_notify_state_changed(cr, (LinphoneChatRoomState)state); } void ChatRoomPrivate::notifyUndecryptableChatMessageReceived (const shared_ptr &chatMessage) { L_Q(); - LinphoneChatRoom *cr = L_GET_C_BACK_PTR(q); - LinphoneChatRoomCbs *cbs = linphone_chat_room_get_callbacks(cr); - LinphoneChatRoomCbsUndecryptableMessageReceivedCb cb = linphone_chat_room_cbs_get_undecryptable_message_received(cbs); - if (cb) - cb(cr, L_GET_C_BACK_PTR(chatMessage)); + LinphoneChatRoom *cr = getCChatRoom(); + _linphone_chat_room_notify_undecryptable_message_received(cr, L_GET_C_BACK_PTR(chatMessage)); linphone_core_notify_message_received_unable_decrypt(q->getCore()->getCCore(), cr, L_GET_C_BACK_PTR(chatMessage)); } @@ -182,7 +181,6 @@ void ChatRoomPrivate::notifyUndecryptableChatMessageReceived (const shared_ptr msg; @@ -196,8 +194,14 @@ LinphoneReason ChatRoomPrivate::onSipMessageReceived (SalOp *op, const SalMessag ); Content content; - content.setContentType(message->content_type); - content.setBodyFromUtf8(message->text ? message->text : ""); + if (message->url && (ContentType(message->content_type) == ContentType::ExternalBody)) { + lInfo() << "Received a message with an external body URL " << message->url; + content.setContentType(ContentType::FileTransfer); + content.setBody(msg->getPrivate()->createFakeFileTransferFromUrl(message->url)); + } else { + content.setContentType(ContentType(message->content_type)); + content.setBodyFromUtf8(message->text ? message->text : ""); + } msg->setInternalContent(content); msg->getPrivate()->setTime(message->time); @@ -217,43 +221,35 @@ LinphoneReason ChatRoomPrivate::onSipMessageReceived (SalOp *op, const SalMessag if (msg->getPrivate()->getContentType() == ContentType::ImIsComposing) { onIsComposingReceived(msg->getFromAddress(), msg->getPrivate()->getText()); - increaseMsgCount = FALSE; if (lp_config_get_int(linphone_core_get_config(cCore), "sip", "deliver_imdn", 0) != 1) { goto end; } } else if (msg->getPrivate()->getContentType() == ContentType::Imdn) { - onImdnReceived(msg->getPrivate()->getText()); - increaseMsgCount = FALSE; + onImdnReceived(msg); if (lp_config_get_int(linphone_core_get_config(cCore), "sip", "deliver_imdn", 0) != 1) { goto end; } } - if (increaseMsgCount) { - /* Mark the message as pending so that if ChatRoom::markAsRead() is called in the - * ChatRoomPrivate::chatMessageReceived() callback, it will effectively be marked as - * being read before being stored. */ - pendingMessage = msg; - } - onChatMessageReceived(msg); - pendingMessage = nullptr; - end: return reason; } void ChatRoomPrivate::onChatMessageReceived (const shared_ptr &chatMessage) { const IdentityAddress &fromAddress = chatMessage->getFromAddress(); - isComposingHandler->stopRemoteRefreshTimer(fromAddress.asString()); - notifyIsComposingReceived(fromAddress, false); + if ((chatMessage->getPrivate()->getContentType() != ContentType::ImIsComposing) + && (chatMessage->getPrivate()->getContentType() != ContentType::Imdn) + ) { + isComposingHandler->stopRemoteRefreshTimer(fromAddress.asString()); + notifyIsComposingReceived(fromAddress, false); + } 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) { @@ -273,6 +269,16 @@ void ChatRoomPrivate::onIsRemoteComposingStateChanged (const Address &remoteAddr notifyIsComposingReceived(remoteAddress, isComposing); } +// ----------------------------------------------------------------------------- + +LinphoneChatRoom *ChatRoomPrivate::getCChatRoom () const { + L_Q(); + if (proxyChatRoom) + return L_GET_C_BACK_PTR(proxyChatRoom); + else + return L_GET_C_BACK_PTR(q); +} + // ============================================================================= ChatRoom::ChatRoom (ChatRoomPrivate &p, const shared_ptr &core, const ChatRoomId &chatRoomId) : @@ -321,12 +327,29 @@ ChatRoom::State ChatRoom::getState () const { // ----------------------------------------------------------------------------- +list> ChatRoom::getMessageHistory (int nLast) const { + return getCore()->getPrivate()->mainDb->getHistory(getChatRoomId(), nLast, MainDb::Filter::ConferenceChatMessageFilter); +} + +list> ChatRoom::getMessageHistoryRange (int begin, int end) const { + return getCore()->getPrivate()->mainDb->getHistoryRange(getChatRoomId(), begin, end, MainDb::Filter::ConferenceChatMessageFilter); +} + list> ChatRoom::getHistory (int nLast) const { - return getCore()->getPrivate()->mainDb->getHistory(getChatRoomId(), nLast); + return getCore()->getPrivate()->mainDb->getHistory( + getChatRoomId(), + nLast, + MainDb::FilterMask({ MainDb::Filter::ConferenceChatMessageFilter, MainDb::Filter::ConferenceInfoNoDeviceFilter }) + ); } list> ChatRoom::getHistoryRange (int begin, int end) const { - return getCore()->getPrivate()->mainDb->getHistoryRange(getChatRoomId(), begin, end); + return getCore()->getPrivate()->mainDb->getHistoryRange( + getChatRoomId(), + begin, + end, + MainDb::FilterMask({ MainDb::Filter::ConferenceChatMessageFilter, MainDb::Filter::ConferenceInfoNoDeviceFilter }) + ); } int ChatRoom::getHistorySize () const { @@ -335,7 +358,9 @@ int ChatRoom::getHistorySize () const { void ChatRoom::deleteFromDb () { L_D(); - Core::deleteChatRoom(this->getSharedFromThis()); + // Keep a ref, otherwise the object might be destroyed before we can set the Deleted state + shared_ptr ref = this->getSharedFromThis(); + Core::deleteChatRoom(ref); d->setState(ChatRoom::State::Deleted); } @@ -419,15 +444,15 @@ void ChatRoom::markAsRead () { L_D(); CorePrivate *dCore = getCore()->getPrivate(); - for (auto &chatMessage : dCore->mainDb->getUnreadChatMessages(d->chatRoomId)) - chatMessage->sendDisplayNotification(); + for (auto &chatMessage : dCore->mainDb->getUnreadChatMessages(d->chatRoomId)) { + // Do not send display notification for file transfer until it has been downloaded (it won't have a file transfer content anymore) + if (!chatMessage->getPrivate()->hasFileTransferContent()) { + chatMessage->sendDisplayNotification(); + chatMessage->getPrivate()->setState(ChatMessage::State::Displayed, true); // True will ensure the setState won't update the database so it will be done below by the markChatMessagesAsRead + } + } dCore->mainDb->markChatMessagesAsRead(d->chatRoomId); - - if (d->pendingMessage) { - d->pendingMessage->updateState(ChatMessage::State::Displayed); - d->pendingMessage->sendDisplayNotification(); - } } LINPHONE_END_NAMESPACE diff --git a/src/chat/chat-room/chat-room.h b/src/chat/chat-room/chat-room.h index 35d887e59..40cad5ae3 100644 --- a/src/chat/chat-room/chat-room.h +++ b/src/chat/chat-room/chat-room.h @@ -30,6 +30,8 @@ class ChatRoomPrivate; class LINPHONE_PUBLIC ChatRoom : public AbstractChatRoom { public: + friend class ProxyChatRoomPrivate; + L_OVERRIDE_SHARED_FROM_THIS(ChatRoom); const ChatRoomId &getChatRoomId () const override; @@ -42,6 +44,8 @@ public: State getState () const override; + std::list> getMessageHistory (int nLast) const override; + std::list> getMessageHistoryRange (int begin, int end) const override; std::list> getHistory (int nLast) const override; std::list> getHistoryRange (int begin, int end) const override; int getHistorySize () const override; diff --git a/src/chat/chat-room/client-group-chat-room-p.h b/src/chat/chat-room/client-group-chat-room-p.h index fa51cf8a3..e617b517d 100644 --- a/src/chat/chat-room/client-group-chat-room-p.h +++ b/src/chat/chat-room/client-group-chat-room-p.h @@ -30,8 +30,6 @@ LINPHONE_BEGIN_NAMESPACE class ClientGroupChatRoomPrivate : public ChatRoomPrivate { public: - ClientGroupChatRoomPrivate () = default; - std::list cleanAddressesList (const std::list &addresses) const; std::shared_ptr createSession (); void notifyReceived (const std::string &body); @@ -46,8 +44,8 @@ public: void onChatRoomDeleteRequested (const std::shared_ptr &chatRoom) override; // CallSessionListener - void onCallSessionSetReleased (const std::shared_ptr &session) override; - void onCallSessionStateChanged (const std::shared_ptr &session, CallSession::State state, const std::string &message) override; + void onCallSessionSetReleased (const std::shared_ptr &session) override; + void onCallSessionStateChanged (const std::shared_ptr &session, CallSession::State state, const std::string &message) override; private: CallSessionListener *callSessionListener = this; @@ -55,7 +53,6 @@ private: ClientGroupChatRoom::CapabilitiesMask capabilities = ClientGroupChatRoom::Capabilities::Conference; bool deletionOnTerminationEnabled = false; BackgroundTask bgTask; - L_DECLARE_PUBLIC(ClientGroupChatRoom); }; diff --git a/src/chat/chat-room/client-group-chat-room.cpp b/src/chat/chat-room/client-group-chat-room.cpp index a3e2847b4..29c872d30 100644 --- a/src/chat/chat-room/client-group-chat-room.cpp +++ b/src/chat/chat-room/client-group-chat-room.cpp @@ -20,16 +20,16 @@ #include "linphone/utils/utils.h" #include "address/address-p.h" -#include "c-wrapper/c-wrapper.h" -#include "basic-chat-room.h" #include "basic-to-client-group-chat-room.h" +#include "c-wrapper/c-wrapper.h" #include "client-group-chat-room-p.h" #include "conference/handlers/remote-conference-event-handler.h" #include "conference/participant-p.h" #include "conference/remote-conference-p.h" #include "conference/session/call-session-p.h" +#include "content/content-disposition.h" +#include "content/content-type.h" #include "core/core-p.h" -#include "event-log/events.h" #include "logger/logger.h" #include "sal/refer-op.h" @@ -65,12 +65,11 @@ shared_ptr ClientGroupChatRoomPrivate::createSession () { if (capabilities & ClientGroupChatRoom::Capabilities::OneToOne) csp.addCustomHeader("One-To-One-Chat-Room", "true"); - shared_ptr focus = qConference->getPrivate()->focus; - shared_ptr session = focus->getPrivate()->createSession(*q, &csp, false, callSessionListener); - const Address &myAddress = q->getMe()->getAddress(); - Address myCleanedAddress(myAddress); - myCleanedAddress.removeUriParam("gr"); // Remove gr parameter for INVITE - session->configure(LinphoneCallOutgoing, nullptr, nullptr, myCleanedAddress, focus->getPrivate()->getDevices().front()->getAddress()); + ParticipantPrivate *dFocus = qConference->getPrivate()->focus->getPrivate(); + shared_ptr session = dFocus->createSession(*q, &csp, false, callSessionListener); + Address myCleanedAddress(q->getMe()->getAddress()); + myCleanedAddress.removeUriParam("gr"); // Remove gr parameter for INVITE. + session->configure(LinphoneCallOutgoing, nullptr, nullptr, myCleanedAddress, dFocus->getDevices().front()->getAddress()); session->initiateOutgoing(); session->getPrivate()->createOp(); return session; @@ -122,7 +121,7 @@ void ClientGroupChatRoomPrivate::onChatRoomDeleteRequested (const shared_ptr &session) { +void ClientGroupChatRoomPrivate::onCallSessionSetReleased (const shared_ptr &session) { L_Q_T(RemoteConference, qConference); ParticipantPrivate *participantPrivate = qConference->getPrivate()->focus->getPrivate(); @@ -131,7 +130,7 @@ void ClientGroupChatRoomPrivate::onCallSessionSetReleased (const shared_ptr &session, + const shared_ptr &session, CallSession::State newState, const string &message ) { @@ -142,19 +141,35 @@ void ClientGroupChatRoomPrivate::onCallSessionStateChanged ( if (q->getState() == ChatRoom::State::CreationPending) { IdentityAddress addr(session->getRemoteContactAddress()->asStringUriOnly()); q->onConferenceCreated(addr); - if (session->getRemoteContactAddress()->hasParam("isfocus")) + if (session->getRemoteContactAddress()->hasParam("isfocus")) { + bgTask.start(q->getCore(), 32); // It will be stopped when receiving the first notify qConference->getPrivate()->eventHandler->subscribe(q->getChatRoomId()); + } } else if (q->getState() == ChatRoom::State::TerminationPending) qConference->getPrivate()->focus->getPrivate()->getSession()->terminate(); - } else if ((newState == CallSession::State::Released) && (q->getState() == ChatRoom::State::TerminationPending)) { - q->onConferenceTerminated(q->getConferenceAddress()); + } else if (newState == CallSession::State::Released) { + if (q->getState() == ChatRoom::State::TerminationPending) { + if (session->getReason() == LinphoneReasonNone) { + // Everything is fine, the chat room has been left on the server side + q->onConferenceTerminated(q->getConferenceAddress()); + } else { + // Go to state TerminationFailed and then back to Created since it has not been terminated + setState(ChatRoom::State::TerminationFailed); + setState(ChatRoom::State::Created); + } + } } else if (newState == CallSession::State::Error) { if (q->getState() == ChatRoom::State::CreationPending) setState(ChatRoom::State::CreationFailed); else if (q->getState() == ChatRoom::State::TerminationPending) { - // Go to state TerminationFailed and then back to Created since it has not been terminated - setState(ChatRoom::State::TerminationFailed); - setState(ChatRoom::State::Created); + if (session->getReason() == LinphoneReasonNotFound) { + // Somehow the chat room is no longer know on the server, so terminate it + q->onConferenceTerminated(q->getConferenceAddress()); + } else { + // Go to state TerminationFailed and then back to Created since it has not been terminated + setState(ChatRoom::State::TerminationFailed); + setState(ChatRoom::State::Created); + } } } } @@ -168,15 +183,14 @@ ClientGroupChatRoom::ClientGroupChatRoom ( const string &subject ) : ChatRoom(*new ClientGroupChatRoomPrivate, core, ChatRoomId(IdentityAddress(), me)), RemoteConference(core, me, nullptr) { - L_D(); L_D_T(RemoteConference, dConference); - - d->bgTask.setName("Client group chat room refer received"); - + L_D(); + IdentityAddress focusAddr(uri); dConference->focus = make_shared(focusAddr); dConference->focus->getPrivate()->addDevice(focusAddr); RemoteConference::setSubject(subject); + d->bgTask.setName("Subscribe/notify of full state conference"); } ClientGroupChatRoom::ClientGroupChatRoom ( @@ -206,6 +220,11 @@ RemoteConference(core, me->getAddress(), nullptr) { dConference->eventHandler->subscribe(getChatRoomId()); } +ClientGroupChatRoom::~ClientGroupChatRoom () { + L_D(); + d->setCallSessionListener(nullptr); +} + shared_ptr ClientGroupChatRoom::getCore () const { return ChatRoom::getCore(); } @@ -291,8 +310,12 @@ void ClientGroupChatRoom::addParticipants ( Content content; content.setBody(getResourceLists(addressesList)); - content.setContentType("application/resource-lists+xml"); - content.setContentDisposition("recipient-list"); + content.setContentType(ContentType::ResourceLists); + content.setContentDisposition(ContentDisposition::RecipientList); + // TODO: Activate compression + //if (linphone_core_content_encoding_supported(getCore()->getCCore(), "deflate")) + // content.setContentEncoding("deflate"); + // TODO: Activate compression shared_ptr session = dConference->focus->getPrivate()->getSession(); if (session) @@ -394,9 +417,11 @@ void ClientGroupChatRoom::join () { shared_ptr session = dConference->focus->getPrivate()->getSession(); if (!session && ((getState() == ChatRoom::State::Instantiated) || (getState() == ChatRoom::State::Terminated))) { - d->bgTask.start(); session = d->createSession(); - session->startInvite(nullptr, "", nullptr); + } + if (session) { + if (getState() != ChatRoom::State::TerminationPending) + session->startInvite(nullptr, "", nullptr); d->setState(ChatRoom::State::CreationPending); } } @@ -405,7 +430,6 @@ void ClientGroupChatRoom::leave () { L_D(); L_D_T(RemoteConference, dConference); - d->bgTask.start(); dConference->eventHandler->unsubscribe(); shared_ptr session = dConference->focus->getPrivate()->getSession(); @@ -442,7 +466,7 @@ void ClientGroupChatRoom::onConferenceTerminated (const IdentityAddress &addr) { L_D_T(RemoteConference, dConference); dConference->eventHandler->resetLastNotify(); d->setState(ChatRoom::State::Terminated); - getCore()->getPrivate()->mainDb->addEvent(make_shared( + d->addEvent(make_shared( EventLog::Type::ConferenceTerminated, time(nullptr), d->chatRoomId @@ -456,31 +480,33 @@ void ClientGroupChatRoom::onConferenceTerminated (const IdentityAddress &addr) { void ClientGroupChatRoom::onFirstNotifyReceived (const IdentityAddress &addr) { L_D(); d->setState(ChatRoom::State::Created); - + if (getParticipantCount() == 1) { ChatRoomId id(getParticipants().front()->getAddress(), getMe()->getAddress()); shared_ptr chatRoom = getCore()->findChatRoom(id); if (chatRoom && (chatRoom->getCapabilities() & ChatRoom::Capabilities::Basic)) { BasicToClientGroupChatRoom::migrate(getSharedFromThis(), chatRoom); + d->bgTask.stop(); return; } } d->chatRoomListener->onChatRoomInsertInDatabaseRequested(getSharedFromThis()); + d->bgTask.stop(); // TODO: Bug. Event is inserted many times. // Avoid this in the future. Deal with signals/slots system. #if 0 - getCore()->getPrivate()->mainDb->addEvent(make_shared( + d->addEvent(make_shared( EventLog::Type::ConferenceCreated, time(nullptr), d->chatRoomId )); #endif - d->bgTask.stop(); } void ClientGroupChatRoom::onParticipantAdded (const shared_ptr &event, bool isFullState) { + L_D(); L_D_T(RemoteConference, dConference); const IdentityAddress &addr = event->getParticipantAddress(); @@ -499,18 +525,16 @@ void ClientGroupChatRoom::onParticipantAdded (const shared_ptrgetPrivate()->mainDb->addEvent(event); + d->addEvent(event); - LinphoneChatRoom *cr = L_GET_C_BACK_PTR(this); - LinphoneChatRoomCbs *cbs = linphone_chat_room_get_callbacks(cr); - LinphoneChatRoomCbsParticipantAddedCb cb = linphone_chat_room_cbs_get_participant_added(cbs); - if (cb) - cb(cr, L_GET_C_BACK_PTR(event)); + LinphoneChatRoom *cr = d->getCChatRoom(); + _linphone_chat_room_notify_participant_added(cr, L_GET_C_BACK_PTR(event)); } void ClientGroupChatRoom::onParticipantRemoved (const shared_ptr &event, bool isFullState) { (void)isFullState; + L_D(); L_D_T(RemoteConference, dConference); const IdentityAddress &addr = event->getParticipantAddress(); @@ -521,16 +545,15 @@ void ClientGroupChatRoom::onParticipantRemoved (const shared_ptrparticipants.remove(participant); - getCore()->getPrivate()->mainDb->addEvent(event); + d->addEvent(event); - LinphoneChatRoom *cr = L_GET_C_BACK_PTR(this); - LinphoneChatRoomCbs *cbs = linphone_chat_room_get_callbacks(cr); - LinphoneChatRoomCbsParticipantRemovedCb cb = linphone_chat_room_cbs_get_participant_removed(cbs); - if (cb) - cb(cr, L_GET_C_BACK_PTR(event)); + LinphoneChatRoom *cr = d->getCChatRoom(); + _linphone_chat_room_notify_participant_removed(cr, L_GET_C_BACK_PTR(event)); } void ClientGroupChatRoom::onParticipantSetAdmin (const shared_ptr &event, bool isFullState) { + L_D(); + const IdentityAddress &addr = event->getParticipantAddress(); shared_ptr participant; if (isMe(addr)) @@ -550,16 +573,15 @@ void ClientGroupChatRoom::onParticipantSetAdmin (const shared_ptrgetPrivate()->mainDb->addEvent(event); + d->addEvent(event); - LinphoneChatRoom *cr = L_GET_C_BACK_PTR(this); - LinphoneChatRoomCbs *cbs = linphone_chat_room_get_callbacks(cr); - LinphoneChatRoomCbsParticipantAdminStatusChangedCb cb = linphone_chat_room_cbs_get_participant_admin_status_changed(cbs); - if (cb) - cb(cr, L_GET_C_BACK_PTR(event)); + LinphoneChatRoom *cr = d->getCChatRoom(); + _linphone_chat_room_notify_participant_admin_status_changed(cr, L_GET_C_BACK_PTR(event)); } void ClientGroupChatRoom::onSubjectChanged (const shared_ptr &event, bool isFullState) { + L_D(); + if (getSubject() == event->getSubject()) return; // No change in the local subject, do not notify RemoteConference::setSubject(event->getSubject()); @@ -567,16 +589,15 @@ void ClientGroupChatRoom::onSubjectChanged (const shared_ptrgetPrivate()->mainDb->addEvent(event); + d->addEvent(event); - LinphoneChatRoom *cr = L_GET_C_BACK_PTR(this); - LinphoneChatRoomCbs *cbs = linphone_chat_room_get_callbacks(cr); - LinphoneChatRoomCbsSubjectChangedCb cb = linphone_chat_room_cbs_get_subject_changed(cbs); - if (cb) - cb(cr, L_GET_C_BACK_PTR(event)); + LinphoneChatRoom *cr = d->getCChatRoom(); + _linphone_chat_room_notify_subject_changed(cr, L_GET_C_BACK_PTR(event)); } void ClientGroupChatRoom::onParticipantDeviceAdded (const shared_ptr &event, bool isFullState) { + L_D(); + const IdentityAddress &addr = event->getParticipantAddress(); shared_ptr participant; if (isMe(addr)) @@ -592,16 +613,15 @@ void ClientGroupChatRoom::onParticipantDeviceAdded (const shared_ptrgetPrivate()->mainDb->addEvent(event); + d->addEvent(event); - LinphoneChatRoom *cr = L_GET_C_BACK_PTR(this); - LinphoneChatRoomCbs *cbs = linphone_chat_room_get_callbacks(cr); - LinphoneChatRoomCbsParticipantDeviceAddedCb cb = linphone_chat_room_cbs_get_participant_device_added(cbs); - if (cb) - cb(cr, L_GET_C_BACK_PTR(event)); + LinphoneChatRoom *cr = d->getCChatRoom(); + _linphone_chat_room_notify_participant_device_added(cr, L_GET_C_BACK_PTR(event)); } void ClientGroupChatRoom::onParticipantDeviceRemoved (const shared_ptr &event, bool isFullState) { + L_D(); + (void)isFullState; const IdentityAddress &addr = event->getParticipantAddress(); @@ -615,13 +635,10 @@ void ClientGroupChatRoom::onParticipantDeviceRemoved (const shared_ptrgetPrivate()->removeDevice(event->getDeviceAddress()); - getCore()->getPrivate()->mainDb->addEvent(event); + d->addEvent(event); - LinphoneChatRoom *cr = L_GET_C_BACK_PTR(this); - LinphoneChatRoomCbs *cbs = linphone_chat_room_get_callbacks(cr); - LinphoneChatRoomCbsParticipantDeviceRemovedCb cb = linphone_chat_room_cbs_get_participant_device_removed(cbs); - if (cb) - cb(cr, L_GET_C_BACK_PTR(event)); + LinphoneChatRoom *cr = d->getCChatRoom(); + _linphone_chat_room_notify_participant_device_removed(cr, L_GET_C_BACK_PTR(event)); } LINPHONE_END_NAMESPACE diff --git a/src/chat/chat-room/client-group-chat-room.h b/src/chat/chat-room/client-group-chat-room.h index dc518e6e3..9bb13f526 100644 --- a/src/chat/chat-room/client-group-chat-room.h +++ b/src/chat/chat-room/client-group-chat-room.h @@ -33,6 +33,7 @@ class LINPHONE_PUBLIC ClientGroupChatRoom : public ChatRoom, public RemoteConfer friend class BasicToClientGroupChatRoomPrivate; friend class ClientGroupToBasicChatRoomPrivate; friend class Core; + friend class CorePrivate; public: L_OVERRIDE_SHARED_FROM_THIS(ClientGroupChatRoom); @@ -55,8 +56,10 @@ public: unsigned int lastNotifyId ); + ~ClientGroupChatRoom (); + std::shared_ptr getCore () const; - + void allowCpim (bool value) override; void allowMultipart (bool value) override; bool canHandleCpim () const override; @@ -100,11 +103,11 @@ private: void onConferenceTerminated (const IdentityAddress &addr) override; void onFirstNotifyReceived (const IdentityAddress &addr) override; void onParticipantAdded (const std::shared_ptr &event, bool isFullState) override; + void onParticipantDeviceAdded (const std::shared_ptr &event, bool isFullState) override; + void onParticipantDeviceRemoved (const std::shared_ptr &event, bool isFullState) override; void onParticipantRemoved (const std::shared_ptr &event, bool isFullState) override; void onParticipantSetAdmin (const std::shared_ptr &event, bool isFullState) override; void onSubjectChanged (const std::shared_ptr &event, bool isFullState) override; - void onParticipantDeviceAdded (const std::shared_ptr &event, bool isFullState) override; - void onParticipantDeviceRemoved (const std::shared_ptr &event, bool isFullState) override; L_DECLARE_PRIVATE(ClientGroupChatRoom); L_DISABLE_COPY(ClientGroupChatRoom); diff --git a/src/chat/chat-room/client-group-to-basic-chat-room.cpp b/src/chat/chat-room/client-group-to-basic-chat-room.cpp index 5890f8252..c72f9cfec 100644 --- a/src/chat/chat-room/client-group-to-basic-chat-room.cpp +++ b/src/chat/chat-room/client-group-to-basic-chat-room.cpp @@ -48,40 +48,41 @@ public: void onChatRoomDeleteRequested (const shared_ptr &chatRoom) override { L_Q(); - q->getCore()->deleteChatRoom(q->getSharedFromThis()); + // Keep a ref, otherwise the object might be destroyed before we can set the Deleted state + shared_ptr ref = q->getSharedFromThis(); + q->getCore()->deleteChatRoom(ref); setState(AbstractChatRoom::State::Deleted); } - void onCallSessionSetReleased (const std::shared_ptr &session) override { + void onCallSessionSetReleased (const shared_ptr &session) override { if (!(chatRoom->getCapabilities() & ChatRoom::Capabilities::Conference)) return; static_pointer_cast(chatRoom)->getPrivate()->onCallSessionSetReleased(session); } void onCallSessionStateChanged ( - const shared_ptr &session, + const shared_ptr &session, CallSession::State newState, const string &message ) override { L_Q(); + // Keep a ref, otherwise the object might be destroyed when calling Core::deleteChatRoom() + shared_ptr ref = q->getSharedFromThis(); + // TODO: Avoid cast, use capabilities. shared_ptr cgcr = dynamic_pointer_cast(chatRoom); if (!cgcr) return; if ((newState == CallSession::State::Error) && (cgcr->getState() == ChatRoom::State::CreationPending) && (session->getReason() == LinphoneReasonNotAcceptable) && (invitedAddresses.size() == 1)) { - teardownCallbacks(); + teardownProxy(); cgcr->getPrivate()->onCallSessionStateChanged(session, newState, message); cgcr->getPrivate()->setCallSessionListener(nullptr); cgcr->getPrivate()->setChatRoomListener(nullptr); Core::deleteChatRoom(q->getSharedFromThis()); - setupCallbacks(); + setupProxy(); LinphoneChatRoom *lcr = L_GET_C_BACK_PTR(q); - shared_ptr bcr = cgcr->getCore()->onlyGetOrCreateBasicChatRoom(invitedAddresses.front()); + shared_ptr bcr = cgcr->getCore()->getOrCreateBasicChatRoom(invitedAddresses.front()); L_SET_CPP_PTR_FROM_C_OBJECT(lcr, bcr); - bcr->getPrivate()->setState(ChatRoom::State::Instantiated); - bcr->getPrivate()->setState(ChatRoom::State::Created); - cgcr->getCore()->getPrivate()->insertChatRoom(bcr); - cgcr->getCore()->getPrivate()->insertChatRoomWithDb(bcr); return; } cgcr->getPrivate()->onCallSessionStateChanged(session, newState, message); @@ -111,7 +112,7 @@ void ClientGroupToBasicChatRoom::addParticipant ( ProxyChatRoom::addParticipant(participantAddress, params, hasMedia); } void ClientGroupToBasicChatRoom::addParticipants ( - const std::list &addresses, + const list &addresses, const CallSessionParams *params, bool hasMedia ) { diff --git a/src/chat/chat-room/proxy-chat-room-p.h b/src/chat/chat-room/proxy-chat-room-p.h index 9559c31c4..b10aa3d8a 100644 --- a/src/chat/chat-room/proxy-chat-room-p.h +++ b/src/chat/chat-room/proxy-chat-room-p.h @@ -21,6 +21,7 @@ #define _L_PROXY_CHAT_ROOM_P_H_ #include "abstract-chat-room-p.h" +#include "proxy-chat-room.h" // ============================================================================= @@ -44,6 +45,10 @@ public: chatRoom->getPrivate()->sendChatMessage(chatMessage); } + inline void addEvent (const std::shared_ptr &eventLog) override { + chatRoom->getPrivate()->addEvent(eventLog); + } + inline void addTransientEvent (const std::shared_ptr &eventLog) override { chatRoom->getPrivate()->addTransientEvent(eventLog); } @@ -68,8 +73,8 @@ public: chatRoom->getPrivate()->onChatMessageReceived(chatMessage); } - void setupCallbacks (); - void teardownCallbacks (); + void setupProxy (); + void teardownProxy (); std::shared_ptr chatRoom; diff --git a/src/chat/chat-room/proxy-chat-room.cpp b/src/chat/chat-room/proxy-chat-room.cpp index bf984d2a1..6ddcb4f74 100644 --- a/src/chat/chat-room/proxy-chat-room.cpp +++ b/src/chat/chat-room/proxy-chat-room.cpp @@ -18,7 +18,7 @@ */ #include "basic-to-client-group-chat-room.h" -#include "chat-room.h" +#include "chat-room-p.h" #include "proxy-chat-room-p.h" #include "c-wrapper/c-wrapper.h" @@ -28,114 +28,13 @@ using namespace std; LINPHONE_BEGIN_NAMESPACE -#define PROXY_CALLBACK(callback, ...) \ - LinphoneChatRoomCbs *proxiedCbs = linphone_chat_room_get_callbacks(cr); \ - ProxyChatRoom *pcr = static_cast(linphone_chat_room_cbs_get_user_data(proxiedCbs)); \ - LinphoneChatRoom *lcr = L_GET_C_BACK_PTR(pcr->getSharedFromThis()); \ - LinphoneChatRoomCbs *cbs = linphone_chat_room_get_callbacks(lcr); \ - if (linphone_chat_room_cbs_get_ ## callback(cbs)) \ - linphone_chat_room_cbs_get_ ## callback(cbs)(lcr, ##__VA_ARGS__) - -static void chatMessageReceived (LinphoneChatRoom *cr, const LinphoneEventLog *event_log) { - PROXY_CALLBACK(chat_message_received, event_log); -} - -static void chatMessageSent (LinphoneChatRoom *cr, const LinphoneEventLog *event_log) { - PROXY_CALLBACK(chat_message_sent, event_log); -} - -static void conferenceAddressGeneration (LinphoneChatRoom *cr) { - PROXY_CALLBACK(conference_address_generation); -} - -static void isComposingReceived (LinphoneChatRoom *cr, const LinphoneAddress *remoteAddr, bool_t isComposing) { - PROXY_CALLBACK(is_composing_received, remoteAddr, isComposing); -} - -static void messageReceived (LinphoneChatRoom *cr, LinphoneChatMessage *msg) { - PROXY_CALLBACK(message_received, msg); -} - -static void participantAdded (LinphoneChatRoom *cr, const LinphoneEventLog *event_log) { - PROXY_CALLBACK(participant_added, event_log); -} - -static void participantAdminStatusChanged (LinphoneChatRoom *cr, const LinphoneEventLog *event_log) { - PROXY_CALLBACK(participant_admin_status_changed, event_log); -} - -static void participantDeviceAdded (LinphoneChatRoom *cr, const LinphoneEventLog *event_log) { - PROXY_CALLBACK(participant_device_added, event_log); -} - -static void participantDeviceFetched (LinphoneChatRoom *cr, const LinphoneAddress *participantAddr) { - PROXY_CALLBACK(participant_device_fetched, participantAddr); -} - -static void participantDeviceRemoved (LinphoneChatRoom *cr, const LinphoneEventLog *event_log) { - PROXY_CALLBACK(participant_device_removed, event_log); -} - -static void participantRemoved (LinphoneChatRoom *cr, const LinphoneEventLog *event_log) { - PROXY_CALLBACK(participant_removed, event_log); -} - -static void participantsCapabilitiesChecked (LinphoneChatRoom *cr, const LinphoneAddress *deviceAddr, const bctbx_list_t *participantsAddr) { - PROXY_CALLBACK(participants_capabilities_checked, deviceAddr, participantsAddr); -} - -static void stateChanged (LinphoneChatRoom *cr, LinphoneChatRoomState newState) { - PROXY_CALLBACK(state_changed, newState); -} - -static void subjectChanged (LinphoneChatRoom *cr, const LinphoneEventLog *event_log) { - PROXY_CALLBACK(subject_changed, event_log); -} - -static void undecryptableMessageReceived (LinphoneChatRoom *cr, LinphoneChatMessage *msg) { - PROXY_CALLBACK(undecryptable_message_received, msg); -} - -void ProxyChatRoomPrivate::setupCallbacks () { +void ProxyChatRoomPrivate::setupProxy () { L_Q(); - LinphoneChatRoom *lcr = L_GET_C_BACK_PTR(chatRoom); - LinphoneChatRoomCbs *cbs = linphone_chat_room_get_callbacks(lcr); - linphone_chat_room_cbs_set_user_data(cbs, q); - linphone_chat_room_cbs_set_chat_message_received(cbs, chatMessageReceived); - linphone_chat_room_cbs_set_chat_message_sent(cbs, chatMessageSent); - linphone_chat_room_cbs_set_conference_address_generation(cbs, conferenceAddressGeneration); - linphone_chat_room_cbs_set_is_composing_received(cbs, isComposingReceived); - linphone_chat_room_cbs_set_message_received(cbs, messageReceived); - linphone_chat_room_cbs_set_participant_added(cbs, participantAdded); - linphone_chat_room_cbs_set_participant_admin_status_changed(cbs, participantAdminStatusChanged); - linphone_chat_room_cbs_set_participant_device_added(cbs, participantDeviceAdded); - linphone_chat_room_cbs_set_participant_device_fetched(cbs, participantDeviceFetched); - linphone_chat_room_cbs_set_participant_device_removed(cbs, participantDeviceRemoved); - linphone_chat_room_cbs_set_participant_removed(cbs, participantRemoved); - linphone_chat_room_cbs_set_participants_capabilities_checked(cbs, participantsCapabilitiesChecked); - linphone_chat_room_cbs_set_state_changed(cbs, stateChanged); - linphone_chat_room_cbs_set_subject_changed(cbs, subjectChanged); - linphone_chat_room_cbs_set_undecryptable_message_received(cbs, undecryptableMessageReceived); + static_pointer_cast(chatRoom)->getPrivate()->setProxyChatRoom(q); } -void ProxyChatRoomPrivate::teardownCallbacks () { - LinphoneChatRoom *lcr = L_GET_C_BACK_PTR(chatRoom); - LinphoneChatRoomCbs *cbs = linphone_chat_room_get_callbacks(lcr); - linphone_chat_room_cbs_set_chat_message_received(cbs, nullptr); - linphone_chat_room_cbs_set_chat_message_sent(cbs, nullptr); - linphone_chat_room_cbs_set_conference_address_generation(cbs, nullptr); - linphone_chat_room_cbs_set_is_composing_received(cbs, nullptr); - linphone_chat_room_cbs_set_message_received(cbs, nullptr); - linphone_chat_room_cbs_set_participant_added(cbs, nullptr); - linphone_chat_room_cbs_set_participant_admin_status_changed(cbs, nullptr); - linphone_chat_room_cbs_set_participant_device_added(cbs, nullptr); - linphone_chat_room_cbs_set_participant_device_fetched(cbs, nullptr); - linphone_chat_room_cbs_set_participant_device_removed(cbs, nullptr); - linphone_chat_room_cbs_set_participant_removed(cbs, nullptr); - linphone_chat_room_cbs_set_participants_capabilities_checked(cbs, nullptr); - linphone_chat_room_cbs_set_state_changed(cbs, nullptr); - linphone_chat_room_cbs_set_subject_changed(cbs, nullptr); - linphone_chat_room_cbs_set_undecryptable_message_received(cbs, nullptr); +void ProxyChatRoomPrivate::teardownProxy () { + static_pointer_cast(chatRoom)->getPrivate()->setProxyChatRoom(nullptr); } // ----------------------------------------------------------------------------- @@ -144,7 +43,7 @@ void ProxyChatRoomPrivate::teardownCallbacks () { AbstractChatRoom(p, chatRoom->getCore()) { L_D(); d->chatRoom = chatRoom; - d->setupCallbacks(); + d->setupProxy(); } // ----------------------------------------------------------------------------- @@ -195,6 +94,16 @@ bool ProxyChatRoom::hasBeenLeft () const { // ----------------------------------------------------------------------------- +list> ProxyChatRoom::getMessageHistory (int nLast) const { + L_D(); + return d->chatRoom->getMessageHistory(nLast); +} + +list> ProxyChatRoom::getMessageHistoryRange (int begin, int end) const { + L_D(); + return d->chatRoom->getMessageHistoryRange(begin, end); +} + list> ProxyChatRoom::getHistory (int nLast) const { L_D(); return d->chatRoom->getHistory(nLast); diff --git a/src/chat/chat-room/proxy-chat-room.h b/src/chat/chat-room/proxy-chat-room.h index 16a5c49aa..5e4c68d11 100644 --- a/src/chat/chat-room/proxy-chat-room.h +++ b/src/chat/chat-room/proxy-chat-room.h @@ -30,6 +30,8 @@ class ChatRoom; class ProxyChatRoomPrivate; class LINPHONE_PUBLIC ProxyChatRoom : public AbstractChatRoom { + friend class CorePrivate; + public: const ChatRoomId &getChatRoomId () const override; @@ -43,6 +45,8 @@ public: State getState () const override; bool hasBeenLeft () const override; + std::list> getMessageHistory (int nLast) const override; + std::list> getMessageHistoryRange (int begin, int end) const override; std::list> getHistory (int nLast) const override; std::list> getHistoryRange (int begin, int end) const override; int getHistorySize () const override; diff --git a/src/chat/chat-room/real-time-text-chat-room-p.h b/src/chat/chat-room/real-time-text-chat-room-p.h index c8e5e2b62..26b89d53b 100644 --- a/src/chat/chat-room/real-time-text-chat-room-p.h +++ b/src/chat/chat-room/real-time-text-chat-room-p.h @@ -29,13 +29,17 @@ LINPHONE_BEGIN_NAMESPACE class RealTimeTextChatRoomPrivate : public BasicChatRoomPrivate { public: - RealTimeTextChatRoomPrivate () = default; + struct Character { + uint32_t value; + bool hasBeenRead; + }; - void realtimeTextReceived (uint32_t character, LinphoneCall *call); + void realtimeTextReceived (uint32_t character, const std::shared_ptr &call); void sendChatMessage (const std::shared_ptr &chatMessage) override; + void setCall (const std::shared_ptr &value) { call = value; } - LinphoneCall *call = nullptr; - std::list receivedRttCharacters; + std::weak_ptr call; + std::list receivedRttCharacters; std::shared_ptr pendingMessage = nullptr; private: diff --git a/src/chat/chat-room/real-time-text-chat-room.cpp b/src/chat/chat-room/real-time-text-chat-room.cpp index f12e120b3..a33a19535 100644 --- a/src/chat/chat-room/real-time-text-chat-room.cpp +++ b/src/chat/chat-room/real-time-text-chat-room.cpp @@ -18,6 +18,7 @@ */ #include "c-wrapper/c-wrapper.h" +#include "call/call.h" #include "chat/chat-message/chat-message-p.h" #include "conference/participant.h" #include "core/core.h" @@ -32,7 +33,7 @@ LINPHONE_BEGIN_NAMESPACE // ----------------------------------------------------------------------------- -void RealTimeTextChatRoomPrivate::realtimeTextReceived (uint32_t character, LinphoneCall *call) { +void RealTimeTextChatRoomPrivate::realtimeTextReceived (uint32_t character, const shared_ptr &call) { L_Q(); const uint32_t new_line = 0x2028; const uint32_t crlf = 0x0D0A; @@ -41,56 +42,48 @@ void RealTimeTextChatRoomPrivate::realtimeTextReceived (uint32_t character, Linp shared_ptr core = q->getCore(); LinphoneCore *cCore = core->getCCore(); - if (call && linphone_call_params_realtime_text_enabled(linphone_call_get_current_params(call))) { - LinphoneChatMessageCharacter *cmc = bctbx_new0(LinphoneChatMessageCharacter, 1); - + if (call && call->getCurrentParams()->realtimeTextEnabled()) { if (!pendingMessage) pendingMessage = q->createChatMessage(""); - cmc->value = character; - cmc->has_been_read = FALSE; + Character cmc; + cmc.value = character; + cmc.hasBeenRead = false; receivedRttCharacters.push_back(cmc); remoteIsComposing.push_back(q->getPeerAddress()); - linphone_core_notify_is_composing_received(cCore, L_GET_C_BACK_PTR(q)); + linphone_core_notify_is_composing_received(cCore, getCChatRoom()); if ((character == new_line) || (character == crlf) || (character == lf)) { - /* End of message */ + // End of message lDebug() << "New line received, forge a message with content " << pendingMessage->getPrivate()->getText().c_str(); - // TODO: REPAIR ME. - // pendingMessage->setFromAddress(peerAddress); - // pendingMessage->setToAddress( - // Address( - // linphone_call_get_dest_proxy(call) - // ? linphone_address_as_string(linphone_call_get_dest_proxy(call)->identity_address) - // : linphone_core_get_identity(cCore) - // ) - // ); pendingMessage->getPrivate()->setState(ChatMessage::State::Delivered); pendingMessage->getPrivate()->setDirection(ChatMessage::Direction::Incoming); + pendingMessage->getPrivate()->setTime(::ms_time(0)); if (lp_config_get_int(linphone_core_get_config(cCore), "misc", "store_rtt_messages", 1) == 1) pendingMessage->getPrivate()->storeInDb(); onChatMessageReceived(pendingMessage); pendingMessage = nullptr; - for (auto &rttChars : receivedRttCharacters) - ms_free(rttChars); receivedRttCharacters.clear(); } else { char *value = Utils::utf8ToChar(character); - char *text = (char *)pendingMessage->getPrivate()->getText().c_str(); - pendingMessage->getPrivate()->setText(ms_strcat_printf(text, value)); + string text(pendingMessage->getPrivate()->getText()); + text += string(value); + pendingMessage->getPrivate()->setText(text); lDebug() << "Received RTT character: " << value << " (" << character << "), pending text is " << pendingMessage->getPrivate()->getText(); - delete value; + delete[] value; } } } void RealTimeTextChatRoomPrivate::sendChatMessage (const shared_ptr &chatMessage) { - if (call && linphone_call_params_realtime_text_enabled(linphone_call_get_current_params(call))) { - uint32_t new_line = 0x2028; - chatMessage->putCharacter(new_line); + L_Q(); + shared_ptr call = q->getCall(); + if (call && call->getCurrentParams()->realtimeTextEnabled()) { + uint32_t newLine = 0x2028; + chatMessage->putCharacter(newLine); } } @@ -99,26 +92,16 @@ void RealTimeTextChatRoomPrivate::sendChatMessage (const shared_ptr RealTimeTextChatRoom::RealTimeTextChatRoom (const shared_ptr &core, const ChatRoomId &chatRoomId) : BasicChatRoom(*new RealTimeTextChatRoomPrivate, core, chatRoomId) {} -RealTimeTextChatRoom::~RealTimeTextChatRoom () { - L_D(); - - if (!d->receivedRttCharacters.empty()) - for (auto &rttChars : d->receivedRttCharacters) - bctbx_free(rttChars); -} - RealTimeTextChatRoom::CapabilitiesMask RealTimeTextChatRoom::getCapabilities () const { return BasicChatRoom::getCapabilities() | Capabilities::RealTimeText; } uint32_t RealTimeTextChatRoom::getChar () const { L_D(); - if (!d->receivedRttCharacters.empty()) { - for (auto &cmc : d->receivedRttCharacters) { - if (!cmc->has_been_read) { - cmc->has_been_read = TRUE; - return cmc->value; - } + for (const auto &cmc : d->receivedRttCharacters) { + if (!cmc.hasBeenRead) { + const_cast(&cmc)->hasBeenRead = true; + return cmc.value; } } return 0; @@ -126,9 +109,9 @@ uint32_t RealTimeTextChatRoom::getChar () const { // ----------------------------------------------------------------------------- -LinphoneCall *RealTimeTextChatRoom::getCall () const { +shared_ptr RealTimeTextChatRoom::getCall () const { L_D(); - return d->call; + return d->call.lock(); } LINPHONE_END_NAMESPACE diff --git a/src/chat/chat-room/real-time-text-chat-room.h b/src/chat/chat-room/real-time-text-chat-room.h index 7206bf3bc..4f0d8cb7f 100644 --- a/src/chat/chat-room/real-time-text-chat-room.h +++ b/src/chat/chat-room/real-time-text-chat-room.h @@ -26,18 +26,20 @@ LINPHONE_BEGIN_NAMESPACE +class Call; class RealTimeTextChatRoomPrivate; class LINPHONE_PUBLIC RealTimeTextChatRoom : public BasicChatRoom { + friend class CallPrivate; friend class CorePrivate; public: - ~RealTimeTextChatRoom (); + ~RealTimeTextChatRoom () = default; CapabilitiesMask getCapabilities () const override; uint32_t getChar () const; - LinphoneCall *getCall () const; + std::shared_ptr getCall () const; private: RealTimeTextChatRoom (const std::shared_ptr &core, const ChatRoomId &chatRoomId); diff --git a/src/chat/chat-room/server-group-chat-room-p.h b/src/chat/chat-room/server-group-chat-room-p.h index 1aeb4cff2..911f31f67 100644 --- a/src/chat/chat-room/server-group-chat-room-p.h +++ b/src/chat/chat-room/server-group-chat-room-p.h @@ -20,24 +20,39 @@ #ifndef _L_SERVER_GROUP_CHAT_ROOM_P_H_ #define _L_SERVER_GROUP_CHAT_ROOM_P_H_ +#include +#include +#include + #include "chat-room-p.h" #include "server-group-chat-room.h" +#include "conference/participant-device.h" // ============================================================================= LINPHONE_BEGIN_NAMESPACE +class ParticipantDevice; + class ServerGroupChatRoomPrivate : public ChatRoomPrivate { public: + void setState (ChatRoom::State state) override; + std::shared_ptr addParticipant (const IdentityAddress &participantAddress); void removeParticipant (const std::shared_ptr &participant); - std::shared_ptr findRemovedParticipant (const std::shared_ptr &session) const; + std::shared_ptr findFilteredParticipant (const std::shared_ptr &session) const; + std::shared_ptr findFilteredParticipant (const IdentityAddress &participantAddress) const; + + ParticipantDevice::State getParticipantDeviceState (const std::shared_ptr &device) const; + void setParticipantDeviceState (const std::shared_ptr &device, ParticipantDevice::State state); + + void acceptSession (const std::shared_ptr &session); void confirmCreation (); void confirmJoining (SalCallOp *op); void confirmRecreation (SalCallOp *op); - - IdentityAddress generateConferenceAddress (const std::shared_ptr &me) const; + void declineSession (const std::shared_ptr &session, LinphoneReason reason); + void dispatchQueuedMessages (); void subscribeReceived (LinphoneEvent *event); @@ -45,6 +60,7 @@ public: void setConferenceAddress (const IdentityAddress &conferenceAddress); void setParticipantDevices (const IdentityAddress &addr, const std::list &devices); + void addParticipantDevice (const IdentityAddress &participantAddress, const IdentityAddress &deviceAddress); void addCompatibleParticipants (const IdentityAddress &deviceAddr, const std::list &compatibleParticipants); void checkCompatibleParticipants (const IdentityAddress &deviceAddr, const std::list &addressesToCheck); @@ -52,21 +68,39 @@ public: private: struct Message { - Message (const std::string &from, const std::string &contentType, const std::string &text) : fromAddr(from) { + Message (const std::string &from, const ContentType &contentType, const std::string &text, const SalCustomHeader *salCustomHeaders) + : fromAddr(from) + { content.setContentType(contentType); if (!text.empty()) content.setBodyFromUtf8(text); + if (salCustomHeaders) + customHeaders = sal_custom_header_clone(salCustomHeaders); + } + + ~Message () { + if (customHeaders) + sal_custom_header_free(customHeaders); } IdentityAddress fromAddr; Content content; + std::chrono::system_clock::time_point timestamp = std::chrono::system_clock::now(); + SalCustomHeader *customHeaders = nullptr; }; + static void copyMessageHeaders (const std::shared_ptr &fromMessage, const std::shared_ptr &toMessage); + void designateAdmin (); - void dispatchMessage (const Message &message); - void dispatchQueuedMessages (); + void dispatchMessage (const std::shared_ptr &message, const std::string &uri); void finalizeCreation (); + void inviteDevice (const std::shared_ptr &device); bool isAdminLeft () const; + void queueMessage (const std::shared_ptr &message); + void queueMessage (const std::shared_ptr &msg, const IdentityAddress &deviceAddress); + void removeNonPresentParticipants (const std::list &compatibleParticipants); + + void onParticipantDeviceLeft (const std::shared_ptr &session); // ChatRoomListener void onChatRoomInsertRequested (const std::shared_ptr &chatRoom) override; @@ -75,16 +109,17 @@ private: // CallSessionListener void onCallSessionStateChanged ( - const std::shared_ptr &session, + const std::shared_ptr &session, CallSession::State newState, const std::string &message ) override; + void onCallSessionSetReleased (const std::shared_ptr &session) override; - std::list> removedParticipants; + std::list> filteredParticipants; ChatRoomListener *chatRoomListener = this; ServerGroupChatRoom::CapabilitiesMask capabilities = ServerGroupChatRoom::Capabilities::Conference; bool joiningPendingAfterCreation = false; - std::list queuedMessages; + std::unordered_map>> queuedMessages; L_DECLARE_PUBLIC(ServerGroupChatRoom); }; diff --git a/src/chat/chat-room/server-group-chat-room-stub.cpp b/src/chat/chat-room/server-group-chat-room-stub.cpp index 1fa4c58f5..d8204fc5b 100644 --- a/src/chat/chat-room/server-group-chat-room-stub.cpp +++ b/src/chat/chat-room/server-group-chat-room-stub.cpp @@ -28,31 +28,47 @@ LINPHONE_BEGIN_NAMESPACE // ----------------------------------------------------------------------------- +void ServerGroupChatRoomPrivate::setState (ChatRoom::State state) { + ChatRoomPrivate::setState(state); +} + shared_ptr ServerGroupChatRoomPrivate::addParticipant (const IdentityAddress &) { return nullptr; } void ServerGroupChatRoomPrivate::removeParticipant (const shared_ptr &) {} -shared_ptr ServerGroupChatRoomPrivate::findRemovedParticipant ( - const shared_ptr & -) const { +shared_ptr ServerGroupChatRoomPrivate::findFilteredParticipant (const shared_ptr &session) const { return nullptr; } +shared_ptr ServerGroupChatRoomPrivate::findFilteredParticipant (const IdentityAddress &participantAddress) const { + return nullptr; +} + +ParticipantDevice::State ServerGroupChatRoomPrivate::getParticipantDeviceState (const shared_ptr &device) const { + return device->getState(); +} + +void ServerGroupChatRoomPrivate::setParticipantDeviceState (const shared_ptr &device, ParticipantDevice::State state) { + device->setState(state); +} + // ----------------------------------------------------------------------------- +void ServerGroupChatRoomPrivate::acceptSession (const shared_ptr &session) {} + void ServerGroupChatRoomPrivate::confirmCreation () {} void ServerGroupChatRoomPrivate::confirmJoining (SalCallOp *) {} void ServerGroupChatRoomPrivate::confirmRecreation (SalCallOp *) {} -// ----------------------------------------------------------------------------- +void ServerGroupChatRoomPrivate::declineSession (const shared_ptr &session, LinphoneReason reason) {} -IdentityAddress ServerGroupChatRoomPrivate::generateConferenceAddress (const shared_ptr &me) const { - return IdentityAddress(); -} +void ServerGroupChatRoomPrivate::dispatchQueuedMessages () {} + +// ----------------------------------------------------------------------------- void ServerGroupChatRoomPrivate::subscribeReceived (LinphoneEvent *) {} @@ -64,6 +80,8 @@ void ServerGroupChatRoomPrivate::setConferenceAddress (const IdentityAddress &) void ServerGroupChatRoomPrivate::setParticipantDevices (const IdentityAddress &addr, const list &devices) {} +void ServerGroupChatRoomPrivate::addParticipantDevice (const IdentityAddress &participantAddress, const IdentityAddress &deviceAddress) {} + void ServerGroupChatRoomPrivate::addCompatibleParticipants (const IdentityAddress &deviceAddr, const list &participantCompatible) {} // ----------------------------------------------------------------------------- @@ -76,16 +94,26 @@ LinphoneReason ServerGroupChatRoomPrivate::onSipMessageReceived (SalOp *, const void ServerGroupChatRoomPrivate::designateAdmin () {} -void ServerGroupChatRoomPrivate::dispatchMessage (const Message &message) {} - -void ServerGroupChatRoomPrivate::dispatchQueuedMessages () {} +void ServerGroupChatRoomPrivate::dispatchMessage (const shared_ptr &message, const string &uri) {} void ServerGroupChatRoomPrivate::finalizeCreation () {} +void ServerGroupChatRoomPrivate::inviteDevice (const shared_ptr &device) {} + bool ServerGroupChatRoomPrivate::isAdminLeft () const { return false; } +void ServerGroupChatRoomPrivate::queueMessage (const shared_ptr &message) {} + +void ServerGroupChatRoomPrivate::queueMessage (const shared_ptr &msg, const IdentityAddress &deviceAddress) {} + +void ServerGroupChatRoomPrivate::removeNonPresentParticipants (const list &compatibleParticipants) {} + +// ----------------------------------------------------------------------------- + +void ServerGroupChatRoomPrivate::onParticipantDeviceLeft (const shared_ptr &session) {} + // ----------------------------------------------------------------------------- void ServerGroupChatRoomPrivate::onChatRoomInsertRequested (const shared_ptr &chatRoom) {} @@ -97,11 +125,13 @@ void ServerGroupChatRoomPrivate::onChatRoomDeleteRequested (const shared_ptr &, + const shared_ptr &, CallSession::State, const string & ) {} +void ServerGroupChatRoomPrivate::onCallSessionSetReleased (const shared_ptr &session) {} + // ============================================================================= ServerGroupChatRoom::ServerGroupChatRoom (const shared_ptr &core, SalCallOp *op) @@ -189,4 +219,12 @@ void ServerGroupChatRoom::join () {} void ServerGroupChatRoom::leave () {} +void ServerGroupChatRoom::onFirstNotifyReceived (const IdentityAddress &addr) {} + +// ----------------------------------------------------------------------------- + +ostream &operator<< (ostream &stream, const ServerGroupChatRoom *chatRoom) { + return stream << "ServerGroupChatRoom [" << reinterpret_cast(chatRoom) << "]"; +} + LINPHONE_END_NAMESPACE diff --git a/src/chat/chat-room/server-group-chat-room.h b/src/chat/chat-room/server-group-chat-room.h index bd480533c..57d49dcfc 100644 --- a/src/chat/chat-room/server-group-chat-room.h +++ b/src/chat/chat-room/server-group-chat-room.h @@ -85,11 +85,16 @@ public: void join () override; void leave () override; + /* ConferenceListener */ + void onFirstNotifyReceived (const IdentityAddress &addr) override; + private: L_DECLARE_PRIVATE(ServerGroupChatRoom); L_DISABLE_COPY(ServerGroupChatRoom); }; +std::ostream &operator<< (std::ostream &stream, const ServerGroupChatRoom *chatRoom); + LINPHONE_END_NAMESPACE #endif // ifndef _L_SERVER_GROUP_CHAT_ROOM_H_ diff --git a/src/chat/cpim/message/cpim-message.cpp b/src/chat/cpim/message/cpim-message.cpp index b1a993559..84753f4ef 100644 --- a/src/chat/cpim/message/cpim-message.cpp +++ b/src/chat/cpim/message/cpim-message.cpp @@ -38,7 +38,7 @@ class Cpim::MessagePrivate : public ObjectPrivate { public: typedef list > PrivHeaderList; - shared_ptr cpimHeaders = make_shared(); + shared_ptr cpimHeaders = make_shared(); // TODO: Remove this useless variable shared_ptr messageHeaders = make_shared(); shared_ptr contentHeaders = make_shared(); string content; @@ -148,10 +148,11 @@ string Cpim::Message::asString () const { L_D(); string output; + // TODO: Remove cpimHeaders for (const auto &cpimHeader : *d->cpimHeaders) output += cpimHeader->asString(); - output += "\r\n"; + // TODO Remove cpimHeaders if (d->messageHeaders->size() > 0) { for (const auto &messageHeader : *d->messageHeaders) diff --git a/src/chat/cpim/message/cpim-message.h b/src/chat/cpim/message/cpim-message.h index 53e33e45e..c7778c929 100644 --- a/src/chat/cpim/message/cpim-message.h +++ b/src/chat/cpim/message/cpim-message.h @@ -36,9 +36,11 @@ namespace Cpim { typedef std::shared_ptr > > HeaderList; + // TODO: Remove these useless methods HeaderList getCpimHeaders () const; bool addCpimHeader (const Header &cpimHeader); void removeCpimHeader (const Header &cpimHeader); + // TODO: Remove these useless methods HeaderList getMessageHeaders () const; bool addMessageHeader (const Header &messageHeader); @@ -51,7 +53,7 @@ namespace Cpim { std::string getContent () const; bool setContent (const std::string &content); - bool isValid () const; + bool isValid () const; // TODO: Remove this useless method std::string asString () const; diff --git a/src/chat/cpim/parser/cpim-parser.cpp b/src/chat/cpim/parser/cpim-parser.cpp index 7acdac5bd..d3beb28d2 100644 --- a/src/chat/cpim/parser/cpim-parser.cpp +++ b/src/chat/cpim/parser/cpim-parser.cpp @@ -171,37 +171,47 @@ namespace Cpim { // Warning: Call this function one time! shared_ptr createMessage () const { size_t size = mHeaders->size(); - if (size < 2 || size > 3) { + if (size < 2 || size > 3) { // TODO: Check that size is == 2 lWarning() << "Bad headers lists size."; return nullptr; } const shared_ptr message = make_shared(); - const shared_ptr cpimHeaders = mHeaders->front(); - if (find_if(cpimHeaders->cbegin(), cpimHeaders->cend(), - [](const shared_ptr &headerNode) { - return Utils::iequals(headerNode->getName(), "content-type") && (ContentType(headerNode->getValue()) == ContentType::Cpim); - }) == cpimHeaders->cend()) { - lWarning() << "No MIME `Content-Type` found!"; - return nullptr; - } - - // Add MIME headers. - for (const auto &headerNode : *cpimHeaders) { - const shared_ptr header = headerNode->createHeader(); - if (!header || !message->addCpimHeader(*header)) + // TODO: To remove + if (size == 3) { + const shared_ptr cpimHeaders = mHeaders->front(); + if (find_if(cpimHeaders->cbegin(), cpimHeaders->cend(), + [](const shared_ptr &headerNode) { + return Utils::iequals(headerNode->getName(), "content-type") && (ContentType(headerNode->getValue()) == ContentType::Cpim); + }) == cpimHeaders->cend()) { + lWarning() << "No MIME `Content-Type` found!"; return nullptr; - } + } - // Add message headers. - if (mHeaders->size() > 2) { + // Add MIME headers. + for (const auto &headerNode : *cpimHeaders) { + const shared_ptr header = headerNode->createHeader(); + if (!header || !message->addCpimHeader(*header)) + return nullptr; + } + + // Add message headers. for (const auto &headerNode : **(++mHeaders->cbegin())) { const shared_ptr header = headerNode->createHeader(); if (!header || !message->addMessageHeader(*header)) return nullptr; } } + // TODO: To remove + else { + // Add message headers. + for (const auto &headerNode : *mHeaders->front()) { + const shared_ptr header = headerNode->createHeader(); + if (!header || !message->addMessageHeader(*header)) + return nullptr; + } + } // Add content headers. for (const auto &headerNode : *mHeaders->back()) { diff --git a/src/chat/modifier/cpim-chat-message-modifier.cpp b/src/chat/modifier/cpim-chat-message-modifier.cpp index ebb07e9a0..8958c0f82 100644 --- a/src/chat/modifier/cpim-chat-message-modifier.cpp +++ b/src/chat/modifier/cpim-chat-message-modifier.cpp @@ -34,10 +34,13 @@ LINPHONE_BEGIN_NAMESPACE ChatMessageModifier::Result CpimChatMessageModifier::encode (const shared_ptr &message, int &errorCode) { Cpim::Message cpimMessage; + + // TODO: Remove this buggy Content-Type header Cpim::GenericHeader cpimContentTypeHeader; cpimContentTypeHeader.setName("Content-Type"); cpimContentTypeHeader.setValue(ContentType::Cpim.asString()); cpimMessage.addCpimHeader(cpimContentTypeHeader); + // TODO: Remove this buggy Content-Type header Cpim::FromHeader cpimFromHeader; cpimFromHeader.setValue(cpimAddressAsString(message->getFromAddress())); @@ -71,11 +74,13 @@ ChatMessageModifier::Result CpimChatMessageModifier::encode (const shared_ptrgetBodyAsString(); cpimMessage.setContent(contentBody); + // TODO: Remove this check because of buggy Content-Type header if (!cpimMessage.isValid()) { lError() << "[CPIM] Message is invalid: " << contentBody; errorCode = 500; return ChatMessageModifier::Result::Error; } + // TODO: Remove this check because of buggy Content-Type header Content newContent; newContent.setContentType(ContentType::Cpim); @@ -99,7 +104,7 @@ ChatMessageModifier::Result CpimChatMessageModifier::decode (const shared_ptrgetBodyAsString(); const shared_ptr cpimMessage = Cpim::Message::createFromString(contentBody); - if (!cpimMessage || !cpimMessage->isValid()) { + if (!cpimMessage) { lError() << "[CPIM] Message is invalid: " << contentBody; errorCode = 500; return ChatMessageModifier::Result::Error; diff --git a/src/chat/modifier/file-transfer-chat-message-modifier.cpp b/src/chat/modifier/file-transfer-chat-message-modifier.cpp index 30d088d2c..d9d0767b2 100644 --- a/src/chat/modifier/file-transfer-chat-message-modifier.cpp +++ b/src/chat/modifier/file-transfer-chat-message-modifier.cpp @@ -35,6 +35,10 @@ using namespace std; LINPHONE_BEGIN_NAMESPACE +FileTransferChatMessageModifier::FileTransferChatMessageModifier (belle_http_provider_t *prov) : provider(prov) { + bgTask.setName("File transfer upload"); +} + belle_http_request_t *FileTransferChatMessageModifier::getHttpRequest () const { return httpRequest; } @@ -44,7 +48,10 @@ void FileTransferChatMessageModifier::setHttpRequest (belle_http_request_t *requ } FileTransferChatMessageModifier::~FileTransferChatMessageModifier () { - releaseHttpRequest(); + if (isFileTransferInProgressAndValid()) + cancelFileTransfer(); //to avoid body handler to still refference zombie FileTransferChatMessageModifier + else + releaseHttpRequest(); } ChatMessageModifier::Result FileTransferChatMessageModifier::encode (const shared_ptr &message, int &errorCode) { @@ -357,27 +364,27 @@ void FileTransferChatMessageModifier::processResponseFromPostFile (const belle_h fileTransferContent->setContentType(ContentType::FileTransfer); fileTransferContent->setFileContent(fileContent); - message->removeContent(*fileContent); - message->addContent(*fileTransferContent); + message->getPrivate()->removeContent(*fileContent); + message->getPrivate()->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(); } @@ -394,7 +401,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(); } @@ -408,7 +415,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(); } @@ -470,7 +477,7 @@ int FileTransferChatMessageModifier::startHttpTransfer (const string &url, const // give msg to listener to be able to start the actual file upload when server answer a 204 No content httpListener = belle_http_request_listener_create_from_callbacks(cbs, this); - belle_http_provider_send_request(message->getCore()->getCCore()->http_provider, httpRequest, httpListener); + belle_http_provider_send_request(provider, httpRequest, httpListener); return 0; error: @@ -480,29 +487,15 @@ error: return -1; } -static void _chat_message_file_upload_background_task_ended (void *data) { - FileTransferChatMessageModifier *d = (FileTransferChatMessageModifier *)data; - d->fileUploadBackgroundTaskEnded(); -} - -void FileTransferChatMessageModifier::fileUploadBackgroundTaskEnded () { - lWarning() << "channel [" << this << "]: file upload background task has to be ended now, but work isn't finished."; - fileUploadEndBackgroundTask(); -} - void FileTransferChatMessageModifier::fileUploadBeginBackgroundTask () { - if (backgroundTaskId == 0) { - backgroundTaskId = sal_begin_background_task("file transfer upload", _chat_message_file_upload_background_task_ended, this); - if (backgroundTaskId) lInfo() << "channel [" << this << "]: starting file upload background task with id=[" << backgroundTaskId << "]."; - } + shared_ptr message = chatMessage.lock(); + if (!message) + return; + bgTask.start(message->getCore()); } void FileTransferChatMessageModifier::fileUploadEndBackgroundTask () { - if (backgroundTaskId) { - lInfo() << "channel [" << this << "]: ending file upload background task with id=[" << backgroundTaskId << "]."; - sal_end_background_task(backgroundTaskId); - backgroundTaskId = 0; - } + bgTask.stop(); } // ---------------------------------------------------------- @@ -763,12 +756,12 @@ void FileTransferChatMessageModifier::onRecvEnd (belle_sip_user_body_handler_t * if (retval <= 0 && message->getState() != ChatMessage::State::FileTransferError) { // Remove the FileTransferContent from the message and store the FileContent FileContent *fileContent = currentFileContentToTransfer; - message->addContent(*fileContent); + message->getPrivate()->addContent(*fileContent); for (Content *content : message->getContents()) { if (content->getContentType() == ContentType::FileTransfer) { FileTransferContent *fileTransferContent = (FileTransferContent*)content; if (fileTransferContent->getFileContent() == fileContent) { - message->removeContent(*content); + message->getPrivate()->removeContent(*content); delete fileTransferContent; break; } @@ -857,7 +850,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(); } @@ -871,7 +864,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(); } @@ -951,10 +944,11 @@ void FileTransferChatMessageModifier::cancelFileTransfer () { ? L_C_TO_STRING(linphone_core_get_file_transfer_server(message->getCore()->getCCore())) : currentFileContentToTransfer->getFilePath().c_str() ); - belle_http_provider_cancel_request(message->getCore()->getCCore()->http_provider, httpRequest); + } else { lInfo() << "Warning: http request still running for ORPHAN msg: this is a memory leak"; } + belle_http_provider_cancel_request(provider, httpRequest); } releaseHttpRequest(); } @@ -974,4 +968,18 @@ void FileTransferChatMessageModifier::releaseHttpRequest () { } } +string FileTransferChatMessageModifier::createFakeFileTransferFromUrl(const string &url) { + string fileName = url.substr(url.find_last_of("/") + 1); + stringstream fakeXml; + fakeXml << "\r\n"; + fakeXml << "\r\n"; + fakeXml << "\r\n"; + fakeXml << "" << fileName << "\r\n"; + fakeXml << "application/binary\r\n"; + fakeXml << "\r\n"; + fakeXml << "\r\n"; + fakeXml << ""; + return fakeXml.str(); +} + LINPHONE_END_NAMESPACE diff --git a/src/chat/modifier/file-transfer-chat-message-modifier.h b/src/chat/modifier/file-transfer-chat-message-modifier.h index c711285d5..d6d9af736 100644 --- a/src/chat/modifier/file-transfer-chat-message-modifier.h +++ b/src/chat/modifier/file-transfer-chat-message-modifier.h @@ -23,6 +23,7 @@ #include #include "chat-message-modifier.h" +#include "utils/background-task.h" // ============================================================================= @@ -35,7 +36,7 @@ class FileTransferContent; class FileTransferChatMessageModifier : public ChatMessageModifier { public: - FileTransferChatMessageModifier () = default; + FileTransferChatMessageModifier (belle_http_provider_t *prov); ~FileTransferChatMessageModifier (); Result encode (const std::shared_ptr &message, int &errorCode) override; @@ -62,20 +63,24 @@ public: int downloadFile(const std::shared_ptr &message, FileTransferContent *fileTransferContent); void cancelFileTransfer(); bool isFileTransferInProgressAndValid(); + std::string createFakeFileTransferFromUrl(const std::string &url); private: - std::weak_ptr chatMessage; - FileContent* currentFileContentToTransfer; - unsigned long backgroundTaskId = 0; - belle_http_request_t *httpRequest = nullptr; - belle_http_request_listener_t *httpListener = nullptr; - int uploadFile(); int startHttpTransfer(const std::string &url, const std::string &action, belle_http_request_listener_callbacks_t *cbs); void fileUploadBeginBackgroundTask(); void fileUploadEndBackgroundTask(); void releaseHttpRequest(); + + std::weak_ptr chatMessage; + FileContent* currentFileContentToTransfer; + + belle_http_request_t *httpRequest = nullptr; + belle_http_request_listener_t *httpListener = nullptr; + belle_http_provider_t *provider = nullptr; + + BackgroundTask bgTask; }; LINPHONE_END_NAMESPACE diff --git a/src/chat/modifier/multipart-chat-message-modifier.cpp b/src/chat/modifier/multipart-chat-message-modifier.cpp index 4abf91210..243cb32e1 100644 --- a/src/chat/modifier/multipart-chat-message-modifier.cpp +++ b/src/chat/modifier/multipart-chat-message-modifier.cpp @@ -59,7 +59,7 @@ ChatMessageModifier::Result MultipartChatMessageModifier::encode ( multipartMessage << "--"; Content newContent; - ContentType newContentType("multipart/mixed"); + ContentType newContentType(ContentType::Multipart); newContentType.setParameter("boundary=" + boundary); newContent.setContentType(newContentType); newContent.setBody(multipartMessage.str()); diff --git a/src/chat/notification/imdn.cpp b/src/chat/notification/imdn.cpp index b3412ce12..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" @@ -40,7 +40,8 @@ string Imdn::createXml (const string &id, time_t time, Imdn::Type imdnType, Linp char *datetime = nullptr; // Check that the chat message has a message id. - if (id.empty()) return nullptr; + if (id.empty()) + return content; buf = xmlBufferCreate(); if (buf == nullptr) { @@ -132,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; @@ -163,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()); @@ -177,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); } } } @@ -190,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/chat/notification/is-composing.cpp b/src/chat/notification/is-composing.cpp index f4abc6760..c567bd3b8 100644 --- a/src/chat/notification/is-composing.cpp +++ b/src/chat/notification/is-composing.cpp @@ -288,7 +288,6 @@ int IsComposing::refreshTimerExpired (void *data, unsigned int revents) { int IsComposing::remoteRefreshTimerExpired (void *data, unsigned int revents) { IsRemoteComposingData *d = reinterpret_cast(data); int result = d->isComposingHandler->remoteRefreshTimerExpired(d->uri); - delete d; return result; } diff --git a/src/conference/conference-interface.h b/src/conference/conference-interface.h index dc4e740a1..aacf6bfab 100644 --- a/src/conference/conference-interface.h +++ b/src/conference/conference-interface.h @@ -34,7 +34,7 @@ class Participant; class LINPHONE_PUBLIC ConferenceInterface { public: - virtual ~ConferenceInterface() = default; + virtual ~ConferenceInterface () = default; virtual void addParticipant ( const IdentityAddress &participantAddress, diff --git a/src/conference/conference-listener.h b/src/conference/conference-listener.h index e90c00760..f185259f8 100644 --- a/src/conference/conference-listener.h +++ b/src/conference/conference-listener.h @@ -20,32 +20,28 @@ #ifndef _L_CONFERENCE_LISTENER_H_ #define _L_CONFERENCE_LISTENER_H_ -#include -#include #include -#include "linphone/utils/general.h" - #include "event-log/events.h" // ============================================================================= LINPHONE_BEGIN_NAMESPACE -class IdentityAddress; - class ConferenceListener { public: - virtual void onConferenceCreated (const IdentityAddress &addr) = 0; + virtual ~ConferenceListener () = default; + + virtual void onConferenceCreated (const IdentityAddress &addr) {} virtual void onConferenceKeywordsChanged (const std::vector &keywords) {} - virtual void onConferenceTerminated (const IdentityAddress &addr) = 0; - virtual void onFirstNotifyReceived (const IdentityAddress &addr) = 0; - virtual void onParticipantAdded (const std::shared_ptr &event, bool isFullState) = 0; - virtual void onParticipantRemoved (const std::shared_ptr &event, bool isFullState) = 0; - virtual void onParticipantSetAdmin (const std::shared_ptr &event, bool isFullState) = 0; - virtual void onSubjectChanged (const std::shared_ptr &event, bool isFullState) = 0; - virtual void onParticipantDeviceAdded (const std::shared_ptr &event, bool isFullState) = 0; - virtual void onParticipantDeviceRemoved (const std::shared_ptr &event, bool isFullState) = 0; + virtual void onConferenceTerminated (const IdentityAddress &addr) {} + virtual void onFirstNotifyReceived (const IdentityAddress &addr) {} + virtual void onParticipantAdded (const std::shared_ptr &event, bool isFullState) {} + virtual void onParticipantRemoved (const std::shared_ptr &event, bool isFullState) {} + virtual void onParticipantSetAdmin (const std::shared_ptr &event, bool isFullState) {} + virtual void onSubjectChanged (const std::shared_ptr &event, bool isFullState) {} + virtual void onParticipantDeviceAdded (const std::shared_ptr &event, bool isFullState) {} + virtual void onParticipantDeviceRemoved (const std::shared_ptr &event, bool isFullState) {} }; LINPHONE_END_NAMESPACE diff --git a/src/conference/conference-p.h b/src/conference/conference-p.h index 0a5441a03..c2f21013c 100644 --- a/src/conference/conference-p.h +++ b/src/conference/conference-p.h @@ -32,6 +32,8 @@ class Participant; class ConferencePrivate { public: + virtual ~ConferencePrivate () = default; + IdentityAddress conferenceAddress; std::list> participants; std::string subject; diff --git a/src/conference/conference.cpp b/src/conference/conference.cpp index b3127d89d..9e22d0207 100644 --- a/src/conference/conference.cpp +++ b/src/conference/conference.cpp @@ -61,11 +61,8 @@ void Conference::addParticipants (const list &addresses, const list sortedAddresses(addresses); sortedAddresses.sort(); sortedAddresses.unique(); - for (const auto &addr: sortedAddresses) { - shared_ptr participant = findParticipant(addr); - if (!participant) - addParticipant(addr, params, hasMedia); - } + for (const auto &addr: sortedAddresses) + addParticipant(addr, params, hasMedia); } bool Conference::canHandleParticipants () const { @@ -83,8 +80,7 @@ shared_ptr Conference::getMe () const { } int Conference::getParticipantCount () const { - L_D(); - return static_cast(d->participants.size()); + return static_cast(getParticipants().size()); } const list> &Conference::getParticipants () const { @@ -145,6 +141,19 @@ shared_ptr Conference::findParticipant (const shared_ptr Conference::findParticipantDevice (const shared_ptr &session) const { + L_D(); + + for (const auto &participant : d->participants) { + for (const auto &device : participant->getPrivate()->getDevices()) { + if (device->getSession() == session) + return device; + } + } + + return nullptr; +} + bool Conference::isMe (const IdentityAddress &addr) const { L_D(); IdentityAddress cleanedAddr(addr); diff --git a/src/conference/conference.h b/src/conference/conference.h index 733c1360e..0df0ae998 100644 --- a/src/conference/conference.h +++ b/src/conference/conference.h @@ -23,6 +23,7 @@ #include "linphone/types.h" #include "conference/conference-interface.h" +#include "conference/conference-listener.h" #include "core/core-accessor.h" // ============================================================================= @@ -33,18 +34,21 @@ class CallSession; class CallSessionListener; class CallSessionPrivate; class ConferencePrivate; +class ParticipantDevice; class LINPHONE_PUBLIC Conference : public ConferenceInterface, + public ConferenceListener, public CoreAccessor { friend class CallSessionPrivate; public: - virtual ~Conference(); + ~Conference(); std::shared_ptr getActiveParticipant () const; std::shared_ptr findParticipant (const std::shared_ptr &session) const; + std::shared_ptr findParticipantDevice (const std::shared_ptr &session) const; /* ConferenceInterface */ void addParticipant (const IdentityAddress &addr, const CallSessionParams *params, bool hasMedia) override; diff --git a/src/conference/handlers/local-conference-event-handler-p.h b/src/conference/handlers/local-conference-event-handler-p.h index 6fce0ddb3..a4ad08b2a 100644 --- a/src/conference/handlers/local-conference-event-handler-p.h +++ b/src/conference/handlers/local-conference-event-handler-p.h @@ -40,11 +40,11 @@ public: std::string createNotifyFullState (int notifyId = -1, bool oneToOne = false); std::string createNotifyMultipart (int notifyId); std::string createNotifyParticipantAdded (const Address &addr, int notifyId = -1); + std::string createNotifyParticipantAdminStatusChanged (const Address &addr, bool isAdmin, int notifyId = -1); std::string createNotifyParticipantRemoved (const Address &addr, int notifyId = -1); - std::string createNotifyParticipantAdmined (const Address &addr, bool isAdmin, int notifyId = -1); - std::string createNotifySubjectChanged (int notifyId = -1); std::string createNotifyParticipantDeviceAdded (const Address &addr, const Address &gruu, int notifyId = -1); std::string createNotifyParticipantDeviceRemoved (const Address &addr, const Address &gruu, int notifyId = -1); + std::string createNotifySubjectChanged (int notifyId = -1); inline unsigned int getLastNotify () const { return lastNotify; }; @@ -54,6 +54,8 @@ private: LocalConference *conf = nullptr; unsigned int lastNotify = 1; + static void notifyResponseCb (const LinphoneEvent *ev); + std::string createNotify (Xsd::ConferenceInfo::ConferenceType confInfo, int notifyId = -1, bool isFullState = false); std::string createNotifySubjectChanged (const std::string &subject, int notifyId = -1); void notifyParticipant (const std::string ¬ify, const std::shared_ptr &participant); diff --git a/src/conference/handlers/local-conference-event-handler.cpp b/src/conference/handlers/local-conference-event-handler.cpp index 7b3989b72..9847a93e9 100644 --- a/src/conference/handlers/local-conference-event-handler.cpp +++ b/src/conference/handlers/local-conference-event-handler.cpp @@ -62,51 +62,6 @@ void LocalConferenceEventHandlerPrivate::notifyAll (const string ¬ify) { notifyParticipant(notify, participant); } -void LocalConferenceEventHandlerPrivate::notifyParticipant (const string ¬ify, const shared_ptr &participant) { - for (const auto &device : participant->getPrivate()->getDevices()) - notifyParticipantDevice(notify, device); -} - -void LocalConferenceEventHandlerPrivate::notifyParticipantDevice (const string ¬ify, const shared_ptr &device, bool multipart) { - if (device->isSubscribedToConferenceEventPackage() && !notify.empty()) { - LinphoneEvent *ev = device->getConferenceSubscribeEvent(); - LinphoneContent *content = linphone_core_create_content(ev->lc); - linphone_content_set_buffer(content, (const uint8_t *)notify.c_str(), strlen(notify.c_str())); - if (multipart) { - linphone_content_set_type(content, "multipart"); - linphone_content_set_subtype(content, "mixed;boundary=---------------------------14737809831466499882746641449"); - } else { - linphone_content_set_type(content, "application"); - linphone_content_set_subtype(content, "conference-info"); - } - linphone_event_notify(ev, content); - linphone_content_unref(content); - } -} - -string LocalConferenceEventHandlerPrivate::createNotify (ConferenceType confInfo, int notifyId, bool isFullState) { - if (notifyId == -1) { - lastNotify = lastNotify + 1; - confInfo.setVersion(lastNotify); - } else { - confInfo.setVersion(static_cast(notifyId)); - } - confInfo.setState(isFullState ? StateType::full : StateType::partial); - if (!confInfo.getConferenceDescription()) { - ConferenceDescriptionType description = ConferenceDescriptionType(); - confInfo.setConferenceDescription(description); - } - - time_t result = time(nullptr); - confInfo.getConferenceDescription()->setFreeText(Utils::toString(static_cast(result))); - - stringstream notify; - Xsd::XmlSchema::NamespaceInfomap map; - map[""].name = "urn:ietf:params:xml:ns:conference-info"; - serializeConferenceInfo(notify, confInfo, map); - return notify.str(); -} - string LocalConferenceEventHandlerPrivate::createNotifyFullState (int notifyId, bool oneToOne) { string entity = conf->getConferenceAddress().asString(); string subject = conf->getSubject(); @@ -177,7 +132,7 @@ string LocalConferenceEventHandlerPrivate::createNotifyMultipart (int notifyId) case EventLog::Type::ConferenceParticipantSetAdmin: { shared_ptr setAdminEvent = static_pointer_cast(eventLog); - body = createNotifyParticipantAdmined( + body = createNotifyParticipantAdminStatusChanged( setAdminEvent->getParticipantAddress(), true, eventNotifyId @@ -186,7 +141,7 @@ string LocalConferenceEventHandlerPrivate::createNotifyMultipart (int notifyId) case EventLog::Type::ConferenceParticipantUnsetAdmin: { shared_ptr unsetAdminEvent = static_pointer_cast(eventLog); - body = createNotifyParticipantAdmined( + body = createNotifyParticipantAdminStatusChanged( unsetAdminEvent->getParticipantAddress(), false, eventNotifyId @@ -263,21 +218,7 @@ string LocalConferenceEventHandlerPrivate::createNotifyParticipantAdded (const A return createNotify(confInfo, notifyId); } -string LocalConferenceEventHandlerPrivate::createNotifyParticipantRemoved (const Address &addr, int notifyId) { - string entity = conf->getConferenceAddress().asString(); - ConferenceType confInfo = ConferenceType(entity); - UsersType users; - confInfo.setUsers(users); - - UserType user = UserType(); - user.setEntity(addr.asStringUriOnly()); - user.setState(StateType::deleted); - confInfo.getUsers()->getUser().push_back(user); - - return createNotify(confInfo, notifyId); -} - -string LocalConferenceEventHandlerPrivate::createNotifyParticipantAdmined (const Address &addr, bool isAdmin, int notifyId) { +string LocalConferenceEventHandlerPrivate::createNotifyParticipantAdminStatusChanged (const Address &addr, bool isAdmin, int notifyId) { string entity = conf->getConferenceAddress().asString(); ConferenceType confInfo = ConferenceType(entity); UsersType users; @@ -294,16 +235,16 @@ string LocalConferenceEventHandlerPrivate::createNotifyParticipantAdmined (const return createNotify(confInfo, notifyId); } -string LocalConferenceEventHandlerPrivate::createNotifySubjectChanged (int notifyId) { - return createNotifySubjectChanged(conf->getSubject(), notifyId); -} - -string LocalConferenceEventHandlerPrivate::createNotifySubjectChanged (const string &subject, int notifyId) { +string LocalConferenceEventHandlerPrivate::createNotifyParticipantRemoved (const Address &addr, int notifyId) { string entity = conf->getConferenceAddress().asString(); ConferenceType confInfo = ConferenceType(entity); - ConferenceDescriptionType confDescr = ConferenceDescriptionType(); - confDescr.setSubject(subject); - confInfo.setConferenceDescription((const ConferenceDescriptionType)confDescr); + UsersType users; + confInfo.setUsers(users); + + UserType user = UserType(); + user.setEntity(addr.asStringUriOnly()); + user.setState(StateType::deleted); + confInfo.getUsers()->getUser().push_back(user); return createNotify(confInfo, notifyId); } @@ -350,6 +291,95 @@ string LocalConferenceEventHandlerPrivate::createNotifyParticipantDeviceRemoved return createNotify(confInfo, notifyId); } +string LocalConferenceEventHandlerPrivate::createNotifySubjectChanged (int notifyId) { + return createNotifySubjectChanged(conf->getSubject(), notifyId); +} + +// ----------------------------------------------------------------------------- + +void LocalConferenceEventHandlerPrivate::notifyResponseCb (const LinphoneEvent *ev) { + LinphoneEventCbs *cbs = linphone_event_get_callbacks(ev); + LocalConferenceEventHandlerPrivate *handler = reinterpret_cast( + linphone_event_cbs_get_user_data(cbs) + ); + linphone_event_cbs_set_user_data(cbs, nullptr); + linphone_event_cbs_set_notify_response(cbs, nullptr); + + if (linphone_event_get_reason(ev) != LinphoneReasonNone) + return; + + for (const auto &p : handler->conf->getParticipants()) { + for (const auto &d : p->getPrivate()->getDevices()) { + if ((d->getConferenceSubscribeEvent() == ev) && (d->getState() == ParticipantDevice::State::Joining)) { + handler->conf->onFirstNotifyReceived(d->getAddress()); + return; + } + } + } +} + +// ----------------------------------------------------------------------------- + +string LocalConferenceEventHandlerPrivate::createNotify (ConferenceType confInfo, int notifyId, bool isFullState) { + confInfo.setVersion(notifyId == -1 ? ++lastNotify : static_cast(notifyId)); + confInfo.setState(isFullState ? StateType::full : StateType::partial); + + if (!confInfo.getConferenceDescription()) { + ConferenceDescriptionType description = ConferenceDescriptionType(); + confInfo.setConferenceDescription(description); + } + + time_t result = time(nullptr); + confInfo.getConferenceDescription()->setFreeText(Utils::toString(static_cast(result))); + + stringstream notify; + Xsd::XmlSchema::NamespaceInfomap map; + map[""].name = "urn:ietf:params:xml:ns:conference-info"; + serializeConferenceInfo(notify, confInfo, map); + return notify.str(); +} + +string LocalConferenceEventHandlerPrivate::createNotifySubjectChanged (const string &subject, int notifyId) { + string entity = conf->getConferenceAddress().asString(); + ConferenceType confInfo = ConferenceType(entity); + ConferenceDescriptionType confDescr = ConferenceDescriptionType(); + confDescr.setSubject(subject); + confInfo.setConferenceDescription((const ConferenceDescriptionType)confDescr); + + return createNotify(confInfo, notifyId); +} + +void LocalConferenceEventHandlerPrivate::notifyParticipant (const string ¬ify, const shared_ptr &participant) { + for (const auto &device : participant->getPrivate()->getDevices()) + notifyParticipantDevice(notify, device); +} + +void LocalConferenceEventHandlerPrivate::notifyParticipantDevice (const string ¬ify, const shared_ptr &device, bool multipart) { + if (!device->isSubscribedToConferenceEventPackage() || notify.empty()) + return; + + LinphoneEvent *ev = device->getConferenceSubscribeEvent(); + LinphoneEventCbs *cbs = linphone_event_get_callbacks(ev); + linphone_event_cbs_set_user_data(cbs, this); + linphone_event_cbs_set_notify_response(cbs, notifyResponseCb); + LinphoneContent *content = linphone_core_create_content(ev->lc); + linphone_content_set_buffer(content, (const uint8_t *)notify.c_str(), strlen(notify.c_str())); + linphone_content_set_type( + content, + multipart ? "multipart" : "application" + ); + linphone_content_set_subtype( + content, + multipart ? "mixed;boundary=---------------------------14737809831466499882746641449" : "conference-info" + ); + // TODO: Activate compression + //if (linphone_core_content_encoding_supported(conf->getCore()->getCCore(), "deflate")) + // linphone_content_set_encoding(content, "deflate"); + // TODO: Activate compression + linphone_event_notify(ev, content); + linphone_content_unref(content); +} + // ============================================================================= LocalConferenceEventHandler::LocalConferenceEventHandler (LocalConference *localConference, unsigned int notify) : @@ -374,15 +404,14 @@ void LocalConferenceEventHandler::subscribeReceived (LinphoneEvent *lev, bool on const LinphoneAddress *lContactAddr = linphone_event_get_remote_contact(lev); char *contactAddrStr = linphone_address_as_string(lContactAddr); - Address contactAddr(contactAddrStr); + IdentityAddress contactAddr(contactAddrStr); bctbx_free(contactAddrStr); - if (contactAddr.getUriParamValue("gr").empty()) { + shared_ptr device = participant->getPrivate()->findDevice(contactAddr); + if (!device) { lError() << "received SUBSCRIBE for conference: " << d->conf->getConferenceAddress().asString() - << "has no GRUU in it's contact address:" << contactAddr.asString() << ", no NOTIFY sent."; + << "device sending subscribe: " << contactAddr.asString() << " is not known, no NOTIFY sent."; return; } - IdentityAddress gruu(contactAddr); - shared_ptr device = participant->getPrivate()->addDevice(gruu); if (linphone_event_get_subscription_state(lev) == LinphoneSubscriptionActive) { unsigned int lastNotify = static_cast(Utils::stoi(linphone_event_get_custom_header(lev, "Last-Notify-Version"))); @@ -434,7 +463,7 @@ shared_ptr LocalConferenceEventHandler::notifyPartic shared_ptr LocalConferenceEventHandler::notifyParticipantSetAdmin (const Address &addr, bool isAdmin) { L_D(); - d->notifyAll(d->createNotifyParticipantAdmined(addr, isAdmin)); + d->notifyAll(d->createNotifyParticipantAdminStatusChanged(addr, isAdmin)); shared_ptr event = make_shared( isAdmin ? EventLog::Type::ConferenceParticipantSetAdmin : EventLog::Type::ConferenceParticipantUnsetAdmin, time(nullptr), diff --git a/src/conference/handlers/remote-conference-event-handler.cpp b/src/conference/handlers/remote-conference-event-handler.cpp index 6df681ccf..090c81ff6 100644 --- a/src/conference/handlers/remote-conference-event-handler.cpp +++ b/src/conference/handlers/remote-conference-event-handler.cpp @@ -19,6 +19,7 @@ #include +#include "linphone/utils/algorithm.h" #include "linphone/utils/utils.h" #include "conference/remote-conference.h" @@ -45,135 +46,150 @@ using namespace Xsd::ConferenceInfo; void RemoteConferenceEventHandlerPrivate::simpleNotifyReceived (const string &xmlBody) { istringstream data(xmlBody); unique_ptr confInfo = parseConferenceInfo(data, Xsd::XmlSchema::Flags::dont_validate); - time_t tm = time(nullptr); - if (confInfo->getConferenceDescription()->getFreeText().present()) - tm = static_cast(Utils::stoll(confInfo->getConferenceDescription()->getFreeText().get())); - - bool isFullState = (confInfo->getState() == StateType::full); - - ConferenceListener *confListener = static_cast(conf); IdentityAddress entityAddress(confInfo->getEntity().c_str()); - if (entityAddress == chatRoomId.getPeerAddress()) { - if (confInfo->getVersion().present()) - lastNotify = confInfo->getVersion().get(); + if (entityAddress != chatRoomId.getPeerAddress()) + return; - if (confInfo->getConferenceDescription().present()) { - if (confInfo->getConferenceDescription().get().getSubject().present() && - !confInfo->getConferenceDescription().get().getSubject().get().empty() - ) { - confListener->onSubjectChanged( - make_shared( - tm, - chatRoomId, - lastNotify, - confInfo->getConferenceDescription().get().getSubject().get() - ), - isFullState - ); - } - if (confInfo->getConferenceDescription().get().getKeywords().present() - && !confInfo->getConferenceDescription().get().getKeywords().get().empty() - ) { - KeywordsType xmlKeywords = confInfo->getConferenceDescription().get().getKeywords().get(); - vector keywords; - for (const auto &k : xmlKeywords) - keywords.push_back(k); - confListener->onConferenceKeywordsChanged(keywords); - } - } + auto &confDescription = confInfo->getConferenceDescription(); - if (!confInfo->getUsers().present()) - return; - - for (const auto &user : confInfo->getUsers()->getUser()) { - LinphoneAddress *cAddr = linphone_core_interpret_url(conf->getCore()->getCCore(), user.getEntity()->c_str()); - char *cAddrStr = linphone_address_as_string(cAddr); - Address addr(cAddrStr); - bctbx_free(cAddrStr); - if (user.getState() == StateType::deleted) { - confListener->onParticipantRemoved( - make_shared( - EventLog::Type::ConferenceParticipantRemoved, - tm, - chatRoomId, - lastNotify, - addr - ), - isFullState - ); - } else { - if (user.getState() == StateType::full) { - confListener->onParticipantAdded( - make_shared( - EventLog::Type::ConferenceParticipantAdded, - tm, - chatRoomId, - lastNotify, - addr - ), - isFullState - ); - } - - if (user.getRoles()) { - bool isAdmin = false; - for (const auto &entry : user.getRoles()->getEntry()) { - if (entry == "admin") { - isAdmin = true; - break; - } - } - confListener->onParticipantSetAdmin( - make_shared( - isAdmin ? EventLog::Type::ConferenceParticipantSetAdmin : EventLog::Type::ConferenceParticipantUnsetAdmin, - tm, - chatRoomId, - lastNotify, - addr - ), - isFullState - ); - } - - for (const auto &endpoint : user.getEndpoint()) { - if (!endpoint.getEntity().present()) - break; - - Address gruu(endpoint.getEntity().get()); - if (endpoint.getState() == StateType::deleted) { - confListener->onParticipantDeviceRemoved( - make_shared( - EventLog::Type::ConferenceParticipantDeviceRemoved, - tm, - chatRoomId, - lastNotify, - addr, - gruu - ), - isFullState - ); - } else if (endpoint.getState() == StateType::full) { - confListener->onParticipantDeviceAdded( - make_shared( - EventLog::Type::ConferenceParticipantDeviceAdded, - tm, - chatRoomId, - lastNotify, - addr, - gruu - ), - isFullState - ); - } - } - } - linphone_address_unref(cAddr); - } - - if (isFullState) - confListener->onFirstNotifyReceived(chatRoomId.getPeerAddress()); + // 1. Compute event time. + time_t creationTime = time(nullptr); + { + auto &freeText = confDescription->getFreeText(); + if (freeText.present()) + creationTime = static_cast(Utils::stoll(freeText.get())); } + + // 2. Update last notify. + { + auto &version = confInfo->getVersion(); + if (version.present()) + lastNotify = version.get(); + } + + bool isFullState = confInfo->getState() == StateType::full; + ConferenceListener *confListener = static_cast(conf); + + // 3. Notify subject and keywords. + if (confDescription.present()) { + auto &subject = confDescription.get().getSubject(); + if (subject.present() && !subject.get().empty()) + confListener->onSubjectChanged( + make_shared( + creationTime, + chatRoomId, + lastNotify, + subject.get() + ), + isFullState + ); + + auto &keywords = confDescription.get().getKeywords(); + if (keywords.present() && !keywords.get().empty()) { + KeywordsType xmlKeywords = keywords.get(); + confListener->onConferenceKeywordsChanged( + vector(xmlKeywords.begin(), xmlKeywords.end()) + ); + } + } + + auto &users = confInfo->getUsers(); + if (!users.present()) + return; + + // 4. Notify changes on users. + for (auto &user : users->getUser()) { + LinphoneAddress *cAddr = linphone_core_interpret_url(conf->getCore()->getCCore(), user.getEntity()->c_str()); + char *cAddrStr = linphone_address_as_string(cAddr); + linphone_address_unref(cAddr); + + Address addr(cAddrStr); + bctbx_free(cAddrStr); + + StateType state = user.getState(); + + if (state == StateType::deleted) { + confListener->onParticipantRemoved( + make_shared( + EventLog::Type::ConferenceParticipantRemoved, + creationTime, + chatRoomId, + lastNotify, + addr + ), + isFullState + ); + + continue; + } + + if (state == StateType::full) + confListener->onParticipantAdded( + make_shared( + EventLog::Type::ConferenceParticipantAdded, + creationTime, + chatRoomId, + lastNotify, + addr + ), + isFullState + ); + + auto &roles = user.getRoles(); + if (roles) { + auto &entry = roles->getEntry(); + confListener->onParticipantSetAdmin( + make_shared( + find(entry, "admin") != entry.end() + ? EventLog::Type::ConferenceParticipantSetAdmin + : EventLog::Type::ConferenceParticipantUnsetAdmin, + creationTime, + chatRoomId, + lastNotify, + addr + ), + isFullState + ); + } + + for (const auto &endpoint : user.getEndpoint()) { + if (!endpoint.getEntity().present()) + break; + + Address gruu(endpoint.getEntity().get()); + StateType state = endpoint.getState(); + + if (state == StateType::deleted) { + confListener->onParticipantDeviceRemoved( + make_shared( + EventLog::Type::ConferenceParticipantDeviceRemoved, + creationTime, + chatRoomId, + lastNotify, + addr, + gruu + ), + isFullState + ); + } else if (state == StateType::full) { + confListener->onParticipantDeviceAdded( + make_shared( + EventLog::Type::ConferenceParticipantDeviceAdded, + creationTime, + chatRoomId, + lastNotify, + addr, + gruu + ), + isFullState + ); + } + } + } + + if (isFullState) + confListener->onFirstNotifyReceived(chatRoomId.getPeerAddress()); } // ----------------------------------------------------------------------------- diff --git a/src/conference/local-conference.cpp b/src/conference/local-conference.cpp index b07f17287..847400131 100644 --- a/src/conference/local-conference.cpp +++ b/src/conference/local-conference.cpp @@ -18,9 +18,11 @@ */ #include "content/content.h" +#include "content/content-disposition.h" #include "content/content-type.h" #include "handlers/local-conference-event-handler.h" #include "local-conference-p.h" +#include "logger/logger.h" #include "participant-p.h" #include "xml/resource-lists.h" @@ -46,8 +48,10 @@ LocalConference::~LocalConference () { void LocalConference::addParticipant (const IdentityAddress &addr, const CallSessionParams *params, bool hasMedia) { L_D(); shared_ptr participant = findParticipant(addr); - if (participant) + if (participant) { + lInfo() << "Not adding participant '" << addr.asString() << "' because it is already a participant of the LocalConference"; return; + } participant = make_shared(addr); participant->getPrivate()->createSession(*this, params, hasMedia, d->listener); d->participants.push_back(participant); @@ -67,7 +71,7 @@ void LocalConference::removeParticipant (const shared_ptr &pa list LocalConference::parseResourceLists (const Content &content) { if ((content.getContentType() == ContentType::ResourceLists) - && (content.getContentDisposition() == "recipient-list") + && (content.getContentDisposition() == ContentDisposition::RecipientList) ) { istringstream data(content.getBodyAsString()); unique_ptr rl(Xsd::ResourceLists::parseResourceLists( diff --git a/src/conference/params/call-session-params-p.h b/src/conference/params/call-session-params-p.h index 77b701291..74a7c0072 100644 --- a/src/conference/params/call-session-params-p.h +++ b/src/conference/params/call-session-params-p.h @@ -34,8 +34,6 @@ class CallSession; class CallSessionParamsPrivate : public ClonableObjectPrivate { public: - CallSessionParamsPrivate () = default; - void clone (const CallSessionParamsPrivate *src); bool getInConference () const { return inConference; } diff --git a/src/conference/params/call-session-params.cpp b/src/conference/params/call-session-params.cpp index 2b65ed6a7..4b8a11912 100644 --- a/src/conference/params/call-session-params.cpp +++ b/src/conference/params/call-session-params.cpp @@ -65,10 +65,10 @@ CallSessionParams::CallSessionParams () : ClonableObject(*new CallSessionParamsP CallSessionParams::CallSessionParams (CallSessionParamsPrivate &p) : ClonableObject(p) {} -CallSessionParams::CallSessionParams (const CallSessionParams &src) +CallSessionParams::CallSessionParams (const CallSessionParams &other) : ClonableObject(*new CallSessionParamsPrivate) { L_D(); - d->clone(src.getPrivate()); + d->clone(other.getPrivate()); } CallSessionParams::~CallSessionParams () { @@ -77,10 +77,10 @@ CallSessionParams::~CallSessionParams () { sal_custom_header_free(d->customHeaders); } -CallSessionParams &CallSessionParams::operator= (const CallSessionParams &src) { +CallSessionParams &CallSessionParams::operator= (const CallSessionParams &other) { L_D(); - if (this != &src) - d->clone(src.getPrivate()); + if (this != &other) + d->clone(other.getPrivate()); return *this; } diff --git a/src/conference/params/call-session-params.h b/src/conference/params/call-session-params.h index fd6fb569d..687b77b17 100644 --- a/src/conference/params/call-session-params.h +++ b/src/conference/params/call-session-params.h @@ -40,10 +40,10 @@ class CallSessionParams : public ClonableObject { public: CallSessionParams (); - CallSessionParams (const CallSessionParams &src); + CallSessionParams (const CallSessionParams &other); virtual ~CallSessionParams (); - CallSessionParams &operator= (const CallSessionParams &src); + CallSessionParams &operator= (const CallSessionParams &other); virtual void initDefault (const std::shared_ptr &core); diff --git a/src/conference/params/media-session-params.cpp b/src/conference/params/media-session-params.cpp index 892210de4..680e8385b 100644 --- a/src/conference/params/media-session-params.cpp +++ b/src/conference/params/media-session-params.cpp @@ -203,11 +203,11 @@ MediaSessionParams::MediaSessionParams () : CallSessionParams(*new MediaSessionP memset(d->customSdpMediaAttributes, 0, sizeof(d->customSdpMediaAttributes)); } -MediaSessionParams::MediaSessionParams (const MediaSessionParams &src) +MediaSessionParams::MediaSessionParams (const MediaSessionParams &other) : CallSessionParams(*new MediaSessionParamsPrivate) { L_D(); memset(d->customSdpMediaAttributes, 0, sizeof(d->customSdpMediaAttributes)); - d->clone(src.getPrivate()); + d->clone(other.getPrivate()); } MediaSessionParams::~MediaSessionParams () { @@ -215,10 +215,10 @@ MediaSessionParams::~MediaSessionParams () { d->clean(); } -MediaSessionParams &MediaSessionParams::operator= (const MediaSessionParams &src) { +MediaSessionParams &MediaSessionParams::operator= (const MediaSessionParams &other) { L_D(); - if (this != &src) - d->clone(src.getPrivate()); + if (this != &other) + d->clone(other.getPrivate()); return *this; } diff --git a/src/conference/params/media-session-params.h b/src/conference/params/media-session-params.h index 3f1df29df..80ceff38f 100644 --- a/src/conference/params/media-session-params.h +++ b/src/conference/params/media-session-params.h @@ -38,10 +38,10 @@ class MediaSessionParams : public CallSessionParams { public: MediaSessionParams (); - MediaSessionParams (const MediaSessionParams &src); + MediaSessionParams (const MediaSessionParams &other); virtual ~MediaSessionParams (); - MediaSessionParams &operator= (const MediaSessionParams &src); + MediaSessionParams &operator= (const MediaSessionParams &other); void initDefault (const std::shared_ptr &core) override; diff --git a/src/conference/participant-device.cpp b/src/conference/participant-device.cpp index ea8b1c7a8..4c147a38d 100644 --- a/src/conference/participant-device.cpp +++ b/src/conference/participant-device.cpp @@ -29,9 +29,8 @@ LINPHONE_BEGIN_NAMESPACE ParticipantDevice::ParticipantDevice () {} -ParticipantDevice::ParticipantDevice (const IdentityAddress &gruu) { - mGruu = gruu; -} +ParticipantDevice::ParticipantDevice (const Participant *participant, const IdentityAddress &gruu) + : mParticipant(participant), mGruu(gruu) {} ParticipantDevice::~ParticipantDevice () { if (mConferenceSubscribeEvent) @@ -48,4 +47,18 @@ void ParticipantDevice::setConferenceSubscribeEvent (LinphoneEvent *ev) { mConferenceSubscribeEvent = linphone_event_ref(ev); } +ostream &operator<< (ostream &stream, ParticipantDevice::State state) { + switch (state) { + case ParticipantDevice::State::Joining: + return stream << "Joining"; + case ParticipantDevice::State::Present: + return stream << "Present"; + case ParticipantDevice::State::Leaving: + return stream << "Leaving"; + case ParticipantDevice::State::Left: + return stream << "Left"; + } + return stream; +} + LINPHONE_END_NAMESPACE diff --git a/src/conference/participant-device.h b/src/conference/participant-device.h index c4ee94e62..e10da6394 100644 --- a/src/conference/participant-device.h +++ b/src/conference/participant-device.h @@ -23,6 +23,7 @@ #include #include "address/identity-address.h" + #include "linphone/types.h" #include "linphone/utils/general.h" @@ -31,18 +32,29 @@ LINPHONE_BEGIN_NAMESPACE class CallSession; +class Participant; class ParticipantDevice { public: + enum class State { + Joining, + Present, + Leaving, + Left + }; + ParticipantDevice (); - explicit ParticipantDevice (const IdentityAddress &gruu); + explicit ParticipantDevice (const Participant *participant, const IdentityAddress &gruu); virtual ~ParticipantDevice (); bool operator== (const ParticipantDevice &device) const; inline const IdentityAddress &getAddress () const { return mGruu; } + const Participant *getParticipant () const { return mParticipant; } inline std::shared_ptr getSession () const { return mSession; } inline void setSession (std::shared_ptr session) { mSession = session; } + inline State getState () const { return mState; } + inline void setState (State newState) { mState = newState; } inline bool isSubscribedToConferenceEventPackage () const { return mConferenceSubscribeEvent != nullptr; } LinphoneEvent *getConferenceSubscribeEvent () const { return mConferenceSubscribeEvent; } @@ -51,13 +63,17 @@ public: bool isValid () const { return mGruu.isValid(); } private: + const Participant *mParticipant = nullptr; IdentityAddress mGruu; std::shared_ptr mSession; LinphoneEvent *mConferenceSubscribeEvent = nullptr; + State mState = State::Joining; L_DISABLE_COPY(ParticipantDevice); }; +std::ostream &operator<< (std::ostream &stream, ParticipantDevice::State state); + LINPHONE_END_NAMESPACE #endif // ifndef _L_PARTICIPANT_DEVICE_H_ diff --git a/src/conference/participant.cpp b/src/conference/participant.cpp index f67694d4e..68d6e26f7 100644 --- a/src/conference/participant.cpp +++ b/src/conference/participant.cpp @@ -47,10 +47,11 @@ shared_ptr ParticipantPrivate::createSession ( // ----------------------------------------------------------------------------- shared_ptr ParticipantPrivate::addDevice (const IdentityAddress &gruu) { + L_Q(); shared_ptr device = findDevice(gruu); if (device) return device; - device = make_shared(gruu); + device = make_shared(q, gruu); devices.push_back(device); return device; } diff --git a/src/conference/remote-conference.cpp b/src/conference/remote-conference.cpp index dd2a8a67d..ffdcde9ab 100644 --- a/src/conference/remote-conference.cpp +++ b/src/conference/remote-conference.cpp @@ -18,6 +18,7 @@ */ #include "handlers/remote-conference-event-handler.h" +#include "logger/logger.h" #include "participant-p.h" #include "remote-conference-p.h" #include "xml/resource-lists.h" @@ -47,8 +48,10 @@ RemoteConference::~RemoteConference () { void RemoteConference::addParticipant (const IdentityAddress &addr, const CallSessionParams *params, bool hasMedia) { L_D(); shared_ptr participant = findParticipant(addr); - if (participant) + if (participant) { + lInfo() << "Not adding participant '" << addr.asString() << "' because it is already a participant of the RemoteConference"; return; + } participant = make_shared(addr); participant->getPrivate()->createSession(*this, params, hasMedia, d->listener); d->participants.push_back(participant); diff --git a/src/conference/remote-conference.h b/src/conference/remote-conference.h index 95f1bee8f..1dd2b5931 100644 --- a/src/conference/remote-conference.h +++ b/src/conference/remote-conference.h @@ -20,7 +20,6 @@ #ifndef _L_REMOTE_CONFERENCE_H_ #define _L_REMOTE_CONFERENCE_H_ -#include "conference-listener.h" #include "conference.h" #include "core/core-accessor.h" @@ -30,7 +29,7 @@ LINPHONE_BEGIN_NAMESPACE class RemoteConferencePrivate; -class LINPHONE_PUBLIC RemoteConference : public Conference, public ConferenceListener { +class LINPHONE_PUBLIC RemoteConference : public Conference { friend class ClientGroupChatRoomPrivate; public: diff --git a/src/conference/session/call-session-listener.h b/src/conference/session/call-session-listener.h index f14d9249c..edc634f22 100644 --- a/src/conference/session/call-session-listener.h +++ b/src/conference/session/call-session-listener.h @@ -22,6 +22,8 @@ #include "conference/session/call-session.h" +#include + // ============================================================================= LINPHONE_BEGIN_NAMESPACE @@ -32,49 +34,52 @@ class LINPHONE_PUBLIC CallSessionListener { public: virtual ~CallSessionListener() = default; - virtual void onAckBeingSent (const std::shared_ptr &session, LinphoneHeaders *headers) {} - virtual void onAckReceived (const std::shared_ptr &session, LinphoneHeaders *headers) {} - virtual void onBackgroundTaskToBeStarted (const std::shared_ptr &session) {} - virtual void onBackgroundTaskToBeStopped (const std::shared_ptr &session) {} - virtual bool onCallSessionAccepted (const std::shared_ptr &session) { return false; } - virtual void onCallSessionConferenceStreamStarting (const std::shared_ptr &session, bool mute) {} - virtual void onCallSessionConferenceStreamStopping (const std::shared_ptr &session) {} - virtual void onCallSessionEarlyFailed (const std::shared_ptr &session, LinphoneErrorInfo *ei) {} - virtual void onCallSessionSetReleased (const std::shared_ptr &session) {} - virtual void onCallSessionSetTerminated (const std::shared_ptr &session) {} - virtual void onCallSessionStartReferred (const std::shared_ptr &session) {} - virtual void onCallSessionStateChanged (const std::shared_ptr &session, CallSession::State state, const std::string &message) {} - virtual void onCallSessionTransferStateChanged (const std::shared_ptr &session, CallSession::State state) {} - virtual void onCheckForAcceptation (const std::shared_ptr &session) {} - virtual void onDtmfReceived (const std::shared_ptr &session, char dtmf) {} - virtual void onIncomingCallSessionNotified (const std::shared_ptr &session) {} - virtual void onIncomingCallSessionStarted (const std::shared_ptr &session) {} - virtual void onIncomingCallSessionTimeoutCheck (const std::shared_ptr &session, int elapsed, bool oneSecondElapsed) {} - virtual void onInfoReceived (const std::shared_ptr &session, const LinphoneInfoMessage *im) {} - virtual void onNoMediaTimeoutCheck (const std::shared_ptr &session, bool oneSecondElapsed) {} + virtual void onAckBeingSent (const std::shared_ptr &session, LinphoneHeaders *headers) {} + virtual void onAckReceived (const std::shared_ptr &session, LinphoneHeaders *headers) {} + virtual void onBackgroundTaskToBeStarted (const std::shared_ptr &session) {} + virtual void onBackgroundTaskToBeStopped (const std::shared_ptr &session) {} + virtual bool onCallSessionAccepted (const std::shared_ptr &session) { return false; } + virtual void onCallSessionConferenceStreamStarting (const std::shared_ptr &session, bool mute) {} + virtual void onCallSessionConferenceStreamStopping (const std::shared_ptr &session) {} + virtual void onCallSessionEarlyFailed (const std::shared_ptr &session, LinphoneErrorInfo *ei) {} + virtual void onCallSessionSetReleased (const std::shared_ptr &session) {} + virtual void onCallSessionSetTerminated (const std::shared_ptr &session) {} + virtual void onCallSessionStartReferred (const std::shared_ptr &session) {} + virtual void onCallSessionStateChanged (const std::shared_ptr &session, CallSession::State state, const std::string &message) {} + virtual void onCallSessionTransferStateChanged (const std::shared_ptr &session, CallSession::State state) {} + virtual void onCheckForAcceptation (const std::shared_ptr &session) {} + virtual void onDtmfReceived (const std::shared_ptr &session, char dtmf) {} + virtual void onIncomingCallSessionNotified (const std::shared_ptr &session) {} + virtual void onIncomingCallSessionStarted (const std::shared_ptr &session) {} + virtual void onIncomingCallSessionTimeoutCheck (const std::shared_ptr &session, int elapsed, bool oneSecondElapsed) {} + virtual void onInfoReceived (const std::shared_ptr &session, const LinphoneInfoMessage *im) {} + virtual void onNoMediaTimeoutCheck (const std::shared_ptr &session, bool oneSecondElapsed) {} + virtual void onTmmbrReceived (const std::shared_ptr &session, int streamIndex, int tmmbr) {} - virtual void onEncryptionChanged (const std::shared_ptr &session, bool activated, const std::string &authToken) {} + virtual void onEncryptionChanged (const std::shared_ptr &session, bool activated, const std::string &authToken) {} - virtual void onCallSessionStateChangedForReporting (const std::shared_ptr &session) {} - virtual void onRtcpUpdateForReporting (const std::shared_ptr &session, SalStreamType type) {} - virtual void onStatsUpdated (const std::shared_ptr &session, const LinphoneCallStats *stats) {} - virtual void onUpdateMediaInfoForReporting (const std::shared_ptr &session, int statsType) {} + virtual void onCallSessionStateChangedForReporting (const std::shared_ptr &session) {} + virtual void onRtcpUpdateForReporting (const std::shared_ptr &session, SalStreamType type) {} + virtual void onStatsUpdated (const std::shared_ptr &session, const LinphoneCallStats *stats) {} + virtual void onUpdateMediaInfoForReporting (const std::shared_ptr &session, int statsType) {} - virtual void onResetCurrentSession (const std::shared_ptr &session) {} - virtual void onSetCurrentSession (const std::shared_ptr &session) {} + virtual void onResetCurrentSession (const std::shared_ptr &session) {} + virtual void onSetCurrentSession (const std::shared_ptr &session) {} - virtual void onFirstVideoFrameDecoded (const std::shared_ptr &session) {} - virtual void onResetFirstVideoFrameDecoded (const std::shared_ptr &session) {} + virtual void onFirstVideoFrameDecoded (const std::shared_ptr &session) {} + virtual void onResetFirstVideoFrameDecoded (const std::shared_ptr &session) {} - virtual void onPlayErrorTone (const std::shared_ptr &session, LinphoneReason reason) {} - virtual void onRingbackToneRequested (const std::shared_ptr &session, bool requested) {} - virtual void onStartRinging (const std::shared_ptr &session) {} - virtual void onStopRinging (const std::shared_ptr &session) {} - virtual void onStopRingingIfInCall (const std::shared_ptr &session) {} - virtual void onStopRingingIfNeeded (const std::shared_ptr &session) {} + virtual void onPlayErrorTone (const std::shared_ptr &session, LinphoneReason reason) {} + virtual void onRingbackToneRequested (const std::shared_ptr &session, bool requested) {} + virtual void onStartRinging (const std::shared_ptr &session) {} + virtual void onStopRinging (const std::shared_ptr &session) {} + virtual void onStopRingingIfInCall (const std::shared_ptr &session) {} + virtual void onStopRingingIfNeeded (const std::shared_ptr &session) {} - virtual bool areSoundResourcesAvailable (const std::shared_ptr &session) { return true; } - virtual bool isPlayingRingbackTone (const std::shared_ptr &session) { return false; } + virtual bool areSoundResourcesAvailable (const std::shared_ptr &session) { return true; } + virtual bool isPlayingRingbackTone (const std::shared_ptr &session) { return false; } + + virtual void onRealTimeTextCharacterReceived (const std::shared_ptr &session, RealtimeTextReceivedCharacter *data) {} }; LINPHONE_END_NAMESPACE diff --git a/src/conference/session/call-session-p.h b/src/conference/session/call-session-p.h index 091fd20ca..d8dbed101 100644 --- a/src/conference/session/call-session-p.h +++ b/src/conference/session/call-session-p.h @@ -31,8 +31,6 @@ LINPHONE_BEGIN_NAMESPACE class CallSessionPrivate : public ObjectPrivate, public CoreListener { public: - CallSessionPrivate () = default; - int computeDuration () const; virtual void initializeParamsAccordingToIncomingCallParams (); void notifyReferState (); @@ -75,7 +73,7 @@ protected: void accept (const CallSessionParams *params); virtual LinphoneStatus acceptUpdate (const CallSessionParams *csp, CallSession::State nextState, const std::string &stateInfo); - LinphoneStatus checkForAcceptation () const; + LinphoneStatus checkForAcceptation (); virtual void handleIncomingReceivedStateInIncomingNotification (); virtual bool isReadyForInvite () const; bool isUpdateAllowed (CallSession::State &nextState) const; @@ -136,8 +134,10 @@ protected: bool broken = false; bool deferIncomingNotification = false; bool deferUpdate = false; + bool deferUpdateInternal = false; bool needLocalIpRefresh = false; bool nonOpError = false; /* Set when the LinphoneErrorInfo was set at higher level than sal */ + bool notifyRinging = true; bool referPending = false; bool reinviteOnCancelResponseRequested = false; diff --git a/src/conference/session/call-session.cpp b/src/conference/session/call-session.cpp index 97aa06f6f..a11a4ed1d 100644 --- a/src/conference/session/call-session.cpp +++ b/src/conference/session/call-session.cpp @@ -62,6 +62,8 @@ void CallSessionPrivate::notifyReferState () { void CallSessionPrivate::setState (CallSession::State newState, const string &message) { L_Q(); + // Keep a ref on the CallSession, otherwise it might get destroyed before the end of the method + shared_ptr ref = q->getSharedFromThis(); if (state != newState){ prevState = state; @@ -86,12 +88,6 @@ void CallSessionPrivate::setState (CallSession::State newState, const string &me } switch (newState) { - case CallSession::State::OutgoingInit: - case CallSession::State::IncomingReceived: - getPlatformHelpers(q->getCore()->getCCore())->acquireWifiLock(); - getPlatformHelpers(q->getCore()->getCCore())->acquireMcastLock(); - getPlatformHelpers(q->getCore()->getCCore())->acquireCpuLock(); - break; case CallSession::State::End: case CallSession::State::Error: switch (linphone_error_info_get_reason(q->getErrorInfo())) { @@ -129,11 +125,6 @@ void CallSessionPrivate::setState (CallSession::State newState, const string &me log->status = LinphoneCallSuccess; log->connected_date_time = ms_time(nullptr); break; - case CallSession::State::Released: - getPlatformHelpers(q->getCore()->getCCore())->acquireWifiLock(); - getPlatformHelpers(q->getCore()->getCCore())->acquireMcastLock(); - getPlatformHelpers(q->getCore()->getCCore())->acquireCpuLock(); - break; default: break; } @@ -476,15 +467,16 @@ void CallSessionPrivate::updated (bool isUpdate) { void CallSessionPrivate::updatedByRemote () { L_Q(); setState(CallSession::State::UpdatedByRemote,"Call updated by remote"); - if (deferUpdate) { - if (state == CallSession::State::UpdatedByRemote) - lInfo() << "CallSession [" << q << "]: UpdatedByRemoted was signaled but defered. LinphoneCore expects the application to call linphone_core_accept_call_update() later."; + if (deferUpdate || deferUpdateInternal) { + if (state == CallSession::State::UpdatedByRemote && !deferUpdateInternal){ + lInfo() << "CallSession [" << q << "]: UpdatedByRemoted was signaled but defered. LinphoneCore expects the application to call linphone_call_accept_update() later"; + } } else { if (state == CallSession::State::UpdatedByRemote) q->acceptUpdate(nullptr); else { - /* Otherwise it means that the app responded by linphone_core_accept_call_update - * within the callback, so job is already done. */ + // Otherwise it means that the app responded by CallSession::acceptUpdate() within the callback, + // so job is already done } } } @@ -521,7 +513,7 @@ LinphoneStatus CallSessionPrivate::acceptUpdate (const CallSessionParams *csp, C return startAcceptUpdate(nextState, stateInfo); } -LinphoneStatus CallSessionPrivate::checkForAcceptation () const { +LinphoneStatus CallSessionPrivate::checkForAcceptation () { L_Q(); switch (state) { case CallSession::State::IncomingReceived: @@ -550,7 +542,8 @@ void CallSessionPrivate::handleIncomingReceivedStateInIncomingNotification () { L_Q(); /* Try to be best-effort in giving real local or routable contact address for 100Rel case */ setContactOp(); - op->notify_ringing(false); + if (notifyRinging) + op->notify_ringing(false); if (op->get_replaces() && lp_config_get_int(linphone_core_get_config(q->getCore()->getCCore()), "sip", "auto_answer_replacing_calls", 1)) q->accept(); } @@ -828,8 +821,9 @@ void CallSessionPrivate::repairIfBroken () { case CallSession::State::Pausing: if (op->dialog_request_pending()) { // Need to cancel first re-INVITE as described in section 5.5 of RFC 6141 - op->cancel_invite(); - reinviteOnCancelResponseRequested = true; + if (op->cancel_invite() == 0){ + reinviteOnCancelResponseRequested = true; + } } break; case CallSession::State::StreamsRunning: @@ -847,8 +841,9 @@ void CallSessionPrivate::repairIfBroken () { break; case CallSession::State::OutgoingInit: case CallSession::State::OutgoingProgress: - op->cancel_invite(); - reinviteOnCancelResponseRequested = true; + if (op->cancel_invite() == 0){ + reinviteOnCancelResponseRequested = true; + } break; case CallSession::State::OutgoingEarlyMedia: case CallSession::State::OutgoingRinging: @@ -1066,8 +1061,9 @@ LinphoneStatus CallSession::redirect (const Address &redirectAddr) { return 0; } -void CallSession::startIncomingNotification () { +void CallSession::startIncomingNotification (bool notifyRinging) { L_D(); + d->notifyRinging = notifyRinging; if (d->listener) { d->listener->onIncomingCallSessionNotified(getSharedFromThis()); d->listener->onBackgroundTaskToBeStarted(getSharedFromThis()); @@ -1226,6 +1222,12 @@ const LinphoneErrorInfo * CallSession::getErrorInfo () const { return d->ei; } +const Address& CallSession::getLocalAddress () const { + L_D(); + return *L_GET_CPP_PTR_FROM_C_OBJECT((d->direction == LinphoneCallIncoming) + ? linphone_call_log_get_to(d->log) : linphone_call_log_get_from(d->log)); +} + LinphoneCallLog * CallSession::getLog () const { L_D(); return d->log; @@ -1251,10 +1253,6 @@ const Address& CallSession::getRemoteAddress () const { ? linphone_call_log_get_from(d->log) : linphone_call_log_get_to(d->log)); } -string CallSession::getRemoteAddressAsString () const { - return getRemoteAddress().asString(); -} - string CallSession::getRemoteContact () const { L_D(); if (d->op) { @@ -1295,6 +1293,11 @@ CallSession::State CallSession::getState () const { return d->state; } +CallSession::State CallSession::getPreviousState () const { + L_D(); + return d->prevState; +} + const Address& CallSession::getToAddress () const { L_D(); d->toAddress = Address(d->op->get_to()); diff --git a/src/conference/session/call-session.h b/src/conference/session/call-session.h index cddbfbaa4..63330f3d0 100644 --- a/src/conference/session/call-session.h +++ b/src/conference/session/call-session.h @@ -54,10 +54,10 @@ public: L_DECLARE_ENUM(State, L_ENUM_VALUES_CALL_SESSION_STATE); CallSession (const std::shared_ptr &core, const CallSessionParams *params, CallSessionListener *listener); - virtual ~CallSession (); + ~CallSession (); LinphoneStatus accept (const CallSessionParams *csp = nullptr); - LinphoneStatus acceptUpdate (const CallSessionParams *csp); + LinphoneStatus acceptUpdate (const CallSessionParams *csp = nullptr); virtual void configure (LinphoneCallDir direction, LinphoneProxyConfig *cfg, SalCallOp *op, const Address &from, const Address &to); LinphoneStatus decline (LinphoneReason reason); LinphoneStatus decline (const LinphoneErrorInfo *ei); @@ -69,7 +69,7 @@ public: virtual void iterate (time_t currentRealTime, bool oneSecondElapsed); LinphoneStatus redirect (const std::string &redirectUri); LinphoneStatus redirect (const Address &redirectAddr); - virtual void startIncomingNotification (); + virtual void startIncomingNotification (bool notifyRinging = true); virtual int startInvite (const Address *destination, const std::string &subject = "", const Content *content = nullptr); LinphoneStatus terminate (const LinphoneErrorInfo *ei = nullptr); LinphoneStatus transfer (const std::shared_ptr &dest); @@ -78,23 +78,23 @@ public: CallSessionParams *getCurrentParams () const; LinphoneCallDir getDirection () const; - const Address& getDiversionAddress () const; + const Address &getDiversionAddress () const; int getDuration () const; const LinphoneErrorInfo * getErrorInfo () const; - LinphoneCallLog * getLog () const; + const Address &getLocalAddress () const; + LinphoneCallLog *getLog () const; virtual const CallSessionParams *getParams () const; LinphoneReason getReason () const; std::shared_ptr getReferer () const; std::string getReferTo () const; - const Address& getRemoteAddress () const; - std::string getRemoteAddressAsString () const; + const Address &getRemoteAddress () const; std::string getRemoteContact () const; const Address *getRemoteContactAddress () const; const CallSessionParams *getRemoteParams (); std::string getRemoteUserAgent () const; std::shared_ptr getReplacedCallSession () const; CallSession::State getState () const; - const Address& getToAddress () const; + const Address &getToAddress () const; CallSession::State getTransferState () const; std::shared_ptr getTransferTarget () const; std::string getToHeader (const std::string &name) const; @@ -103,6 +103,7 @@ public: protected: explicit CallSession (CallSessionPrivate &p, const std::shared_ptr &core); + CallSession::State getPreviousState () const; private: L_DECLARE_PRIVATE(CallSession); diff --git a/src/conference/session/media-session-p.h b/src/conference/session/media-session-p.h index a4ba68257..7013e7b18 100644 --- a/src/conference/session/media-session-p.h +++ b/src/conference/session/media-session-p.h @@ -20,8 +20,6 @@ #ifndef _L_MEDIA_SESSION_P_H_ #define _L_MEDIA_SESSION_P_H_ -#include - #include "call-session-p.h" #include "media-session.h" @@ -36,9 +34,6 @@ LINPHONE_BEGIN_NAMESPACE class MediaSessionPrivate : public CallSessionPrivate { -public: - MediaSessionPrivate () = default; - public: static int resumeAfterFailedTransfer (void *userData, unsigned int); static bool_t startPendingRefer (void *userData); @@ -79,7 +74,7 @@ public: void setParams (MediaSessionParams *msp); void setRemoteParams (MediaSessionParams *msp); - IceSession *getIceSession () const { return iceAgent->getIceSession(); } + IceSession *getIceSession () const { return iceAgent ? iceAgent->getIceSession() : nullptr; } SalMediaDescription *getLocalDesc () const { return localDesc; } @@ -135,7 +130,7 @@ private: void updateRemoteSessionIdAndVer (); void initStats (LinphoneCallStats *stats, LinphoneStreamType type); - void notifyStatsUpdated (int streamIndex) const; + void notifyStatsUpdated (int streamIndex); OrtpEvQueue *getEventQueue (int streamIndex) const; MediaStream *getMediaStream (int streamIndex) const; @@ -259,7 +254,6 @@ private: int sendDtmf (); void stunAuthRequestedCb (const char *realm, const char *nonce, const char **username, const char **password, const char **ha1); - private: static const std::string ecStateStore; static const int ecStateMaxLen; @@ -289,8 +283,8 @@ private: int mainTextStreamIndex = LINPHONE_CALL_STATS_TEXT; LinphoneNatPolicy *natPolicy = nullptr; - StunClient *stunClient = nullptr; - IceAgent *iceAgent = nullptr; + std::unique_ptr stunClient; + std::unique_ptr iceAgent; // The address family to prefer for RTP path, guessed from signaling path. int af; @@ -330,6 +324,7 @@ private: bool automaticallyPaused = false; bool pausedByApp = false; bool recordActive = false; + bool incomingIceReinvitePending = false; std::string onHoldFile; diff --git a/src/conference/session/media-session.cpp b/src/conference/session/media-session.cpp index 013406e9f..a124200cf 100644 --- a/src/conference/session/media-session.cpp +++ b/src/conference/session/media-session.cpp @@ -382,6 +382,8 @@ void MediaSessionPrivate::updated (bool isUpdate) { CallSessionPrivate::updated(isUpdate); } + + void MediaSessionPrivate::updating (bool isUpdate) { L_Q(); SalMediaDescription *rmd = op->get_remote_media_description(); @@ -624,7 +626,7 @@ void MediaSessionPrivate::stopStreams () { rtpIoVideoProfile = nullptr; } - linphone_core_soundcard_hint_check(q->getCore()->getCCore()); + q->getCore()->soundcardHintCheck(); } // ----------------------------------------------------------------------------- @@ -702,6 +704,10 @@ shared_ptr MediaSessionPrivate::getMe () const { void MediaSessionPrivate::setState (CallSession::State newState, const string &message) { L_Q(); + SalMediaDescription *rmd; + + lInfo()<<"MediaSessionPrivate::setState"; + /* Take a ref on the session otherwise it might get destroyed during the call to setState */ shared_ptr sessionRef = q->getSharedFromThis(); if ((newState != state) && (newState != CallSession::State::StreamsRunning)) @@ -709,6 +715,21 @@ void MediaSessionPrivate::setState (CallSession::State newState, const string &m CallSessionPrivate::setState(newState, message); if (listener) listener->onCallSessionStateChangedForReporting(q->getSharedFromThis()); + switch(newState){ + case CallSession::State::UpdatedByRemote: + /*Handle specifically the case of an incoming ICE-concluded reINVITE*/ + lInfo()<<"Checking for ICE reINVITE"; + rmd = op->get_remote_media_description(); + if (iceAgent && rmd != nullptr && iceAgent->checkIceReinviteNeedsDeferedResponse(rmd)){ + deferUpdate = true; + deferUpdateInternal = true; + incomingIceReinvitePending = true; + lInfo()<<"CallSession [" << q << "]: ICE reinvite received, but one or more check-lists are not completed. Response will be sent later, when ICE has completed"; + } + break; + default: + break; + } } // ----------------------------------------------------------------------------- @@ -933,7 +954,7 @@ void MediaSessionPrivate::initStats (LinphoneCallStats *stats, LinphoneStreamTyp _linphone_call_stats_set_ice_state(stats, LinphoneIceStateNotActivated); } -void MediaSessionPrivate::notifyStatsUpdated (int streamIndex) const { +void MediaSessionPrivate::notifyStatsUpdated (int streamIndex) { L_Q(); LinphoneCallStats *stats = nullptr; if (streamIndex == mainAudioStreamIndex) @@ -1177,7 +1198,7 @@ string MediaSessionPrivate::getPublicIpForStream (int streamIndex) { void MediaSessionPrivate::runStunTestsIfNeeded () { L_Q(); if (linphone_nat_policy_stun_enabled(natPolicy) && !(linphone_nat_policy_ice_enabled(natPolicy) || linphone_nat_policy_turn_enabled(natPolicy))) { - stunClient = new StunClient(q->getCore()); + stunClient = makeUnique(q->getCore()); int ret = stunClient->run(mediaPorts[mainAudioStreamIndex].rtpPort, mediaPorts[mainVideoStreamIndex].rtpPort, mediaPorts[mainTextStreamIndex].rtpPort); if (ret >= 0) pingTime = ret; @@ -2191,11 +2212,14 @@ void MediaSessionPrivate::handleIceEvents (OrtpEvent *ev) { OrtpEventData *evd = ortp_event_get_data(ev); if (evt == ORTP_EVENT_ICE_SESSION_PROCESSING_FINISHED) { if (iceAgent->hasCompletedCheckList()) { - /* At least one ICE session has succeeded, so perform a call update */ + /* The ICE session has succeeded, so perform a call update */ if (iceAgent->isControlling() && q->getCurrentParams()->getPrivate()->getUpdateCallWhenIceCompleted()) { MediaSessionParams newParams(*getParams()); newParams.getPrivate()->setInternalCallUpdate(true); q->update(&newParams); + }else if (!iceAgent->isControlling() && incomingIceReinvitePending){ + q->acceptUpdate(nullptr); + incomingIceReinvitePending = false; } startDtlsOnAllStreams(); } @@ -2238,6 +2262,7 @@ void MediaSessionPrivate::handleIceEvents (OrtpEvent *ev) { } void MediaSessionPrivate::handleStreamEvents (int streamIndex) { + L_Q(); MediaStream *ms = (streamIndex == mainAudioStreamIndex) ? &audioStream->ms : (streamIndex == mainVideoStreamIndex ? &videoStream->ms : &textStream->ms); if (ms) { @@ -2272,14 +2297,28 @@ void MediaSessionPrivate::handleStreamEvents (int streamIndex) { stats = videoStats; else stats = textStats; + + OrtpEventType evt = ortp_event_get_type(ev); + OrtpEventData *evd = ortp_event_get_data(ev); + + /*This MUST be done before any call to "linphone_call_stats_fill" since it has ownership over evd->packet*/ + if (evt == ORTP_EVENT_RTCP_PACKET_RECEIVED) { + do { + if (evd->packet && rtcp_is_RTPFB(evd->packet)) { + if (rtcp_RTPFB_get_type(evd->packet) == RTCP_RTPFB_TMMBR) { + listener->onTmmbrReceived(q->getSharedFromThis(), streamIndex, (int)rtcp_RTPFB_tmmbr_get_max_bitrate(evd->packet)); + } + } + } while (rtcp_next_packet(evd->packet)); + rtcp_rewind(evd->packet); + } + /* And yes the MediaStream must be taken at each iteration, because it may have changed due to the handling of events * in this loop*/ ms = getMediaStream(streamIndex); if (ms) linphone_call_stats_fill(stats, ms, ev); notifyStatsUpdated(streamIndex); - OrtpEventType evt = ortp_event_get_type(ev); - OrtpEventData *evd = ortp_event_get_data(ev); if (evt == ORTP_EVENT_ZRTP_ENCRYPTION_CHANGED) { if (streamIndex == mainAudioStreamIndex) audioStreamEncryptionChanged(!!evd->info.zrtp_stream_encrypted); @@ -2621,7 +2660,7 @@ void MediaSessionPrivate::startAudioStream (CallSession::State targetState, bool if (playcard) { ms_snd_card_set_stream_type(playcard, MS_SND_CARD_STREAM_VOICE); } - + media_stream_set_max_network_bitrate(&audioStream->ms, linphone_core_get_upload_bandwidth(q->getCore()->getCCore()) * 1000); bool useEc = captcard && linphone_core_echo_cancellation_enabled(q->getCore()->getCCore()); audio_stream_enable_echo_canceller(audioStream, useEc); if (playcard && (stream->max_rate > 0)) @@ -2658,7 +2697,7 @@ void MediaSessionPrivate::startAudioStream (CallSession::State targetState, bool io.output.soundcard = playcard; } else { io.output.type = MSResourceFile; - io.output.file = recfile.c_str(); + io.output.file = recfile.empty() ? nullptr : recfile.c_str(); } } else { io.input.type = io.output.type = MSResourceRtp; @@ -2672,7 +2711,7 @@ void MediaSessionPrivate::startAudioStream (CallSession::State targetState, bool io.output.soundcard = playcard; } else { io.output.type = MSResourceFile; - io.output.file = recfile.c_str(); + io.output.file = recfile.empty() ? nullptr : recfile.c_str(); } if (captcard) { io.input.type = MSResourceSoundcard; @@ -2830,7 +2869,7 @@ void MediaSessionPrivate::startTextStream () { rtp_session_set_multicast_ttl(textStream->ms.sessions.rtp_session, tstream->ttl); text_stream_start(textStream, textProfile, rtpAddr, tstream->rtp_port, rtcpAddr, (linphone_core_rtcp_enabled(q->getCore()->getCCore()) && !isMulticast) ? (tstream->rtcp_port ? tstream->rtcp_port : tstream->rtp_port + 1) : 0, usedPt); - ms_filter_add_notify_callback(textStream->rttsink, realTimeTextCharacterReceived, q, false); + ms_filter_add_notify_callback(textStream->rttsink, realTimeTextCharacterReceived, this, false); ms_media_stream_sessions_set_encryption_mandatory(&textStream->ms.sessions, isEncryptionMandatory()); } } else @@ -2860,6 +2899,7 @@ void MediaSessionPrivate::startVideoStream (CallSession::State targetState) { getCurrentParams()->getPrivate()->setUsedVideoCodec(rtp_profile_get_payload(videoProfile, usedPt)); getCurrentParams()->enableVideo(true); rtp_session_enable_rtcp_mux(videoStream->ms.sessions.rtp_session, vstream->rtcp_mux); + media_stream_set_max_network_bitrate(&videoStream->ms, linphone_core_get_upload_bandwidth(q->getCore()->getCCore()) * 1000); if (q->getCore()->getCCore()->video_conf.preview_vsize.width != 0) video_stream_set_preview_size(videoStream, q->getCore()->getCCore()->video_conf.preview_vsize); video_stream_set_fps(videoStream, linphone_core_get_preferred_framerate(q->getCore()->getCCore())); @@ -3432,7 +3472,7 @@ void MediaSessionPrivate::reportBandwidth () { lInfo() << "Bandwidth usage for CallSession [" << q << "]:\n" << fixed << setprecision(2) << "\tRTP audio=[d=" << linphone_call_stats_get_download_bandwidth(audioStats) << ",u=" << linphone_call_stats_get_upload_bandwidth(audioStats) << - "], video=[d=" << linphone_call_stats_get_download_bandwidth(videoStats) << ",u=" << linphone_call_stats_get_upload_bandwidth(videoStats) << ",ed=" << linphone_call_stats_get_estimated_download_bandwidth(videoStats) << + "], video=[d=" << linphone_call_stats_get_download_bandwidth(videoStats) << ",u=" << linphone_call_stats_get_upload_bandwidth(videoStats) << ",ed=" << linphone_call_stats_get_estimated_download_bandwidth(videoStats) << "], text=[d=" << linphone_call_stats_get_download_bandwidth(textStats) << ",u=" << linphone_call_stats_get_upload_bandwidth(textStats) << "] kbits/sec\n" << "\tRTCP audio=[d=" << linphone_call_stats_get_rtcp_download_bandwidth(audioStats) << ",u=" << linphone_call_stats_get_rtcp_upload_bandwidth(audioStats) << "], video=[d=" << linphone_call_stats_get_rtcp_download_bandwidth(videoStats) << ",u=" << linphone_call_stats_get_rtcp_upload_bandwidth(videoStats) << @@ -3806,12 +3846,11 @@ void MediaSessionPrivate::videoStreamEventCb (const MSFilter *f, const unsigned #endif void MediaSessionPrivate::realTimeTextCharacterReceived (MSFilter *f, unsigned int id, void *arg) { + L_Q(); if (id == MS_RTT_4103_RECEIVED_CHAR) { -#if 0 RealtimeTextReceivedCharacter *data = reinterpret_cast(arg); - LinphoneChatRoom * chat_room = linphone_call_get_chat_room(call); - linphone_core_real_time_text_received(call->core, chat_room, data->character, call); -#endif + if (listener) + listener->onRealTimeTextCharacterReceived(q->getSharedFromThis(), data); } } @@ -3925,7 +3964,7 @@ MediaSession::MediaSession (const shared_ptr &core, shared_ptrsetPortConfig(d->mainTextStreamIndex, make_pair(minPort, maxPort)); memset(d->sessions, 0, sizeof(d->sessions)); - d->iceAgent = new IceAgent(*this); + d->iceAgent = makeUnique(*this); lInfo() << "New MediaSession [" << this << "] initialized (LinphoneCore version: " << linphone_core_get_version() << ")"; } @@ -3943,9 +3982,6 @@ MediaSession::~MediaSession () { linphone_call_stats_unref(d->textStats); if (d->natPolicy) linphone_nat_policy_unref(d->natPolicy); - if (d->stunClient) - delete d->stunClient; - delete d->iceAgent; if (d->localDesc) sal_media_description_unref(d->localDesc); if (d->biggestDesc) @@ -4203,7 +4239,7 @@ void MediaSession::sendVfuRequest () { #endif } -void MediaSession::startIncomingNotification () { +void MediaSession::startIncomingNotification (bool notifyRinging) { L_D(); d->makeLocalMediaDescription(); d->op->set_local_media_description(d->localDesc); @@ -4219,7 +4255,7 @@ void MediaSession::startIncomingNotification () { } } - CallSession::startIncomingNotification(); + CallSession::startIncomingNotification(notifyRinging); } int MediaSession::startInvite (const Address *destination, const string &subject, const Content *content) { @@ -4255,7 +4291,7 @@ int MediaSession::startInvite (const Address *destination, const string &subject void MediaSession::startRecording () { L_D(); if (d->getParams()->getRecordFilePath().empty()) { - lError() << "MediaSession::startRecording(): no output file specified. Use linphone_call_params_set_record_file()"; + lError() << "MediaSession::startRecording(): no output file specified. Use MediaSessionParams::setRecordFilePath()"; return; } if (d->audioStream && !d->getParams()->getPrivate()->getInConference()) diff --git a/src/conference/session/media-session.h b/src/conference/session/media-session.h index 10a7f29ab..18fc76d2a 100644 --- a/src/conference/session/media-session.h +++ b/src/conference/session/media-session.h @@ -40,7 +40,7 @@ class LINPHONE_PUBLIC MediaSession : public CallSession { public: MediaSession (const std::shared_ptr &core, std::shared_ptr me, const CallSessionParams *params, CallSessionListener *listener); - virtual ~MediaSession (); + ~MediaSession (); LinphoneStatus accept (const MediaSessionParams *msp = nullptr); LinphoneStatus acceptEarlyMedia (const MediaSessionParams *msp = nullptr); @@ -56,7 +56,7 @@ public: LinphoneStatus sendDtmf (char dtmf); LinphoneStatus sendDtmfs (const std::string &dtmfs); void sendVfuRequest (); - void startIncomingNotification () override; + void startIncomingNotification (bool notifyRinging = true) override; int startInvite (const Address *destination, const std::string &subject = "", const Content *content = nullptr) override; void startRecording (); void stopRecording (); diff --git a/src/conference/session/port-config.h b/src/conference/session/port-config.h index 44da9a6bb..1bebccf13 100644 --- a/src/conference/session/port-config.h +++ b/src/conference/session/port-config.h @@ -22,6 +22,8 @@ #include +#include "linphone/utils/general.h" + // ============================================================================= LINPHONE_BEGIN_NAMESPACE diff --git a/src/containers/lru-cache.h b/src/containers/lru-cache.h new file mode 100644 index 000000000..5bab2c5f1 --- /dev/null +++ b/src/containers/lru-cache.h @@ -0,0 +1,107 @@ +/* + * lru-cache.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_LRU_CACHE_H_ +#define _L_LRU_CACHE_H_ + +#include +#include + +#include "linphone/utils/general.h" + +// ============================================================================= + +LINPHONE_BEGIN_NAMESPACE + +template +class LruCache { +public: + LruCache (int capacity = DefaultCapacity) : mCapacity(capacity) { + if (capacity < MinCapacity) + capacity = MinCapacity; + } + + int getCapacity () const { + return mCapacity; + } + + int getSize () const { + return int(mKeyToPair.size()); + } + + Value *operator[] (const Key &key) { + auto it = mKeyToPair.find(key); + return it == mKeyToPair.end() ? nullptr : &it->second.second; + } + + const Value *operator[] (const Key &key) const { + auto it = mKeyToPair.find(key); + return it == mKeyToPair.cend() ? nullptr : &it->second.second; + } + + void insert (const Key &key, const Value &value) { + auto it = mKeyToPair.find(key); + if (it != mKeyToPair.end()) + mKeys.erase(it->second.first); + else if (int(mKeyToPair.size()) == mCapacity) { + Key lastKey = mKeys.back(); + mKeys.pop_back(); + mKeyToPair.erase(lastKey); + } + + mKeys.push_front(key); + mKeyToPair.insert({ key, { mKeys.begin(), value } }); + } + + void insert (const Key &key, Value &&value) { + auto it = mKeyToPair.find(key); + if (it != mKeyToPair.end()) + mKeys.erase(it->second.first); + else if (int(mKeyToPair.size()) == mCapacity) { + Key lastKey = mKeys.back(); + mKeys.pop_back(); + mKeyToPair.erase(lastKey); + } + + mKeys.push_front(key); + mKeyToPair.insert({ key, std::make_pair(mKeys.begin(), std::move(value)) }); + } + + void clear () { + mKeyToPair.clear(); + mKeys.clear(); + } + + static constexpr int MinCapacity = 10; + static constexpr int DefaultCapacity = 1000; + +private: + using Pair = std::pair::iterator, Value>; + + const int mCapacity; + + // See: https://stackoverflow.com/questions/16781886/can-we-store-unordered-maptiterator + // Do not store iterator key. + std::list mKeys; + std::unordered_map mKeyToPair; +}; + +LINPHONE_END_NAMESPACE + +#endif // ifndef _L_LRU_CACHE_H_ diff --git a/src/content/content-disposition.cpp b/src/content/content-disposition.cpp new file mode 100644 index 000000000..ccaf7da15 --- /dev/null +++ b/src/content/content-disposition.cpp @@ -0,0 +1,107 @@ +/* + * content-disposition.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/utils/utils.h" + +#include "content-disposition.h" +#include "object/clonable-object-p.h" + +// ============================================================================= + +using namespace std; + +LINPHONE_BEGIN_NAMESPACE + +// ----------------------------------------------------------------------------- + +class ContentDispositionPrivate : public ClonableObjectPrivate { +public: + string disposition; + string parameter; +}; + +// ----------------------------------------------------------------------------- + +const ContentDisposition ContentDisposition::RecipientList("recipient-list"); +const ContentDisposition ContentDisposition::RecipientListHistory("recipient-list-history; handling=optional"); + +// ----------------------------------------------------------------------------- + +ContentDisposition::ContentDisposition (const string &disposition) : ClonableObject(*new ContentDispositionPrivate) { + L_D(); + size_t posParam = disposition.find(";"); + d->disposition = Utils::trim(disposition.substr(0, posParam)); + if (posParam != string::npos) + setParameter(Utils::trim(disposition.substr(posParam + 1))); +} + +ContentDisposition::ContentDisposition (const ContentDisposition &other) + : ContentDisposition(other.asString()) {} + +ContentDisposition &ContentDisposition::operator= (const ContentDisposition &other) { + L_D(); + if (this != &other) { + d->disposition = other.getPrivate()->disposition; + setParameter(other.getParameter()); + } + return *this; +} + +bool ContentDisposition::operator== (const ContentDisposition &other) const { + L_D(); + return d->disposition == other.getPrivate()->disposition + && getParameter() == other.getParameter(); +} + +bool ContentDisposition::operator!= (const ContentDisposition &other) const { + return !(*this == other); +} + +bool ContentDisposition::isEmpty () const { + L_D(); + return d->disposition.empty(); +} + +bool ContentDisposition::isValid () const { + L_D(); + return !d->disposition.empty(); +} + +const string &ContentDisposition::getParameter () const { + L_D(); + return d->parameter; +} + +void ContentDisposition::setParameter (const string ¶meter) { + L_D(); + d->parameter = parameter; +} + +string ContentDisposition::asString () const { + L_D(); + if (isValid()) { + string asString = d->disposition; + if (!d->parameter.empty()) + asString += ";" + d->parameter; + return asString; + } + return ""; +} + +LINPHONE_END_NAMESPACE diff --git a/src/content/content-disposition.h b/src/content/content-disposition.h new file mode 100644 index 000000000..6f2fcc9ca --- /dev/null +++ b/src/content/content-disposition.h @@ -0,0 +1,63 @@ +/* + * content-disposition.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_CONTENT_DISPOSITION_H_ +#define _L_CONTENT_DISPOSITION_H_ + +#include "object/clonable-object.h" + +// ============================================================================= + +LINPHONE_BEGIN_NAMESPACE + +class ContentDispositionPrivate; + +class LINPHONE_PUBLIC ContentDisposition : public ClonableObject { +public: + explicit ContentDisposition (const std::string &contentDisposition = ""); + ContentDisposition (const ContentDisposition &other); + + ContentDisposition &operator= (const ContentDisposition &other); + + bool operator== (const ContentDisposition &other) const; + bool operator!= (const ContentDisposition &other) const; + + // Delete these operators to prevent putting complicated content-disposition strings + // in the code. Instead define static const ContentDisposition objects below. + bool operator== (const std::string &other) const = delete; + bool operator!= (const std::string &other) const = delete; + + bool isEmpty () const; + bool isValid () const; + + const std::string &getParameter () const; + void setParameter (const std::string ¶meter); + + std::string asString () const; + + static const ContentDisposition RecipientList; + static const ContentDisposition RecipientListHistory; + +private: + L_DECLARE_PRIVATE(ContentDisposition); +}; + +LINPHONE_END_NAMESPACE + +#endif // ifndef _L_CONTENT_DISPOSITION_H_ diff --git a/src/content/content-manager.cpp b/src/content/content-manager.cpp index 0eb453329..94ee69ce9 100644 --- a/src/content/content-manager.cpp +++ b/src/content/content-manager.cpp @@ -23,44 +23,46 @@ #include "content-type.h" #include "content/content.h" -#define MULTIPART_BOUNDARY "---------------------------14737809831466499882746641449" - // ============================================================================= using namespace std; LINPHONE_BEGIN_NAMESPACE +namespace { + constexpr const char MultipartBoundary[] = "---------------------------14737809831466499882746641449"; +} + // ----------------------------------------------------------------------------- list ContentManager::multipartToContentList (const Content &content) { + const string body = content.getBodyAsString(); belle_sip_multipart_body_handler_t *mpbh = belle_sip_multipart_body_handler_new_from_buffer( - (void *)content.getBodyAsString().c_str(), - content.getBodyAsString().length(), - MULTIPART_BOUNDARY + body.c_str(), body.length(), MultipartBoundary ); belle_sip_object_ref(mpbh); list contents; for (const belle_sip_list_t *parts = belle_sip_multipart_body_handler_get_parts(mpbh); parts; parts = parts->next) { belle_sip_body_handler_t *part = BELLE_SIP_BODY_HANDLER(parts->data); - const belle_sip_list_t *part_headers = belle_sip_body_handler_get_headers(part); - belle_sip_header_content_type_t *part_content_type = nullptr; - for (belle_sip_list_t *it = (belle_sip_list_t *)part_headers; it; it = it->next) { + belle_sip_header_content_type_t *partContentType = nullptr; + for (const belle_sip_list_t *it = belle_sip_body_handler_get_headers(part); it; it = it->next) { belle_sip_header_t *header = BELLE_SIP_HEADER(it->data); if (strcasecmp("Content-Type", belle_sip_header_get_name(header)) == 0) { - part_content_type = BELLE_SIP_HEADER_CONTENT_TYPE(header); + partContentType = BELLE_SIP_HEADER_CONTENT_TYPE(header); break; } } - Content retContent; - retContent.setBody((const char *)belle_sip_memory_body_handler_get_buffer(BELLE_SIP_MEMORY_BODY_HANDLER(part))); - retContent.setContentType(ContentType( - belle_sip_header_content_type_get_type(part_content_type), - belle_sip_header_content_type_get_subtype(part_content_type) + Content content; + content.setBody(static_cast( + belle_sip_memory_body_handler_get_buffer(BELLE_SIP_MEMORY_BODY_HANDLER(part)) )); - contents.push_back(retContent); + content.setContentType(ContentType( + belle_sip_header_content_type_get_type(partContentType), + belle_sip_header_content_type_get_subtype(partContentType) + )); + contents.push_back(move(content)); } belle_sip_object_unref(mpbh); @@ -68,39 +70,49 @@ list ContentManager::multipartToContentList (const Content &content) { } Content ContentManager::contentListToMultipart (const list &contents) { - belle_sip_memory_body_handler_t *mbh = nullptr; belle_sip_multipart_body_handler_t *mpbh = belle_sip_multipart_body_handler_new( - nullptr, nullptr, nullptr, MULTIPART_BOUNDARY + nullptr, nullptr, nullptr, MultipartBoundary ); belle_sip_object_ref(mpbh); for (const auto &content : contents) { const ContentType &contentType = content.getContentType(); - stringstream subtype; - subtype << contentType.getSubType() << "; charset=\"UTF-8\""; belle_sip_header_t *cContentType = BELLE_SIP_HEADER( belle_sip_header_content_type_create( contentType.getType().c_str(), - subtype.str().c_str() + string(contentType.getSubType() + "; charset=\"UTF-8\"").c_str() ) ); - string body = content.getBodyAsString(); - mbh = belle_sip_memory_body_handler_new_copy_from_buffer( - (void *)body.c_str(), body.length(), nullptr, nullptr + const string body = content.getBodyAsString(); + belle_sip_memory_body_handler_t *mbh = belle_sip_memory_body_handler_new_copy_from_buffer( + body.c_str(), body.length(), nullptr, nullptr ); belle_sip_body_handler_add_header(BELLE_SIP_BODY_HANDLER(mbh), cContentType); + + for (const auto &header : content.getHeaders()) { + belle_sip_header_t *additionalHeader = BELLE_SIP_HEADER( + belle_sip_header_create( + header.first.c_str(), + header.second.c_str() + ) + ); + belle_sip_body_handler_add_header(BELLE_SIP_BODY_HANDLER(mbh), additionalHeader); + } + belle_sip_multipart_body_handler_add_part(mpbh, BELLE_SIP_BODY_HANDLER(mbh)); } - char *desc = belle_sip_object_to_string(mpbh); + char *desc = belle_sip_object_to_string(mpbh); Content content; content.setBody(desc); - content.setContentType(ContentType::Multipart); - belle_sip_free(desc); belle_sip_object_unref(mpbh); + ContentType contentType = ContentType::Multipart; + contentType.setParameter("boundary=" + string(MultipartBoundary)); + content.setContentType(contentType); + return content; } diff --git a/src/content/content-p.h b/src/content/content-p.h index e67dce5d6..167712313 100644 --- a/src/content/content-p.h +++ b/src/content/content-p.h @@ -20,8 +20,7 @@ #ifndef _L_CONTENT_P_H_ #define _L_CONTENT_P_H_ -#include - +#include "content-disposition.h" #include "content-type.h" #include "content.h" #include "object/clonable-object-p.h" @@ -34,7 +33,9 @@ class ContentPrivate : public ClonableObjectPrivate { private: std::vector body; ContentType contentType; - std::string contentDisposition; + ContentDisposition contentDisposition; + std::string contentEncoding; + std::list> headers; L_DECLARE_PUBLIC(Content); }; diff --git a/src/content/content-type.cpp b/src/content/content-type.cpp index 66b22e7f9..78c356af6 100644 --- a/src/content/content-type.cpp +++ b/src/content/content-type.cpp @@ -56,20 +56,20 @@ ContentType::ContentType (const string &contentType) : ClonableObject(*new Conte L_D(); size_t pos = contentType.find('/'); - size_t posParam = contentType.find("; "); + size_t posParam = contentType.find(";"); size_t end = contentType.length(); if (pos == string::npos) return; - if (setType(contentType.substr(0, pos))) { + if (setType(Utils::trim(contentType.substr(0, pos)))) { if (posParam != string::npos) end = posParam; - if (!setSubType(contentType.substr(pos + 1, end - (pos + 1)))) + if (!setSubType(Utils::trim(contentType.substr(pos + 1, end - (pos + 1))))) d->type.clear(); } if (posParam != string::npos) - setParameter(contentType.substr(posParam + 2)); // We remove the blankspace after the ;. + setParameter(Utils::trim(contentType.substr(posParam + 1))); } ContentType::ContentType (const string &type, const string &subType) : ClonableObject(*new ContentTypePrivate) { @@ -91,26 +91,26 @@ ContentType::ContentType ( setParameter(parameter); } -ContentType::ContentType (const ContentType &src) : ContentType(src.getType(), src.getSubType(), src.getParameter()) {} +ContentType::ContentType (const ContentType &other) : ContentType(other.getType(), other.getSubType(), other.getParameter()) {} -ContentType &ContentType::operator= (const ContentType &src) { - if (this != &src) { - setType(src.getType()); - setSubType(src.getSubType()); - setParameter(src.getParameter()); +ContentType &ContentType::operator= (const ContentType &other) { + if (this != &other) { + setType(other.getType()); + setSubType(other.getSubType()); + setParameter(other.getParameter()); } return *this; } -bool ContentType::operator== (const ContentType &contentType) const { - return getType() == contentType.getType() && - getSubType() == contentType.getSubType() && - getParameter() == contentType.getParameter(); +bool ContentType::operator== (const ContentType &other) const { + return getType() == other.getType() && + getSubType() == other.getSubType() && + getParameter() == other.getParameter(); } -bool ContentType::operator!= (const ContentType &contentType) const { - return !(*this == contentType); +bool ContentType::operator!= (const ContentType &other) const { + return !(*this == other); } const string &ContentType::getType () const { diff --git a/src/content/content-type.h b/src/content/content-type.h index d37168f08..b13063115 100644 --- a/src/content/content-type.h +++ b/src/content/content-type.h @@ -33,17 +33,17 @@ public: explicit ContentType (const std::string &contentType = ""); ContentType (const std::string &type, const std::string &subType); ContentType (const std::string &type, const std::string &subType, const std::string ¶meter); - ContentType (const ContentType &src); + ContentType (const ContentType &other); - ContentType &operator= (const ContentType &src); + ContentType &operator= (const ContentType &other); - bool operator== (const ContentType &contentType) const; - bool operator!= (const ContentType &contentType) const; + bool operator== (const ContentType &other) const; + bool operator!= (const ContentType &other) const; // Delete these operators to prevent putting complicated content-type strings // in the code. Instead define static const ContentType objects below. - bool operator== (const std::string &contentType) const = delete; - bool operator!= (const std::string &contentType) const = delete; + bool operator== (const std::string &other) const = delete; + bool operator!= (const std::string &other) const = delete; bool isEmpty () const; bool isValid () const; diff --git a/src/content/content.cpp b/src/content/content.cpp index 732996494..aa30503d4 100644 --- a/src/content/content.cpp +++ b/src/content/content.cpp @@ -19,6 +19,8 @@ // TODO: Remove me later. #include "linphone/core.h" + +#include "linphone/utils/algorithm.h" #include "linphone/utils/utils.h" #include "content-p.h" @@ -34,18 +36,23 @@ LINPHONE_BEGIN_NAMESPACE Content::Content () : ClonableObject(*new ContentPrivate) {} -Content::Content (const Content &src) : ClonableObject(*new ContentPrivate), AppDataContainer(src) { +Content::Content (const Content &other) : ClonableObject(*new ContentPrivate), AppDataContainer(other) { L_D(); - d->body = src.getBody(); - d->contentType = src.getContentType(); - d->contentDisposition = src.getContentDisposition(); + d->body = other.getBody(); + d->contentType = other.getContentType(); + d->contentDisposition = other.getContentDisposition(); + d->contentEncoding = other.getContentEncoding(); + d->headers = other.getHeaders(); } -Content::Content (Content &&src) : ClonableObject(*new ContentPrivate), AppDataContainer(move(src)) { +Content::Content (Content &&other) : ClonableObject(*new ContentPrivate), AppDataContainer(move(other)) { L_D(); - d->body = move(src.getPrivate()->body); - d->contentType = move(src.getPrivate()->contentType); - d->contentDisposition = move(src.getPrivate()->contentDisposition); + ContentPrivate *dOther = other.getPrivate(); + d->body = move(dOther->body); + d->contentType = move(dOther->contentType); + d->contentDisposition = move(dOther->contentDisposition); + d->contentEncoding = move(dOther->contentEncoding); + d->headers = move(dOther->headers); } Content::Content (ContentPrivate &p) : ClonableObject(p) {} @@ -59,32 +66,38 @@ Content::~Content () { d->body.assign(d->body.size(), 0); } -Content &Content::operator= (const Content &src) { +Content &Content::operator= (const Content &other) { L_D(); - if (this != &src) { - d->body = src.getBody(); - d->contentType = src.getContentType(); - d->contentDisposition = src.getContentDisposition(); - AppDataContainer::operator=(src); + if (this != &other) { + AppDataContainer::operator=(other); + d->body = other.getBody(); + d->contentType = other.getContentType(); + d->contentDisposition = other.getContentDisposition(); + d->contentEncoding = other.getContentEncoding(); + d->headers = other.getHeaders(); } - return *this; } -Content &Content::operator= (Content &&src) { +Content &Content::operator= (Content &&other) { L_D(); - d->body = move(src.getPrivate()->body); - d->contentType = move(src.getPrivate()->contentType); - d->contentDisposition = move(src.getPrivate()->contentDisposition); - AppDataContainer::operator=(move(src)); + AppDataContainer::operator=(move(other)); + ContentPrivate *dOther = other.getPrivate(); + d->body = move(dOther->body); + d->contentType = move(dOther->contentType); + d->contentDisposition = move(dOther->contentDisposition); + d->contentEncoding = move(dOther->contentEncoding); + d->headers = move(dOther->headers); return *this; } -bool Content::operator== (const Content &content) const { +bool Content::operator== (const Content &other) const { L_D(); - return d->contentType == content.getContentType() && - d->body == content.getBody() && - d->contentDisposition == content.getContentDisposition(); + return d->contentType == other.getContentType() && + d->body == other.getBody() && + d->contentDisposition == other.getContentDisposition() && + d->contentEncoding == other.getContentEncoding() && + d->headers == other.getHeaders(); } const ContentType &Content::getContentType () const { @@ -97,21 +110,26 @@ void Content::setContentType (const ContentType &contentType) { d->contentType = contentType; } -void Content::setContentType (const string &contentType) { - L_D(); - d->contentType = ContentType(contentType); -} - -const string &Content::getContentDisposition () const { +const ContentDisposition &Content::getContentDisposition () const { L_D(); return d->contentDisposition; } -void Content::setContentDisposition (const string &contentDisposition) { +void Content::setContentDisposition (const ContentDisposition &contentDisposition) { L_D(); d->contentDisposition = contentDisposition; } +const string &Content::getContentEncoding () const { + L_D(); + return d->contentEncoding; +} + +void Content::setContentEncoding (const string &contentEncoding) { + L_D(); + d->contentEncoding = contentEncoding; +} + const vector &Content::getBody () const { L_D(); return d->body; @@ -172,6 +190,31 @@ bool Content::isFile () const { return false; } +void Content::addHeader (const string &headerName, const string &headerValue) { + L_D(); + removeHeader(headerName); + d->headers.push_back(make_pair(headerName, headerValue)); +} + +const list> &Content::getHeaders () const { + L_D(); + return d->headers; +} + +void Content::removeHeader (const string &headerName) { + L_D(); + auto it = findHeader(headerName); + if (it != d->headers.cend()) + d->headers.remove(*it); +} + +list>::const_iterator Content::findHeader (const string &headerName) const { + L_D(); + return findIf(d->headers, [&headerName](const pair &pair) { + return pair.first == headerName; + }); +} + LinphoneContent *Content::toLinphoneContent () const { LinphoneContent *content = linphone_core_create_content(nullptr); linphone_content_set_type(content, getContentType().getType().c_str()); diff --git a/src/content/content.h b/src/content/content.h index 4885feadf..c570670c5 100644 --- a/src/content/content.h +++ b/src/content/content.h @@ -20,6 +20,7 @@ #ifndef _L_CONTENT_H_ #define _L_CONTENT_H_ +#include #include #include "object/app-data-container.h" @@ -31,27 +32,30 @@ L_DECL_C_STRUCT(LinphoneContent); LINPHONE_BEGIN_NAMESPACE +class ContentDisposition; class ContentType; class ContentPrivate; class LINPHONE_PUBLIC Content : public ClonableObject, public AppDataContainer { public: Content (); - Content (const Content &src); - Content (Content &&src); + Content (const Content &other); + Content (Content &&other); ~Content (); - Content &operator= (const Content &src); - Content &operator= (Content &&src); + Content &operator= (const Content &other); + Content &operator= (Content &&other); - bool operator== (const Content &content) const; + bool operator== (const Content &other) const; const ContentType &getContentType () const; void setContentType (const ContentType &contentType); - void setContentType (const std::string &contentType); - const std::string &getContentDisposition () const; - void setContentDisposition (const std::string &contentDisposition); + const ContentDisposition &getContentDisposition () const; + void setContentDisposition (const ContentDisposition &contentDisposition); + + const std::string &getContentEncoding () const; + void setContentEncoding (const std::string &contentEncoding); const std::vector &getBody () const; std::string getBodyAsString () const; @@ -71,6 +75,11 @@ public: virtual bool isFile () const; + const std::list> &getHeaders () const; + void addHeader (const std::string &headerName, const std::string &headerValue); + void removeHeader (const std::string &headerName); + std::list>::const_iterator findHeader (const std::string &headerName) const; + // TODO: Remove me later. virtual LinphoneContent *toLinphoneContent () const; diff --git a/src/content/file-content.cpp b/src/content/file-content.cpp index 09de486b6..851d935d3 100644 --- a/src/content/file-content.cpp +++ b/src/content/file-content.cpp @@ -43,52 +43,52 @@ public: FileContent::FileContent () : Content(*new FileContentPrivate) {} -FileContent::FileContent (const FileContent &src) : Content(*new FileContentPrivate) { +FileContent::FileContent (const FileContent &other) : Content(*new FileContentPrivate) { L_D(); - d->fileName = src.getFileName(); - d->filePath = src.getFilePath(); - d->fileSize = src.getFileSize(); - d->fileKey = src.getFileKey(); + d->fileName = other.getFileName(); + d->filePath = other.getFilePath(); + d->fileSize = other.getFileSize(); + d->fileKey = other.getFileKey(); } -FileContent::FileContent (FileContent &&src) : Content(*new FileContentPrivate) { +FileContent::FileContent (FileContent &&other) : Content(*new FileContentPrivate) { L_D(); - d->fileName = move(src.getPrivate()->fileName); - d->filePath = move(src.getPrivate()->filePath); - d->fileSize = move(src.getPrivate()->fileSize); - d->fileKey = move(src.getPrivate()->fileKey); + d->fileName = move(other.getPrivate()->fileName); + d->filePath = move(other.getPrivate()->filePath); + d->fileSize = move(other.getPrivate()->fileSize); + d->fileKey = move(other.getPrivate()->fileKey); } -FileContent &FileContent::operator= (const FileContent &src) { +FileContent &FileContent::operator= (const FileContent &other) { L_D(); - if (this != &src) { - Content::operator=(src); - d->fileName = src.getFileName(); - d->filePath = src.getFilePath(); - d->fileSize = src.getFileSize(); - d->fileKey = src.getFileKey(); + if (this != &other) { + Content::operator=(other); + d->fileName = other.getFileName(); + d->filePath = other.getFilePath(); + d->fileSize = other.getFileSize(); + d->fileKey = other.getFileKey(); } return *this; } -FileContent &FileContent::operator= (FileContent &&src) { +FileContent &FileContent::operator= (FileContent &&other) { L_D(); - Content::operator=(move(src)); - d->fileName = move(src.getPrivate()->fileName); - d->filePath = move(src.getPrivate()->filePath); - d->fileSize = move(src.getPrivate()->fileSize); - d->fileKey = move(src.getPrivate()->fileKey); + Content::operator=(move(other)); + d->fileName = move(other.getPrivate()->fileName); + d->filePath = move(other.getPrivate()->filePath); + d->fileSize = move(other.getPrivate()->fileSize); + d->fileKey = move(other.getPrivate()->fileKey); return *this; } -bool FileContent::operator== (const FileContent &content) const { +bool FileContent::operator== (const FileContent &other) const { L_D(); - return Content::operator==(content) && - d->fileName == content.getFileName() && - d->filePath == content.getFilePath() && - d->fileSize == content.getFileSize() && - d->fileKey == content.getFileKey(); + return Content::operator==(other) && + d->fileName == other.getFileName() && + d->filePath == other.getFilePath() && + d->fileSize == other.getFileSize() && + d->fileKey == other.getFileKey(); } void FileContent::setFileSize (size_t size) { diff --git a/src/content/file-content.h b/src/content/file-content.h index 4876370fb..645fc6312 100644 --- a/src/content/file-content.h +++ b/src/content/file-content.h @@ -31,13 +31,13 @@ class FileContentPrivate; class LINPHONE_PUBLIC FileContent : public Content { public: FileContent (); - FileContent (const FileContent &src); - FileContent (FileContent &&src); + FileContent (const FileContent &other); + FileContent (FileContent &&other); - FileContent &operator= (const FileContent &src); - FileContent &operator= (FileContent &&src); + FileContent &operator= (const FileContent &other); + FileContent &operator= (FileContent &&other); - bool operator== (const FileContent &content) const; + bool operator== (const FileContent &other) const; void setFileSize (size_t size); size_t getFileSize () const; diff --git a/src/content/file-transfer-content.cpp b/src/content/file-transfer-content.cpp index 4ce62bc19..4bc5ed691 100644 --- a/src/content/file-transfer-content.cpp +++ b/src/content/file-transfer-content.cpp @@ -44,56 +44,56 @@ public: FileTransferContent::FileTransferContent () : Content(*new FileTransferContentPrivate) {} -FileTransferContent::FileTransferContent (const FileTransferContent &src) : Content(*new FileTransferContentPrivate) { +FileTransferContent::FileTransferContent (const FileTransferContent &other) : Content(*new FileTransferContentPrivate) { L_D(); - d->fileName = src.getFileName(); - d->fileUrl = src.getFileUrl(); - d->filePath = src.getFilePath(); - d->fileContent = src.getFileContent(); - d->fileSize = src.getFileSize(); + d->fileName = other.getFileName(); + d->fileUrl = other.getFileUrl(); + d->filePath = other.getFilePath(); + d->fileContent = other.getFileContent(); + d->fileSize = other.getFileSize(); } -FileTransferContent::FileTransferContent (FileTransferContent &&src) : Content(*new FileTransferContentPrivate) { +FileTransferContent::FileTransferContent (FileTransferContent &&other) : Content(*new FileTransferContentPrivate) { L_D(); - d->fileName = move(src.getPrivate()->fileName); - d->fileUrl = move(src.getPrivate()->fileUrl); - d->filePath = move(src.getPrivate()->filePath); - d->fileContent = move(src.getPrivate()->fileContent); - d->fileSize = move(src.getPrivate()->fileSize); + d->fileName = move(other.getPrivate()->fileName); + d->fileUrl = move(other.getPrivate()->fileUrl); + d->filePath = move(other.getPrivate()->filePath); + d->fileContent = move(other.getPrivate()->fileContent); + d->fileSize = move(other.getPrivate()->fileSize); } -FileTransferContent &FileTransferContent::operator= (const FileTransferContent &src) { +FileTransferContent &FileTransferContent::operator= (const FileTransferContent &other) { L_D(); - if (this != &src) { - Content::operator=(src); - d->fileName = src.getFileName(); - d->fileUrl = src.getFileUrl(); - d->filePath = src.getFilePath(); - d->fileContent = src.getFileContent(); - d->fileSize = src.getFileSize(); + if (this != &other) { + Content::operator=(other); + d->fileName = other.getFileName(); + d->fileUrl = other.getFileUrl(); + d->filePath = other.getFilePath(); + d->fileContent = other.getFileContent(); + d->fileSize = other.getFileSize(); } return *this; } -FileTransferContent &FileTransferContent::operator= (FileTransferContent &&src) { +FileTransferContent &FileTransferContent::operator= (FileTransferContent &&other) { L_D(); - Content::operator=(move(src)); - d->fileName = move(src.getPrivate()->fileName); - d->fileUrl = move(src.getPrivate()->fileUrl); - d->filePath = move(src.getPrivate()->filePath); - d->fileContent = move(src.getPrivate()->fileContent); - d->fileSize = move(src.getPrivate()->fileSize); + Content::operator=(move(other)); + d->fileName = move(other.getPrivate()->fileName); + d->fileUrl = move(other.getPrivate()->fileUrl); + d->filePath = move(other.getPrivate()->filePath); + d->fileContent = move(other.getPrivate()->fileContent); + d->fileSize = move(other.getPrivate()->fileSize); return *this; } -bool FileTransferContent::operator== (const FileTransferContent &content) const { +bool FileTransferContent::operator== (const FileTransferContent &other) const { L_D(); - return Content::operator==(content) && - d->fileName == content.getFileName() && - d->fileUrl == content.getFileUrl() && - d->filePath == content.getFilePath() && - d->fileSize == content.getFileSize(); + return Content::operator==(other) && + d->fileName == other.getFileName() && + d->fileUrl == other.getFileUrl() && + d->filePath == other.getFilePath() && + d->fileSize == other.getFileSize(); } void FileTransferContent::setFileName (const string &name) { diff --git a/src/content/file-transfer-content.h b/src/content/file-transfer-content.h index 4b668a0ac..59b4cc125 100644 --- a/src/content/file-transfer-content.h +++ b/src/content/file-transfer-content.h @@ -32,13 +32,13 @@ class FileTransferContentPrivate; class LINPHONE_PUBLIC FileTransferContent : public Content { public: FileTransferContent (); - FileTransferContent (const FileTransferContent &src); - FileTransferContent (FileTransferContent &&src); + FileTransferContent (const FileTransferContent &other); + FileTransferContent (FileTransferContent &&other); - FileTransferContent &operator= (const FileTransferContent &src); - FileTransferContent &operator= (FileTransferContent &&src); + FileTransferContent &operator= (const FileTransferContent &other); + FileTransferContent &operator= (FileTransferContent &&other); - bool operator== (const FileTransferContent &content) const; + bool operator== (const FileTransferContent &other) const; void setFileName (const std::string &name); const std::string &getFileName () const; diff --git a/src/core/core-call.cpp b/src/core/core-call.cpp index 44c6bacdd..5bc9912a7 100644 --- a/src/core/core-call.cpp +++ b/src/core/core-call.cpp @@ -86,7 +86,9 @@ bool CorePrivate::isAlreadyInCallWithAddress (const Address &addr) const { } void CorePrivate::iterateCalls (time_t currentRealTime, bool oneSecondElapsed) const { - for (const auto &call : calls) { + // Make a copy of the list af calls because it may be altered during calls to the Call::iterate method + list> savedCalls(calls); + for (const auto &call : savedCalls) { call->getPrivate()->iterate(currentRealTime, oneSecondElapsed); } } diff --git a/src/core/core-chat-room.cpp b/src/core/core-chat-room.cpp index 511963e3b..291c2a600 100644 --- a/src/core/core-chat-room.cpp +++ b/src/core/core-chat-room.cpp @@ -23,6 +23,8 @@ #include "chat/chat-room/basic-chat-room.h" #include "chat/chat-room/basic-to-client-group-chat-room.h" #include "chat/chat-room/chat-room-p.h" +#include "chat/chat-room/client-group-chat-room-p.h" +#include "chat/chat-room/client-group-to-basic-chat-room.h" #include "chat/chat-room/real-time-text-chat-room.h" #include "conference/participant.h" #include "core-p.h" @@ -74,9 +76,15 @@ shared_ptr CorePrivate::createBasicChatRoom ( chatRoom.reset(new RealTimeTextChatRoom(q->getSharedFromThis(), chatRoomId)); else { BasicChatRoom *basicChatRoom = new BasicChatRoom(q->getSharedFromThis(), chatRoomId); + LinphoneAddress *lAddr = linphone_address_new(chatRoomId.getLocalAddress().asString().c_str()); + LinphoneProxyConfig *proxy = linphone_core_lookup_known_proxy(q->getCCore(), lAddr); + linphone_address_unref(lAddr); + const char *conferenceFactoryUri = nullptr; + if (proxy) + conferenceFactoryUri = linphone_proxy_config_get_conference_factory_uri(proxy); if ( capabilities & ChatRoom::Capabilities::Migratable && - linphone_core_get_conference_factory_uri(q->getCCore()) && + conferenceFactoryUri && linphone_config_get_bool(linphone_core_get_config(q->getCCore()), "misc", "enable_basic_to_client_group_chat_room_migration", FALSE) ) @@ -92,25 +100,75 @@ shared_ptr CorePrivate::createBasicChatRoom ( return chatRoom; } -shared_ptr CorePrivate::createClientGroupChatRoom (const string &subject, bool fallback) { +shared_ptr CorePrivate::createClientGroupChatRoom (const string &subject, const string &uri, bool fallback) { L_Q(); - return L_GET_CPP_PTR_FROM_C_OBJECT( - _linphone_client_group_chat_room_new( - q->getCCore(), - linphone_core_get_conference_factory_uri(q->getCCore()), - L_STRING_TO_C(subject), - fallback ? TRUE : FALSE - ) - ); + + string usedUri; + string from; + { + LinphoneProxyConfig *proxy = nullptr; + if (uri.empty()) { + proxy = linphone_core_get_default_proxy_config(q->getCCore()); + if (!proxy) + return nullptr; + const char *conferenceFactoryUri = linphone_proxy_config_get_conference_factory_uri(proxy); + if (!conferenceFactoryUri) + return nullptr; + usedUri = conferenceFactoryUri; + } else { + LinphoneAddress *addr = linphone_address_new(uri.c_str()); + proxy = linphone_core_lookup_known_proxy(q->getCCore(), addr); + linphone_address_unref(addr); + usedUri = uri; + } + if (proxy) { + const LinphoneAddress *contactAddr = linphone_proxy_config_get_contact(proxy); + if (contactAddr) { + char *cFrom = linphone_address_as_string(contactAddr); + from = string(cFrom); + bctbx_free(cFrom); + } else + from = L_GET_CPP_PTR_FROM_C_OBJECT(linphone_proxy_config_get_identity_address(proxy))->asString(); + } + } + + shared_ptr chatRoom; + { + shared_ptr clientGroupChatRoom = make_shared( + q->getSharedFromThis(), usedUri, IdentityAddress(from), subject + ); + ClientGroupChatRoomPrivate *dClientGroupChatRoom = clientGroupChatRoom->getPrivate(); + + if (fallback) { + // Create a ClientGroupToBasicChatRoom to handle fallback from ClientGroupChatRoom to BasicGroupChatRoom if + // only one participant is invited and that it does not support group chat + chatRoom = make_shared(clientGroupChatRoom); + dClientGroupChatRoom->setCallSessionListener(chatRoom->getPrivate()); + dClientGroupChatRoom->setChatRoomListener(chatRoom->getPrivate()); + } else + chatRoom = clientGroupChatRoom; + } + chatRoom->getPrivate()->setState(ChatRoom::State::Instantiated); + + noCreatedClientGroupChatRooms[chatRoom.get()] = chatRoom; + + return chatRoom; } void CorePrivate::insertChatRoom (const shared_ptr &chatRoom) { L_ASSERT(chatRoom); - L_Q(); - q->deleteChatRoom(chatRoom); - chatRooms.push_back(chatRoom); - chatRoomsById[chatRoom->getChatRoomId()] = chatRoom; + const ChatRoomId &chatRoomId = chatRoom->getChatRoomId(); + auto it = chatRoomsById.find(chatRoomId); + // Chat room not exist or yes but with the same pointer! + L_ASSERT(it == chatRoomsById.end() || it->second == chatRoom); + if (it == chatRoomsById.end()) { + // Remove chat room from workaround cache. + noCreatedClientGroupChatRooms.erase(chatRoom.get()); + + chatRooms.push_back(chatRoom); + chatRoomsById[chatRoomId] = chatRoom; + } } void CorePrivate::insertChatRoomWithDb (const shared_ptr &chatRoom) { @@ -118,6 +176,13 @@ void CorePrivate::insertChatRoomWithDb (const shared_ptr &chat mainDb->insertChatRoom(chatRoom); } +void CorePrivate::loadChatRooms () { + chatRooms.clear(); + chatRoomsById.clear(); + for (auto &chatRoom : mainDb->getChatRooms()) + insertChatRoom(chatRoom); +} + void CorePrivate::replaceChatRoom (const shared_ptr &replacedChatRoom, const shared_ptr &newChatRoom) { const ChatRoomId &replacedChatRoomId = replacedChatRoom->getChatRoomId(); const ChatRoomId &newChatRoomId = newChatRoom->getChatRoomId(); @@ -146,10 +211,8 @@ shared_ptr Core::findChatRoom (const ChatRoomId &chatRoomId) c if (it != d->chatRoomsById.cend()) return it->second; - lInfo() << "Unable to find chat room in RAM: (peer=" << - chatRoomId.getPeerAddress().asString() << ", local=" << chatRoomId.getLocalAddress().asString() << ")."; - - return shared_ptr(); + lInfo() << "Unable to find chat room in RAM: " << chatRoomId << "."; + return nullptr; } list> Core::findChatRooms (const IdentityAddress &peerAddress) const { @@ -173,11 +236,29 @@ shared_ptr Core::findOneToOneChatRoom ( L_D(); for (const auto &chatRoom : d->chatRooms) { const IdentityAddress &curLocalAddress = chatRoom->getLocalAddress(); + ChatRoom::CapabilitiesMask capabilities = chatRoom->getCapabilities(); + + // We are looking for a one to one chatroom + // Do not return a group chat room that everyone except one person has left + if (!(capabilities & ChatRoom::Capabilities::OneToOne)) + continue; + + // One to one client group chat room + // The only participant's address must match the participantAddress argument if ( - chatRoom->getParticipantCount() == 1 && ( - (curLocalAddress == localAddress && participantAddress == chatRoom->getParticipants().front()->getAddress()) || - (curLocalAddress == localAddress.getAddressWithoutGruu() && chatRoom->getPeerAddress() == participantAddress) - ) + (capabilities & ChatRoom::Capabilities::Conference) && + !chatRoom->getParticipants().empty() && + localAddress == curLocalAddress && + participantAddress.getAddressWithoutGruu() == chatRoom->getParticipants().front()->getAddress() + ) + return chatRoom; + + // One to one basic chat room (addresses without gruu) + // The peer address must match the participantAddress argument + if ( + (capabilities & ChatRoom::Capabilities::Basic) && + localAddress.getAddressWithoutGruu() == curLocalAddress.getAddressWithoutGruu() && + participantAddress.getAddressWithoutGruu() == chatRoom->getPeerAddress().getAddressWithoutGruu() ) return chatRoom; } @@ -186,21 +267,7 @@ shared_ptr Core::findOneToOneChatRoom ( shared_ptr Core::createClientGroupChatRoom (const string &subject) { L_D(); - return d->createClientGroupChatRoom(subject, true); -} - -shared_ptr Core::onlyGetOrCreateBasicChatRoom (const IdentityAddress &peerAddress, bool isRtt) { - list> chatRooms = findChatRooms(peerAddress); - if (!chatRooms.empty()) - return chatRooms.front(); - - const ChatRoomId &chatRoomId = ChatRoomId(peerAddress, getDefaultLocalAddress(getSharedFromThis(), peerAddress)); - shared_ptr chatRoom; - - BasicChatRoom *basicChatRoom = new BasicChatRoom(getSharedFromThis(), chatRoomId); - chatRoom.reset(basicChatRoom); - - return chatRoom; + return d->createClientGroupChatRoom(subject); } shared_ptr Core::getOrCreateBasicChatRoom (const ChatRoomId &chatRoomId, bool isRtt) { @@ -251,6 +318,7 @@ shared_ptr Core::getOrCreateBasicChatRoomFromUri (const string void Core::deleteChatRoom (const shared_ptr &chatRoom) { CorePrivate *d = chatRoom->getCore()->getPrivate(); + d->noCreatedClientGroupChatRooms.erase(chatRoom.get()); const ChatRoomId &chatRoomId = chatRoom->getChatRoomId(); auto chatRoomsByIdIt = d->chatRoomsById.find(chatRoomId); if (chatRoomsByIdIt != d->chatRoomsById.end()) { diff --git a/src/core/core-p.h b/src/core/core-p.h index 71feb8492..0106dd502 100644 --- a/src/core/core-p.h +++ b/src/core/core-p.h @@ -57,10 +57,11 @@ public: void postConfigureAudioStream (AudioStream *stream, bool muted); void setPlaybackGainDb (AudioStream *stream, float gain); + void loadChatRooms (); void insertChatRoom (const std::shared_ptr &chatRoom); void insertChatRoomWithDb (const std::shared_ptr &chatRoom); std::shared_ptr createBasicChatRoom (const ChatRoomId &chatRoomId, AbstractChatRoom::CapabilitiesMask capabilities); - std::shared_ptr createClientGroupChatRoom (const std::string &subject, bool fallback = true); + std::shared_ptr createClientGroupChatRoom (const std::string &subject, const std::string &uri = "", bool fallback = true); void replaceChatRoom (const std::shared_ptr &replacedChatRoom, const std::shared_ptr &newChatRoom); std::unique_ptr mainDb; @@ -72,8 +73,12 @@ private: std::shared_ptr currentCall; std::list> chatRooms; + std::unordered_map> chatRoomsById; + // Ugly cache to deal with C code. + std::unordered_map> noCreatedClientGroupChatRooms; + L_DECLARE_PUBLIC(Core); }; diff --git a/src/core/core.cpp b/src/core/core.cpp index c09784f5c..9e9d94cc6 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -20,6 +20,7 @@ #include #include +#include "address/address-p.h" #include "call/call.h" #include "core/core-listener.h" #include "core/core-p.h" @@ -57,8 +58,7 @@ void CorePrivate::init () { if (!mainDb->connect(backend, uri)) lFatal() << "Unable to open linphone database."; - for (auto &chatRoom : mainDb->getChatRooms()) - insertChatRoom(chatRoom); + loadChatRooms(); } void CorePrivate::registerListener (CoreListener *listener) { @@ -76,6 +76,12 @@ void CorePrivate::uninit () { linphone_core_iterate(L_GET_C_BACK_PTR(q)); ms_usleep(10000); } + + chatRooms.clear(); + chatRoomsById.clear(); + noCreatedClientGroupChatRooms.clear(); + + AddressPrivate::clearSipAddressesCache(); } // ----------------------------------------------------------------------------- diff --git a/src/core/core.h b/src/core/core.h index b4ae4d921..a8a15630a 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -107,8 +107,9 @@ public: const IdentityAddress &localAddress ); - std::shared_ptr onlyGetOrCreateBasicChatRoom (const IdentityAddress &peerAddress, bool isRtt = false); std::shared_ptr getOrCreateBasicChatRoom (const ChatRoomId &chatRoomId, bool isRtt = false); + + // TODO: Remove me in the future, a chatroom is identified by a local and peer address now! std::shared_ptr getOrCreateBasicChatRoom (const IdentityAddress &peerAddress, bool isRtt = false); std::shared_ptr getOrCreateBasicChatRoomFromUri (const std::string &uri, bool isRtt = false); diff --git a/src/db/abstract/abstract-db-p.h b/src/db/abstract/abstract-db-p.h index fa2d12c3f..4064c5408 100644 --- a/src/db/abstract/abstract-db-p.h +++ b/src/db/abstract/abstract-db-p.h @@ -30,11 +30,11 @@ LINPHONE_BEGIN_NAMESPACE class AbstractDbPrivate : public ObjectPrivate { public: - AbstractDbPrivate () = default; - DbSession dbSession; private: + void safeInit (); + AbstractDb::Backend backend; L_DECLARE_PUBLIC(AbstractDb); diff --git a/src/db/abstract/abstract-db.cpp b/src/db/abstract/abstract-db.cpp index ed6295891..18531439a 100644 --- a/src/db/abstract/abstract-db.cpp +++ b/src/db/abstract/abstract-db.cpp @@ -21,6 +21,10 @@ #include #endif // ifdef __APPLE__ +#if (TARGET_OS_IPHONE || defined(__ANDROID__)) + #include +#endif // if (TARGET_OS_IPHONE || defined(__ANDROID__)) + #include "abstract-db-p.h" #include "logger/logger.h" @@ -30,20 +34,37 @@ using namespace std; LINPHONE_BEGIN_NAMESPACE -AbstractDb::AbstractDb (AbstractDbPrivate &p) : Object(p) {} - -// Force static sqlite3 linking for IOS and Android. -#if defined(SOCI_ENABLED) && (TARGET_OS_IPHONE || defined(__ANDROID__)) +#if (TARGET_OS_IPHONE || defined(__ANDROID__)) + // Force static sqlite3 linking for IOS and Android. extern "C" void register_factory_sqlite3(); -#endif // if defined(SOCI_ENABLED) && (TARGET_OS_IPHONE || defined(__ANDROID__)) + + static void sqlite3Log (void *, int iErrCode, const char *zMsg) { + lInfo() << "[sqlite3][" << iErrCode << "]" << zMsg; + } +#endif // if (TARGET_OS_IPHONE || defined(__ANDROID__)) + +void AbstractDbPrivate::safeInit () { + L_Q(); + dbSession.enableForeignKeys(false); + q->init(); + dbSession.enableForeignKeys(true); +} + +AbstractDb::AbstractDb (AbstractDbPrivate &p) : Object(p) {} bool AbstractDb::connect (Backend backend, const string ¶meters) { L_D(); - #if defined(SOCI_ENABLED) && (TARGET_OS_IPHONE || defined(__ANDROID__)) - if (backend == Sqlite3) - register_factory_sqlite3(); - #endif // if defined(SOCI_ENABLED) && (TARGET_OS_IPHONE || defined(__ANDROID__)) + #if (TARGET_OS_IPHONE || defined(__ANDROID__)) + if (backend == Sqlite3) { + static bool registered = false; + if (!registered) { + registered = true; + register_factory_sqlite3(); + sqlite3_config(SQLITE_CONFIG_LOG, sqlite3Log, nullptr); + } + } + #endif // if (TARGET_OS_IPHONE || defined(__ANDROID__)) d->backend = backend; d->dbSession = DbSession( @@ -52,11 +73,7 @@ bool AbstractDb::connect (Backend backend, const string ¶meters) { if (d->dbSession) { try { - #ifdef SOCI_ENABLED - d->dbSession.enableForeignKeys(false); - init(); - d->dbSession.enableForeignKeys(true); - #endif // ifdef SOCI_ENABLED + d->safeInit(); } catch (const exception &e) { lWarning() << "Unable to init database: " << e.what(); @@ -80,32 +97,28 @@ bool AbstractDb::forceReconnect () { return false; } - #ifdef SOCI_ENABLED - constexpr int retryCount = 2; - lInfo() << "Trying sql backend reconnect..."; + constexpr int retryCount = 2; + lInfo() << "Trying sql backend reconnect..."; - try { - soci::session *session = d->dbSession.getBackendSession(); - session->close(); - - for (int i = 0; i < retryCount; ++i) { - try { - lInfo() << "Reconnect... Try: " << i; - session->reconnect(); - lInfo() << "Database reconnection successful!"; - return true; - } catch (const soci::soci_error &e) { - if (e.get_error_category() != soci::soci_error::connection_error) - throw e; - } + try { + for (int i = 0; i < retryCount; ++i) { + try { + lInfo() << "Reconnect... Try: " << i; + d->dbSession.getBackendSession()->reconnect(); // Equivalent to close and connect. + d->safeInit(); + lInfo() << "Database reconnection successful!"; + return true; + } catch (const soci::soci_error &e) { + if (e.get_error_category() != soci::soci_error::connection_error) + throw e; } - } catch (const exception &e) { - lError() << "Unable to reconnect: `" << e.what() << "`."; - return false; } + } catch (const exception &e) { + lError() << "Unable to reconnect: `" << e.what() << "`."; + return false; + } - lError() << "Database reconnection failed!"; - #endif // ifdef SOCI_ENABLED + lError() << "Database reconnection failed!"; return false; } diff --git a/src/db/abstract/abstract-db.h b/src/db/abstract/abstract-db.h index 1d612b58b..a6b401dd8 100644 --- a/src/db/abstract/abstract-db.h +++ b/src/db/abstract/abstract-db.h @@ -28,7 +28,7 @@ LINPHONE_BEGIN_NAMESPACE class AbstractDbPrivate; -class LINPHONE_PUBLIC AbstractDb : public Object { +class AbstractDb : public Object { public: enum Backend { Mysql, diff --git a/src/db/internal/db-transaction.h b/src/db/internal/db-transaction.h new file mode 100644 index 000000000..dbb57fa12 --- /dev/null +++ b/src/db/internal/db-transaction.h @@ -0,0 +1,181 @@ +/* + * db-transaction.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_DB_TRANSACTION_H_ +#define _L_DB_TRANSACTION_H_ + +#include "db/main-db-p.h" +#include "logger/logger.h" + +// ============================================================================= + +#define L_DB_TRANSACTION_C(CONTEXT) \ + LinphonePrivate::DbTransactionInfo().set(__func__, CONTEXT) * [&](SmartTransaction &tr) + +#define L_DB_TRANSACTION L_DB_TRANSACTION_C(this) + +LINPHONE_BEGIN_NAMESPACE + +class SmartTransaction { +public: + SmartTransaction (soci::session *session, const char *name) : + mSession(session), mName(name), mIsCommitted(false) { + lInfo() << "Start transaction " << this << " in MainDb::" << mName << "."; + mSession->begin(); + } + + ~SmartTransaction () { + if (!mIsCommitted) { + lInfo() << "Rollback transaction " << this << " in MainDb::" << mName << "."; + mSession->rollback(); + } + } + + void commit () { + if (mIsCommitted) { + lError() << "Transaction " << this << " in MainDb::" << mName << " already committed!!!"; + return; + } + + lInfo() << "Commit transaction " << this << " in MainDb::" << mName << "."; + mIsCommitted = true; + mSession->commit(); + } + +private: + soci::session *mSession; + const char *mName; + bool mIsCommitted; + + L_DISABLE_COPY(SmartTransaction); +}; + +struct DbTransactionInfo { + DbTransactionInfo &set (const char *_name, const MainDb *_mainDb) { + name = _name; + mainDb = const_cast(_mainDb); + return *this; + } + + const char *name = nullptr; + MainDb *mainDb = nullptr; +}; + +template +class DbTransaction { + using InternalReturnType = typename std::remove_reference< + decltype(std::declval()(std::declval())) + >::type; + +public: + using ReturnType = typename std::conditional< + std::is_same::value, + bool, + InternalReturnType + >::type; + + DbTransaction (DbTransactionInfo &info, Function &&function) : mFunction(std::move(function)) { + MainDb *mainDb = info.mainDb; + const char *name = info.name; + soci::session *session = mainDb->getPrivate()->dbSession.getBackendSession(); + + try { + SmartTransaction tr(session, name); + mResult = exec(tr); + } catch (const soci::soci_error &e) { + lWarning() << "Catched exception in MainDb::" << name << "(" << e.what() << ")."; + soci::soci_error::error_category category = e.get_error_category(); + if ( + (category == soci::soci_error::connection_error || category == soci::soci_error::unknown) && + mainDb->forceReconnect() + ) { + try { + SmartTransaction tr(session, name); + mResult = exec(tr); + } catch (const std::exception &e) { + lError() << "Unable to execute query after reconnect in MainDb::" << name << "(" << e.what() << ")."; + } + return; + } + lError() << "Unhandled [" << getErrorCategoryAsString(category) << "] exception in MainDb::" << + name << ": `" << e.what() << "`."; + } catch (const std::exception &e) { + lError() << "Unhandled generic exception in MainDb::" << name << ": `" << e.what() << "`."; + } + } + + DbTransaction (DbTransaction &&DbTransaction) : mFunction(std::move(DbTransaction.mFunction)) {} + + operator ReturnType () const { + return mResult; + } + +private: + // Exec function with no return type. + template + typename std::enable_if::value, bool>::type exec (SmartTransaction &tr) const { + mFunction(tr); + return true; + } + + // Exec function with return type. + template + typename std::enable_if::value, T>::type exec (SmartTransaction &tr) const { + return mFunction(tr); + } + + static const char *getErrorCategoryAsString (soci::soci_error::error_category category) { + switch (category) { + case soci::soci_error::connection_error: + return "CONNECTION ERROR"; + case soci::soci_error::invalid_statement: + return "INVALID STATEMENT"; + case soci::soci_error::no_privilege: + return "NO PRIVILEGE"; + case soci::soci_error::no_data: + return "NO DATA"; + case soci::soci_error::constraint_violation: + return "CONSTRAINT VIOLATION"; + case soci::soci_error::unknown_transaction_state: + return "UNKNOWN TRANSACTION STATE"; + case soci::soci_error::system_error: + return "SYSTEM ERROR"; + case soci::soci_error::unknown: + return "UNKNOWN"; + } + + // Unreachable. + L_ASSERT(false); + return nullptr; + } + + Function mFunction; + ReturnType mResult{}; + + L_DISABLE_COPY(DbTransaction); +}; + +template +typename DbTransaction::ReturnType operator* (DbTransactionInfo &info, Function &&function) { + return DbTransaction(info, std::forward(function)); +} + +LINPHONE_END_NAMESPACE + +#endif // ifndef _L_DB_TRANSACTION_H_ diff --git a/src/db/internal/statements.cpp b/src/db/internal/statements.cpp new file mode 100644 index 000000000..6b6754228 --- /dev/null +++ b/src/db/internal/statements.cpp @@ -0,0 +1,132 @@ +/* + * statements.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 "statements.h" + +// ============================================================================= + +LINPHONE_BEGIN_NAMESPACE + +namespace Statements { + using Backend = AbstractDb::Backend; + + struct Statement { + template + constexpr Statement (Backend _backend, const char (&_sql)[N]) : backend(_backend), sql(_sql) {} + + Backend backend; + const char *sql; + }; + + struct AbstractStatement { + public: + template + constexpr AbstractStatement (const char (&_sql)[N]) : mSql{ _sql, nullptr } {} + + // TODO: Improve, check backends. + constexpr AbstractStatement (const Statement &a, const Statement &b) : mSql{ a.sql, b.sql } {} + + const char *get (Backend backend) const { + return backend == Backend::Mysql && mSql[1] ? mSql[1] : mSql[0]; + } + + private: + const char *mSql[2]; + }; + + // --------------------------------------------------------------------------- + // Select statements. + // --------------------------------------------------------------------------- + + constexpr const char *select[SelectCount] = { + [SelectSipAddressId] = R"( + SELECT id + FROM sip_address + WHERE value = :1 + )", + + [SelectChatRoomId] = R"( + SELECT id + FROM chat_room + WHERE peer_sip_address_id = :1 AND local_sip_address_id = :2 + )", + + [SelectChatRoomParticipantId] = R"( + SELECT id + FROM chat_room_participant + WHERE chat_room_id = :1 AND participant_sip_address_id = :2 + )", + + [SelectOneToOneChatRoomId] = R"( + SELECT chat_room_id + FROM one_to_one_chat_room + WHERE participant_a_sip_address_id IN (:1, :2) + AND participant_b_sip_address_id IN (:3, :4) + )", + + [SelectConferenceEvent] = R"( + SELECT conference_event_view.id AS event_id, type, conference_event_view.creation_time, from_sip_address.value, to_sip_address.value, time, imdn_message_id, state, direction, is_secured, notify_id, device_sip_address.value, participant_sip_address.value, conference_event_view.subject, peer_sip_address.value, local_sip_address.value + FROM conference_event_view + JOIN chat_room ON chat_room.id = chat_room_id + JOIN sip_address AS peer_sip_address ON peer_sip_address.id = peer_sip_address_id + JOIN sip_address AS local_sip_address ON local_sip_address.id = local_sip_address_id + LEFT JOIN sip_address AS from_sip_address ON from_sip_address.id = from_sip_address_id + LEFT JOIN sip_address AS to_sip_address ON to_sip_address.id = to_sip_address_id + LEFT JOIN sip_address AS device_sip_address ON device_sip_address.id = device_sip_address_id + LEFT JOIN sip_address AS participant_sip_address ON participant_sip_address.id = participant_sip_address_id + WHERE event_id = :1 + )", + + [SelectConferenceEvents] = R"( + SELECT conference_event_view.id AS event_id, type, creation_time, from_sip_address.value, to_sip_address.value, time, imdn_message_id, state, direction, is_secured, notify_id, device_sip_address.value, participant_sip_address.value, subject + FROM conference_event_view + LEFT JOIN sip_address AS from_sip_address ON from_sip_address.id = from_sip_address_id + LEFT JOIN sip_address AS to_sip_address ON to_sip_address.id = to_sip_address_id + LEFT JOIN sip_address AS device_sip_address ON device_sip_address.id = device_sip_address_id + LEFT JOIN sip_address AS participant_sip_address ON participant_sip_address.id = participant_sip_address_id + WHERE chat_room_id = :1 + )" + }; + + // --------------------------------------------------------------------------- + // Select statements. + // --------------------------------------------------------------------------- + + constexpr AbstractStatement insert[InsertCount] = { + [InsertOneToOneChatRoom] = R"( + INSERT INTO one_to_one_chat_room ( + chat_room_id, participant_a_sip_address_id, participant_b_sip_address_id + ) VALUES (:1, :2, :3) + )" + }; + + // --------------------------------------------------------------------------- + // Getters. + // --------------------------------------------------------------------------- + + const char *get (Select selectStmt) { + return selectStmt >= Select::SelectCount ? nullptr : select[selectStmt]; + } + + const char *get (Insert insertStmt, AbstractDb::Backend backend) { + return insertStmt >= Insert::InsertCount ? nullptr : insert[insertStmt].get(backend); + } +} + +LINPHONE_END_NAMESPACE diff --git a/src/db/internal/statements.h b/src/db/internal/statements.h new file mode 100644 index 000000000..4f518ccad --- /dev/null +++ b/src/db/internal/statements.h @@ -0,0 +1,51 @@ +/* + * statements.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_STATEMENTS_H_ +#define _L_STATEMENTS_H_ + +#include "db/abstract/abstract-db.h" + +// ============================================================================= + +LINPHONE_BEGIN_NAMESPACE + +namespace Statements { + enum Select { + SelectSipAddressId, + SelectChatRoomId, + SelectChatRoomParticipantId, + SelectOneToOneChatRoomId, + SelectConferenceEvent, + SelectConferenceEvents, + SelectCount + }; + + enum Insert { + InsertOneToOneChatRoom, + InsertCount + }; + + const char *get (Select selectStmt); + const char *get (Insert insertStmt, AbstractDb::Backend backend); +} + +LINPHONE_END_NAMESPACE + +#endif // ifndef _L_STATEMENTS_H_ diff --git a/src/db/main-db-key.cpp b/src/db/main-db-key.cpp index 68c0e4451..11557b04b 100644 --- a/src/db/main-db-key.cpp +++ b/src/db/main-db-key.cpp @@ -37,23 +37,23 @@ MainDbKey::MainDbKey (const shared_ptr &core, long long storageId) : MainD d->storageId = storageId; } -MainDbKey::MainDbKey (const MainDbKey &src) : MainDbKey() { +MainDbKey::MainDbKey (const MainDbKey &other) : MainDbKey() { L_D(); - const MainDbKeyPrivate *dSrc = src.getPrivate(); + const MainDbKeyPrivate *dOther = other.getPrivate(); - d->core = dSrc->core; - d->storageId = dSrc->storageId; + d->core = dOther->core; + d->storageId = dOther->storageId; } MainDbKey::~MainDbKey () {} -MainDbKey &MainDbKey::operator= (const MainDbKey &src) { +MainDbKey &MainDbKey::operator= (const MainDbKey &other) { L_D(); - if (this != &src) { - const MainDbKeyPrivate *dSrc = src.getPrivate(); - d->core = dSrc->core; - d->storageId = dSrc->storageId; + if (this != &other) { + const MainDbKeyPrivate *dOther = other.getPrivate(); + d->core = dOther->core; + d->storageId = dOther->storageId; } return *this; diff --git a/src/db/main-db-key.h b/src/db/main-db-key.h index 04b69efc7..bd94582ab 100644 --- a/src/db/main-db-key.h +++ b/src/db/main-db-key.h @@ -36,10 +36,10 @@ class MainDbKey : public ClonableObject { public: MainDbKey (); MainDbKey (const std::shared_ptr &core, long long storageId); - MainDbKey (const MainDbKey &src); + MainDbKey (const MainDbKey &other); virtual ~MainDbKey () = 0; - MainDbKey &operator= (const MainDbKey &src); + MainDbKey &operator= (const MainDbKey &other); bool isValid () const; diff --git a/src/db/main-db-p.h b/src/db/main-db-p.h index 5f3ea6896..8a8a52ad1 100644 --- a/src/db/main-db-p.h +++ b/src/db/main-db-p.h @@ -22,16 +22,14 @@ #include +#include "linphone/utils/utils.h" + #include "abstract/abstract-db-p.h" #include "event-log/event-log.h" #include "main-db.h" // ============================================================================= -namespace soci { - class row; -} - LINPHONE_BEGIN_NAMESPACE class Content; @@ -43,17 +41,17 @@ public: private: // --------------------------------------------------------------------------- - // SOCI helpers. + // Misc helpers. // --------------------------------------------------------------------------- - long long resolveId (const soci::row &row, int col) const; + std::shared_ptr findChatRoom (const ChatRoomId &chatRoomId) const; // --------------------------------------------------------------------------- // Low level API. // --------------------------------------------------------------------------- long long insertSipAddress (const std::string &sipAddress); - void insertContent (long long messageEventId, const Content &content); + void insertContent (long long chatMessageId, const Content &content); long long insertContentType (const std::string &contentType); long long insertOrUpdateImportedBasicChatRoom ( long long peerSipAddressId, @@ -63,7 +61,7 @@ private: long long insertChatRoom (const std::shared_ptr &chatRoom); long long insertChatRoomParticipant (long long chatRoomId, long long participantSipAddressId, bool isAdmin); void insertChatRoomParticipantDevice (long long participantId, long long participantDeviceSipAddressId); - void insertChatMessageParticipant (long long messageEventId, long long sipAddressId, int state); + void insertChatMessageParticipant (long long chatMessageId, long long sipAddressId, int state); long long selectSipAddressId (const std::string &sipAddress) const; long long selectChatRoomId (long long peerSipAddressId, long long localSipAddressId) const; @@ -71,7 +69,7 @@ private: long long selectChatRoomParticipantId (long long chatRoomId, long long participantSipAddressId) const; long long selectOneToOneChatRoomId (long long sipAddressIdA, long long sipAddressIdB) const; - void deleteContents (long long messageEventId); + void deleteContents (long long chatMessageId); void deleteChatRoomParticipant (long long chatRoomId, long long participantSipAddressId); void deleteChatRoomParticipantDevice (long long participantId, long long participantDeviceSipAddressId); @@ -79,53 +77,65 @@ private: // Events API. // --------------------------------------------------------------------------- + long long getConferenceEventIdFromRow (const soci::row &row) const { + return dbSession.resolveId(row, -1); + } + + time_t getConferenceEventCreationTimeFromRow (const soci::row &row) const { + return Utils::getTmAsTimeT(row.get(2)); + } + + unsigned int getConferenceEventNotifyIdFromRow (const soci::row &row) const { + L_Q(); + return q->getBackend() == MainDb::Backend::Mysql + ? row.get(10, 0) + : static_cast(row.get(10, 0)); + } + std::shared_ptr selectGenericConferenceEvent ( - long long eventId, - EventLog::Type type, - time_t creationTime, - const ChatRoomId &chatRoomId + const std::shared_ptr &chatRoom, + const soci::row &row + ) const; + + std::shared_ptr selectGenericConferenceNotifiedEvent ( + const ChatRoomId &chatRoomId, + const soci::row &row ) const; std::shared_ptr selectConferenceEvent ( - long long eventId, + const ChatRoomId &chatRoomId, EventLog::Type type, - time_t creationTime, - const ChatRoomId &chatRoomId + const soci::row &row ) const; std::shared_ptr selectConferenceCallEvent ( - long long eventId, + const ChatRoomId &chatRoomId, EventLog::Type type, - time_t creationTime, - const ChatRoomId &chatRoomId + const soci::row &row ) const; std::shared_ptr selectConferenceChatMessageEvent ( - long long eventId, + const std::shared_ptr &chatRoom, EventLog::Type type, - time_t creationTime, - const ChatRoomId &chatRoomId + const soci::row &row ) const; std::shared_ptr selectConferenceParticipantEvent ( - long long eventId, + const ChatRoomId &chatRoomId, EventLog::Type type, - time_t creationTime, - const ChatRoomId &chatRoomId + const soci::row &row ) const; std::shared_ptr selectConferenceParticipantDeviceEvent ( - long long eventId, + const ChatRoomId &chatRoomId, EventLog::Type type, - time_t creationTime, - const ChatRoomId &chatRoomId + const soci::row &row ) const; std::shared_ptr selectConferenceSubjectEvent ( - long long eventId, + const ChatRoomId &chatRoomId, EventLog::Type type, - time_t creationTime, - const ChatRoomId &chatRoomId + const soci::row &row ) const; long long insertEvent (const std::shared_ptr &eventLog); @@ -156,6 +166,7 @@ private: unsigned int getModuleVersion (const std::string &name); void updateModuleVersion (const std::string &name, unsigned int version); + void updateSchema (); // --------------------------------------------------------------------------- // Import. diff --git a/src/db/main-db.cpp b/src/db/main-db.cpp index 1e76e96ae..983630099 100644 --- a/src/db/main-db.cpp +++ b/src/db/main-db.cpp @@ -17,29 +17,23 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include #include -#include "linphone/utils/utils.h" +#include "linphone/utils/algorithm.h" +#include "linphone/utils/static-string.h" #include "chat/chat-message/chat-message-p.h" #include "chat/chat-room/chat-room-p.h" #include "chat/chat-room/client-group-chat-room.h" #include "chat/chat-room/server-group-chat-room.h" #include "conference/participant-p.h" -#include "content/content-type.h" -#include "content/content.h" #include "core/core-p.h" #include "event-log/event-log-p.h" #include "event-log/events.h" -#include "logger/logger.h" #include "main-db-key-p.h" -#include "main-db-p.h" -#define DB_MODULE_VERSION_EVENTS L_VERSION(1, 0, 0) -#define DB_MODULE_VERSION_FRIENDS L_VERSION(1, 0, 0) -#define DB_MODULE_VERSION_LEGACY_FRIENDS_IMPORT L_VERSION(1, 0, 0) -#define DB_MODULE_VERSION_LEGACY_HISTORY_IMPORT L_VERSION(1, 0, 0) +#include "internal/db-transaction.h" +#include "internal/statements.h" // ============================================================================= @@ -52,102 +46,79 @@ using namespace std; LINPHONE_BEGIN_NAMESPACE -// ----------------------------------------------------------------------------- +namespace { + constexpr unsigned int ModuleVersionEvents = makeVersion(1, 0, 1); + constexpr unsigned int ModuleVersionFriends = makeVersion(1, 0, 0); + constexpr unsigned int ModuleVersionLegacyFriendsImport = makeVersion(1, 0, 0); + constexpr unsigned int ModuleVersionLegacyHistoryImport = makeVersion(1, 0, 0); -MainDb::MainDb (const shared_ptr &core) : AbstractDb(*new MainDbPrivate), CoreAccessor(core) {} + constexpr int LegacyFriendListColId = 0; + constexpr int LegacyFriendListColName = 1; + constexpr int LegacyFriendListColRlsUri = 2; + constexpr int LegacyFriendListColSyncUri = 3; + constexpr int LegacyFriendListColRevision = 4; -#ifdef SOCI_ENABLED + constexpr int LegacyFriendColFriendListId = 1; + constexpr int LegacyFriendColSipAddress = 2; + constexpr int LegacyFriendColSubscribePolicy = 3; + constexpr int LegacyFriendColSendSubscribe = 4; + constexpr int LegacyFriendColRefKey = 5; + constexpr int LegacyFriendColVCard = 6; + constexpr int LegacyFriendColVCardEtag = 7; + constexpr int LegacyFriendColVCardSyncUri = 8; + constexpr int LegacyFriendColPresenceReceived = 9; -// ----------------------------------------------------------------------------- -// Errors handling. -// ----------------------------------------------------------------------------- - -#define L_SAFE_TRANSACTION_C(CONTEXT) \ - LinphonePrivate::SafeTransactionInfo().set(__func__, CONTEXT) * [&]() - -#define L_SAFE_TRANSACTION L_SAFE_TRANSACTION_C(this) - -struct SafeTransactionInfo { - SafeTransactionInfo &set (const char *_name, const MainDb *_mainDb) { - name = _name; - mainDb = const_cast(_mainDb); - return *this; - } - - const char *name = nullptr; - MainDb *mainDb = nullptr; -}; - -template -class SafeTransaction { -public: - using ReturnType = typename remove_reference()())>::type; - - SafeTransaction (SafeTransactionInfo &info, Function function) : mFunction(move(function)) { - try { - mResult = mFunction(); - } catch (const soci::soci_error &e) { - lWarning() << "Catched exception in MainDb::" << info.name << "."; - soci::soci_error::error_category category = e.get_error_category(); - if ((category == soci::soci_error::connection_error - || category == soci::soci_error::unknown) && info.mainDb->forceReconnect()) { - mResult = mFunction(); - return; - } - lError() << "Unhandled [" << getErrorCategoryAsString(category) << "] exception in MainDb::" << - info.name << ": `" << e.what() << "`."; - } catch (const exception &e) { - lError() << "Unhandled generic exception in MainDb::" << info.name << ": `" << e.what() << "`."; - } - } - - SafeTransaction (SafeTransaction &&safeTransaction) : mFunction(move(safeTransaction.mFunction)) {} - - operator ReturnType () const { - return mResult; - } - -private: - static const char *getErrorCategoryAsString (soci::soci_error::error_category category) { - switch (category) { - case soci::soci_error::connection_error: - return "CONNECTION ERROR"; - case soci::soci_error::invalid_statement: - return "INVALID STATEMENT"; - case soci::soci_error::no_privilege: - return "NO PRIVILEGE"; - case soci::soci_error::no_data: - return "NO DATA"; - case soci::soci_error::constraint_violation: - return "CONSTRAINT VIOLATION"; - case soci::soci_error::unknown_transaction_state: - return "UNKNOWN TRANSACTION STATE"; - case soci::soci_error::system_error: - return "SYSTEM ERROR"; - case soci::soci_error::unknown: - return "UNKNOWN"; - } - - // Unreachable. - L_ASSERT(false); - return nullptr; - } - - Function mFunction; - ReturnType mResult{}; - - L_DISABLE_COPY(SafeTransaction); -}; - -template -typename SafeTransaction::ReturnType operator* (SafeTransactionInfo &info, Function &&function) { - return SafeTransaction(info, forward(function)); + constexpr int LegacyMessageColLocalAddress = 1; + constexpr int LegacyMessageColRemoteAddress = 2; + constexpr int LegacyMessageColDirection = 3; + constexpr int LegacyMessageColText = 4; + constexpr int LegacyMessageColState = 7; + constexpr int LegacyMessageColUrl = 8; + constexpr int LegacyMessageColDate = 9; + constexpr int LegacyMessageColAppData = 10; + constexpr int LegacyMessageColContentId = 11; + constexpr int LegacyMessageColContentType = 13; + constexpr int LegacyMessageColIsSecured = 14; } // ----------------------------------------------------------------------------- -// Soci backend. +// soci helpers. // ----------------------------------------------------------------------------- +static inline vector blobToVector (soci::blob &in) { + size_t len = in.get_len(); + if (!len) + return vector(); + vector out(len); + in.read(0, &out[0], len); + return out; +} + +static inline string blobToString (soci::blob &in) { + vector out = blobToVector(in); + return string(out.begin(), out.end()); +} + +static constexpr string &blobToString (string &in) { + return in; +} + +template +static T getValueFromRow (const soci::row &row, int index, bool &isNull) { + isNull = false; + + if (row.get_indicator(size_t(index)) == soci::i_null) { + isNull = true; + return T(); + } + return row.get(size_t(index)); +} + +// ----------------------------------------------------------------------------- +// Event filter tools. +// ----------------------------------------------------------------------------- + +// Some tools to build filters at compile time. template struct EnumToSql { T first; @@ -161,15 +132,61 @@ static constexpr const char *mapEnumToSql (const EnumToSql enumToSql[], size_ ); } -static constexpr EnumToSql eventFilterToSql[] = { - { MainDb::ConferenceCallFilter, "3, 4" }, - { MainDb::ConferenceChatMessageFilter, "5" }, - { MainDb::ConferenceInfoFilter, "1, 2, 6, 7, 8, 9, 10, 11, 12" } +template +struct SqlEventFilterBuilder {}; + +template +struct SqlEventFilterBuilder { + static constexpr Private::StaticString<1 + getIntLength(int(Type)) + sums((1 + getIntLength(int(List)))...)> get () { + return StaticIntString() + "," + SqlEventFilterBuilder::get(); + } }; -static constexpr const char *mapEventFilterToSql (MainDb::Filter filter) { +template +struct SqlEventFilterBuilder { + static constexpr Private::StaticString<1 + getIntLength(int(Type))> get () { + return StaticIntString(); + } +}; + +// ----------------------------------------------------------------------------- +// Event filters. +// ----------------------------------------------------------------------------- + +namespace { + constexpr auto ConferenceCallFilter = SqlEventFilterBuilder< + EventLog::Type::ConferenceCallStart, + EventLog::Type::ConferenceCallEnd + >::get(); + + constexpr auto ConferenceChatMessageFilter = SqlEventFilterBuilder::get(); + + constexpr auto ConferenceInfoNoDeviceFilter = SqlEventFilterBuilder< + EventLog::Type::ConferenceCreated, + EventLog::Type::ConferenceTerminated, + EventLog::Type::ConferenceParticipantAdded, + EventLog::Type::ConferenceParticipantRemoved, + EventLog::Type::ConferenceParticipantSetAdmin, + EventLog::Type::ConferenceParticipantUnsetAdmin, + EventLog::Type::ConferenceSubjectChanged + >::get(); + + constexpr auto ConferenceInfoFilter = ConferenceInfoNoDeviceFilter + "," + SqlEventFilterBuilder< + EventLog::Type::ConferenceParticipantDeviceAdded, + EventLog::Type::ConferenceParticipantDeviceRemoved + >::get(); + + constexpr EnumToSql EventFilterToSql[] = { + { MainDb::ConferenceCallFilter, ConferenceCallFilter }, + { MainDb::ConferenceChatMessageFilter, ConferenceChatMessageFilter }, + { MainDb::ConferenceInfoNoDeviceFilter, ConferenceInfoNoDeviceFilter }, + { MainDb::ConferenceInfoFilter, ConferenceInfoFilter } + }; +} + +static const char *mapEventFilterToSql (MainDb::Filter filter) { return mapEnumToSql( - eventFilterToSql, sizeof eventFilterToSql / sizeof eventFilterToSql[0], filter + EventFilterToSql, sizeof EventFilterToSql / sizeof EventFilterToSql[0], filter ); } @@ -180,11 +197,7 @@ static string buildSqlEventFilter ( MainDb::FilterMask mask, const string &condKeyWord = "WHERE" ) { - L_ASSERT( - find_if(filters.cbegin(), filters.cend(), [](const MainDb::Filter &filter) { - return filter == MainDb::NoFilter; - }) == filters.cend() - ); + L_ASSERT(findIf(filters, [](const MainDb::Filter &filter) { return filter == MainDb::NoFilter; }) == filters.cend()); if (mask == MainDb::NoFilter) return ""; @@ -209,58 +222,39 @@ static string buildSqlEventFilter ( return sql; } +// ----------------------------------------------------------------------------- +// Misc helpers. // ----------------------------------------------------------------------------- -static inline vector blobToVector (soci::blob &in) { - size_t len = in.get_len(); - if (!len) - return vector(); - vector out(len); - in.read(0, &out[0], len); - return out; -} - -static inline string blobToString (soci::blob &in) { - vector out = blobToVector(in); - return string(out.begin(), out.end()); -} - -static constexpr string &blobToString (string &in) { - return in; +shared_ptr MainDbPrivate::findChatRoom (const ChatRoomId &chatRoomId) const { + L_Q(); + shared_ptr chatRoom = q->getCore()->findChatRoom(chatRoomId); + if (!chatRoom) + lError() << "Unable to find chat room: " << chatRoomId << "."; + return chatRoom; } // ----------------------------------------------------------------------------- - -long long MainDbPrivate::resolveId (const soci::row &row, int col) const { - L_Q(); - // See: http://soci.sourceforge.net/doc/master/backends/ - // `row id` is not supported by soci on Sqlite3. It's necessary to cast id to int... - return q->getBackend() == AbstractDb::Sqlite3 - ? static_cast(row.get(0)) - : static_cast(row.get(0)); -} - +// Low level API. // ----------------------------------------------------------------------------- long long MainDbPrivate::insertSipAddress (const string &sipAddress) { - soci::session *session = dbSession.getBackendSession(); - long long id = selectSipAddressId(sipAddress); if (id >= 0) return id; lInfo() << "Insert new sip address in database: `" << sipAddress << "`."; - *session << "INSERT INTO sip_address (value) VALUES (:sipAddress)", soci::use(sipAddress); + *dbSession.getBackendSession() << "INSERT INTO sip_address (value) VALUES (:sipAddress)", soci::use(sipAddress); return dbSession.getLastInsertId(); } -void MainDbPrivate::insertContent (long long eventId, const Content &content) { +void MainDbPrivate::insertContent (long long chatMessageId, const Content &content) { soci::session *session = dbSession.getBackendSession(); const long long &contentTypeId = insertContentType(content.getContentType().asString()); const string &body = content.getBodyAsString(); *session << "INSERT INTO chat_message_content (event_id, content_type_id, body) VALUES" - " (:eventId, :contentTypeId, :body)", soci::use(eventId), soci::use(contentTypeId), + " (:chatMessageId, :contentTypeId, :body)", soci::use(chatMessageId), soci::use(contentTypeId), soci::use(body); const long long &chatMessageContentId = dbSession.getLastInsertId(); @@ -269,7 +263,7 @@ void MainDbPrivate::insertContent (long long eventId, const Content &content) { const string &name = fileContent.getFileName(); const size_t &size = fileContent.getFileSize(); const string &path = fileContent.getFilePath(); - *session << "INSERT INTO chat_message_file_content (chat_message_content_id, name, size, path) VALUES " + *session << "INSERT INTO chat_message_file_content (chat_message_content_id, name, size, path) VALUES" " (:chatMessageContentId, :name, :size, :path)", soci::use(chatMessageContentId), soci::use(name), soci::use(size), soci::use(path); } @@ -307,7 +301,7 @@ long long MainDbPrivate::insertOrUpdateImportedBasicChatRoom ( return id; } - static const int capabilities = ChatRoom::CapabilitiesMask( + const int capabilities = ChatRoom::CapabilitiesMask( { ChatRoom::Capabilities::Basic, ChatRoom::Capabilities::Migratable } ); lInfo() << "Insert new chat room in database: (peer=" << peerSipAddressId << @@ -322,32 +316,28 @@ long long MainDbPrivate::insertOrUpdateImportedBasicChatRoom ( } long long MainDbPrivate::insertChatRoom (const shared_ptr &chatRoom) { - soci::session *session = dbSession.getBackendSession(); - const ChatRoomId &chatRoomId = chatRoom->getChatRoomId(); const long long &peerSipAddressId = insertSipAddress(chatRoomId.getPeerAddress().asString()); const long long &localSipAddressId = insertSipAddress(chatRoomId.getLocalAddress().asString()); long long id = selectChatRoomId(peerSipAddressId, localSipAddressId); - if (id >= 0) { - lError() << "Unable to insert chat room (it already exists): (peer=" << peerSipAddressId << - ", local=" << localSipAddressId << ")."; + if (id >= 0) return id; - } - lInfo() << "Insert new chat room in database: (peer=" << peerSipAddressId << - ", local=" << localSipAddressId << ")."; + lInfo() << "Insert new chat room in database: " << chatRoomId << "."; const tm &creationTime = Utils::getTimeTAsTm(chatRoom->getCreationTime()); + const tm &lastUpdateTime = Utils::getTimeTAsTm(chatRoom->getLastUpdateTime()); + // Remove capabilities like `Proxy`. const int &capabilities = chatRoom->getCapabilities() & ~ChatRoom::CapabilitiesMask(ChatRoom::Capabilities::Proxy); const string &subject = chatRoom->getSubject(); const int &flags = chatRoom->hasBeenLeft(); - *session << "INSERT INTO chat_room (" + *dbSession.getBackendSession() << "INSERT INTO chat_room (" " peer_sip_address_id, local_sip_address_id, creation_time, last_update_time, capabilities, subject, flags" ") VALUES (:peerSipAddressId, :localSipAddressId, :creationTime, :lastUpdateTime, :capabilities, :subject, :flags)", - soci::use(peerSipAddressId), soci::use(localSipAddressId), soci::use(creationTime), soci::use(creationTime), + soci::use(peerSipAddressId), soci::use(localSipAddressId), soci::use(creationTime), soci::use(lastUpdateTime), soci::use(capabilities), soci::use(subject), soci::use(flags); id = dbSession.getLastInsertId(); @@ -393,8 +383,6 @@ long long MainDbPrivate::insertChatRoomParticipant ( return id; } - lInfo() << "Insert new chat room participant in database: `" << participantSipAddressId << - "` (isAdmin=" << isAdmin << ")."; *session << "INSERT INTO chat_room_participant (chat_room_id, participant_sip_address_id, is_admin)" " VALUES (:chatRoomId, :participantSipAddressId, :isAdmin)", soci::use(chatRoomId), soci::use(participantSipAddressId), soci::use(static_cast(isAdmin)); @@ -415,45 +403,38 @@ void MainDbPrivate::insertChatRoomParticipantDevice ( if (count) return; - lInfo() << "Insert new chat room participant device in database: `" << participantDeviceSipAddressId << "`."; *session << "INSERT INTO chat_room_participant_device (chat_room_participant_id, participant_device_sip_address_id)" " VALUES (:participantId, :participantDeviceSipAddressId)", soci::use(participantId), soci::use(participantDeviceSipAddressId); } -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 != static_cast(ChatMessage::State::Displayed)) - *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); +void MainDbPrivate::insertChatMessageParticipant (long long chatMessageId, long long sipAddressId, int state) { + if (state != static_cast(ChatMessage::State::Displayed)) + *dbSession.getBackendSession() << + "INSERT INTO chat_message_participant (event_id, participant_sip_address_id, state)" + " VALUES (:chatMessageId, :sipAddressId, :state)", + soci::use(chatMessageId), soci::use(sipAddressId), soci::use(state); } // ----------------------------------------------------------------------------- long long MainDbPrivate::selectSipAddressId (const string &sipAddress) const { - soci::session *session = dbSession.getBackendSession(); - long long id; - *session << "SELECT id FROM sip_address WHERE value = :sipAddress", soci::use(sipAddress), soci::into(id); + + soci::session *session = dbSession.getBackendSession(); + *session << Statements::get(Statements::SelectSipAddressId), + soci::use(sipAddress), soci::into(id); + return session->got_data() ? id : -1; } long long MainDbPrivate::selectChatRoomId (long long peerSipAddressId, long long localSipAddressId) const { - soci::session *session = dbSession.getBackendSession(); - long long id; - *session << "SELECT id FROM chat_room" - " WHERE peer_sip_address_id = :peerSipAddressId AND local_sip_address_id = :localSipAddressId", + + soci::session *session = dbSession.getBackendSession(); + *session << Statements::get(Statements::SelectChatRoomId), soci::use(peerSipAddressId), soci::use(localSipAddressId), soci::into(id); + return session->got_data() ? id : -1; } @@ -471,36 +452,35 @@ long long MainDbPrivate::selectChatRoomId (const ChatRoomId &chatRoomId) const { long long MainDbPrivate::selectChatRoomParticipantId (long long chatRoomId, long long participantSipAddressId) const { long long id; + soci::session *session = dbSession.getBackendSession(); - *session << "SELECT id from chat_room_participant" - " WHERE chat_room_id = :chatRoomId AND participant_sip_address_id = :participantSipAddressId", - soci::into(id), soci::use(chatRoomId), soci::use(participantSipAddressId); + *session << Statements::get(Statements::SelectChatRoomParticipantId), + soci::use(chatRoomId), soci::use(participantSipAddressId), soci::into(id); + return session->got_data() ? id : -1; } long long MainDbPrivate::selectOneToOneChatRoomId (long long sipAddressIdA, long long sipAddressIdB) const { long long id; + soci::session *session = dbSession.getBackendSession(); - *session << "SELECT chat_room_id" - " FROM one_to_one_chat_room" - " WHERE participant_a_sip_address_id IN (:sipAddressIdA, :sipAddressIdB)" - " AND participant_b_sip_address_id IN (:sipAddressIdABis, :sipAddressIdBBis)", - soci::into(id), - soci::use(sipAddressIdA), soci::use(sipAddressIdB), soci::use(sipAddressIdA), soci::use(sipAddressIdB); + *session << Statements::get(Statements::SelectOneToOneChatRoomId), + soci::use(sipAddressIdA), soci::use(sipAddressIdB), soci::use(sipAddressIdA), soci::use(sipAddressIdB), + soci::into(id); + return session->got_data() ? id : -1; } // ----------------------------------------------------------------------------- -void MainDbPrivate::deleteContents (long long messageEventId) { - soci::session *session = dbSession.getBackendSession(); - *session << "DELETE FROM chat_message_content WHERE event_id = :messageEventId", soci::use(messageEventId); +void MainDbPrivate::deleteContents (long long chatMessageId) { + *dbSession.getBackendSession() << "DELETE FROM chat_message_content WHERE event_id = :chatMessageId", + soci::use(chatMessageId); } void MainDbPrivate::deleteChatRoomParticipant (long long chatRoomId, long long participantSipAddressId) { - soci::session *session = dbSession.getBackendSession(); - *session << "DELETE FROM chat_room_participant" - " WHERE chat_room_id = :chatRoomId AND participant_sip_address_id = :participantSipAddressId", + *dbSession.getBackendSession() << "DELETE FROM chat_room_participant" + " WHERE chat_room_id = :chatRoomId AND participant_sip_address_id = :participantSipAddressId", soci::use(chatRoomId), soci::use(participantSipAddressId); } @@ -508,55 +488,74 @@ void MainDbPrivate::deleteChatRoomParticipantDevice ( long long participantId, long long participantDeviceSipAddressId ) { - soci::session *session = dbSession.getBackendSession(); - *session << "DELETE FROM chat_room_participant_device" - " WHERE chat_room_participant_id = :participantId" - " AND participant_device_sip_address_id = :participantDeviceSipAddressId", + *dbSession.getBackendSession() << "DELETE FROM chat_room_participant_device" + " WHERE chat_room_participant_id = :participantId" + " AND participant_device_sip_address_id = :participantDeviceSipAddressId", soci::use(participantId), soci::use(participantDeviceSipAddressId); } +// ----------------------------------------------------------------------------- +// Events API. // ----------------------------------------------------------------------------- shared_ptr MainDbPrivate::selectGenericConferenceEvent ( - long long eventId, - EventLog::Type type, - time_t creationTime, - const ChatRoomId &chatRoomId + const shared_ptr &chatRoom, + const soci::row &row ) const { - shared_ptr eventLog; + EventLog::Type type = EventLog::Type(row.get(1)); + if (type == EventLog::Type::ConferenceChatMessage) { + long long eventId = getConferenceEventIdFromRow(row); + shared_ptr eventLog = getEventFromCache(eventId); + if (!eventLog) { + eventLog = selectConferenceChatMessageEvent(chatRoom, type, row); + if (eventLog) + cache(eventLog, eventId); + } + return eventLog; + } + return selectGenericConferenceNotifiedEvent(chatRoom->getChatRoomId(), row); +} + +shared_ptr MainDbPrivate::selectGenericConferenceNotifiedEvent ( + const ChatRoomId &chatRoomId, + const soci::row &row +) const { + long long eventId = getConferenceEventIdFromRow(row); + shared_ptr eventLog = getEventFromCache(eventId); + if (eventLog) + return eventLog; + + EventLog::Type type = EventLog::Type(row.get(1)); switch (type) { case EventLog::Type::None: + case EventLog::Type::ConferenceChatMessage: return nullptr; case EventLog::Type::ConferenceCreated: case EventLog::Type::ConferenceTerminated: - eventLog = selectConferenceEvent(eventId, type, creationTime, chatRoomId); + eventLog = selectConferenceEvent(chatRoomId, type, row); break; case EventLog::Type::ConferenceCallStart: case EventLog::Type::ConferenceCallEnd: - eventLog = selectConferenceCallEvent(eventId, type, creationTime, chatRoomId); - break; - - case EventLog::Type::ConferenceChatMessage: - eventLog = selectConferenceChatMessageEvent(eventId, type, creationTime, chatRoomId); + eventLog = selectConferenceCallEvent(chatRoomId, type, row); break; case EventLog::Type::ConferenceParticipantAdded: case EventLog::Type::ConferenceParticipantRemoved: case EventLog::Type::ConferenceParticipantSetAdmin: case EventLog::Type::ConferenceParticipantUnsetAdmin: - eventLog = selectConferenceParticipantEvent(eventId, type, creationTime, chatRoomId); + eventLog = selectConferenceParticipantEvent(chatRoomId, type, row); break; case EventLog::Type::ConferenceParticipantDeviceAdded: case EventLog::Type::ConferenceParticipantDeviceRemoved: - eventLog = selectConferenceParticipantDeviceEvent(eventId, type, creationTime, chatRoomId); + eventLog = selectConferenceParticipantDeviceEvent(chatRoomId, type, row); break; case EventLog::Type::ConferenceSubjectChanged: - eventLog = selectConferenceSubjectEvent(eventId, type, creationTime, chatRoomId); + eventLog = selectConferenceSubjectEvent(chatRoomId, type, row); break; } @@ -567,255 +566,114 @@ shared_ptr MainDbPrivate::selectGenericConferenceEvent ( } shared_ptr MainDbPrivate::selectConferenceEvent ( - long long, + const ChatRoomId &chatRoomId, EventLog::Type type, - time_t creationTime, - const ChatRoomId &chatRoomId + const soci::row &row ) const { return make_shared( type, - creationTime, + getConferenceEventCreationTimeFromRow(row), chatRoomId ); } shared_ptr MainDbPrivate::selectConferenceCallEvent ( - long long eventId, + const ChatRoomId &chatRoomId, EventLog::Type type, - time_t creationTime, - const ChatRoomId &chatRoomId + const soci::row &row ) const { // TODO. return nullptr; } -template -static void fetchContentAppData (soci::session *session, Content &content, long long contentId, T &data) { - static const string query = "SELECT name, data FROM chat_message_content_app_data" - " WHERE chat_message_content_id = :contentId"; - - string name; - soci::statement statement = (session->prepare << query, soci::use(contentId), soci::into(name), soci::into(data)); - statement.execute(); - while (statement.fetch()) - content.setAppData(name, blobToString(data)); -} - shared_ptr MainDbPrivate::selectConferenceChatMessageEvent ( - long long eventId, + const shared_ptr &chatRoom, EventLog::Type type, - time_t creationTime, - const ChatRoomId &chatRoomId + const soci::row &row ) const { - L_Q(); - - shared_ptr core = q->getCore(); - shared_ptr chatRoom = core->findChatRoom(chatRoomId); - if (!chatRoom) { - lError() << "Unable to find chat room storage id of (peer=" + - chatRoomId.getPeerAddress().asString() + - ", local=" + chatRoomId.getLocalAddress().asString() + "`)."; - return nullptr; - } - - bool hasFileTransferContent = false; - - // 1 - Fetch chat message. + long long eventId = getConferenceEventIdFromRow(row); shared_ptr chatMessage = getChatMessageFromCache(eventId); - if (chatMessage) - goto end; - { - string fromSipAddress; - string toSipAddress; - - tm messageTime; - - string imdnMessageId; - - int state; - int direction; - int isSecured; - - soci::session *session = dbSession.getBackendSession(); - *session << "SELECT from_sip_address.value, to_sip_address.value, time, imdn_message_id, state, direction, is_secured" - " FROM event, conference_chat_message_event, sip_address AS from_sip_address, sip_address AS to_sip_address" - " WHERE event_id = :eventId" - " AND event_id = event.id" - " AND from_sip_address_id = from_sip_address.id" - " AND to_sip_address_id = to_sip_address.id", soci::into(fromSipAddress), soci::into(toSipAddress), - soci::into(messageTime), soci::into(imdnMessageId), soci::into(state), soci::into(direction), - soci::into(isSecured), soci::use(eventId); - + if (!chatMessage) { chatMessage = shared_ptr(new ChatMessage( chatRoom, - static_cast(direction) + ChatMessage::Direction(row.get(8)) )); - chatMessage->setIsSecured(static_cast(isSecured)); + chatMessage->setIsSecured(bool(row.get(9))); ChatMessagePrivate *dChatMessage = chatMessage->getPrivate(); - dChatMessage->setState(static_cast(state), true); + ChatMessage::State messageState = ChatMessage::State(row.get(7)); + // This is necessary if linphone has crashed while sending a message. It will set the correct state so the user can resend it. + if (messageState == ChatMessage::State::Idle || messageState == ChatMessage::State::InProgress) + messageState = ChatMessage::State::NotDelivered; + dChatMessage->setState(messageState, true); - dChatMessage->forceFromAddress(IdentityAddress(fromSipAddress)); - dChatMessage->forceToAddress(IdentityAddress(toSipAddress)); + dChatMessage->forceFromAddress(IdentityAddress(row.get(3))); + dChatMessage->forceToAddress(IdentityAddress(row.get(4))); - dChatMessage->setTime(Utils::getTmAsTimeT(messageTime)); - dChatMessage->setImdnMessageId(imdnMessageId); + dChatMessage->setTime(Utils::getTmAsTimeT(row.get(5))); + dChatMessage->setImdnMessageId(row.get(6)); + + dChatMessage->markContentsAsNotLoaded(); + dChatMessage->setIsReadOnly(true); + + cache(chatMessage, eventId); } - // 2 - Fetch contents. - { - soci::session *session = dbSession.getBackendSession(); - static const string query = "SELECT chat_message_content.id, content_type.id, content_type.value, body" - " FROM chat_message_content, content_type" - " WHERE event_id = :eventId AND content_type_id = content_type.id"; - soci::rowset rows = (session->prepare << query, soci::use(eventId)); - for (const auto &row : rows) { - ContentType contentType(row.get(2)); - const long long &contentId = resolveId(row, 0); - Content *content; - - if (contentType == ContentType::FileTransfer) { - hasFileTransferContent = true; - content = new FileTransferContent(); - } - else if (contentType.isFile()) { - // 2.1 - Fetch contents' file informations. - string name; - int size; - string path; - - *session << "SELECT name, size, path FROM chat_message_file_content" - " WHERE chat_message_content_id = :contentId", - soci::into(name), soci::into(size), soci::into(path), soci::use(contentId); - - FileContent *fileContent = new FileContent(); - fileContent->setFileName(name); - fileContent->setFileSize(static_cast(size)); - fileContent->setFilePath(path); - - content = fileContent; - } else - content = new Content(); - - content->setContentType(contentType); - content->setBody(row.get(3)); - - // 2.2 - Fetch contents' app data. - if (q->getBackend() == MainDb::Backend::Sqlite3) { - soci::blob data(*session); - fetchContentAppData(session, *content, contentId, data); - } else { - string data; - fetchContentAppData(session, *content, contentId, data); - } - chatMessage->addContent(*content); - } - } - - // 3 - Load external body url from body into FileTransferContent if needed. - if (hasFileTransferContent) - chatMessage->getPrivate()->loadFileTransferUrlFromBodyToContent(); - - cache(chatMessage, eventId); - -end: return make_shared( - creationTime, + getConferenceEventCreationTimeFromRow(row), chatMessage ); } shared_ptr MainDbPrivate::selectConferenceParticipantEvent ( - long long eventId, + const ChatRoomId &chatRoomId, EventLog::Type type, - time_t creationTime, - const ChatRoomId &chatRoomId + const soci::row &row ) const { - unsigned int notifyId; - string participantAddress; - - soci::session *session = dbSession.getBackendSession(); - *session << "SELECT notify_id, participant_address.value" - " FROM conference_notified_event, conference_participant_event, sip_address as participant_address" - " WHERE conference_participant_event.event_id = :eventId" - " AND conference_notified_event.event_id = conference_participant_event.event_id" - " AND participant_address.id = participant_sip_address_id", - soci::into(notifyId), soci::into(participantAddress), soci::use(eventId); - return make_shared( type, - creationTime, + getConferenceEventCreationTimeFromRow(row), chatRoomId, - notifyId, - IdentityAddress(participantAddress) + getConferenceEventNotifyIdFromRow(row), + IdentityAddress(row.get(12)) ); } shared_ptr MainDbPrivate::selectConferenceParticipantDeviceEvent ( - long long eventId, + const ChatRoomId &chatRoomId, EventLog::Type type, - time_t creationTime, - const ChatRoomId &chatRoomId + const soci::row &row ) const { - unsigned int notifyId; - string participantAddress; - string deviceAddress; - - soci::session *session = dbSession.getBackendSession(); - *session << "SELECT notify_id, participant_address.value, device_address.value" - " FROM conference_notified_event, conference_participant_event, conference_participant_device_event," - " sip_address AS participant_address, sip_address AS device_address" - " WHERE conference_participant_device_event.event_id = :eventId" - " AND conference_participant_event.event_id = conference_participant_device_event.event_id" - " AND conference_notified_event.event_id = conference_participant_event.event_id" - " AND participant_address.id = participant_sip_address_id" - " AND device_address.id = device_sip_address_id", - soci::into(notifyId), soci::into(participantAddress), soci::into(deviceAddress), soci::use(eventId); - return make_shared( type, - creationTime, + getConferenceEventCreationTimeFromRow(row), chatRoomId, - notifyId, - IdentityAddress(participantAddress), - IdentityAddress(deviceAddress) + getConferenceEventNotifyIdFromRow(row), + IdentityAddress(row.get(12)), + IdentityAddress(row.get(11)) ); } shared_ptr MainDbPrivate::selectConferenceSubjectEvent ( - long long eventId, + const ChatRoomId &chatRoomId, EventLog::Type type, - time_t creationTime, - const ChatRoomId &chatRoomId + const soci::row &row ) const { - unsigned int notifyId; - string subject; - - soci::session *session = dbSession.getBackendSession(); - *session << "SELECT notify_id, subject" - " FROM conference_notified_event, conference_subject_event" - " WHERE conference_subject_event.event_id = :eventId" - " AND conference_notified_event.event_id = conference_subject_event.event_id", - soci::into(notifyId), soci::into(subject), soci::use(eventId); - return make_shared( - creationTime, + getConferenceEventCreationTimeFromRow(row), chatRoomId, - notifyId, - subject + getConferenceEventNotifyIdFromRow(row), + row.get(13) ); } // ----------------------------------------------------------------------------- long long MainDbPrivate::insertEvent (const shared_ptr &eventLog) { - soci::session *session = dbSession.getBackendSession(); - - const int &type = static_cast(eventLog->getType()); + const int &type = int(eventLog->getType()); const tm &creationTime = Utils::getTimeTAsTm(eventLog->getCreationTime()); - *session << "INSERT INTO event (type, creation_time) VALUES (:type, :creationTime)", - soci::use(type), - soci::use(creationTime); + *dbSession.getBackendSession() << "INSERT INTO event (type, creation_time) VALUES (:type, :creationTime)", + soci::use(type), soci::use(creationTime); return dbSession.getLastInsertId(); } @@ -829,9 +687,7 @@ long long MainDbPrivate::insertConferenceEvent (const shared_ptr &even // A conference event can be inserted in database only if chat room exists. // Otherwise it's an error. const ChatRoomId &chatRoomId = conferenceEvent->getChatRoomId(); - lError() << "Unable to find chat room storage id of (peer=" + - chatRoomId.getPeerAddress().asString() + - ", local=" + chatRoomId.getLocalAddress().asString() + "`)."; + lError() << "Unable to find chat room storage id of: " << chatRoomId << "."; } else { eventId = insertEvent(eventLog); @@ -860,28 +716,20 @@ long long MainDbPrivate::insertConferenceCallEvent (const shared_ptr & } long long MainDbPrivate::insertConferenceChatMessageEvent (const shared_ptr &eventLog) { - shared_ptr chatMessage = static_pointer_cast(eventLog)->getChatMessage(); - shared_ptr chatRoom = chatMessage->getChatRoom(); - if (!chatRoom) { - lError() << "Unable to get a valid chat room. It was removed from database."; - return -1; - } - const long long &eventId = insertConferenceEvent(eventLog); if (eventId < 0) return -1; - soci::session *session = dbSession.getBackendSession(); - + shared_ptr chatMessage = static_pointer_cast(eventLog)->getChatMessage(); const long long &fromSipAddressId = insertSipAddress(chatMessage->getFromAddress().asString()); const long long &toSipAddressId = insertSipAddress(chatMessage->getToAddress().asString()); const tm &messageTime = Utils::getTimeTAsTm(chatMessage->getTime()); - const int &state = static_cast(chatMessage->getState()); - const int &direction = static_cast(chatMessage->getDirection()); + const int &state = int(chatMessage->getState()); + const int &direction = int(chatMessage->getDirection()); const string &imdnMessageId = chatMessage->getImdnMessageId(); const int &isSecured = chatMessage->isSecured() ? 1 : 0; - *session << "INSERT INTO conference_chat_message_event (" + *dbSession.getBackendSession() << "INSERT INTO conference_chat_message_event (" " event_id, from_sip_address_id, to_sip_address_id," " time, state, direction, imdn_message_id, is_secured" ") VALUES (" @@ -894,25 +742,24 @@ long long MainDbPrivate::insertConferenceChatMessageEvent (const shared_ptrgetContents()) insertContent(eventId, *content); + for (const auto &participant : chatMessage->getChatRoom()->getParticipants()) { + const long long &participantSipAddressId = selectSipAddressId(participant->getAddress().asString()); + insertChatMessageParticipant(eventId, participantSipAddressId, state); + } + return eventId; } void MainDbPrivate::updateConferenceChatMessageEvent (const shared_ptr &eventLog) { shared_ptr chatMessage = static_pointer_cast(eventLog)->getChatMessage(); - shared_ptr chatRoom = chatMessage->getChatRoom(); - if (!chatRoom) { - lError() << "Unable to get a valid chat room. It was removed from database."; - return; - } const EventLogPrivate *dEventLog = eventLog->getPrivate(); MainDbKeyPrivate *dEventKey = static_cast(dEventLog->dbKey).getPrivate(); const long long &eventId = dEventKey->storageId; - soci::session *session = dbSession.getBackendSession(); - const int &state = static_cast(chatMessage->getState()); + const int &state = int(chatMessage->getState()); const string &imdnMessageId = chatMessage->getImdnMessageId(); - *session << "UPDATE conference_chat_message_event SET state = :state, imdn_message_id = :imdnMessageId" + *dbSession.getBackendSession() << "UPDATE conference_chat_message_event SET state = :state, imdn_message_id = :imdnMessageId" " WHERE event_id = :eventId", soci::use(state), soci::use(imdnMessageId), soci::use(eventId); @@ -957,9 +804,8 @@ long long MainDbPrivate::insertConferenceParticipantEvent ( participantEvent->getParticipantAddress().asString() ); - soci::session *session = dbSession.getBackendSession(); - *session << "INSERT INTO conference_participant_event (event_id, participant_sip_address_id)" - " VALUES (:eventId, :participantAddressId)", soci::use(eventId), soci::use(participantAddressId); + *dbSession.getBackendSession() << "INSERT INTO conference_participant_event (event_id, participant_sip_address_id)" + " VALUES (:eventId, :participantAddressId)", soci::use(eventId), soci::use(participantAddressId); bool isAdmin = eventLog->getType() == EventLog::Type::ConferenceParticipantSetAdmin; switch (eventLog->getType()) { @@ -1001,15 +847,14 @@ long long MainDbPrivate::insertConferenceParticipantDeviceEvent (const shared_pt const long long &participantId = selectChatRoomParticipantId(chatRoomId, participantAddressId); if (participantId < 0) { lError() << "Unable to find valid participant id in database with chat room id = " << chatRoomId << - " and participant address id = " << participantId; + " and participant address id = " << participantAddressId; return -1; } const long long &deviceAddressId = insertSipAddress( participantDeviceEvent->getDeviceAddress().asString() ); - soci::session *session = dbSession.getBackendSession(); - *session << "INSERT INTO conference_participant_device_event (event_id, device_sip_address_id)" + *dbSession.getBackendSession() << "INSERT INTO conference_participant_device_event (event_id, device_sip_address_id)" " VALUES (:eventId, :deviceAddressId)", soci::use(eventId), soci::use(deviceAddressId); switch (eventLog->getType()) { @@ -1046,6 +891,8 @@ long long MainDbPrivate::insertConferenceSubjectEvent (const shared_ptr MainDbPrivate::getEventFromCache (long long storageId) const { @@ -1054,7 +901,6 @@ shared_ptr MainDbPrivate::getEventFromCache (long long storageId) cons return nullptr; shared_ptr eventLog = it->second.lock(); - // Must exist. If not, implementation bug. L_ASSERT(eventLog); return eventLog; } @@ -1065,7 +911,6 @@ shared_ptr MainDbPrivate::getChatMessageFromCache (long long storag return nullptr; shared_ptr chatMessage = it->second.lock(); - // Must exist. If not, implementation bug. L_ASSERT(chatMessage); return chatMessage; } @@ -1091,17 +936,15 @@ void MainDbPrivate::cache (const shared_ptr &chatMessage, long long } void MainDbPrivate::invalidConferenceEventsFromQuery (const string &query, long long chatRoomId) { - soci::session *session = dbSession.getBackendSession(); - soci::rowset rows = (session->prepare << query, soci::use(chatRoomId)); + soci::rowset rows = (dbSession.getBackendSession()->prepare << query, soci::use(chatRoomId)); for (const auto &row : rows) { - long long eventId = resolveId(row, 0); + long long eventId = dbSession.resolveId(row, 0); shared_ptr eventLog = getEventFromCache(eventId); if (eventLog) { const EventLogPrivate *dEventLog = eventLog->getPrivate(); L_ASSERT(dEventLog->dbKey.isValid()); dEventLog->dbKey = MainDbEventKey(); } - // TODO: Try to add a better code here... shared_ptr chatMessage = getChatMessageFromCache(eventId); if (chatMessage) { const ChatMessagePrivate *dChatMessage = chatMessage->getPrivate(); @@ -1111,6 +954,8 @@ void MainDbPrivate::invalidConferenceEventsFromQuery (const string &query, long } } +// ----------------------------------------------------------------------------- +// Versions. // ----------------------------------------------------------------------------- unsigned int MainDbPrivate::getModuleVersion (const string &name) { @@ -1131,75 +976,53 @@ void MainDbPrivate::updateModuleVersion (const string &name, unsigned int versio soci::use(name), soci::use(version); } +void MainDbPrivate::updateSchema () { + soci::session *session = dbSession.getBackendSession(); + unsigned int version = getModuleVersion("events"); + + if (version < makeVersion(1, 0, 1)) + *session << "ALTER TABLE chat_room_participant_device ADD COLUMN state TINYINT UNSIGNED DEFAULT 0"; +} + +// ----------------------------------------------------------------------------- +// Import. // ----------------------------------------------------------------------------- -#define CHECK_LEGACY_TABLE_EXISTS(SESSION, NAME) \ - do { \ - SESSION << "SELECT name FROM sqlite_master WHERE type='table' AND name='" NAME "'"; \ - return SESSION.got_data() > 0; \ - } while (false); +static inline bool checkLegacyTableExists (soci::session &session, const string &name) { + session << "SELECT name FROM sqlite_master WHERE type='table' AND name = :name", soci::use(name); + return session.got_data() > 0; +} static inline bool checkLegacyFriendsTableExists (soci::session &session) { - CHECK_LEGACY_TABLE_EXISTS(session, "friends"); + return checkLegacyTableExists(session, "friends"); } static inline bool checkLegacyHistoryTableExists (soci::session &session) { - CHECK_LEGACY_TABLE_EXISTS(session, "history"); + return checkLegacyTableExists(session, "history"); } -template -static T getValueFromRow (const soci::row &row, int index, bool &isNull) { - isNull = false; - - try { - return row.get(static_cast(index)); - } catch (const exception &) { - isNull = true; - } - - return T(); -} - -// ----------------------------------------------------------------------------- - -#define LEGACY_FRIEND_LIST_COL_ID 0 -#define LEGACY_FRIEND_LIST_COL_NAME 1 -#define LEGACY_FRIEND_LIST_COL_RLS_URI 2 -#define LEGACY_FRIEND_LIST_COL_SYNC_URI 3 -#define LEGACY_FRIEND_LIST_COL_REVISION 4 - -#define LEGACY_FRIEND_COL_FRIEND_LIST_ID 1 -#define LEGACY_FRIEND_COL_SIP_ADDRESS 2 -#define LEGACY_FRIEND_COL_SUBSCRIBE_POLICY 3 -#define LEGACY_FRIEND_COL_SEND_SUBSCRIBE 4 -#define LEGACY_FRIEND_COL_REF_KEY 5 -#define LEGACY_FRIEND_COL_V_CARD 6 -#define LEGACY_FRIEND_COL_V_CARD_ETAG 7 -#define LEGACY_FRIEND_COL_V_CARD_SYNC_URI 8 -#define LEGACY_FRIEND_COL_PRESENCE_RECEIVED 9 - void MainDbPrivate::importLegacyFriends (DbSession &inDbSession) { - soci::session *inSession = inDbSession.getBackendSession(); - soci::transaction tr(*dbSession.getBackendSession()); + L_Q(); + L_DB_TRANSACTION_C(q) { + if (getModuleVersion("legacy-friends-import") >= makeVersion(1, 0, 0)) + return; + updateModuleVersion("legacy-friends-import", ModuleVersionLegacyFriendsImport); - if (getModuleVersion("legacy-friends-import") >= L_VERSION(1, 0, 0)) - return; - updateModuleVersion("legacy-friends-import", DB_MODULE_VERSION_LEGACY_FRIENDS_IMPORT); + soci::session *inSession = inDbSession.getBackendSession(); + if (!checkLegacyFriendsTableExists(*inSession)) + return; - if (!checkLegacyFriendsTableExists(*inSession)) - return; + unordered_map resolvedListsIds; + soci::session *session = dbSession.getBackendSession(); - unordered_map resolvedListsIds; - soci::session *session = dbSession.getBackendSession(); + soci::rowset friendsLists = (inSession->prepare << "SELECT * FROM friends_lists"); - soci::rowset friendsLists = (inSession->prepare << "SELECT * FROM friends_lists"); - try { set names; for (const auto &friendList : friendsLists) { - const string &name = friendList.get(LEGACY_FRIEND_LIST_COL_NAME, ""); - const string &rlsUri = friendList.get(LEGACY_FRIEND_LIST_COL_RLS_URI, ""); - const string &syncUri = friendList.get(LEGACY_FRIEND_LIST_COL_SYNC_URI, ""); - const int &revision = friendList.get(LEGACY_FRIEND_LIST_COL_REVISION, 0); + const string &name = friendList.get(LegacyFriendListColName, ""); + const string &rlsUri = friendList.get(LegacyFriendListColRlsUri, ""); + const string &syncUri = friendList.get(LegacyFriendListColSyncUri, ""); + const int &revision = friendList.get(LegacyFriendListColRevision, 0); string uniqueName = name; for (int id = 0; names.find(uniqueName) != names.end(); uniqueName = name + "-" + Utils::toString(id++)); @@ -1208,31 +1031,26 @@ void MainDbPrivate::importLegacyFriends (DbSession &inDbSession) { *session << "INSERT INTO friends_list (name, rls_uri, sync_uri, revision) VALUES (" " :name, :rlsUri, :syncUri, :revision" ")", soci::use(uniqueName), soci::use(rlsUri), soci::use(syncUri), soci::use(revision); - resolvedListsIds[friendList.get(LEGACY_FRIEND_LIST_COL_ID)] = dbSession.getLastInsertId(); + resolvedListsIds[friendList.get(LegacyFriendListColId)] = dbSession.getLastInsertId(); } - } catch (const exception &e) { - lWarning() << "Failed to import legacy friends list: " << e.what() << "."; - return; - } - soci::rowset friends = (inSession->prepare << "SELECT * FROM friends"); - try { + soci::rowset friends = (inSession->prepare << "SELECT * FROM friends"); for (const auto &friendInfo : friends) { long long friendsListId; { - auto it = resolvedListsIds.find(friendInfo.get(LEGACY_FRIEND_COL_FRIEND_LIST_ID, -1)); + auto it = resolvedListsIds.find(friendInfo.get(LegacyFriendColFriendListId, -1)); if (it == resolvedListsIds.end()) continue; friendsListId = it->second; } - const long long &sipAddressId = insertSipAddress(friendInfo.get(LEGACY_FRIEND_COL_SIP_ADDRESS, "")); - const int &subscribePolicy = friendInfo.get(LEGACY_FRIEND_COL_SUBSCRIBE_POLICY, LinphoneSPAccept); - const int &sendSubscribe = friendInfo.get(LEGACY_FRIEND_COL_SEND_SUBSCRIBE, 1); - const string &vCard = friendInfo.get(LEGACY_FRIEND_COL_V_CARD, ""); - const string &vCardEtag = friendInfo.get(LEGACY_FRIEND_COL_V_CARD_ETAG, ""); - const string &vCardSyncUri = friendInfo.get(LEGACY_FRIEND_COL_V_CARD_SYNC_URI, ""); - const int &presenceReveived = friendInfo.get(LEGACY_FRIEND_COL_PRESENCE_RECEIVED, 0); + const long long &sipAddressId = insertSipAddress(friendInfo.get(LegacyFriendColSipAddress, "")); + const int &subscribePolicy = friendInfo.get(LegacyFriendColSubscribePolicy, LinphoneSPAccept); + const int &sendSubscribe = friendInfo.get(LegacyFriendColSendSubscribe, 1); + const string &vCard = friendInfo.get(LegacyFriendColVCard, ""); + const string &vCardEtag = friendInfo.get(LegacyFriendColVCardEtag, ""); + const string &vCardSyncUri = friendInfo.get(LegacyFriendColVCardSyncUri, ""); + const int &presenceReveived = friendInfo.get(LegacyFriendColPresenceReceived, 0); *session << "INSERT INTO friend (" " sip_address_id, friends_list_id, subscribe_policy, send_subscribe," @@ -1244,70 +1062,92 @@ void MainDbPrivate::importLegacyFriends (DbSession &inDbSession) { soci::use(presenceReveived), soci::use(vCard), soci::use(vCardEtag), soci::use(vCardSyncUri); bool isNull; - const string &data = getValueFromRow(friendInfo, LEGACY_FRIEND_COL_REF_KEY, isNull); + const string &data = getValueFromRow(friendInfo, LegacyFriendColRefKey, isNull); if (!isNull) *session << "INSERT INTO friend_app_data (friend_id, name, data) VALUES" " (:friendId, 'legacy', :data)", soci::use(dbSession.getLastInsertId()), soci::use(data); } tr.commit(); - } catch (const exception &e) { - lWarning() << "Failed to import legacy friends: " << e.what() << "."; - return; - } - - lInfo() << "Successful import of legacy friends."; + lInfo() << "Successful import of legacy friends."; + }; } -#define LEGACY_MESSAGE_COL_LOCAL_ADDRESS 1 -#define LEGACY_MESSAGE_COL_REMOTE_ADDRESS 2 -#define LEGACY_MESSAGE_COL_DIRECTION 3 -#define LEGACY_MESSAGE_COL_TEXT 4 -#define LEGACY_MESSAGE_COL_STATE 7 -#define LEGACY_MESSAGE_COL_URL 8 -#define LEGACY_MESSAGE_COL_DATE 9 -#define LEGACY_MESSAGE_COL_APP_DATA 10 -#define LEGACY_MESSAGE_COL_CONTENT_ID 11 -#define LEGACY_MESSAGE_COL_IMDN_MESSAGE_ID 12 -#define LEGACY_MESSAGE_COL_CONTENT_TYPE 13 -#define LEGACY_MESSAGE_COL_IS_SECURED 14 +// TODO: Move in a helper file? With others xml. +struct XmlCharObjectDeleter { + void operator() (void *ptr) const { + xmlFree(ptr); + } +}; +using XmlCharObject = unique_ptr; + +struct XmlDocObjectDeleter { + void operator() (xmlDocPtr ptr) const { + xmlFreeDoc(ptr); + } +}; +using XmlDocObject = unique_ptr::type, XmlDocObjectDeleter>; + +typedef const xmlChar* XmlCharPtr; + +static string extractLegacyFileContentType (const string &xml) { + XmlDocObject xmlMessageBody(xmlParseDoc(XmlCharPtr(xml.c_str()))); + xmlNodePtr xmlElement = xmlDocGetRootElement(xmlMessageBody.get()); + if (!xmlElement) + return ""; + + for (xmlElement = xmlElement->xmlChildrenNode; xmlElement; xmlElement = xmlElement->next) { + if (xmlStrcmp(xmlElement->name, XmlCharPtr("file-info"))) + continue; + + XmlCharObject typeAttribute(xmlGetProp(xmlElement, XmlCharPtr("type"))); + if (xmlStrcmp(typeAttribute.get(), XmlCharPtr("file"))) + continue; + + for (xmlElement = xmlElement->xmlChildrenNode; xmlElement; xmlElement = xmlElement->next) + if (!xmlStrcmp(xmlElement->name, XmlCharPtr("content-type"))) { + XmlCharObject xmlContentType(xmlNodeListGetString(xmlMessageBody.get(), xmlElement->xmlChildrenNode, 1)); + return ContentType(reinterpret_cast(xmlContentType.get())).asString(); + } + } + + return ""; +} void MainDbPrivate::importLegacyHistory (DbSession &inDbSession) { - soci::session *inSession = inDbSession.getBackendSession(); - soci::transaction tr(*dbSession.getBackendSession()); + L_Q(); + L_DB_TRANSACTION_C(q) { + if (getModuleVersion("legacy-history-import") >= makeVersion(1, 0, 0)) + return; + updateModuleVersion("legacy-history-import", ModuleVersionLegacyHistoryImport); - unsigned int version = getModuleVersion("legacy-history-import"); - if (version >= L_VERSION(1, 0, 0)) - return; - updateModuleVersion("legacy-history-import", DB_MODULE_VERSION_LEGACY_HISTORY_IMPORT); + soci::session *inSession = inDbSession.getBackendSession(); + if (!checkLegacyHistoryTableExists(*inSession)) + return; - if (!checkLegacyHistoryTableExists(*inSession)) - return; - - soci::rowset messages = (inSession->prepare << "SELECT * FROM history"); - try { + soci::rowset messages = (inSession->prepare << "SELECT * FROM history"); for (const auto &message : messages) { - const int direction = message.get(LEGACY_MESSAGE_COL_DIRECTION); + const int direction = message.get(LegacyMessageColDirection); if (direction != 0 && direction != 1) { lWarning() << "Unable to import legacy message with invalid direction."; continue; } const int &state = message.get( - LEGACY_MESSAGE_COL_STATE, static_cast(ChatMessage::State::Displayed) + LegacyMessageColState, int(ChatMessage::State::Displayed) ); - if (state < 0 || state > static_cast(ChatMessage::State::Displayed)) { + if (state < 0 || state > int(ChatMessage::State::Displayed)) { lWarning() << "Unable to import legacy message with invalid state."; continue; } - const tm &creationTime = Utils::getTimeTAsTm(message.get(LEGACY_MESSAGE_COL_DATE, 0)); + const tm &creationTime = Utils::getTimeTAsTm(message.get(LegacyMessageColDate, 0)); bool isNull; - getValueFromRow(message, LEGACY_MESSAGE_COL_URL, isNull); + getValueFromRow(message, LegacyMessageColUrl, isNull); - const int &contentId = message.get(LEGACY_MESSAGE_COL_CONTENT_ID, -1); - ContentType contentType(message.get(LEGACY_MESSAGE_COL_CONTENT_TYPE, "")); + const int &contentId = message.get(LegacyMessageColContentId, -1); + ContentType contentType(message.get(LegacyMessageColContentType, "")); if (!contentType.isValid()) contentType = contentId != -1 ? ContentType::FileTransfer @@ -1317,48 +1157,62 @@ void MainDbPrivate::importLegacyHistory (DbSession &inDbSession) { continue; } - const string &text = getValueFromRow(message, LEGACY_MESSAGE_COL_TEXT, isNull); + const string &text = getValueFromRow(message, LegacyMessageColText, isNull); - Content content; - content.setContentType(contentType); - if (contentType == ContentType::PlainText) { - if (isNull) { - lWarning() << "Unable to import legacy message with no text."; - continue; - } - content.setBody(text); - } else { - if (contentType != ContentType::FileTransfer) { - lWarning() << "Unable to import unsupported legacy content."; - continue; - } - - const string appData = getValueFromRow(message, LEGACY_MESSAGE_COL_APP_DATA, isNull); + unique_ptr content; + if (contentType == ContentType::FileTransfer) { + const string appData = getValueFromRow(message, LegacyMessageColAppData, isNull); if (isNull) { lWarning() << "Unable to import legacy file message without app data."; continue; } - content.setAppData("legacy", appData); + string contentTypeString = extractLegacyFileContentType(text); + if (contentTypeString.empty()) { + lWarning() << "Unable to extract file content type form legacy transfer message"; + continue; + } + ContentType fileContentType(contentTypeString); + content.reset(new FileContent()); + content->setContentType(fileContentType); + content->setAppData("legacy", appData); + content->setBody(text); + } else { + content.reset(new Content()); + content->setContentType(contentType); + if (contentType == ContentType::PlainText) { + if (isNull) { + lWarning() << "Unable to import legacy message with no text."; + continue; + } + content->setBody(text); + } else { + lWarning() << "Unable to import unsupported legacy content."; + continue; + } } soci::session *session = dbSession.getBackendSession(); - const int &eventType = static_cast(EventLog::Type::ConferenceChatMessage); + const int &eventType = int(EventLog::Type::ConferenceChatMessage); *session << "INSERT INTO event (type, creation_time) VALUES (:type, :creationTime)", soci::use(eventType), soci::use(creationTime); const long long &eventId = dbSession.getLastInsertId(); - const long long &localSipAddressId = insertSipAddress(message.get(LEGACY_MESSAGE_COL_LOCAL_ADDRESS)); - const long long &remoteSipAddressId = insertSipAddress(message.get(LEGACY_MESSAGE_COL_REMOTE_ADDRESS)); + const long long &localSipAddressId = insertSipAddress( + IdentityAddress(message.get(LegacyMessageColLocalAddress)).asString() + ); + const long long &remoteSipAddressId = insertSipAddress( + IdentityAddress(message.get(LegacyMessageColRemoteAddress)).asString() + ); const long long &chatRoomId = insertOrUpdateImportedBasicChatRoom( remoteSipAddressId, localSipAddressId, creationTime ); - const int &isSecured = message.get(LEGACY_MESSAGE_COL_IS_SECURED, 0); + const int &isSecured = message.get(LegacyMessageColIsSecured, 0); *session << "INSERT INTO conference_event (event_id, chat_room_id)" - " VALUES (:eventId, :chatRoomId)", soci::use(eventId), soci::use(chatRoomId); + " VALUES (:eventId, :chatRoomId)", soci::use(eventId), soci::use(chatRoomId); *session << "INSERT INTO conference_chat_message_event (" " event_id, from_sip_address_id, to_sip_address_id," @@ -1370,35 +1224,35 @@ void MainDbPrivate::importLegacyHistory (DbSession &inDbSession) { soci::use(creationTime), soci::use(state), soci::use(direction), soci::use(isSecured); - insertContent(eventId, content); + if (content) + insertContent(eventId, *content); insertChatRoomParticipant(chatRoomId, remoteSipAddressId, false); - if (state != static_cast(ChatMessage::State::Displayed)) + if (state != int(ChatMessage::State::Displayed)) insertChatMessageParticipant(eventId, remoteSipAddressId, state); } - tr.commit(); - } catch (const exception &e) { - lInfo() << "Failed to import legacy messages: " << e.what() << "."; - return; - } - - lInfo() << "Successful import of legacy messages."; + lInfo() << "Successful import of legacy messages."; + }; } -// ----------------------------------------------------------------------------- +// ============================================================================= + +MainDb::MainDb (const shared_ptr &core) : AbstractDb(*new MainDbPrivate), CoreAccessor(core) {} void MainDb::init () { L_D(); - const string charset = getBackend() == Mysql ? "DEFAULT CHARSET=utf8" : ""; + Backend backend = getBackend(); + + const string charset = backend == Mysql ? "DEFAULT CHARSET=utf8" : ""; soci::session *session = d->dbSession.getBackendSession(); using namespace placeholders; - auto primaryKeyRefStr = bind(&DbSession::primaryKeyRefStr, d->dbSession, _1); - auto primaryKeyStr = bind(&DbSession::primaryKeyStr, d->dbSession, _1); - auto timestampType = bind(&DbSession::timestampType, d->dbSession); - auto varcharPrimaryKeyStr = bind(&DbSession::varcharPrimaryKeyStr, d->dbSession, _1); + auto primaryKeyRefStr = bind(&DbSession::primaryKeyRefStr, &d->dbSession, _1); + auto primaryKeyStr = bind(&DbSession::primaryKeyStr, &d->dbSession, _1); + auto timestampType = bind(&DbSession::timestampType, &d->dbSession); + auto varcharPrimaryKeyStr = bind(&DbSession::varcharPrimaryKeyStr, &d->dbSession, _1); *session << "CREATE TABLE IF NOT EXISTS sip_address (" @@ -1454,7 +1308,6 @@ void MainDb::init () { " ON DELETE CASCADE" ") " + charset; - if (linphone_core_conference_server_enabled(getCore()->getCCore())) { *session << "CREATE TABLE IF NOT EXISTS one_to_one_chat_room (" " chat_room_id" + primaryKeyStr("BIGINT UNSIGNED") + "," @@ -1472,7 +1325,6 @@ void MainDb::init () { " REFERENCES sip_address(id)" " ON DELETE CASCADE" ") " + charset; - } *session << "CREATE TABLE IF NOT EXISTS chat_room_participant (" @@ -1672,32 +1524,6 @@ void MainDb::init () { " ON DELETE CASCADE" ") " + charset; - // Trigger to delete participant_message cache entries. - // TODO: Fix me in the future. (Problem on Mysql backend.) - #if 0 - string displayedId = Utils::toString(static_cast(ChatMessage::State::Displayed)); - string participantMessageDeleter = - "CREATE TRIGGER IF NOT EXISTS chat_message_participant_deleter" - " AFTER UPDATE OF state ON chat_message_participant FOR EACH ROW" - " WHEN NEW.state = "; - participantMessageDeleter += displayedId; - participantMessageDeleter += " AND (SELECT COUNT(*) FROM (" - " SELECT state FROM chat_message_participant WHERE" - " NEW.event_id = chat_message_participant.event_id" - " AND state <> "; - participantMessageDeleter += displayedId; - participantMessageDeleter += " LIMIT 1" - " )) = 0" - " BEGIN" - " DELETE FROM chat_message_participant WHERE NEW.event_id = chat_message_participant.event_id;" - " UPDATE conference_chat_message_event SET state = "; - participantMessageDeleter += displayedId; - participantMessageDeleter += " WHERE event_id = NEW.event_id;" - " END"; - - *session << participantMessageDeleter; - #endif - *session << "CREATE TABLE IF NOT EXISTS friends_list (" " id" + primaryKeyStr("INT UNSIGNED") + "," @@ -1744,29 +1570,66 @@ void MainDb::init () { " ON DELETE CASCADE" ") " + charset; + { + string query; + if (getBackend() == Backend::Mysql) + query = "CREATE OR REPLACE VIEW conference_event_view AS"; + else + query = "CREATE VIEW IF NOT EXISTS conference_event_view AS"; + + *session << query + + " SELECT id, type, creation_time, chat_room_id, from_sip_address_id, to_sip_address_id, time, imdn_message_id, state, direction, is_secured, notify_id, device_sip_address_id, participant_sip_address_id, subject" + " FROM event" + " LEFT JOIN conference_event ON conference_event.event_id = event.id" + " LEFT JOIN conference_chat_message_event ON conference_chat_message_event.event_id = event.id" + " LEFT JOIN conference_notified_event ON conference_notified_event.event_id = event.id" + " LEFT JOIN conference_participant_device_event ON conference_participant_device_event.event_id = event.id" + " LEFT JOIN conference_participant_event ON conference_participant_event.event_id = event.id" + " LEFT JOIN conference_subject_event ON conference_subject_event.event_id = event.id"; + } + *session << "CREATE TABLE IF NOT EXISTS db_module_version (" " name" + varcharPrimaryKeyStr(255) + "," " version INT UNSIGNED NOT NULL" ") " + charset; - d->updateModuleVersion("events", DB_MODULE_VERSION_EVENTS); - d->updateModuleVersion("friends", DB_MODULE_VERSION_FRIENDS); + if (getBackend() == Backend::Mysql) { + *session << + "DROP TRIGGER IF EXISTS chat_message_participant_deleter"; + *session << + "CREATE TRIGGER chat_message_participant_deleter" + " AFTER UPDATE ON conference_chat_message_event FOR EACH ROW" + " BEGIN" + " IF NEW.state = " + Utils::toString(int(ChatMessage::State::Displayed)) + " THEN" + " DELETE FROM chat_message_participant WHERE event_id = NEW.event_id;" + " END IF;" + " END "; + } else + *session << + "CREATE TRIGGER IF NOT EXISTS chat_message_participant_deleter" + " AFTER UPDATE OF state ON conference_chat_message_event FOR EACH ROW" + " WHEN NEW.state = " + Utils::toString(int(ChatMessage::State::Displayed)) + + " BEGIN" + " DELETE FROM chat_message_participant WHERE event_id = NEW.event_id;" + " END "; + + d->updateSchema(); + + d->updateModuleVersion("events", ModuleVersionEvents); + d->updateModuleVersion("friends", ModuleVersionFriends); } bool MainDb::addEvent (const shared_ptr &eventLog) { - const EventLogPrivate *dEventLog = eventLog->getPrivate(); - if (dEventLog->dbKey.isValid()) { + if (eventLog->getPrivate()->dbKey.isValid()) { lWarning() << "Unable to add an event twice!!!"; return false; } - return L_SAFE_TRANSACTION { + return L_DB_TRANSACTION { L_D(); - long long storageId = 0; - - soci::transaction tr(*d->dbSession.getBackendSession()); + long long eventId = -1; EventLog::Type type = eventLog->getType(); switch (type) { @@ -1775,41 +1638,41 @@ bool MainDb::addEvent (const shared_ptr &eventLog) { case EventLog::Type::ConferenceCreated: case EventLog::Type::ConferenceTerminated: - storageId = d->insertConferenceEvent(eventLog); + eventId = d->insertConferenceEvent(eventLog); break; case EventLog::Type::ConferenceCallStart: case EventLog::Type::ConferenceCallEnd: - storageId = d->insertConferenceCallEvent(eventLog); + eventId = d->insertConferenceCallEvent(eventLog); break; case EventLog::Type::ConferenceChatMessage: - storageId = d->insertConferenceChatMessageEvent(eventLog); + eventId = d->insertConferenceChatMessageEvent(eventLog); break; case EventLog::Type::ConferenceParticipantAdded: case EventLog::Type::ConferenceParticipantRemoved: case EventLog::Type::ConferenceParticipantSetAdmin: case EventLog::Type::ConferenceParticipantUnsetAdmin: - storageId = d->insertConferenceParticipantEvent(eventLog); + eventId = d->insertConferenceParticipantEvent(eventLog); break; case EventLog::Type::ConferenceParticipantDeviceAdded: case EventLog::Type::ConferenceParticipantDeviceRemoved: - storageId = d->insertConferenceParticipantDeviceEvent(eventLog); + eventId = d->insertConferenceParticipantDeviceEvent(eventLog); break; case EventLog::Type::ConferenceSubjectChanged: - storageId = d->insertConferenceSubjectEvent(eventLog); + eventId = d->insertConferenceSubjectEvent(eventLog); break; } - if (storageId >= 0) { + if (eventId >= 0) { tr.commit(); - d->cache(eventLog, storageId); + d->cache(eventLog, eventId); if (type == EventLog::Type::ConferenceChatMessage) - d->cache(static_pointer_cast(eventLog)->getChatMessage(), storageId); + d->cache(static_pointer_cast(eventLog)->getChatMessage(), eventId); return true; } @@ -1819,17 +1682,14 @@ bool MainDb::addEvent (const shared_ptr &eventLog) { } bool MainDb::updateEvent (const shared_ptr &eventLog) { - const EventLogPrivate *dEventLog = eventLog->getPrivate(); - if (!dEventLog->dbKey.isValid()) { + if (!eventLog->getPrivate()->dbKey.isValid()) { lWarning() << "Unable to update an event that wasn't inserted yet!!!"; return false; } - return L_SAFE_TRANSACTION { + return L_DB_TRANSACTION { L_D(); - soci::transaction tr(*d->dbSession.getBackendSession()); - switch (eventLog->getType()) { case EventLog::Type::None: return false; @@ -1871,9 +1731,10 @@ bool MainDb::deleteEvent (const shared_ptr &eventLog) { MainDb &mainDb = *core->getPrivate()->mainDb.get(); - return L_SAFE_TRANSACTION_C(&mainDb) { + return L_DB_TRANSACTION_C(&mainDb) { soci::session *session = mainDb.getPrivate()->dbSession.getBackendSession(); *session << "DELETE FROM event WHERE id = :id", soci::use(dEventKey->storageId); + tr.commit(); dEventLog->dbKey = MainDbEventKey(); @@ -1887,19 +1748,19 @@ bool MainDb::deleteEvent (const shared_ptr &eventLog) { } int MainDb::getEventCount (FilterMask mask) const { - string query = "SELECT COUNT(*) FROM event" + - buildSqlEventFilter({ ConferenceCallFilter, ConferenceChatMessageFilter, ConferenceInfoFilter }, mask); + const string query = "SELECT COUNT(*) FROM event" + + buildSqlEventFilter( + { ConferenceCallFilter, ConferenceChatMessageFilter, ConferenceInfoFilter, ConferenceInfoNoDeviceFilter }, + mask + ); - DurationLogger durationLogger( - "Get events count with mask=" + Utils::toString(mask) + "." - ); + DurationLogger durationLogger("Get event count with mask=" + Utils::toString(mask) + "."); - return L_SAFE_TRANSACTION { + return L_DB_TRANSACTION { L_D(); int count; - soci::session *session = d->dbSession.getBackendSession(); - *session << query, soci::into(count); + *d->dbSession.getBackendSession() << query, soci::into(count); return count; }; } @@ -1913,39 +1774,23 @@ shared_ptr MainDb::getEventFromKey (const MainDbKey &dbKey) { unique_ptr &q = dbKey.getPrivate()->core.lock()->getPrivate()->mainDb; MainDbPrivate *d = q->getPrivate(); - const long long &storageId = dbKey.getPrivate()->storageId; - { - shared_ptr event = d->getEventFromCache(storageId); - if (event) - return event; - } + const long long &eventId = dbKey.getPrivate()->storageId; + shared_ptr event = d->getEventFromCache(eventId); + if (event) + return event; - // TODO: Improve. Deal with all events in the future. - static const string query = "SELECT peer_sip_address.value, local_sip_address.value, type, event.creation_time" - " FROM event, conference_event, chat_room, sip_address AS peer_sip_address, sip_address as local_sip_address" - " WHERE event.id = :eventId" - " AND conference_event.event_id = event.id" - " AND conference_event.chat_room_id = chat_room.id" - " AND chat_room.peer_sip_address_id = peer_sip_address.id" - " AND chat_room.local_sip_address_id = local_sip_address.id"; + return L_DB_TRANSACTION_C(q.get()) { + // TODO: Improve. Deal with all events in the future. + soci::row row; + *d->dbSession.getBackendSession() << Statements::get(Statements::SelectConferenceEvent), + soci::into(row), soci::use(eventId); - return L_SAFE_TRANSACTION_C(q.get()) { - soci::session *session = d->dbSession.getBackendSession(); - soci::transaction tr(*session); + ChatRoomId chatRoomId(IdentityAddress(row.get(14)), IdentityAddress(row.get(15))); + shared_ptr chatRoom = d->findChatRoom(chatRoomId); + if (!chatRoom) + return shared_ptr(); - string peerSipAddress; - string localSipAddress; - int type; - tm creationTime; - *session << query, soci::into(peerSipAddress), soci::into(localSipAddress), soci::into(type), - soci::into(creationTime), soci::use(storageId); - - return d->selectGenericConferenceEvent( - storageId, - static_cast(type), - Utils::getTmAsTimeT(creationTime), - ChatRoomId(IdentityAddress(peerSipAddress), IdentityAddress(localSipAddress)) - ); + return d->selectGenericConferenceEvent(chatRoom, row); }; } @@ -1953,12 +1798,9 @@ list> MainDb::getConferenceNotifiedEvents ( const ChatRoomId &chatRoomId, unsigned int lastNotifyId ) const { - static const string query = "SELECT id, type, creation_time FROM event" - " WHERE id IN (" - " SELECT event_id FROM conference_notified_event WHERE event_id IN (" - " SELECT event_id FROM conference_event WHERE chat_room_id = :chatRoomId" - " ) AND notify_id > :lastNotifyId" - " )"; + // TODO: Optimize. + const string query = Statements::get(Statements::SelectConferenceEvents) + + string(" AND notify_id > :lastNotifyId"); DurationLogger durationLogger( "Get conference notified events of: (peer=" + chatRoomId.getPeerAddress().asString() + @@ -1966,28 +1808,17 @@ list> MainDb::getConferenceNotifiedEvents ( ", lastNotifyId=" + Utils::toString(lastNotifyId) + ")." ); - return L_SAFE_TRANSACTION { + return L_DB_TRANSACTION { L_D(); soci::session *session = d->dbSession.getBackendSession(); - soci::transaction tr(*session); const long long &dbChatRoomId = d->selectChatRoomId(chatRoomId); list> events; soci::rowset rows = (session->prepare << query, soci::use(dbChatRoomId), soci::use(lastNotifyId)); - for (const auto &row : rows) { - long long eventId = d->resolveId(row, 0); - shared_ptr eventLog = d->getEventFromCache(eventId); - - events.push_back(eventLog ? eventLog : d->selectGenericConferenceEvent( - eventId, - static_cast(row.get(1)), - Utils::getTmAsTimeT(row.get(2)), - chatRoomId - )); - } - + for (const auto &row : rows) + events.push_back(d->selectGenericConferenceNotifiedEvent(chatRoomId, row)); return events; }; } @@ -1998,7 +1829,7 @@ int MainDb::getChatMessageCount (const ChatRoomId &chatRoomId) const { ", local=" + chatRoomId.getLocalAddress().asString() + ")." ); - return L_SAFE_TRANSACTION { + return L_DB_TRANSACTION { L_D(); int count; @@ -2009,11 +1840,10 @@ int MainDb::getChatMessageCount (const ChatRoomId &chatRoomId) const { if (!chatRoomId.isValid()) *session << query, soci::into(count); else { - query += " WHERE event_id IN (" + query += " WHERE event_id IN (" " SELECT event_id FROM conference_event WHERE chat_room_id = :chatRoomId" ")"; - soci::transaction tr(*session); const long long &dbChatRoomId = d->selectChatRoomId(chatRoomId); *session << query, soci::use(dbChatRoomId), soci::into(count); } @@ -2029,15 +1859,15 @@ int MainDb::getUnreadChatMessageCount (const ChatRoomId &chatRoomId) const { " SELECT event_id FROM conference_event WHERE chat_room_id = :chatRoomId" ") AND"; - query += " direction = " + Utils::toString(static_cast(ChatMessage::Direction::Incoming)) + - + " AND state <> " + Utils::toString(static_cast(ChatMessage::State::Displayed)); + query += " direction = " + Utils::toString(int(ChatMessage::Direction::Incoming)) + + + " AND state <> " + Utils::toString(int(ChatMessage::State::Displayed)); DurationLogger durationLogger( "Get unread chat messages count of: (peer=" + chatRoomId.getPeerAddress().asString() + ", local=" + chatRoomId.getLocalAddress().asString() + ")." ); - return L_SAFE_TRANSACTION { + return L_DB_TRANSACTION { L_D(); int count = 0; @@ -2047,7 +1877,6 @@ int MainDb::getUnreadChatMessageCount (const ChatRoomId &chatRoomId) const { if (!chatRoomId.isValid()) *session << query, soci::into(count); else { - soci::transaction tr(*session); const long long &dbChatRoomId = d->selectChatRoomId(chatRoomId); *session << query, soci::use(dbChatRoomId), soci::into(count); } @@ -2060,81 +1889,55 @@ void MainDb::markChatMessagesAsRead (const ChatRoomId &chatRoomId) const { if (getUnreadChatMessageCount(chatRoomId) == 0) return; - string query = "UPDATE conference_chat_message_event" - " SET state = " + Utils::toString(static_cast(ChatMessage::State::Displayed)) + " "; - query += "WHERE"; - if (chatRoomId.isValid()) - query += " event_id IN (" - " SELECT event_id FROM conference_event WHERE chat_room_id = :chatRoomId" - ") AND"; - query += " direction = " + Utils::toString(static_cast(ChatMessage::Direction::Incoming)); + static const string query = "UPDATE conference_chat_message_event" + " SET state = " + Utils::toString(int(ChatMessage::State::Displayed)) + + " WHERE event_id IN (" + " SELECT event_id FROM conference_event WHERE chat_room_id = :chatRoomId" + ") AND direction = " + Utils::toString(int(ChatMessage::Direction::Incoming)); DurationLogger durationLogger( "Mark chat messages as read of: (peer=" + chatRoomId.getPeerAddress().asString() + ", local=" + chatRoomId.getLocalAddress().asString() + ")." ); - L_SAFE_TRANSACTION { + L_DB_TRANSACTION { L_D(); - soci::session *session = d->dbSession.getBackendSession(); + const long long &dbChatRoomId = d->selectChatRoomId(chatRoomId); + *d->dbSession.getBackendSession() << query, soci::use(dbChatRoomId); - if (!chatRoomId.isValid()) - *session << query; - else { - soci::transaction tr(*session); - const long long &dbChatRoomId = d->selectChatRoomId(chatRoomId); - *session << query, soci::use(dbChatRoomId); - tr.commit(); - } - - return true; + tr.commit(); }; } list> MainDb::getUnreadChatMessages (const ChatRoomId &chatRoomId) const { - string query = "SELECT id, creation_time FROM event WHERE" - " id IN (" - " SELECT conference_event.event_id FROM conference_event, conference_chat_message_event" - " WHERE"; - if (chatRoomId.isValid()) - query += " chat_room_id = :chatRoomId AND "; - query += " conference_event.event_id = conference_chat_message_event.event_id" - " AND direction = " + Utils::toString(static_cast(ChatMessage::Direction::Incoming)) + - " AND state <> " + Utils::toString(static_cast(ChatMessage::State::Displayed)) + - ")"; + // TODO: Optimize. + static const string query = Statements::get(Statements::SelectConferenceEvents) + + string(" AND direction = ") + Utils::toString(int(ChatMessage::Direction::Incoming)) + + " AND state <> " + Utils::toString(int(ChatMessage::State::Displayed)); DurationLogger durationLogger( "Get unread chat messages: (peer=" + chatRoomId.getPeerAddress().asString() + ", local=" + chatRoomId.getLocalAddress().asString() + ")." ); - return L_SAFE_TRANSACTION { + return L_DB_TRANSACTION { L_D(); soci::session *session = d->dbSession.getBackendSession(); - soci::transaction tr(*session); - - long long dbChatRoomId; - if (chatRoomId.isValid()) - dbChatRoomId = d->selectChatRoomId(chatRoomId); + long long dbChatRoomId = d->selectChatRoomId(chatRoomId); + shared_ptr chatRoom = d->findChatRoom(chatRoomId); list> chatMessages; - soci::rowset rows = chatRoomId.isValid() - ? (session->prepare << query, soci::use(dbChatRoomId)) - : (session->prepare << query); + if (!chatRoom) + return chatMessages; + soci::rowset rows = (session->prepare << query, soci::use(dbChatRoomId)); for (const auto &row : rows) { - long long eventId = d->resolveId(row, 0); - shared_ptr event = d->getEventFromCache(eventId); - - if (!event) - event = d->selectGenericConferenceEvent( - eventId, - EventLog::Type::ConferenceChatMessage, - Utils::getTmAsTimeT(row.get(1)), - chatRoomId - ); + shared_ptr event = d->selectGenericConferenceEvent( + chatRoom, + row + ); if (event) chatMessages.push_back(static_pointer_cast(event)->getChatMessage()); @@ -2144,6 +1947,72 @@ list> MainDb::getUnreadChatMessages (const ChatRoomId &c }; } +list MainDb::getChatMessageParticipantStates (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; + + unsigned int state; + soci::statement statement = ( + d->dbSession.getBackendSession()->prepare << "SELECT state FROM chat_message_participant WHERE event_id = :eventId", + soci::into(state), soci::use(eventId) + ); + statement.execute(); + + list states; + while (statement.fetch()) + states.push_back(ChatMessage::State(state)); + + return states; + }; +} + +ChatMessage::State MainDb::getChatMessageParticipantState ( + const shared_ptr &eventLog, + const IdentityAddress &participantAddress +) 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; + const long long &participantSipAddressId = d->selectSipAddressId(participantAddress.asString()); + + unsigned int state; + *d->dbSession.getBackendSession() << "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 ChatMessage::State(state); + }; +} + +void MainDb::setChatMessageParticipantState ( + const shared_ptr &eventLog, + const IdentityAddress &participantAddress, + ChatMessage::State state +) { + L_DB_TRANSACTION { + L_D(); + + 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); + + *d->dbSession.getBackendSession() << "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); + + tr.commit(); + }; +} + shared_ptr MainDb::getLastChatMessage (const ChatRoomId &chatRoomId) const { list> chatList = getHistory(chatRoomId, 1, Filter::ConferenceChatMessageFilter); if (chatList.empty()) @@ -2156,45 +2025,33 @@ list> MainDb::findChatMessages ( const ChatRoomId &chatRoomId, const string &imdnMessageId ) const { - static const string query = "SELECT id, type, creation_time FROM event" - " WHERE id IN (" - " SELECT event_id FROM conference_event" - " WHERE event_id IN (SELECT event_id FROM conference_chat_message_event WHERE imdn_message_id = :imdnMessageId)" - " AND chat_room_id = :chatRoomId" - " )"; + // TODO: Optimize. + static const string query = Statements::get(Statements::SelectConferenceEvents) + + string(" AND imdn_message_id = :imdnMessageId"); DurationLogger durationLogger( "Find chat messages: (peer=" + chatRoomId.getPeerAddress().asString() + ", local=" + chatRoomId.getLocalAddress().asString() + ")." ); - return L_SAFE_TRANSACTION { + return L_DB_TRANSACTION { L_D(); - soci::session *session = d->dbSession.getBackendSession(); - soci::transaction tr(*session); - + shared_ptr chatRoom = d->findChatRoom(chatRoomId); list> chatMessages; + if (!chatRoom) + return chatMessages; const long long &dbChatRoomId = d->selectChatRoomId(chatRoomId); - soci::rowset rows = (session->prepare << query, soci::use(imdnMessageId), soci::use(dbChatRoomId)); + soci::rowset rows = ( + d->dbSession.getBackendSession()->prepare << query, soci::use(dbChatRoomId), soci::use(imdnMessageId) + ); for (const auto &row : rows) { - long long eventId = d->resolveId(row, 0); - shared_ptr event = d->getEventFromCache(eventId); - - if (!event) - event = d->selectGenericConferenceEvent( - eventId, - static_cast(row.get(1)), - Utils::getTmAsTimeT(row.get(2)), - chatRoomId - ); - + shared_ptr event = d->selectGenericConferenceEvent(chatRoom, row); if (event) { L_ASSERT(event->getType() == EventLog::Type::ConferenceChatMessage); chatMessages.push_back(static_pointer_cast(event)->getChatMessage()); - } else - lWarning() << "Unable to fetch event: " << eventId; + } } return chatMessages; @@ -2222,22 +2079,18 @@ list> MainDb::getHistoryRange ( return events; } - string query = "SELECT id, type, creation_time FROM event" - " WHERE id IN (" - " SELECT event_id FROM conference_event WHERE chat_room_id = :chatRoomId" - " )"; - query += buildSqlEventFilter({ - ConferenceCallFilter, ConferenceChatMessageFilter, ConferenceInfoFilter + string query = Statements::get(Statements::SelectConferenceEvents) + buildSqlEventFilter({ + ConferenceCallFilter, ConferenceChatMessageFilter, ConferenceInfoFilter, ConferenceInfoNoDeviceFilter }, mask, "AND"); - query += " ORDER BY creation_time DESC"; + query += " ORDER BY event_id DESC"; if (end > 0) - query += " LIMIT " + Utils::toString(end - begin); + query += " LIMIT " + Utils::toString(end - begin); else - query += " LIMIT " + d->dbSession.noLimitValue(); + query += " LIMIT " + d->dbSession.noLimitValue(); if (begin > 0) - query += " OFFSET " + Utils::toString(begin); + query += " OFFSET " + Utils::toString(begin); DurationLogger durationLogger( "Get history range of: (peer=" + chatRoomId.getPeerAddress().asString() + @@ -2245,30 +2098,19 @@ list> MainDb::getHistoryRange ( ", begin=" + Utils::toString(begin) + ", end=" + Utils::toString(end) + ")." ); - return L_SAFE_TRANSACTION { + return L_DB_TRANSACTION { L_D(); - soci::session *session = d->dbSession.getBackendSession(); - soci::transaction tr(*session); + shared_ptr chatRoom = d->findChatRoom(chatRoomId); + if (!chatRoom) + return events; const long long &dbChatRoomId = d->selectChatRoomId(chatRoomId); - soci::rowset rows = (session->prepare << query, soci::use(dbChatRoomId)); + soci::rowset rows = (d->dbSession.getBackendSession()->prepare << query, soci::use(dbChatRoomId)); for (const auto &row : rows) { - long long eventId = d->resolveId(row, 0); - shared_ptr event = d->getEventFromCache(eventId); - - if (!event) - event = d->selectGenericConferenceEvent( - eventId, - static_cast(row.get(1)), - Utils::getTmAsTimeT(row.get(2)), - chatRoomId - ); - + shared_ptr event = d->selectGenericConferenceEvent(chatRoom, row); if (event) events.push_front(event); - else - lWarning() << "Unable to fetch event: " << eventId; } return events; @@ -2279,27 +2121,100 @@ int MainDb::getHistorySize (const ChatRoomId &chatRoomId, FilterMask mask) const const string query = "SELECT COUNT(*) FROM event, conference_event" " WHERE chat_room_id = :chatRoomId" " AND event_id = event.id" + buildSqlEventFilter({ - ConferenceCallFilter, ConferenceChatMessageFilter, ConferenceInfoFilter + ConferenceCallFilter, ConferenceChatMessageFilter, ConferenceInfoFilter, ConferenceInfoNoDeviceFilter }, mask, "AND"); - return L_SAFE_TRANSACTION { + return L_DB_TRANSACTION { L_D(); - soci::session *session = d->dbSession.getBackendSession(); - soci::transaction tr(*session); - int count; const long long &dbChatRoomId = d->selectChatRoomId(chatRoomId); - *session << query, soci::into(count), soci::use(dbChatRoomId); + *d->dbSession.getBackendSession() << query, soci::into(count), soci::use(dbChatRoomId); return count; }; } +template +static void fetchContentAppData (soci::session *session, Content &content, long long contentId, T &data) { + static const string query = "SELECT name, data FROM chat_message_content_app_data" + " WHERE chat_message_content_id = :contentId"; + + string name; + soci::statement statement = (session->prepare << query, soci::use(contentId), soci::into(name), soci::into(data)); + statement.execute(); + while (statement.fetch()) + content.setAppData(name, blobToString(data)); +} + +void MainDb::loadChatMessageContents (const shared_ptr &chatMessage) { + L_DB_TRANSACTION { + L_D(); + + soci::session *session = d->dbSession.getBackendSession(); + + bool hasFileTransferContent = false; + + ChatMessagePrivate *dChatMessage = chatMessage->getPrivate(); + MainDbKeyPrivate *dEventKey = static_cast(dChatMessage->dbKey).getPrivate(); + const long long &eventId = dEventKey->storageId; + + static const string query = "SELECT chat_message_content.id, content_type.id, content_type.value, body" + " FROM chat_message_content, content_type" + " WHERE event_id = :eventId AND content_type_id = content_type.id"; + soci::rowset rows = (session->prepare << query, soci::use(eventId)); + for (const auto &row : rows) { + ContentType contentType(row.get(2)); + const long long &contentId = d->dbSession.resolveId(row, 0); + Content *content; + + if (contentType == ContentType::FileTransfer) { + hasFileTransferContent = true; + content = new FileTransferContent(); + } else if (contentType.isFile()) { + // 1.1 - Fetch contents' file informations. + string name; + int size; + string path; + + *session << "SELECT name, size, path FROM chat_message_file_content" + " WHERE chat_message_content_id = :contentId", + soci::into(name), soci::into(size), soci::into(path), soci::use(contentId); + + FileContent *fileContent = new FileContent(); + fileContent->setFileName(name); + fileContent->setFileSize(size_t(size)); + fileContent->setFilePath(path); + + content = fileContent; + } else + content = new Content(); + + content->setContentType(contentType); + content->setBody(row.get(3)); + + // 1.2 - Fetch contents' app data. + // TODO: Do not test backend, encapsulate!!! + if (getBackend() == MainDb::Backend::Sqlite3) { + soci::blob data(*session); + fetchContentAppData(session, *content, contentId, data); + } else { + string data; + fetchContentAppData(session, *content, contentId, data); + } + chatMessage->addContent(*content); + } + + // 2 - Load external body url from body into FileTransferContent if needed. + if (hasFileTransferContent) + dChatMessage->loadFileTransferUrlFromBodyToContent(); + }; +} + void MainDb::cleanHistory (const ChatRoomId &chatRoomId, FilterMask mask) { const string query = "SELECT event_id FROM conference_event WHERE chat_room_id = :chatRoomId" + buildSqlEventFilter({ - ConferenceCallFilter, ConferenceChatMessageFilter, ConferenceInfoFilter + ConferenceCallFilter, ConferenceChatMessageFilter, ConferenceInfoFilter, ConferenceInfoNoDeviceFilter }, mask); DurationLogger durationLogger( @@ -2308,42 +2223,36 @@ void MainDb::cleanHistory (const ChatRoomId &chatRoomId, FilterMask mask) { ", mask=" + Utils::toString(mask) + ")." ); - L_SAFE_TRANSACTION { + L_DB_TRANSACTION { L_D(); - soci::session *session = d->dbSession.getBackendSession(); - soci::transaction tr(*session); - const long long &dbChatRoomId = d->selectChatRoomId(chatRoomId); d->invalidConferenceEventsFromQuery(query, dbChatRoomId); - *session << "DELETE FROM event WHERE id IN (" + query + ")", soci::use(dbChatRoomId); + *d->dbSession.getBackendSession() << "DELETE FROM event WHERE id IN (" + query + ")", soci::use(dbChatRoomId); tr.commit(); - - return true; }; } // ----------------------------------------------------------------------------- list> MainDb::getChatRooms () const { - static const string query = "SELECT chat_room.id, peer_sip_address.value, local_sip_address.value, " - "creation_time, last_update_time, capabilities, subject, last_notify_id, flags" - " FROM chat_room, sip_address AS peer_sip_address, sip_address AS local_sip_address" - " WHERE chat_room.peer_sip_address_id = peer_sip_address.id AND chat_room.local_sip_address_id = local_sip_address.id" - " ORDER BY last_update_time DESC"; + static const string query = "SELECT chat_room.id, peer_sip_address.value, local_sip_address.value," + " creation_time, last_update_time, capabilities, subject, last_notify_id, flags" + " FROM chat_room, sip_address AS peer_sip_address, sip_address AS local_sip_address" + " WHERE chat_room.peer_sip_address_id = peer_sip_address.id AND chat_room.local_sip_address_id = local_sip_address.id" + " ORDER BY last_update_time DESC"; DurationLogger durationLogger("Get chat rooms."); - return L_SAFE_TRANSACTION { + return L_DB_TRANSACTION { L_D(); list> chatRooms; shared_ptr core = getCore(); soci::session *session = d->dbSession.getBackendSession(); - soci::transaction tr(*session); soci::rowset rows = (session->prepare << query); for (const auto &row : rows) { @@ -2371,12 +2280,12 @@ list> MainDb::getChatRooms () const { } else if (capabilities & ChatRoom::CapabilitiesMask(ChatRoom::Capabilities::Conference)) { list> participants; - const long long &dbChatRoomId = d->resolveId(row, 0); + const long long &dbChatRoomId = d->dbSession.resolveId(row, 0); static const string query = "SELECT chat_room_participant.id, sip_address.value, is_admin" - " FROM sip_address, chat_room, chat_room_participant" - " WHERE chat_room.id = :chatRoomId" - " AND sip_address.id = chat_room_participant.participant_sip_address_id" - " AND chat_room_participant.chat_room_id = chat_room.id"; + " FROM sip_address, chat_room, chat_room_participant" + " WHERE chat_room.id = :chatRoomId" + " AND sip_address.id = chat_room_participant.participant_sip_address_id" + " AND chat_room_participant.chat_room_id = chat_room.id"; // Fetch participants. soci::rowset rows = (session->prepare << query, soci::use(dbChatRoomId)); @@ -2388,14 +2297,16 @@ list> MainDb::getChatRooms () const { // Fetch devices. { - const long long &participantId = d->resolveId(row, 0); - static const string query = "SELECT sip_address.value FROM chat_room_participant_device, sip_address" - " WHERE chat_room_participant_id = :participantId" - " AND participant_device_sip_address_id = sip_address.id"; + const long long &participantId = d->dbSession.resolveId(row, 0); + static const string query = "SELECT sip_address.value, state FROM chat_room_participant_device, sip_address" + " WHERE chat_room_participant_id = :participantId" + " AND participant_device_sip_address_id = sip_address.id"; soci::rowset rows = (session->prepare << query, soci::use(participantId)); - for (const auto &row : rows) - dParticipant->addDevice(IdentityAddress(row.get(0))); + for (const auto &row : rows) { + shared_ptr device = dParticipant->addDevice(IdentityAddress(row.get(0))); + device->setState(ParticipantDevice::State(static_cast(row.get(1, 0)))); + } } if (participant->getAddress() == chatRoomId.getLocalAddress().getAddressWithoutGruu()) @@ -2461,35 +2372,17 @@ list> MainDb::getChatRooms () const { } void MainDb::insertChatRoom (const shared_ptr &chatRoom) { - const ChatRoomId &chatRoomId = chatRoom->getChatRoomId(); - DurationLogger durationLogger( - "Insert chat room: (peer=" + chatRoomId.getPeerAddress().asString() + - ", local=" + chatRoomId.getLocalAddress().asString() + ")." - ); - - L_SAFE_TRANSACTION { + L_DB_TRANSACTION { L_D(); - soci::transaction tr(*d->dbSession.getBackendSession()); - d->insertChatRoom(chatRoom); - tr.commit(); - - return true; }; } void MainDb::deleteChatRoom (const ChatRoomId &chatRoomId) { - DurationLogger durationLogger( - "Delete chat room: (peer=" + chatRoomId.getPeerAddress().asString() + - ", local=" + chatRoomId.getLocalAddress().asString() + "`)." - ); - - L_SAFE_TRANSACTION { + L_DB_TRANSACTION { L_D(); - soci::session *session = d->dbSession.getBackendSession(); - soci::transaction tr(*session); const long long &dbChatRoomId = d->selectChatRoomId(chatRoomId); @@ -2498,11 +2391,9 @@ void MainDb::deleteChatRoom (const ChatRoomId &chatRoomId) { dbChatRoomId ); - *session << "DELETE FROM chat_room WHERE id = :chatRoomId", soci::use(dbChatRoomId); + *d->dbSession.getBackendSession() << "DELETE FROM chat_room WHERE id = :chatRoomId", soci::use(dbChatRoomId); tr.commit(); - - return true; }; } @@ -2513,14 +2404,10 @@ void MainDb::migrateBasicToClientGroupChatRoom ( L_ASSERT(basicChatRoom->getCapabilities().isSet(ChatRoom::Capabilities::Basic)); L_ASSERT(clientGroupChatRoom->getCapabilities().isSet(ChatRoom::Capabilities::Conference)); - L_SAFE_TRANSACTION { + L_DB_TRANSACTION { L_D(); // TODO: Update events and chat messages. (Or wait signals.) - - soci::session *session = d->dbSession.getBackendSession(); - soci::transaction tr(*session); - const long long &dbChatRoomId = d->selectChatRoomId(basicChatRoom->getChatRoomId()); const ChatRoomId &newChatRoomId = clientGroupChatRoom->getChatRoomId(); @@ -2528,11 +2415,11 @@ void MainDb::migrateBasicToClientGroupChatRoom ( const long long &localSipAddressId = d->insertSipAddress(newChatRoomId.getLocalAddress().asString()); const int &capabilities = clientGroupChatRoom->getCapabilities(); - *session << "UPDATE chat_room" - " SET capabilities = :capabilities," - " peer_sip_address_id = :peerSipAddressId," - " local_sip_address_id = :localSipAddressId" - " WHERE id = :chatRoomId", soci::use(capabilities), soci::use(peerSipAddressId), + *d->dbSession.getBackendSession() << "UPDATE chat_room" + " SET capabilities = :capabilities," + " peer_sip_address_id = :peerSipAddressId," + " local_sip_address_id = :localSipAddressId" + " WHERE id = :chatRoomId", soci::use(capabilities), soci::use(peerSipAddressId), soci::use(localSipAddressId), soci::use(dbChatRoomId); shared_ptr me = clientGroupChatRoom->getMe(); @@ -2555,8 +2442,6 @@ void MainDb::migrateBasicToClientGroupChatRoom ( } tr.commit(); - - return true; }; } @@ -2568,12 +2453,9 @@ IdentityAddress MainDb::findMissingOneToOneConferenceChatRoomParticipantAddress L_ASSERT(chatRoom->getCapabilities() & ChatRoom::Capabilities::OneToOne); L_ASSERT(chatRoom->getParticipantCount() == 1); - return L_SAFE_TRANSACTION { + return L_DB_TRANSACTION { L_D(); - soci::session *session = d->dbSession.getBackendSession(); - soci::transaction tr(*session); - string missingParticipantAddress; string participantASipAddress; string participantBSipAddress; @@ -2581,7 +2463,7 @@ IdentityAddress MainDb::findMissingOneToOneConferenceChatRoomParticipantAddress const long long &chatRoomId = d->selectChatRoomId(chatRoom->getChatRoomId()); L_ASSERT(chatRoomId != -1); - *session << "SELECT participant_a_sip_address.value, participant_b_sip_address.value" + *d->dbSession.getBackendSession() << "SELECT participant_a_sip_address.value, participant_b_sip_address.value" " FROM one_to_one_chat_room, sip_address AS participant_a_sip_address, sip_address AS participant_b_sip_address" " WHERE chat_room_id = :chatRoomId" " AND participant_a_sip_address_id = participant_a_sip_address.id" @@ -2602,21 +2484,18 @@ IdentityAddress MainDb::findOneToOneConferenceChatRoomAddress ( const IdentityAddress &participantA, const IdentityAddress &participantB ) const { - return L_SAFE_TRANSACTION { + return L_DB_TRANSACTION { L_D(); - soci::session *session = d->dbSession.getBackendSession(); - soci::transaction tr(*session); - const long long &participantASipAddressId = d->selectSipAddressId(participantA.asString()); const long long &participantBSipAddressId = d->selectSipAddressId(participantB.asString()); - if ((participantASipAddressId == -1) || (participantBSipAddressId == -1)) + if (participantASipAddressId == -1 || participantBSipAddressId == -1) return IdentityAddress(); const long long &chatRoomId = d->selectOneToOneChatRoomId(participantASipAddressId, participantBSipAddressId); string chatRoomAddress; - *session << "SELECT sip_address.value" + *d->dbSession.getBackendSession() << "SELECT sip_address.value" " FROM chat_room, sip_address" " WHERE chat_room.id = :chatRoomId AND peer_sip_address_id = sip_address.id", soci::use(chatRoomId), soci::into(chatRoomAddress); @@ -2629,12 +2508,9 @@ void MainDb::insertOneToOneConferenceChatRoom (const shared_ptrgetCore()->getCCore())); L_ASSERT(chatRoom->getCapabilities() & ChatRoom::Capabilities::OneToOne); - L_SAFE_TRANSACTION { + L_DB_TRANSACTION { L_D(); - soci::session *session = d->dbSession.getBackendSession(); - soci::transaction tr(*session); - const list> &participants = chatRoom->getParticipants(); const long long &participantASipAddressId = d->selectSipAddressId(participants.front()->getAddress().asString()); const long long &participantBSipAddressId = d->selectSipAddressId(participants.back()->getAddress().asString()); @@ -2644,24 +2520,19 @@ void MainDb::insertOneToOneConferenceChatRoom (const shared_ptrselectOneToOneChatRoomId(participantASipAddressId, participantBSipAddressId); if (chatRoomId == -1) { chatRoomId = d->selectChatRoomId(chatRoom->getChatRoomId()); - *session << "INSERT INTO one_to_one_chat_room (" - " chat_room_id, participant_a_sip_address_id, participant_b_sip_address_id" - ") VALUES (:chatRoomId, :participantASipAddressId, :participantBSipAddressId)", + *d->dbSession.getBackendSession() << Statements::get(Statements::InsertOneToOneChatRoom, getBackend()), soci::use(chatRoomId), soci::use(participantASipAddressId), soci::use(participantBSipAddressId); } tr.commit(); - - return true; }; } void MainDb::enableChatRoomMigration (const ChatRoomId &chatRoomId, bool enable) { - L_SAFE_TRANSACTION { + L_DB_TRANSACTION { L_D(); soci::session *session = d->dbSession.getBackendSession(); - soci::transaction tr(*session); const long long &dbChatRoomId = d->selectChatRoomId(chatRoomId); @@ -2669,15 +2540,33 @@ void MainDb::enableChatRoomMigration (const ChatRoomId &chatRoomId, bool enable) *session << "SELECT capabilities FROM chat_room WHERE id = :chatRoomId", soci::use(dbChatRoomId), soci::into(capabilities); if (enable) - capabilities |= static_cast(ChatRoom::Capabilities::Migratable); + capabilities |= int(ChatRoom::Capabilities::Migratable); else - capabilities &= ~static_cast(ChatRoom::Capabilities::Migratable); + capabilities &= ~int(ChatRoom::Capabilities::Migratable); *session << "UPDATE chat_room SET capabilities = :capabilities WHERE id = :chatRoomId", soci::use(capabilities), soci::use(dbChatRoomId); tr.commit(); + }; +} - return true; +void MainDb::updateChatRoomParticipantDevice ( + const shared_ptr &chatRoom, + const shared_ptr &device +) { + L_DB_TRANSACTION { + L_D(); + + const long long &dbChatRoomId = d->selectChatRoomId(chatRoom->getChatRoomId()); + const long long &participantSipAddressId = d->selectSipAddressId(device->getParticipant()->getAddress().asString()); + const long long &participantId = d->selectChatRoomParticipantId(dbChatRoomId, participantSipAddressId); + const long long &participantSipDeviceAddressId = d->selectSipAddressId(device->getAddress().asString()); + unsigned int state = static_cast(device->getState()); + *d->dbSession.getBackendSession() << "UPDATE chat_room_participant_device SET state = :state" + " WHERE chat_room_participant_id = :participantId AND participant_device_sip_address_id = :participantSipDeviceAddressId", + soci::use(state), soci::use(participantId), soci::use(participantSipDeviceAddressId); + + tr.commit(); }; } @@ -2695,116 +2584,13 @@ bool MainDb::import (Backend, const string ¶meters) { return false; } - L_SAFE_TRANSACTION { - // TODO: Remove condition after cpp migration in friends/friends list. - if (false) - d->importLegacyFriends(inDbSession); - return true; - }; + // TODO: Remove condition after cpp migration in friends/friends list. + if (false) + d->importLegacyFriends(inDbSession); - L_SAFE_TRANSACTION { - d->importLegacyHistory(inDbSession); - return true; - }; + d->importLegacyHistory(inDbSession); return true; } -// ----------------------------------------------------------------------------- -// No backend. -// ----------------------------------------------------------------------------- - -#else - -void MainDb::init () {} - -bool MainDb::addEvent (const shared_ptr &) { - return false; -} - -bool MainDb::updateEvent (const shared_ptr &) { - return false; -} - -bool MainDb::deleteEvent (const shared_ptr &) { - return false; -} - -int MainDb::getEventCount (FilterMask) const { - return 0; -} - -shared_ptr MainDb::getEventFromKey (const MainDbKey &) { - return nullptr; -} - -list> MainDb::getConferenceNotifiedEvents ( - const ChatRoomId &, - unsigned int -) const { - return list>(); -} - -int MainDb::getChatMessageCount (const ChatRoomId &) const { - return 0; -} - -int MainDb::getUnreadChatMessageCount (const ChatRoomId &) const { - return 0; -} - -shared_ptr MainDb::getLastChatMessage (const ChatRoomId &) const { - return nullptr; -} - -list> MainDb::findChatMessages (const ChatRoomId &, const string &) const { - return list>(); -} - -void MainDb::markChatMessagesAsRead (const ChatRoomId &) const {} - -list> MainDb::getUnreadChatMessages (const ChatRoomId &) const { - return list>(); -} - -list> MainDb::getHistory (const ChatRoomId &, int, FilterMask) const { - return list>(); -} - -list> MainDb::getHistoryRange (const ChatRoomId &, int, int, FilterMask) const { - return list>(); -} - -int MainDb::getHistorySize (const ChatRoomId &, FilterMask) const { - return 0; -} - -list> MainDb::getChatRooms () const { - return list>(); -} - -void MainDb::insertChatRoom (const shared_ptr &) {} - -void MainDb::deleteChatRoom (const ChatRoomId &) {} - -void MainDb::migrateBasicToClientGroupChatRoom ( - const shared_ptr &, - const shared_ptr & -) {} - -IdentityAddress MainDb::findOneToOneConferenceChatRoomAddress ( - const IdentityAddress &, - const IdentityAddress & -) const { - return IdentityAddress(); -} - -void MainDb::cleanHistory (const ChatRoomId &, FilterMask) {} - -bool MainDb::import (Backend, const string &) { - return false; -} - -#endif // ifdef SOCI_ENABLED - LINPHONE_END_NAMESPACE diff --git a/src/db/main-db.h b/src/db/main-db.h index f3301b007..f0f4b6f06 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" @@ -38,8 +39,12 @@ class Core; class EventLog; class MainDbKey; class MainDbPrivate; +class ParticipantDevice; class MainDb : public AbstractDb, public CoreAccessor { + template + friend class DbTransaction; + friend class MainDbChatMessageKey; friend class MainDbEventKey; @@ -48,7 +53,8 @@ public: NoFilter = 0x0, ConferenceCallFilter = 0x1, ConferenceChatMessageFilter = 0x2, - ConferenceInfoFilter = 0x4 + ConferenceInfoFilter = 0x4, + ConferenceInfoNoDeviceFilter = 0x8 }; typedef EnumMask FilterMask; @@ -81,8 +87,19 @@ public: int getChatMessageCount (const ChatRoomId &chatRoomId = ChatRoomId()) const; int getUnreadChatMessageCount (const ChatRoomId &chatRoomId = ChatRoomId()) const; - void markChatMessagesAsRead (const ChatRoomId &chatRoomId = ChatRoomId()) const; - std::list> getUnreadChatMessages (const ChatRoomId &chatRoomId = ChatRoomId()) const; + void markChatMessagesAsRead (const ChatRoomId &chatRoomId) const; + std::list> getUnreadChatMessages (const 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; @@ -111,6 +128,12 @@ public: void cleanHistory (const ChatRoomId &chatRoomId, FilterMask mask = NoFilter); + // --------------------------------------------------------------------------- + // Chat messages. + // --------------------------------------------------------------------------- + + void loadChatMessageContents (const std::shared_ptr &chatMessage); + // --------------------------------------------------------------------------- // Chat rooms. // --------------------------------------------------------------------------- @@ -135,6 +158,11 @@ public: ) const; void insertOneToOneConferenceChatRoom (const std::shared_ptr &chatRoom); + void updateChatRoomParticipantDevice ( + const std::shared_ptr &chatRoom, + const std::shared_ptr &device + ); + // --------------------------------------------------------------------------- // Other. // --------------------------------------------------------------------------- diff --git a/src/db/session/db-session.cpp b/src/db/session/db-session.cpp index 6c2fda1cc..e9fbc4450 100644 --- a/src/db/session/db-session.cpp +++ b/src/db/session/db-session.cpp @@ -19,7 +19,7 @@ #include "linphone/utils/utils.h" -#include "db-session-p.h" +#include "db-session.h" #include "logger/logger.h" // ============================================================================= @@ -28,20 +28,40 @@ using namespace std; LINPHONE_BEGIN_NAMESPACE -DbSession::DbSession () : ClonableObject(*new DbSessionPrivate) {} +class DbSessionPrivate { +public: + enum class Backend { + None, + Mysql, + Sqlite3 + } backend = Backend::None; + + std::unique_ptr backendSession; +}; + +DbSession::DbSession () : mPrivate(new DbSessionPrivate) {} DbSession::DbSession (const string &uri) : DbSession() { - #ifdef SOCI_ENABLED - try { - L_D(); - d->backendSession = make_unique(uri); - d->backend = !uri.find("mysql") ? DbSessionPrivate::Backend::Mysql : DbSessionPrivate::Backend::Sqlite3; - } catch (const exception &e) { - lWarning() << "Unable to build db session with uri: " << e.what(); - } - #else - lWarning() << "Unable to build db session with uri: soci not enabled."; - #endif // ifdef SOCI_ENABLED + try { + L_D(); + d->backendSession = makeUnique(uri); + d->backend = !uri.find("mysql") ? DbSessionPrivate::Backend::Mysql : DbSessionPrivate::Backend::Sqlite3; + } catch (const exception &e) { + lWarning() << "Unable to build db session with uri: " << e.what(); + } +} + +DbSession::DbSession (DbSession &&other) : mPrivate(other.mPrivate) { + other.mPrivate = nullptr; +} + +DbSession::~DbSession () { + delete mPrivate; +} + +DbSession &DbSession::operator= (DbSession &&other) { + std::swap(mPrivate, other.mPrivate); + return *this; } DbSession::operator bool () const { @@ -49,14 +69,10 @@ DbSession::operator bool () const { return d->backend != DbSessionPrivate::Backend::None; } -L_USE_DEFAULT_CLONABLE_OBJECT_SHARED_IMPL(DbSession); - -#ifdef SOCI_ENABLED - soci::session *DbSession::getBackendSession () const { - L_D(); - return d->backendSession.get(); - } -#endif // ifdef SOCI_ENABLED +soci::session *DbSession::getBackendSession () const { + L_D(); + return d->backendSession.get(); +} string DbSession::primaryKeyStr (const string &type) const { L_D(); @@ -157,48 +173,59 @@ long long DbSession::getLastInsertId () const { break; } - #ifdef SOCI_ENABLED - *d->backendSession << sql, soci::into(id); - #endif // ifdef SOCI_ENABLED + *d->backendSession << sql, soci::into(id); return id; } void DbSession::enableForeignKeys (bool status) { - #ifdef SOCI_ENABLED - L_D(); - switch (d->backend) { - case DbSessionPrivate::Backend::Mysql: - *d->backendSession << string("SET FOREIGN_KEY_CHECKS = ") + (status ? "1" : "0"); - break; - case DbSessionPrivate::Backend::Sqlite3: - *d->backendSession << string("PRAGMA foreign_keys = ") + (status ? "ON" : "OFF"); - break; - case DbSessionPrivate::Backend::None: - break; - } - #endif // ifdef SOCI_ENABLED + L_D(); + + switch (d->backend) { + case DbSessionPrivate::Backend::Mysql: + *d->backendSession << string("SET FOREIGN_KEY_CHECKS = ") + (status ? "1" : "0"); + break; + case DbSessionPrivate::Backend::Sqlite3: + *d->backendSession << string("PRAGMA foreign_keys = ") + (status ? "ON" : "OFF"); + break; + case DbSessionPrivate::Backend::None: + break; + } } bool DbSession::checkTableExists (const string &table) const { - #ifdef SOCI_ENABLED - L_D(); - soci::session *session = d->backendSession.get(); - switch (d->backend) { - case DbSessionPrivate::Backend::Mysql: - *session << "SHOW TABLES LIKE :table", soci::use(table); - return session->got_data() > 0; - case DbSessionPrivate::Backend::Sqlite3: - *session << "SELECT name FROM sqlite_master WHERE type='table' AND name=:table", soci::use(table); - return session->got_data() > 0; - case DbSessionPrivate::Backend::None: - return false; - } - L_ASSERT(false); - #endif // ifdef SOCI_ENABLED + L_D(); - (void)table; + soci::session *session = d->backendSession.get(); + switch (d->backend) { + case DbSessionPrivate::Backend::Mysql: + *session << "SHOW TABLES LIKE :table", soci::use(table); + return session->got_data() > 0; + case DbSessionPrivate::Backend::Sqlite3: + *session << "SELECT name FROM sqlite_master WHERE type='table' AND name=:table", soci::use(table); + return session->got_data() > 0; + case DbSessionPrivate::Backend::None: + return false; + } + + L_ASSERT(false); return false; } +long long DbSession::resolveId (const soci::row &row, int col) const { + L_D(); + + switch (d->backend) { + case DbSessionPrivate::Backend::Mysql: + return static_cast(row.get(0)); + case DbSessionPrivate::Backend::Sqlite3: + return static_cast(row.get(0)); + case DbSessionPrivate::Backend::None: + return 0; + } + + L_ASSERT(false); + return 0; +} + LINPHONE_END_NAMESPACE diff --git a/src/db/session/db-session.h b/src/db/session/db-session.h index 2acfdfc73..0189bf997 100644 --- a/src/db/session/db-session.h +++ b/src/db/session/db-session.h @@ -20,11 +20,9 @@ #ifndef _L_DB_SESSION_H_ #define _L_DB_SESSION_H_ -#ifdef SOCI_ENABLED - #include -#endif // ifdef SOCI_ENABLED +#include -#include "object/clonable-object.h" +#include "linphone/utils/general.h" // ============================================================================= @@ -32,21 +30,18 @@ LINPHONE_BEGIN_NAMESPACE class DbSessionPrivate; -class DbSession : public ClonableObject { - friend class DbSessionProvider; - +class DbSession { public: DbSession (); explicit DbSession (const std::string &uri); - DbSession (const DbSession &src); + DbSession (DbSession &&other); + ~DbSession (); - DbSession &operator= (const DbSession &src); + DbSession &operator= (DbSession &&other); operator bool () const; - #ifdef SOCI_ENABLED - soci::session *getBackendSession () const; - #endif // ifdef SOCI_ENABLED + soci::session *getBackendSession () const; std::string primaryKeyStr (const std::string &type = "INT") const; std::string primaryKeyRefStr (const std::string &type = "INT") const; @@ -62,7 +57,11 @@ public: bool checkTableExists (const std::string &table) const; + long long resolveId (const soci::row &row, int col) const; + private: + DbSessionPrivate *mPrivate; + L_DECLARE_PRIVATE(DbSession); }; diff --git a/src/dial-plan/dial-plan.cpp b/src/dial-plan/dial-plan.cpp index 5802b7f46..c3b20a49d 100644 --- a/src/dial-plan/dial-plan.cpp +++ b/src/dial-plan/dial-plan.cpp @@ -283,24 +283,24 @@ DialPlan::DialPlan ( d->internationalCallPrefix = icp; } -DialPlan::DialPlan (const DialPlan &src) : ClonableObject(*new DialPlanPrivate) { +DialPlan::DialPlan (const DialPlan &other) : ClonableObject(*new DialPlanPrivate) { L_D(); - d->country = src.getCountry(); - d->isoCountryCode = src.getIsoCountryCode(); - d->countryCallingCode = src.getCountryCallingCode(); - d->nationalNumberLength = src.getNationalNumberLength(); - d->internationalCallPrefix = src.getInternationalCallPrefix(); + d->country = other.getCountry(); + d->isoCountryCode = other.getIsoCountryCode(); + d->countryCallingCode = other.getCountryCallingCode(); + d->nationalNumberLength = other.getNationalNumberLength(); + d->internationalCallPrefix = other.getInternationalCallPrefix(); } -DialPlan &DialPlan::operator= (const DialPlan &src) { +DialPlan &DialPlan::operator= (const DialPlan &other) { L_D(); - if (this != &src) { - d->country = src.getCountry(); - d->isoCountryCode = src.getIsoCountryCode(); - d->countryCallingCode = src.getCountryCallingCode(); - d->nationalNumberLength = src.getNationalNumberLength(); - d->internationalCallPrefix = src.getInternationalCallPrefix(); + if (this != &other) { + d->country = other.getCountry(); + d->isoCountryCode = other.getIsoCountryCode(); + d->countryCallingCode = other.getCountryCallingCode(); + d->nationalNumberLength = other.getNationalNumberLength(); + d->internationalCallPrefix = other.getInternationalCallPrefix(); } return *this; diff --git a/src/dial-plan/dial-plan.h b/src/dial-plan/dial-plan.h index 7ea380251..d944f2e98 100644 --- a/src/dial-plan/dial-plan.h +++ b/src/dial-plan/dial-plan.h @@ -39,9 +39,9 @@ public: int nnl = 0, const std::string &icp = "" ); - DialPlan (const DialPlan &src); + DialPlan (const DialPlan &other); - DialPlan &operator= (const DialPlan &src); + DialPlan &operator= (const DialPlan &other); const std::string &getCountry () const; const std::string &getIsoCountryCode () const; diff --git a/src/logger/logger.h b/src/logger/logger.h index c25438c24..d3729b582 100644 --- a/src/logger/logger.h +++ b/src/logger/logger.h @@ -54,7 +54,7 @@ class DurationLoggerPrivate; class DurationLogger : public BaseObject { public: - DurationLogger (const std::string &label, Logger::Level level = Logger::Debug); + DurationLogger (const std::string &label, Logger::Level level = Logger::Info); ~DurationLogger (); private: diff --git a/src/nat/ice-agent.cpp b/src/nat/ice-agent.cpp index bca4f7cdb..726312f9f 100644 --- a/src/nat/ice-agent.cpp +++ b/src/nat/ice-agent.cpp @@ -45,6 +45,12 @@ void IceAgent::checkSession (IceRole role, bool isReinvite) { return; LinphoneConfig *config = linphone_core_get_config(mediaSession.getCore()->getCCore()); + + if (lp_config_get_int(config, "net", "force_ice_disablement", 0)){ + lWarning()<<"ICE is disabled in this version"; + return; + } + if (isReinvite && (lp_config_get_int(config, "net", "allow_late_ice", 0) == 0)) return; @@ -539,7 +545,7 @@ void IceAgent::createIceCheckListsAndParseIceAttributes (const SalMediaDescripti } if (!iceRestarted) { bool_t losingPairsAdded = false; - for (int j = 0; j < SAL_MEDIA_DESCRIPTION_MAX_ICE_CANDIDATES; j++) { + for (int j = 0; j < SAL_MEDIA_DESCRIPTION_MAX_ICE_REMOTE_CANDIDATES; j++) { const SalIceRemoteCandidate *remoteCandidate = &stream->ice_remote_candidates[j]; const char *addr = nullptr; int port = 0; @@ -732,4 +738,35 @@ void IceAgent::updateIceStateInCallStatsForStream (LinphoneCallStats *stats, Ice } } +bool IceAgent::checkIceReinviteNeedsDeferedResponse(SalMediaDescription *md){ + int i,j; + IceCheckList *cl; + + if (!iceSession) return false; + + if (ice_session_state(iceSession) != IS_Running ) return false; + + for (i = 0; i < md->nb_streams; i++) { + SalStreamDescription *stream = &md->streams[i]; + cl = ice_session_check_list(iceSession, i); + + if (cl==NULL) continue; + if (stream->ice_mismatch == TRUE) { + return false; + } + if (stream->rtp_port == 0) { + continue; + } + + if (ice_check_list_state(cl) != ICL_Running) continue; + + for (j = 0; j < SAL_MEDIA_DESCRIPTION_MAX_ICE_CANDIDATES; j++) { + const SalIceRemoteCandidate *remote_candidate = &stream->ice_remote_candidates[j]; + if (remote_candidate->addr[0] != '\0') return true; + + } + } + return false; +} + LINPHONE_END_NAMESPACE diff --git a/src/nat/ice-agent.h b/src/nat/ice-agent.h index 3af487df1..0e702010b 100644 --- a/src/nat/ice-agent.h +++ b/src/nat/ice-agent.h @@ -66,6 +66,11 @@ public: void updateFromRemoteMediaDescription (const SalMediaDescription *localDesc, const SalMediaDescription *remoteDesc, bool isOffer); void updateIceStateInCallStats (); void updateLocalMediaDescriptionFromIce (SalMediaDescription *desc); + /* + * Checks if an incoming offer with ICE needs a delayed answer, because the ice session hasn't completed yet with + * connecvity checks. + */ + bool checkIceReinviteNeedsDeferedResponse(SalMediaDescription *md); private: void addLocalIceCandidates (int family, const char *addr, IceCheckList *audioCl, IceCheckList *videoCl, IceCheckList *textCl); diff --git a/src/nat/stun-client.cpp b/src/nat/stun-client.cpp index 82a4eb72f..590abfd1d 100644 --- a/src/nat/stun-client.cpp +++ b/src/nat/stun-client.cpp @@ -30,6 +30,7 @@ using namespace std; LINPHONE_BEGIN_NAMESPACE int StunClient::run (int audioPort, int videoPort, int textPort) { + stunDiscoveryDone = false; if (linphone_core_ipv6_enabled(getCore()->getCCore())) { lWarning() << "STUN support is not implemented for ipv6"; return -1; @@ -139,10 +140,12 @@ int StunClient::run (int audioPort, int videoPort, int textPort) { close_socket(sockAudio); if (sockVideo != -1) close_socket(sockVideo); if (sockText != -1) close_socket(sockText); + stunDiscoveryDone = true; return ret; } void StunClient::updateMediaDescription (SalMediaDescription *md) const { + if (!stunDiscoveryDone) return; for (int i = 0; i < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; i++) { if (!sal_stream_description_active(&md->streams[i])) continue; diff --git a/src/nat/stun-client.h b/src/nat/stun-client.h index ec8b399a1..d686ea9cb 100644 --- a/src/nat/stun-client.h +++ b/src/nat/stun-client.h @@ -38,7 +38,7 @@ LINPHONE_BEGIN_NAMESPACE class StunClient : public CoreAccessor { struct Candidate { std::string address; - int port; + int port = 0; }; public: @@ -67,6 +67,7 @@ private: Candidate audioCandidate; Candidate videoCandidate; Candidate textCandidate; + bool stunDiscoveryDone = false; }; LINPHONE_END_NAMESPACE diff --git a/src/object/app-data-container.cpp b/src/object/app-data-container.cpp index 644bbfc0d..b55a5e180 100644 --- a/src/object/app-data-container.cpp +++ b/src/object/app-data-container.cpp @@ -41,19 +41,19 @@ AppDataContainer::AppDataContainer () : mPrivate(new AppDataContainerPrivate) { d->appData = make_shared>(); } -AppDataContainer::AppDataContainer (const AppDataContainer &src) : mPrivate(new AppDataContainerPrivate) { +AppDataContainer::AppDataContainer (const AppDataContainer &other) : mPrivate(new AppDataContainerPrivate) { L_D(); - d->appData = src.getPrivate()->appData; + d->appData = other.getPrivate()->appData; } AppDataContainer::~AppDataContainer () { delete mPrivate; } -AppDataContainer &AppDataContainer::operator= (const AppDataContainer &src) { +AppDataContainer &AppDataContainer::operator= (const AppDataContainer &other) { L_D(); - if (this != &src) - d->appData = src.getPrivate()->appData; + if (this != &other) + d->appData = other.getPrivate()->appData; return *this; } diff --git a/src/object/app-data-container.h b/src/object/app-data-container.h index 7dc87ca6f..05641a004 100644 --- a/src/object/app-data-container.h +++ b/src/object/app-data-container.h @@ -34,10 +34,10 @@ class AppDataContainerPrivate; class LINPHONE_PUBLIC AppDataContainer { public: AppDataContainer (); - AppDataContainer (const AppDataContainer &src); + AppDataContainer (const AppDataContainer &other); virtual ~AppDataContainer (); - AppDataContainer &operator= (const AppDataContainer &src); + AppDataContainer &operator= (const AppDataContainer &other); const std::unordered_map &getAppDataMap () const; diff --git a/src/object/clonable-object.h b/src/object/clonable-object.h index b1951cb7b..0352ce3dd 100644 --- a/src/object/clonable-object.h +++ b/src/object/clonable-object.h @@ -26,12 +26,12 @@ // ============================================================================= #define L_USE_DEFAULT_CLONABLE_OBJECT_SHARED_IMPL(CLASS) \ - CLASS::CLASS (const CLASS &src) : ClonableObject( \ - const_cast::type &>(*src.getPrivate()) \ + CLASS::CLASS (const CLASS &other) : ClonableObject( \ + const_cast::type &>(*other.getPrivate()) \ ) {} \ - CLASS &CLASS::operator= (const CLASS &src) { \ - if (this != &src) \ - setRef(*src.getPrivate()); \ + CLASS &CLASS::operator= (const CLASS &other) { \ + if (this != &other) \ + setRef(*other.getPrivate()); \ return *this; \ } diff --git a/src/object/clonable-shared-pointer.h b/src/object/clonable-shared-pointer.h new file mode 100644 index 000000000..52c640d5c --- /dev/null +++ b/src/object/clonable-shared-pointer.h @@ -0,0 +1,156 @@ +/* + * clonable-shared-pointer.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_CLONABLE_SHARED_POINTER_H_ +#define _L_CLONABLE_SHARED_POINTER_H_ + +#include "linphone/utils/general.h" + +// ============================================================================= + +LINPHONE_BEGIN_NAMESPACE + +class SharedObject { +template +friend class ClonableSharedPointer; + +public: + int getRefCount () { + return mRefCounter; + } + +protected: + SharedObject () : mRefCounter(1) {} + + // Do not use virtual here. Avoid extra storage (little bonus). + ~SharedObject () = default; + +private: + int mRefCounter; + + L_DISABLE_COPY(SharedObject); +}; + +// ----------------------------------------------------------------------------- + +template +class ClonableSharedPointer { + static_assert(std::is_base_of::value, "T must be a inherited class of SharedObject."); + +public: + explicit ClonableSharedPointer (T *pointer = nullptr) : mPointer(pointer) {} + + ClonableSharedPointer (const ClonableSharedPointer &other) : mPointer(other.mPointer) { + ref(); + } + + ClonableSharedPointer (ClonableSharedPointer &&other) : mPointer(other.mPointer) { + other.mPointer = nullptr; + } + + ~ClonableSharedPointer () { + unref(); + } + + ClonableSharedPointer &operator= (const ClonableSharedPointer &other) { + if (mPointer != other.mPointer) { + unref(); + mPointer = other.mPointer; + ref(); + } + return *this; + } + + ClonableSharedPointer &operator= (ClonableSharedPointer &&other) { + std::swap(mPointer, other.mPointer); + return *this; + } + + bool operator== (const ClonableSharedPointer &other) const { + return mPointer == other.mPointer; + } + + bool operator!= (const ClonableSharedPointer &other) const { + return mPointer != other.mPointer; + } + + T &operator* () { + N_ASSERT(mPointer); + tryClone(); + return *mPointer; + } + + const T &operator* () const { + N_ASSERT(mPointer); + return *mPointer; + } + + T *operator-> () { + tryClone(); + return mPointer; + } + + const T *operator-> () const { + return mPointer; + } + + explicit operator bool () const { + return mPointer; + } + + bool operator! () const { + return !mPointer; + } + + T *get () { + return mPointer; + } + + const T *get () const { + return mPointer; + } + +private: + void ref () { + if (mPointer) + ++mPointer->mRefCounter; + } + + void unref () { + if (mPointer && --mPointer->mRefCounter == 0) { + delete mPointer; + mPointer = nullptr; + } + } + + void tryClone () { + if (mPointer && mPointer->mRefCounter > 1) { + T *newPointer = new T(*mPointer); + if (--mPointer->mRefCounter == 0) + delete mPointer; + mPointer = newPointer; + } + } + + T *mPointer; +}; + +LINPHONE_END_NAMESPACE + +#endif // _L_CLONABLE_SHARED_POINTER_H_ diff --git a/src/object/property-container.cpp b/src/object/property-container.cpp index 24e46174e..dbd216ace 100644 --- a/src/object/property-container.cpp +++ b/src/object/property-container.cpp @@ -36,14 +36,14 @@ public: // ----------------------------------------------------------------------------- -PropertyContainer::PropertyContainer () : mPrivate(new PropertyContainerPrivate) {} +PropertyContainer::PropertyContainer () : mPrivate(nullptr) {} /* * Empty copy constructor. Don't change this pattern. * PropertyContainer is an Entity component, not a simple structure. * An Entity is UNIQUE. */ -PropertyContainer::PropertyContainer (const PropertyContainer &) : mPrivate(new PropertyContainerPrivate) {} +PropertyContainer::PropertyContainer (const PropertyContainer &) : mPrivate(nullptr) {} PropertyContainer::~PropertyContainer () { delete mPrivate; @@ -54,19 +54,23 @@ PropertyContainer &PropertyContainer::operator= (const PropertyContainer &) { } Variant PropertyContainer::getProperty (const string &name) const { - L_D(); - auto it = d->properties.find(name); - return it == d->properties.cend() ? Variant() : it->second; + if (!mPrivate) + return Variant(); + auto &properties = mPrivate->properties; + auto it = properties.find(name); + return it == properties.cend() ? Variant() : it->second; } void PropertyContainer::setProperty (const string &name, const Variant &value) { - L_D(); - d->properties[name] = value; + if (!mPrivate) + mPrivate = new PropertyContainerPrivate(); + mPrivate->properties[name] = value; } void PropertyContainer::setProperty (const string &name, Variant &&value) { - L_D(); - d->properties[name] = move(value); + if (!mPrivate) + mPrivate = new PropertyContainerPrivate(); + mPrivate->properties[name] = move(value); } LINPHONE_END_NAMESPACE diff --git a/src/object/property-container.h b/src/object/property-container.h index 50e0abc47..8f9db93bb 100644 --- a/src/object/property-container.h +++ b/src/object/property-container.h @@ -31,19 +31,17 @@ class PropertyContainerPrivate; class LINPHONE_PUBLIC PropertyContainer { public: PropertyContainer (); - PropertyContainer (const PropertyContainer &src); + PropertyContainer (const PropertyContainer &other); virtual ~PropertyContainer (); - PropertyContainer &operator= (const PropertyContainer &src); + PropertyContainer &operator= (const PropertyContainer &other); Variant getProperty (const std::string &name) const; void setProperty (const std::string &name, const Variant &value); void setProperty (const std::string &name, Variant &&value); private: - PropertyContainerPrivate *mPrivate = nullptr; - - L_DECLARE_PRIVATE(PropertyContainer); + PropertyContainerPrivate *mPrivate; }; LINPHONE_END_NAMESPACE diff --git a/src/sal/call-op.cpp b/src/sal/call-op.cpp index d2f494064..238e6d255 100644 --- a/src/sal/call-op.cpp +++ b/src/sal/call-op.cpp @@ -24,6 +24,7 @@ #include #include +#include "content/content-disposition.h" #include "content/content-type.h" using namespace std; @@ -102,7 +103,8 @@ belle_sip_header_allow_t *SalCallOp::create_allow(bool_t enable_update) { int SalCallOp::set_custom_body(belle_sip_message_t *msg, const Content &body) { ContentType contentType = body.getContentType(); - string contentDisposition = body.getContentDisposition(); + auto contentDisposition = body.getContentDisposition(); + string contentEncoding = body.getContentEncoding(); size_t bodySize = body.getBody().size(); if (bodySize > SIP_MESSAGE_BODY_LIMIT) { @@ -114,16 +116,21 @@ int SalCallOp::set_custom_body(belle_sip_message_t *msg, const Content &body) { belle_sip_header_content_type_t *content_type = belle_sip_header_content_type_create(contentType.getType().c_str(), contentType.getSubType().c_str()); belle_sip_message_add_header(msg, BELLE_SIP_HEADER(content_type)); } - if (!contentDisposition.empty()) { - belle_sip_header_content_disposition_t *contentDispositionHeader = belle_sip_header_content_disposition_create(contentDisposition.c_str()); + if (contentDisposition.isValid()) { + belle_sip_header_content_disposition_t *contentDispositionHeader = belle_sip_header_content_disposition_create( + contentDisposition.asString().c_str() + ); belle_sip_message_add_header(msg, BELLE_SIP_HEADER(contentDispositionHeader)); } + if (!contentEncoding.empty()) + belle_sip_message_add_header(msg, belle_sip_header_create("Content-Encoding", contentEncoding.c_str())); belle_sip_header_content_length_t *content_length = belle_sip_header_content_length_create(bodySize); belle_sip_message_add_header(msg, BELLE_SIP_HEADER(content_length)); if (bodySize > 0) { - char *buffer = bctbx_new(char, bodySize); + char *buffer = bctbx_new(char, bodySize + 1); memcpy(buffer, body.getBody().data(), bodySize); + buffer[bodySize] = '\0'; belle_sip_message_assign_body(msg, buffer, bodySize); } @@ -239,7 +246,9 @@ Content SalCallOp::extract_body(belle_sip_message_t *message) { if (type_str && subtype_str) body.setContentType(ContentType(type_str, subtype_str)); if (contentDisposition) - body.setContentDisposition(belle_sip_header_content_disposition_get_content_disposition(contentDisposition)); + body.setContentDisposition( + ContentDisposition(belle_sip_header_content_disposition_get_content_disposition(contentDisposition)) + ); if (length > 0 && body_str) body.setBody(body_str, length); return body; } @@ -367,7 +376,7 @@ void SalCallOp::set_error(belle_sip_response_t* response, bool_t fatal){ int SalCallOp::vfu_retry_cb (void *user_data, unsigned int events) { SalCallOp *op=(SalCallOp *)user_data; op->send_vfu_request(); - op->ref(); + op->unref(); return BELLE_SIP_STOP; } @@ -467,7 +476,7 @@ void SalCallOp::process_response_cb(void *op_base, const belle_sip_response_even && strcmp("media_control+xml",belle_sip_header_content_type_get_subtype(header_content_type))==0) { unsigned int retry_in = rand() % 1001; // [0;1000] belle_sip_source_t *s=op->root->create_timer(vfu_retry_cb,op->ref(), retry_in, "vfu request retry"); - ms_message("Rejected vfu request on op [%p], just retry in [%ui] ms",op,retry_in); + ms_message("Rejected vfu request on op [%p], just retry in [%u] ms",op,retry_in); belle_sip_object_unref(s); }else { /*ignoring*/ @@ -1111,9 +1120,15 @@ int SalCallOp::update(const char *subject, bool_t no_user_consent) { return -1; } -void SalCallOp::cancel_invite_with_info(const SalErrorInfo *info) { +int SalCallOp::cancel_invite_with_info(const SalErrorInfo *info) { belle_sip_request_t* cancel; ms_message("Cancelling INVITE request from [%s] to [%s] ",get_from(), get_to()); + + if (this->pending_client_trans == NULL){ + ms_warning("There is no transaction to cancel."); + return -1; + } + cancel = belle_sip_client_transaction_create_cancel(this->pending_client_trans); if (cancel){ if (info != NULL){ @@ -1121,6 +1136,7 @@ void SalCallOp::cancel_invite_with_info(const SalErrorInfo *info) { belle_sip_message_add_header(BELLE_SIP_MESSAGE(cancel),BELLE_SIP_HEADER(reason)); } send_request(cancel); + return 0; }else if (this->dialog){ belle_sip_dialog_state_t state = belle_sip_dialog_get_state(this->dialog);; /*case where the response received is invalid (could not establish a dialog), but the transaction is not cancellable @@ -1136,6 +1152,7 @@ void SalCallOp::cancel_invite_with_info(const SalErrorInfo *info) { break; } } + return -1; } SalMediaDescription *SalCallOp::get_final_media_description() { diff --git a/src/sal/call-op.h b/src/sal/call-op.h index 414a808b6..fea958e6d 100644 --- a/src/sal/call-op.h +++ b/src/sal/call-op.h @@ -45,8 +45,8 @@ public: int decline(SalReason reason, const char *redirection /*optional*/); int decline_with_error_info(const SalErrorInfo *info, const SalAddress *redirectionAddr /*optional*/); int update(const char *subject, bool_t no_user_consent); - void cancel_invite() {cancel_invite_with_info(NULL);} - void cancel_invite_with_info(const SalErrorInfo *info); + int cancel_invite() { return cancel_invite_with_info(NULL);} + int cancel_invite_with_info(const SalErrorInfo *info); int refer(const char *refer_to_); int refer_with_replaces(SalCallOp *other_call_op); int set_referer(SalCallOp *refered_call); diff --git a/src/sal/event-op.cpp b/src/sal/event-op.cpp index 8e99fcd9e..89cbdb5e7 100644 --- a/src/sal/event-op.cpp +++ b/src/sal/event-op.cpp @@ -31,13 +31,10 @@ void SalSubscribeOp::subscribe_process_io_error_cb(void *user_ctx, const belle_s belle_sip_request_t* req = belle_sip_transaction_get_request((belle_sip_transaction_t*)tr); const char *method=belle_sip_request_get_method(req); - if (!op->dialog) { - /*this is handling outgoing out-of-dialog notifies*/ - if (strcmp(method,"NOTIFY")==0){ - SalErrorInfo *ei=&op->error_info; - sal_error_info_set(ei,SalReasonIOError, "SIP", 0,NULL,NULL); - op->root->callbacks.on_notify_response(op); - } + if (strcmp(method,"NOTIFY")==0){ + SalErrorInfo *ei=&op->error_info; + sal_error_info_set(ei,SalReasonIOError, "SIP", 0,NULL,NULL); + op->root->callbacks.on_notify_response(op); } } } @@ -52,11 +49,9 @@ void SalSubscribeOp::subscribe_response_event_cb(void *op_base, const belle_sip_ req = belle_sip_transaction_get_request((belle_sip_transaction_t*)tr); method = belle_sip_request_get_method(req); - if (!op->dialog) { - if (strcmp(method,"NOTIFY")==0){ - op->set_error_info_from_response(belle_sip_response_event_get_response(event)); - op->root->callbacks.on_notify_response(op); - } + if (strcmp(method,"NOTIFY")==0){ + op->set_error_info_from_response(belle_sip_response_event_get_response(event)); + op->root->callbacks.on_notify_response(op); } } @@ -70,13 +65,10 @@ void SalSubscribeOp::subscribe_process_timeout_cb(void *user_ctx, const belle_si req = belle_sip_transaction_get_request((belle_sip_transaction_t*)tr); method = belle_sip_request_get_method(req); - if (!op->dialog) { - /*this is handling outgoing out-of-dialog notifies*/ - if (strcmp(method,"NOTIFY")==0){ - SalErrorInfo *ei=&op->error_info; - sal_error_info_set(ei,SalReasonRequestTimeout, "SIP", 0,NULL,NULL); - op->root->callbacks.on_notify_response(op); - } + if (strcmp(method,"NOTIFY")==0){ + SalErrorInfo *ei=&op->error_info; + sal_error_info_set(ei,SalReasonRequestTimeout, "SIP", 0,NULL,NULL); + op->root->callbacks.on_notify_response(op); } } diff --git a/src/sal/refer-op.cpp b/src/sal/refer-op.cpp index a6ea0425f..6cc1b34b1 100644 --- a/src/sal/refer-op.cpp +++ b/src/sal/refer-op.cpp @@ -59,12 +59,14 @@ void SalReferOp::process_request_event_cb(void *op_base, const belle_sip_request if (!refer_to){ ms_warning("cannot do anything with the refer without destination"); op->reply(SalReasonUnknown);/*is mapped on bad request*/ + op->unref(); return; } SalAddress *referToAddr = sal_address_new(belle_sip_header_get_unparsed_value(BELLE_SIP_HEADER(refer_to))); op->root->callbacks.refer_received(op, referToAddr); /*the app is expected to reply in the callback*/ sal_address_unref(referToAddr); + op->unref(); } void SalReferOp::fill_cbs() { diff --git a/src/sal/sal.cpp b/src/sal/sal.cpp index 41054ddc8..a25236587 100644 --- a/src/sal/sal.cpp +++ b/src/sal/sal.cpp @@ -118,7 +118,6 @@ void Sal::process_request_event_cb (void *ud, const belle_sip_request_event_t *e op->fill_cbs(); }else if (strcmp("REFER",method)==0) { op=new SalReferOp(sal); - op->fill_cbs(); }else if (strcmp("OPTIONS",method)==0) { resp=belle_sip_response_create_from_request(req,200); belle_sip_provider_send_response(sal->prov,resp); @@ -219,11 +218,14 @@ void Sal::process_request_event_cb (void *ud, const belle_sip_request_event_t *e if (!op->call_id) { op->call_id=ms_strdup(belle_sip_header_call_id_get_call_id(BELLE_SIP_HEADER_CALL_ID(belle_sip_message_get_header_by_type(BELLE_SIP_MESSAGE(req), belle_sip_header_call_id_t)))); } - /*It is worth noting that proxies can (and - will) remove this header field*/ + /*It is worth noting that proxies can (and will) remove this header field*/ op->set_privacy_from_message((belle_sip_message_t*)req); - - op->assign_recv_headers((belle_sip_message_t*)req); + + if (strcmp("ACK",method) != 0){ + /*The ACK custom header is processed specifically later on*/ + op->assign_recv_headers((belle_sip_message_t*)req); + } + if (op->callbacks && op->callbacks->process_request_event) { op->callbacks->process_request_event(op,event); } else { diff --git a/src/sal/sal.h b/src/sal/sal.h index bf9322d48..3e48bc481 100644 --- a/src/sal/sal.h +++ b/src/sal/sal.h @@ -171,6 +171,7 @@ public: void enable_test_features(bool_t enabled) {this->_enable_test_features=enabled;} void use_no_initial_route(bool_t enabled) {this->no_initial_route=enabled;} void enable_unconditional_answer(int value) {belle_sip_provider_enable_unconditional_answer(this->prov,value);} + void enable_reconnect_to_primary_asap(bool_t enabled) {belle_sip_stack_enable_reconnect_to_primary_asap(this->stack,enabled);} bctbx_list_t *get_pending_auths() const {return bctbx_list_copy(this->pending_auths);} diff --git a/src/search/magic-search-p.h b/src/search/magic-search-p.h new file mode 100644 index 000000000..0a75e5cab --- /dev/null +++ b/src/search/magic-search-p.h @@ -0,0 +1,44 @@ +/* + * magic-search-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_MAGIC_SEARCH_P_H_ +#define _L_MAGIC_SEARCH_P_H_ + +#include "magic-search.h" +#include "object/object-p.h" + +LINPHONE_BEGIN_NAMESPACE + +class MagicSearchPrivate : public ObjectPrivate{ +private: + unsigned int mMaxWeight; + unsigned int mMinWeight; + unsigned int mSearchLimit; // Number of ResultSearch maximum when the search is limited + bool mLimitedSearch; // Limit the search + std::string mDelimiter; // Delimiter use for the search + bool mUseDelimiter; + std::list *mCacheResult; + + L_DECLARE_PUBLIC(MagicSearch); +}; + +LINPHONE_END_NAMESPACE + +#endif //_L_MAGIC_SEARCH_P_H_ + diff --git a/src/search/magic-search.cpp b/src/search/magic-search.cpp new file mode 100644 index 000000000..6e8a8fa94 --- /dev/null +++ b/src/search/magic-search.cpp @@ -0,0 +1,319 @@ +/* + * magic-search.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 "magic-search-p.h" + +#include + +#include "c-wrapper/internal/c-tools.h" +#include "linphone/utils/utils.h" +#include "linphone/core.h" +#include "linphone/types.h" +#include "private.h" + +using namespace std; + +LINPHONE_BEGIN_NAMESPACE + +MagicSearch::MagicSearch(const std::shared_ptr &core) : CoreAccessor(core), Object(*new MagicSearchPrivate){ + L_D(); + d->mMinWeight = 0; + d->mMaxWeight = 1000; + d->mSearchLimit = 30; + d->mLimitedSearch = true; + d->mDelimiter = "+_-"; + d->mUseDelimiter = true; + d->mCacheResult = nullptr; +} + +MagicSearch::~MagicSearch() { + resetSearchCache(); +} + +void MagicSearch::setMinWeight(const unsigned int weight) { + L_D(); + d->mMinWeight = weight; +} + +unsigned int MagicSearch::getMinWeight() const { + L_D(); + return d->mMinWeight; +} + +void MagicSearch::setMaxWeight(const unsigned int weight) { + L_D(); + d->mMaxWeight = weight; +} + +unsigned int MagicSearch::getMaxWeight() const { + L_D(); + return d->mMaxWeight; +} + +const string &MagicSearch::getDelimiter() const { + L_D(); + return d->mDelimiter; +} + +void MagicSearch::setDelimiter(const string &delimiter) { + L_D(); + d->mDelimiter = delimiter; +} + +bool MagicSearch::getUseDelimiter() const { + L_D(); + return d->mUseDelimiter; +} + +void MagicSearch::setUseDelimiter(bool enable) { + L_D(); + d->mUseDelimiter = enable; +} + +unsigned int MagicSearch::getSearchLimit() const { + L_D(); + return d->mSearchLimit; +} + +void MagicSearch::setSearchLimit(const unsigned int limit) { + L_D(); + d->mSearchLimit = limit; +} + +bool MagicSearch::getLimitedSearch() const { + L_D(); + return d->mLimitedSearch; +} + +void MagicSearch::setLimitedSearch(const bool limited) { + L_D(); + d->mLimitedSearch = limited; +} + +void MagicSearch::resetSearchCache() { + L_D(); + if (d->mCacheResult) { + delete d->mCacheResult; + d->mCacheResult = nullptr; + } +} + +list MagicSearch::getContactListFromFilter(const string &filter, const string &withDomain) { + list *resultList; + list returnList; + LinphoneProxyConfig *proxy = nullptr; + + if (filter.empty()) return list(); + + if (getSearchCache() != nullptr) { + resultList = continueSearch(filter, withDomain); + resetSearchCache(); + } else { + resultList = beginNewSearch(filter, withDomain); + } + + resultList->sort([](const SearchResult& lsr, const SearchResult& rsr){ + return (!rsr.getFriend() && lsr.getFriend()) || lsr >= rsr; + }); + + setSearchCache(resultList); + returnList = *resultList; + + if (getLimitedSearch() && returnList.size() > getSearchLimit()) { + auto limitIterator = returnList.begin(); + advance(limitIterator, (int)getSearchLimit()); + returnList.erase(limitIterator, returnList.end()); + } + + proxy = linphone_core_get_default_proxy_config(this->getCore()->getCCore()); + // Adding last item if proxy exist + if (proxy) { + const char *domain = linphone_proxy_config_get_domain(proxy); + if (domain) { + string filterAddress = "sip:" + filter + "@" + domain; + LinphoneAddress *lastResult = linphone_core_create_address(this->getCore()->getCCore(), filterAddress.c_str()); + if (lastResult) returnList.push_back(SearchResult(0, lastResult, nullptr)); + } + } + + return returnList; +} + +///////////////////// +// Private Methods // +///////////////////// + +list *MagicSearch::getSearchCache() const { + L_D(); + return d->mCacheResult; +} + +void MagicSearch::setSearchCache(list *cache) { + L_D(); + if (d->mCacheResult != cache) resetSearchCache(); + d->mCacheResult = cache; +} + +list *MagicSearch::beginNewSearch(const string &filter, const string &withDomain) { + list *resultList = new list(); + LinphoneFriendList *list = linphone_core_get_default_friend_list(this->getCore()->getCCore()); + const bctbx_list_t *callLog = linphone_core_get_call_logs(this->getCore()->getCCore()); + + // For all friends or when we reach the search limit + for (bctbx_list_t *f = list->friends ; f != nullptr ; f = bctbx_list_next(f)) { + SearchResult result = searchInFriend(reinterpret_cast(f->data), filter, withDomain); + if (result.getWeight() > getMinWeight()) { + resultList->push_back(result); + } + } + + // For all call log or when we reach the search limit + for (const bctbx_list_t *f = callLog ; f != nullptr ; f = bctbx_list_next(f)) { + LinphoneCallLog *log = reinterpret_cast(f->data); + const LinphoneAddress *addr = (linphone_call_log_get_dir(log) == LinphoneCallDir::LinphoneCallIncoming) ? + linphone_call_log_get_from_address(log) : linphone_call_log_get_to_address(log); + if (addr) { + unsigned int weight = searchInAddress(addr, filter, withDomain); + if (weight > getMinWeight()) { + resultList->push_back(SearchResult(weight, addr, nullptr)); + } + } + } + + return resultList; +} + +list *MagicSearch::continueSearch(const string &filter, const string &withDomain) { + list *resultList = new list(); + const list *cacheList = getSearchCache(); + + for (const auto sr : *cacheList) { + if (sr.getFriend()) { + SearchResult result = searchInFriend(sr.getFriend(), filter, withDomain); + if (result.getWeight() > getMinWeight()) { + resultList->push_back(result); + } + } + } + + return resultList; +} + +SearchResult MagicSearch::searchInFriend(const LinphoneFriend *lFriend, const string &filter, const string &withDomain) { + unsigned int weight = getMinWeight(); + const LinphoneAddress* lAddress = linphone_friend_get_address(lFriend); + + if (!checkDomain(lFriend, lAddress, withDomain)) return SearchResult(weight, nullptr); + + // NAME + if (linphone_core_vcard_supported()) { + if (linphone_friend_get_vcard(lFriend)) { + weight += getWeight(linphone_vcard_get_full_name(linphone_friend_get_vcard(lFriend)), filter) * 3; + } + } + + //SIP URI + weight += searchInAddress(lAddress, filter, withDomain) * 1; + + // PHONE NUMBER + bctbx_list_t *begin, *phoneNumbers = linphone_friend_get_phone_numbers(lFriend); + begin = phoneNumbers; + while (phoneNumbers != nullptr && phoneNumbers->data != nullptr) { + string number = static_cast(phoneNumbers->data); + const LinphonePresenceModel *presence = linphone_friend_get_presence_model_for_uri_or_tel(lFriend, number.c_str()); + weight += getWeight(number, filter); + if (presence != nullptr) { + weight += getWeight(linphone_presence_model_get_contact(presence), filter) * 2; + } + phoneNumbers = phoneNumbers->next; + } + if (begin) bctbx_list_free(begin); + + return SearchResult(weight, lAddress, lFriend); +} + +unsigned int MagicSearch::searchInAddress(const LinphoneAddress *lAddress, const string &filter, const string &withDomain) { + unsigned int weight = getMinWeight(); + if (lAddress != nullptr && checkDomain(nullptr, lAddress, withDomain)) { + // SIPURI + if (linphone_address_get_username(lAddress) != nullptr) { + weight += getWeight(linphone_address_get_username(lAddress), filter); + } + // DISPLAYNAME + if (linphone_address_get_display_name(lAddress) != nullptr) { + weight += getWeight(linphone_address_get_display_name(lAddress), filter); + } + } + return weight; +} + +unsigned int MagicSearch::getWeight(const string &stringWords, const string &filter) const { + size_t weight = string::npos; + + // Finding all occurrences of "filter" in "stringWords" + for (size_t w = stringWords.find(filter); + w != string::npos; + w = stringWords.find(filter, w + filter.length()) + ) { + // weight max if occurence find at beginning + if (w == 0) { + weight = getMaxWeight(); + } else { + bool isDelimiter = false; + if (getUseDelimiter()) { + // get the char before the matched filter + const char l = stringWords.at(w - 1); + // Check if it's a delimiter + for (const char d : getDelimiter()) { + if (l == d) { + isDelimiter = true; + break; + } + } + } + unsigned int newWeight = getMaxWeight() - (unsigned int)((isDelimiter) ? 1 : w + 1); + weight = (weight != string::npos) ? weight + newWeight : newWeight; + } + // Only one search on the stringWords for the moment + // due to weight calcul which dos not take into the case of multiple occurence + break; + } + + return (weight != string::npos) ? (unsigned int)(weight) : getMinWeight(); +} + +bool MagicSearch::checkDomain(const LinphoneFriend *lFriend, const LinphoneAddress *lAddress, const string &withDomain) const { + bool onlySipUri = !withDomain.empty() && withDomain.compare("*") != 0; + const LinphonePresenceModel *presenceModel = (lFriend) ? linphone_friend_get_presence_model(lFriend) : nullptr; + const char *contactPresence = (presenceModel) ? linphone_presence_model_get_contact(presenceModel) : nullptr; + const LinphoneAddress *addrPresence = (contactPresence) ? + linphone_core_create_address(this->getCore()->getCCore(), contactPresence) : nullptr; + + return ( + // If we don't want Sip URI only or Address or Presence model + (!onlySipUri || lAddress != nullptr || presenceModel != nullptr) && + // And If we don't want Sip URI only or Address match or Address presence match + (!onlySipUri || + (lAddress != nullptr && withDomain.compare(linphone_address_get_domain(lAddress)) == 0) || + (addrPresence != nullptr && withDomain.compare(linphone_address_get_domain(addrPresence)) == 0) + ) + ); +} + +LINPHONE_END_NAMESPACE diff --git a/src/search/magic-search.h b/src/search/magic-search.h new file mode 100644 index 000000000..7fa34f294 --- /dev/null +++ b/src/search/magic-search.h @@ -0,0 +1,200 @@ +/* + * magic-search.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_MAGIC_SEARCH_H_ +#define _L_MAGIC_SEARCH_H_ + +#include +#include +#include + +#include "core/core.h" +#include "core/core-accessor.h" +#include "search-result.h" + +LINPHONE_BEGIN_NAMESPACE + +class MagicSearchPrivate; + +class LINPHONE_PUBLIC MagicSearch : public CoreAccessor, public Object{ +public: + + MagicSearch(const std::shared_ptr &core); + MagicSearch(const MagicSearch &ms) = delete; + ~MagicSearch(); + + /** + * Set the minimum value used to calculate the weight in search + * @param[in] weight minimum weight + **/ + void setMinWeight(const unsigned int weight); + + /** + * @return the minimum value used to calculate the weight in search + **/ + unsigned int getMinWeight() const; + + /** + * Set the maximum value used to calculate the weight in search + * @param[in] weight maximum weight + **/ + void setMaxWeight(const unsigned int weight); + + /** + * @return the maximum value used to calculate the weight in search + **/ + unsigned int getMaxWeight() const; + + /** + * @return the delimiter used to find matched filter word + **/ + const std::string& getDelimiter() const; + + /** + * Set the delimiter used to find matched filter word + * @param[in] delimiter delimiter (example "-_.,") + **/ + void setDelimiter(const std::string &delimiter); + + /** + * @return if the delimiter search is used + **/ + bool getUseDelimiter() const; + + /** + * Enable or disable the delimiter in search + * @param[in] enable + **/ + void setUseDelimiter(bool enable); + + /** + * @return the number of the maximum SearchResult which will be return + **/ + unsigned int getSearchLimit() const; + + /** + * Set the number of the maximum SearchResult which will be return + * @param[in] limit + **/ + void setSearchLimit(const unsigned int limit); + + /** + * @return if the search is limited + **/ + bool getLimitedSearch() const; + + /** + * Enable or disable the limited search + * @param[in] limited + **/ + void setLimitedSearch(const bool limited); + + /** + * Reset the cache to begin a new search + **/ + void resetSearchCache(); + + /** + * Create a sorted list of SearchResult from SipUri, Contact name, + * Contact displayname, Contact phone number, which match with a filter word + * The last item list will be an address formed with "filter" if a proxy config exist + * During the first search, a cache is created and used for the next search + * Use resetSearchCache() to begin a new search + * @param[in] filter word we search + * @param[in] withDomain + * - "" for searching in all contact + * - "*" for searching in contact with sip SipUri + * - "yourdomain" for searching in contact from "yourdomain" domain + * @return sorted list of SearchResult with "filter" or an empty list if "filter" is empty + **/ + std::list getContactListFromFilter(const std::string &filter, const std::string &withDomain = ""); + +private: + + /** + * @return the cache of precedent result + * @private + **/ + std::list *getSearchCache() const; + + /** + * Save a result for future search + * @param[in] cache result we want to save + * @private + **/ + void setSearchCache(std::list *cache); + + /** + * Begin the search from friend list + * @param[in] filter word we search + * @param[in] withDomain domain which we want to search only + * @private + **/ + std::list *beginNewSearch(const std::string &filter, const std::string &withDomain); + + /** + * Continue the search from the cache of precedent search + * @param[in] filter word we search + * @param[in] withDomain domain which we want to search only + * @private + **/ + std::list *continueSearch(const std::string &filter, const std::string &withDomain); + + /** + * Search informations in friend given + * @param[in] lFriend friend whose informations will be check + * @param[in] filter word we search + * @param[in] withDomain domain which we want to search only + * @private + **/ + SearchResult searchInFriend(const LinphoneFriend* lFriend, const std::string &filter, const std::string &withDomain); + + /** + * Search informations in address given + * @param[in] lAddress address whose informations will be check + * @param[in] filter word we search + * @param[in] withDomain domain which we want to search only + * @private + **/ + unsigned int searchInAddress(const LinphoneAddress *lAddress, const std::string &filter, const std::string &withDomain); + + /** + * Return a weight for a searched in with a filter + * @param[in] stringWords which where we are searching + * @param[in] filter what we are searching + * @return calculate weight + * @private + **/ + unsigned int getWeight(const std::string &stringWords, const std::string &filter) const; + + /** + * Return if the given address match domain policy + * @param[in] lFriend friend whose domain will be check + * @param[in] lAddress address whose domain will be check + * @param[in] withDomain domain policy + * @private + **/ + bool checkDomain(const LinphoneFriend* lFriend, const LinphoneAddress *lAddress, const std::string &withDomain) const; + + L_DECLARE_PRIVATE(MagicSearch); +}; + +LINPHONE_END_NAMESPACE + +#endif //_L_MAGIC_SEARCH_H_ diff --git a/src/db/session/db-session-p.h b/src/search/search-result-p.h similarity index 65% rename from src/db/session/db-session-p.h rename to src/search/search-result-p.h index aae12db9c..07830689a 100644 --- a/src/db/session/db-session-p.h +++ b/src/search/search-result-p.h @@ -1,5 +1,5 @@ /* - * db-session-p.h + * search-result-p.h * Copyright (C) 2010-2018 Belledonne Communications SARL * * This program is free software; you can redistribute it and/or @@ -17,31 +17,26 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef _L_DB_SESSION_P_H_ -#define _L_DB_SESSION_P_H_ +#ifndef _L_SEARCH_RESULT_P_H_ +#define _L_SEARCH_RESULT_P_H_ -#include "db-session.h" +#include "search-result.h" #include "object/clonable-object-p.h" -// ============================================================================= +#include "linphone/types.h" LINPHONE_BEGIN_NAMESPACE -class DbSessionPrivate : public ClonableObjectPrivate { +class SearchResultPrivate : public ClonableObjectPrivate { private: - enum class Backend { - None, - Mysql, - Sqlite3 - } backend = Backend::None; + const LinphoneFriend *mFriend; + const LinphoneAddress *mAddress; + unsigned int mWeight; - #ifdef SOCI_ENABLED - std::unique_ptr backendSession; - #endif // ifdef SOCI_ENABLED - - L_DECLARE_PUBLIC(DbSession); + L_DECLARE_PUBLIC(SearchResult); }; LINPHONE_END_NAMESPACE -#endif // ifndef _L_DB_SESSION_P_H_ +#endif //_L_SEARCH_RESULT_P_H_ + diff --git a/src/search/search-result.cpp b/src/search/search-result.cpp new file mode 100644 index 000000000..2c032e76b --- /dev/null +++ b/src/search/search-result.cpp @@ -0,0 +1,74 @@ +/* + * search-result.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 "search-result-p.h" +#include "linphone/utils/utils.h" + +using namespace LinphonePrivate; + +LINPHONE_BEGIN_NAMESPACE + +SearchResult::SearchResult(const unsigned int weight, const LinphoneAddress *a, const LinphoneFriend *f) : ClonableObject(*new SearchResultPrivate) { + L_D(); + d->mWeight = weight; + d->mAddress = a; + d->mFriend = f; +} + +SearchResult::SearchResult(const SearchResult &sr) : ClonableObject(*new SearchResultPrivate) { + L_D(); + d->mWeight = sr.getWeight(); + d->mAddress = sr.getAddress(); + d->mFriend = sr.getFriend(); +} + +SearchResult::~SearchResult() {}; + +bool SearchResult::operator<(const SearchResult& rsr) const{ + return this->getWeight() < rsr.getWeight(); +} + +bool SearchResult::operator>(const SearchResult& rsr) const{ + return this->getWeight() > rsr.getWeight(); +} + +bool SearchResult::operator>=(const SearchResult& rsr) const{ + return this->getWeight() >= rsr.getWeight(); +} + +bool SearchResult::operator=(const SearchResult& rsr) const{ + return this->getWeight() == rsr.getWeight(); +} + +const LinphoneFriend* SearchResult::getFriend() const { + L_D(); + return d->mFriend; +} + +const LinphoneAddress *SearchResult::getAddress() const { + L_D(); + return d->mAddress; +} + +unsigned int SearchResult::getWeight() const { + L_D(); + return d->mWeight; +} + +LINPHONE_END_NAMESPACE diff --git a/src/search/search-result.h b/src/search/search-result.h new file mode 100644 index 000000000..d453a273a --- /dev/null +++ b/src/search/search-result.h @@ -0,0 +1,65 @@ +/* + * search-result.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_SEARCH_RESULT_H_ +#define _L_SEARCH_RESULT_H_ + +#include "object/clonable-object.h" +#include "linphone/utils/general.h" +#include "linphone/types.h" +#include "private.h" + +LINPHONE_BEGIN_NAMESPACE + +class SearchResultPrivate; + +class LINPHONE_PUBLIC SearchResult : public ClonableObject { +public: + SearchResult() = delete; + SearchResult(const unsigned int weight, const LinphoneAddress *a, const LinphoneFriend *f = nullptr); + SearchResult(const SearchResult &sr); + ~SearchResult(); + + bool operator<(const SearchResult& rsr) const; + bool operator>(const SearchResult& rsr) const; + bool operator>=(const SearchResult& rsr) const; + bool operator=(const SearchResult& rsr) const; + + /** + * @return LinphoneFriend associed + **/ + const LinphoneFriend* getFriend() const; + + /** + * @return LinphoneAddress associed + **/ + const LinphoneAddress* getAddress() const; + + /** + * @return the result weight + **/ + unsigned int getWeight() const; + +private: + L_DECLARE_PRIVATE(SearchResult); +}; + +LINPHONE_END_NAMESPACE + +#endif //_L_SEARCH_RESULT_H_ diff --git a/src/address/identity-address-p.h b/src/sip-tools/sip-headers.h similarity index 68% rename from src/address/identity-address-p.h rename to src/sip-tools/sip-headers.h index 052bd7965..969735841 100644 --- a/src/address/identity-address-p.h +++ b/src/sip-tools/sip-headers.h @@ -1,5 +1,5 @@ /* - * identity-address-p.h + * sip-headers.h * Copyright (C) 2010-2018 Belledonne Communications SARL * * This program is free software; you can redistribute it and/or @@ -17,26 +17,27 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef _L_IDENTITY_ADDRESS_P_H_ -#define _L_IDENTITY_ADDRESS_P_H_ +#ifndef _L_SIP_HEADERS_H_ +#define _L_SIP_HEADERS_H_ -#include "identity-address.h" -#include "object/clonable-object-p.h" +#include "linphone/utils/general.h" // ============================================================================= +using namespace std; + LINPHONE_BEGIN_NAMESPACE -class IdentityAddressPrivate : public ClonableObjectPrivate { -private: - std::string scheme; - std::string username; - std::string domain; - std::string gruu; +namespace PriorityHeader { + constexpr const char HeaderName[] = "Priority"; - L_DECLARE_PUBLIC(IdentityAddress); -}; + // Values + constexpr const char NonUrgent[] = "non-urgent"; + constexpr const char Urgent[] = "urgent"; + constexpr const char Emergency[] = "emergency"; + constexpr const char Normal[] = "normal"; +} LINPHONE_END_NAMESPACE -#endif // ifndef _L_IDENTITY_ADDRESS_P_H_ +#endif // _L_SIP_HEADERS_H_ diff --git a/src/utils/background-task.cpp b/src/utils/background-task.cpp index a83d1b14b..0aaeb3397 100644 --- a/src/utils/background-task.cpp +++ b/src/utils/background-task.cpp @@ -20,38 +20,66 @@ #include "background-task.h" #include "c-wrapper/internal/c-sal.h" #include "logger/logger.h" +#include "core/core-p.h" + +// TODO: Remove me +#include "private.h" // To get access to the Sal // ============================================================================= LINPHONE_BEGIN_NAMESPACE -static void notifier (void *context) { +void BackgroundTask::sHandleTimeout (void *context) { static_cast(context)->handleTimeout(); } -void BackgroundTask::start () { +int BackgroundTask::sHandleSalTimeout (void *data, unsigned int events) { + static_cast(data)->handleSalTimeout(); + return BELLE_SIP_STOP; +} + +void BackgroundTask::handleSalTimeout () { + lWarning() << "Background task [" << mId << "] with name: [" << mName << "] is now expiring"; + stop(); +} + +void BackgroundTask::start (const std::shared_ptr &core, int maxDurationSeconds) { if (mName.empty()) { - lError() << "No name was set on background task."; + lError() << "No name was set on background task"; return; } - unsigned long newId = sal_begin_background_task(mName.c_str(), notifier, this); - lInfo() << "Starting background task [" << newId << "] with name : [" << mName << "]."; + unsigned long newId = sal_begin_background_task(mName.c_str(), sHandleTimeout, this); stop(); + if (newId == 0) + return; + + lInfo() << "Starting background task [" << newId << "] with name: [" << mName + << "] and expiration of [" << maxDurationSeconds << "]"; mId = newId; + if (maxDurationSeconds > 0) { + mSal = core->getCCore()->sal; + mTimeout = mSal->create_timer(sHandleSalTimeout, this, (unsigned int)maxDurationSeconds * 1000, mName.c_str()); + } } void BackgroundTask::stop () { if (mId == 0) return; - lInfo() << "Ending background task [" << mId << "] with name : [" << mName << "]."; + lInfo() << "Ending background task [" << mId << "] with name: [" << mName << "]"; sal_end_background_task(mId); + if (mTimeout) { + mSal->cancel_timer(mTimeout); + belle_sip_object_unref(mTimeout); + mTimeout = nullptr; + } mId = 0; } void BackgroundTask::handleTimeout () { - lWarning() << "Background task [" << mId << "] with name : [" << mName << "] has expired before completion..."; + lWarning() << "Background task [" << mId << "] with name: [" << mName + << "] is expiring from OS before completion..."; stop(); } diff --git a/src/utils/background-task.h b/src/utils/background-task.h index 45b2002fb..2567a5b6d 100644 --- a/src/utils/background-task.h +++ b/src/utils/background-task.h @@ -23,15 +23,19 @@ #include #include "linphone/utils/general.h" +#include "sal/sal.h" // ============================================================================= LINPHONE_BEGIN_NAMESPACE +class Sal; +class Core; + class BackgroundTask { public: - BackgroundTask () = default; - BackgroundTask (const std::string &name) : mName(name) {} + BackgroundTask () {} + BackgroundTask (const std::string &name) {} virtual ~BackgroundTask () { stop(); } @@ -40,16 +44,26 @@ public: mName = name; } - void start (); - void stop (); - const std::string &getName () const { return mName; } + /** + * Start a long running task for at most max_duration_seconds, after which it is automatically terminated + */ + void start (const std::shared_ptr &core, int maxDurationSeconds = 15 * 60); // 15 min by default, like on iOS + void stop (); + +protected: virtual void handleTimeout (); private: + static int sHandleSalTimeout(void *data, unsigned int events); + static void sHandleTimeout(void *data); + void handleSalTimeout(); + + belle_sip_source_t *mTimeout = nullptr; + Sal *mSal = nullptr; std::string mName; unsigned long mId = 0; }; diff --git a/src/utils/general.cpp b/src/utils/general.cpp index 16a956214..73ac6f1b8 100644 --- a/src/utils/general.cpp +++ b/src/utils/general.cpp @@ -25,7 +25,7 @@ LINPHONE_BEGIN_NAMESPACE -void l_assert (const char *condition, const char *file, int line) { +void lAssert (const char *condition, const char *file, int line) { lFatal() << "ASSERT: " << condition << " in " << file << " line " << line << "."; } diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index b7fb58925..eb5ba94d2 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -174,6 +175,12 @@ char *Utils::utf8ToChar (uint32_t ic) { return result; } +string Utils::trim (const string &str) { + auto itFront = find_if_not(str.begin(), str.end(), [] (int c) { return isspace(c); }); + auto itBack = find_if_not(str.rbegin(), str.rend(), [] (int c) { return isspace(c); }).base(); + return (itBack <= itFront ? string() : string(itFront, itBack)); +} + // ----------------------------------------------------------------------------- tm Utils::getTimeTAsTm (time_t time) { diff --git a/src/variant/variant.cpp b/src/variant/variant.cpp index 75aa69953..a3342c863 100644 --- a/src/variant/variant.cpp +++ b/src/variant/variant.cpp @@ -92,27 +92,27 @@ Variant::Variant (Type type) : Variant() { d->setType(type); } -Variant::Variant (const Variant &src) { +Variant::Variant (const Variant &other) { // Don't call placement new. L_ASSERT(!mPrivate); mPrivate = new VariantPrivate(); L_D(); - int type = src.getPrivate()->getType(); + int type = other.getPrivate()->getType(); d->setType(type); - const VariantPrivate::Value &value = src.getPrivate()->value; + const VariantPrivate::Value &value = other.getPrivate()->value; if (type == String) *d->value.str = *value.str; else d->value = value; } -Variant::Variant (Variant &&src) { +Variant::Variant (Variant &&other) { // Don't call placement new. L_ASSERT(!mPrivate); - ::swap(mPrivate, src.mPrivate); + ::swap(mPrivate, other.mPrivate); } Variant::Variant (int value) : Variant(Int) { @@ -188,31 +188,16 @@ Variant::~Variant () { delete d; } -bool Variant::operator!= (const Variant &variant) const { - // TODO. - return false; -} - -bool Variant::operator< (const Variant &variant) const { - // TODO. - return false; -} - -bool Variant::operator<= (const Variant &variant) const { - // TODO. - return false; -} - -Variant &Variant::operator= (const Variant &variant) { +Variant &Variant::operator= (const Variant &other) { L_D(); - if (this != &variant) { + if (this != &other) { // Update type. - int type = variant.getPrivate()->getType(); + int type = other.getPrivate()->getType(); d->setType(type); // Update value. - const VariantPrivate::Value &value = variant.getPrivate()->value; + const VariantPrivate::Value &value = other.getPrivate()->value; if (type == String) *d->value.str = *value.str; else @@ -222,26 +207,11 @@ Variant &Variant::operator= (const Variant &variant) { return *this; } -Variant &Variant::operator= (Variant &&variant) { - ::swap(mPrivate, variant.mPrivate); +Variant &Variant::operator= (Variant &&other) { + ::swap(mPrivate, other.mPrivate); return *this; } -bool Variant::operator== (const Variant &variant) const { - // TODO. - return false; -} - -bool Variant::operator> (const Variant &variant) const { - // TODO. - return false; -} - -bool Variant::operator>= (const Variant &variant) const { - // TODO. - return false; -} - bool Variant::isValid () const { L_D(); return d->getType() != Invalid; @@ -252,7 +222,7 @@ void Variant::clear () { d->setType(Invalid); } -void Variant::swap (const Variant &variant) { +void Variant::swap (const Variant &other) { // TODO. } diff --git a/src/variant/variant.h b/src/variant/variant.h index 17fb51a35..5036712d9 100644 --- a/src/variant/variant.h +++ b/src/variant/variant.h @@ -64,8 +64,8 @@ public: Variant (); Variant (Type type); - Variant (const Variant &src); - Variant (Variant &&src); + Variant (const Variant &other); + Variant (Variant &&other); Variant (int value); Variant (unsigned int value); @@ -87,14 +87,8 @@ public: ~Variant (); - bool operator!= (const Variant &variant) const; - bool operator< (const Variant &variant) const; - bool operator<= (const Variant &variant) const; - Variant &operator= (const Variant &variant); - Variant &operator= (Variant &&variant); - bool operator== (const Variant &variant) const; - bool operator> (const Variant &variant) const; - bool operator>= (const Variant &variant) const; + Variant &operator= (const Variant &other); + Variant &operator= (Variant &&other); template void setValue (const T &value) { diff --git a/tester/call_multi_tester.c b/tester/call_multi_tester.c index 1889a3683..da58f20eb 100644 --- a/tester/call_multi_tester.c +++ b/tester/call_multi_tester.c @@ -30,7 +30,17 @@ #define unlink _unlink #endif - +/* + * With IPV6, Flexisip automatically switches to TCP, so it's no more possible to really have Laure configured with UDP + * Anyway for IPV4, it's still a good opportunity to test UDP. + */ +static const char* get_laure_rc(void) { + if (liblinphone_tester_ipv6_available()) { + return "laure_tcp_rc"; + } else { + return "laure_rc_udp"; + } +} static void call_waiting_indication_with_param(bool_t enable_caller_privacy) { bctbx_list_t *iterator; bctbx_list_t* lcs; @@ -45,7 +55,7 @@ static void call_waiting_indication_with_param(bool_t enable_caller_privacy) { linphone_core_remove_supported_tag(pauline->lc,"gruu"); linphone_core_manager_start(pauline,TRUE); LinphoneCoreManager *laure = ms_new0(LinphoneCoreManager, 1); - linphone_core_manager_init(laure, "laure_rc_udp", NULL); + linphone_core_manager_init(laure, get_laure_rc(), NULL); linphone_core_remove_supported_tag(laure->lc,"gruu"); linphone_core_manager_start(laure, TRUE); LinphoneCallParams *laure_params=linphone_core_create_call_params(laure->lc, NULL); @@ -187,7 +197,7 @@ static void second_call_allowed_if_not_using_audio(void){ static void incoming_call_accepted_when_outgoing_call_in_state(LinphoneCallState state) { LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_tcp_rc"); - LinphoneCoreManager* laure = linphone_core_manager_new( "laure_rc_udp"); + LinphoneCoreManager* laure = linphone_core_manager_new( get_laure_rc()); bctbx_list_t* lcs; LinphoneCallParams *laure_params=linphone_core_create_call_params(laure->lc, NULL); LinphoneCallParams *marie_params=linphone_core_create_call_params(marie->lc, NULL); @@ -385,7 +395,7 @@ end: static void simple_conference(void) { LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_tcp_rc"); - LinphoneCoreManager* laure = linphone_core_manager_new( "laure_rc_udp"); + LinphoneCoreManager* laure = linphone_core_manager_new( get_laure_rc()); simple_conference_base(marie,pauline,laure, NULL, FALSE); linphone_core_manager_destroy(marie); linphone_core_manager_destroy(pauline); @@ -396,7 +406,7 @@ static void simple_conference(void) { static void simple_conference_from_scratch(void){ LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_tcp_rc"); - LinphoneCoreManager* laure = linphone_core_manager_new( "laure_rc_udp"); + LinphoneCoreManager* laure = linphone_core_manager_new( get_laure_rc()); LinphoneConference *conf; LinphoneConferenceParams *conf_params; LinphoneCall *pauline_call, *laure_call; @@ -469,7 +479,7 @@ static void simple_conference_from_scratch(void){ static void simple_encrypted_conference_with_ice(LinphoneMediaEncryption mode) { LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_tcp_rc"); - LinphoneCoreManager* laure = linphone_core_manager_new( "laure_rc_udp"); + LinphoneCoreManager* laure = linphone_core_manager_new( get_laure_rc()); if (linphone_core_media_encryption_supported(marie->lc,mode)) { linphone_core_set_firewall_policy(marie->lc,LinphonePolicyUseIce); @@ -504,7 +514,7 @@ static void simple_zrtp_conference_with_ice(void) { static void conference_hang_up_call_on_hold(void) { LinphoneCoreManager* marie = linphone_core_manager_new("marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new("pauline_tcp_rc"); - LinphoneCoreManager* laure = linphone_core_manager_new("laure_rc_udp"); + LinphoneCoreManager* laure = linphone_core_manager_new(get_laure_rc()); simple_conference_base(marie, pauline, laure, NULL, TRUE); linphone_core_manager_destroy(marie); linphone_core_manager_destroy(pauline); @@ -514,7 +524,7 @@ static void conference_hang_up_call_on_hold(void) { static void simple_call_transfer(void) { LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_tcp_rc"); - LinphoneCoreManager* laure = linphone_core_manager_new( "laure_rc_udp"); + LinphoneCoreManager* laure = linphone_core_manager_new( get_laure_rc()); LinphoneCall* pauline_called_by_marie; LinphoneCall *marie_calling_pauline; LinphoneCall *marie_calling_laure; @@ -579,7 +589,7 @@ end: static void unattended_call_transfer(void) { LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_tcp_rc"); - LinphoneCoreManager* laure = linphone_core_manager_new( "laure_rc_udp"); + LinphoneCoreManager* laure = linphone_core_manager_new( get_laure_rc()); LinphoneCall* pauline_called_by_marie; char* laure_identity=linphone_address_as_string(laure->identity); @@ -668,7 +678,7 @@ static void unattended_call_transfer_with_error(void) { static void call_transfer_existing_call(bool_t outgoing_call) { LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_tcp_rc"); - LinphoneCoreManager* laure = linphone_core_manager_new( "laure_rc_udp"); + LinphoneCoreManager* laure = linphone_core_manager_new( get_laure_rc()); LinphoneCall* marie_call_pauline; LinphoneCall* pauline_called_by_marie; LinphoneCall* marie_call_laure; @@ -768,7 +778,7 @@ static void call_transfer_existing_call_incoming_call(void) { static void call_transfer_existing_ringing_call(void) { LinphoneCoreManager *marie = linphone_core_manager_new("marie_rc"); LinphoneCoreManager *pauline = linphone_core_manager_new("pauline_tcp_rc"); - LinphoneCoreManager *laure = linphone_core_manager_new("laure_rc_udp"); + LinphoneCoreManager *laure = linphone_core_manager_new(get_laure_rc()); LinphoneCall *marie_call_pauline; LinphoneCall *pauline_called_by_marie; LinphoneCall *marie_call_laure; @@ -934,7 +944,7 @@ end: static void eject_from_3_participants_local_conference(void) { LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_tcp_rc"); - LinphoneCoreManager* laure = linphone_core_manager_new( "laure_rc_udp"); + LinphoneCoreManager* laure = linphone_core_manager_new( get_laure_rc()); eject_from_3_participants_conference(marie, pauline, laure, NULL); @@ -946,7 +956,7 @@ static void eject_from_3_participants_local_conference(void) { static void eject_from_4_participants_conference(void) { LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_tcp_rc"); - LinphoneCoreManager* laure = linphone_core_manager_new( "laure_rc_udp"); + LinphoneCoreManager* laure = linphone_core_manager_new( get_laure_rc()); LinphoneCoreManager* michelle = linphone_core_manager_new( "michelle_rc_udp"); int timeout_ms = 5000; stats initial_laure_stat; @@ -1038,7 +1048,7 @@ end: void simple_remote_conference(void) { LinphoneCoreManager *marie = linphone_core_manager_new("marie_rc"); LinphoneCoreManager *pauline = linphone_core_manager_new("pauline_tcp_rc"); - LinphoneCoreManager *laure = linphone_core_manager_new("laure_rc_udp"); + LinphoneCoreManager *laure = linphone_core_manager_new(get_laure_rc()); LinphoneConferenceServer *focus = linphone_conference_server_new("conference_focus_rc", TRUE); LpConfig *marie_config = linphone_core_get_config(marie->lc); LinphoneProxyConfig *focus_proxy_config = linphone_core_get_default_proxy_config(((LinphoneCoreManager *)focus)->lc); @@ -1064,7 +1074,7 @@ void simple_remote_conference(void) { void simple_remote_conference_shut_down_focus(void) { LinphoneCoreManager *marie = linphone_core_manager_new("marie_rc"); LinphoneCoreManager *pauline = linphone_core_manager_new("pauline_tcp_rc"); - LinphoneCoreManager *laure = linphone_core_manager_new("laure_rc_udp"); + LinphoneCoreManager *laure = linphone_core_manager_new(get_laure_rc()); LinphoneConferenceServer *focus = linphone_conference_server_new("conference_focus_rc", FALSE); LpConfig *marie_config = linphone_core_get_config(marie->lc); LinphoneProxyConfig *focus_proxy_config = linphone_core_get_default_proxy_config(((LinphoneCoreManager *)focus)->lc); @@ -1090,7 +1100,7 @@ void simple_remote_conference_shut_down_focus(void) { void eject_from_3_participants_remote_conference(void) { LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_tcp_rc"); - LinphoneCoreManager* laure = linphone_core_manager_new( "laure_rc_udp"); + LinphoneCoreManager* laure = linphone_core_manager_new( get_laure_rc()); LinphoneConferenceServer *focus = linphone_conference_server_new("conference_focus_rc", TRUE); LpConfig *marie_config = linphone_core_get_config(marie->lc); LinphoneProxyConfig *focus_proxy_config = linphone_core_get_default_proxy_config(((LinphoneCoreManager *)focus)->lc); @@ -1116,7 +1126,7 @@ void eject_from_3_participants_remote_conference(void) { void do_not_stop_ringing_when_declining_one_of_two_incoming_calls(void) { LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_tcp_rc"); - LinphoneCoreManager* laure = linphone_core_manager_new( "laure_rc_udp"); + LinphoneCoreManager* laure = linphone_core_manager_new( get_laure_rc()); LinphoneCall* pauline_called_by_marie; LinphoneCall* pauline_called_by_laure; LinphoneCallParams *laure_params=linphone_core_create_call_params(laure->lc, NULL); diff --git a/tester/call_single_tester.c b/tester/call_single_tester.c index eea49d2a3..88a6de226 100644 --- a/tester/call_single_tester.c +++ b/tester/call_single_tester.c @@ -810,10 +810,22 @@ static void multiple_answers_call_with_media_relay(void) { /* Scenario is this: pauline calls marie, which is registered 2 times. * Both linphones answer at the same time, and only one should get the * call running, the other should be terminated */ - LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_tcp_rc" ); - LinphoneCoreManager* marie1 = linphone_core_manager_new( "marie_rc" ); - LinphoneCoreManager* marie2 = linphone_core_manager_new( "marie_rc" ); + LinphoneCoreManager* pauline = linphone_core_manager_new2( "pauline_tcp_rc", FALSE); + LinphoneCoreManager* marie1 = linphone_core_manager_new2( "marie_rc", FALSE); + LinphoneCoreManager* marie2 = linphone_core_manager_new2( "marie_rc", FALSE ); + /* This tests a feature of the proxy (nta_msg_ackbye()) that doesn't work with gruu. + * Actually nta_msg_ackbye() is deprecated because it is not the task of the proxy to handle the race conditions of 200 Ok arriving at the same time. + * It is the job of the user-agent, see test multiple_answers_call() above. + */ + linphone_core_remove_supported_tag(pauline->lc,"gruu"); + linphone_core_remove_supported_tag(marie1->lc,"gruu"); + linphone_core_remove_supported_tag(marie2->lc,"gruu"); + + linphone_core_manager_start(pauline, TRUE); + linphone_core_manager_start(marie1, TRUE); + linphone_core_manager_start(marie2, TRUE); + LinphoneCall* call1, *call2; bctbx_list_t* lcs = bctbx_list_append(NULL,pauline->lc); @@ -1151,6 +1163,8 @@ static void cancel_other_device_after_accept(void) { BC_ASSERT_TRUE(wait_for(caller_mgr->lc,callee_mgr_2->lc,&callee_mgr_2->stat.number_of_LinphoneCallEnd,1)); BC_ASSERT_TRUE(wait_for(caller_mgr->lc,callee_mgr_2->lc,&callee_mgr_2->stat.number_of_LinphoneCallReleased,1)); + wait_for_until(caller_mgr->lc,callee_mgr_2->lc,NULL,0,500); + rei = linphone_call_get_error_info(call_callee_2); BC_ASSERT_PTR_NOT_NULL(rei); if (rei){ @@ -1341,9 +1355,10 @@ static void cancelled_ringing_call(void) { BC_ASSERT_EQUAL(pauline->stat.number_of_LinphoneCallEnd,1, int, "%d"); call_history = linphone_core_get_call_history(marie->lc); - BC_ASSERT_PTR_NOT_NULL(call_history); - BC_ASSERT_EQUAL((int)bctbx_list_size(call_history),1, int,"%i"); - BC_ASSERT_EQUAL(linphone_call_log_get_status((LinphoneCallLog*)bctbx_list_get_data(call_history)), LinphoneCallMissed, LinphoneCallStatus, "%i"); + if (BC_ASSERT_PTR_NOT_NULL(call_history)) { + BC_ASSERT_EQUAL((int)bctbx_list_size(call_history),1, int,"%i"); + BC_ASSERT_EQUAL(linphone_call_log_get_status((LinphoneCallLog*)bctbx_list_get_data(call_history)), LinphoneCallMissed, LinphoneCallStatus, "%i"); + } linphone_call_unref(out_call); linphone_core_manager_destroy(marie); @@ -1450,18 +1465,21 @@ static void call_declined_with_error(void) { linphone_core_manager_destroy(caller_mgr); } -static void call_declined(void) { +static void call_declined_base(bool_t use_timeout) { LinphoneCoreManager* marie = linphone_core_manager_new("marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new(transport_supported(LinphoneTransportTls) ? "pauline_rc" : "pauline_tcp_rc"); LinphoneCall* in_call; LinphoneCall* out_call = linphone_core_invite_address(pauline->lc,marie->identity); + if (use_timeout) + linphone_core_set_inc_timeout(marie->lc, 1); linphone_call_ref(out_call); BC_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneCallIncomingReceived,1)); BC_ASSERT_PTR_NOT_NULL(in_call=linphone_core_get_current_call(marie->lc)); if (in_call) { linphone_call_ref(in_call); - linphone_call_terminate(in_call); + if (!use_timeout) + linphone_call_terminate(in_call); BC_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneCallReleased,1)); BC_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&pauline->stat.number_of_LinphoneCallReleased,1)); BC_ASSERT_EQUAL(marie->stat.number_of_LinphoneCallEnd,1, int, "%d"); @@ -1477,6 +1495,14 @@ static void call_declined(void) { linphone_core_manager_destroy(pauline); } +static void call_declined(void) { + call_declined_base(FALSE); +} + +static void call_declined_on_timeout(void) { + call_declined_base(TRUE); +} + static void call_terminated_by_caller(void) { LinphoneCoreManager* marie = linphone_core_manager_new("marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new(transport_supported(LinphoneTransportTls) ? "pauline_rc" : "pauline_tcp_rc"); @@ -5059,6 +5085,7 @@ static void recovered_call_on_network_switch_during_reinvite_1(void) { wait_for(marie->lc, pauline->lc, &marie->stat.number_of_NetworkReachableTrue, 2); BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallPaused, 1)); + wait_for_until(marie->lc, pauline->lc, NULL, 1, 2000); linphone_call_terminate(incoming_call); BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallEnd, 1)); BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallReleased, 1)); @@ -5818,6 +5845,10 @@ static void call_with_zrtp_configured_callee_base(LinphoneCoreManager *marie, Li } } +static bool_t is_matching_local_v4_or_v6(const char *ip, const char *localv4, const char *localv6){ + if (strlen(ip)==0) return FALSE; + return strcmp(ip, localv4) == 0 || strcmp(ip, localv6) == 0; +} /* * this test checks the 'dont_default_to_stun_candidates' mode, where the c= line is left to host @@ -5825,20 +5856,24 @@ static void call_with_zrtp_configured_callee_base(LinphoneCoreManager *marie, Li static void call_with_ice_with_default_candidate_not_stun(void){ LinphoneCoreManager * marie = linphone_core_manager_new( "marie_rc"); LinphoneCoreManager *pauline = linphone_core_manager_new(transport_supported(LinphoneTransportTls) ? "pauline_rc" : "pauline_tcp_rc"); - char localip[LINPHONE_IPADDR_SIZE]; + char localip[LINPHONE_IPADDR_SIZE]={0}; + char localip6[LINPHONE_IPADDR_SIZE]={0}; bool_t call_ok; lp_config_set_int(linphone_core_get_config(marie->lc), "net", "dont_default_to_stun_candidates", 1); linphone_core_set_firewall_policy(marie->lc, LinphonePolicyUseIce); linphone_core_set_firewall_policy(pauline->lc, LinphonePolicyUseIce); linphone_core_get_local_ip(marie->lc, AF_INET, NULL, localip); + linphone_core_get_local_ip(marie->lc, AF_INET6, NULL, localip6); call_ok = call(marie, pauline); if (call_ok){ check_ice(marie, pauline, LinphoneIceStateHostConnection); - BC_ASSERT_STRING_EQUAL(_linphone_call_get_local_desc(linphone_core_get_current_call(marie->lc))->addr, localip); - BC_ASSERT_STRING_EQUAL(_linphone_call_get_result_desc(linphone_core_get_current_call(pauline->lc))->addr, localip); - BC_ASSERT_STRING_EQUAL(_linphone_call_get_local_desc(linphone_core_get_current_call(marie->lc))->streams[0].rtp_addr, localip); - BC_ASSERT_STRING_EQUAL(_linphone_call_get_result_desc(linphone_core_get_current_call(pauline->lc))->streams[0].rtp_addr, ""); + BC_ASSERT_TRUE(is_matching_local_v4_or_v6(_linphone_call_get_local_desc(linphone_core_get_current_call(marie->lc))->addr, localip, localip6)); + BC_ASSERT_TRUE(is_matching_local_v4_or_v6(_linphone_call_get_local_desc(linphone_core_get_current_call(marie->lc))->streams[0].rtp_addr, localip, localip6)); + BC_ASSERT_TRUE(is_matching_local_v4_or_v6(_linphone_call_get_local_desc(linphone_core_get_current_call(marie->lc))->streams[0].rtp_addr, localip, localip6)); + BC_ASSERT_TRUE(is_matching_local_v4_or_v6(_linphone_call_get_result_desc(linphone_core_get_current_call(pauline->lc))->streams[0].rtp_addr, localip, localip6) + || is_matching_local_v4_or_v6(_linphone_call_get_result_desc(linphone_core_get_current_call(pauline->lc))->addr, localip, localip6) + ); } end_call(marie, pauline); linphone_core_manager_destroy(marie); @@ -5869,6 +5904,22 @@ static void call_with_ice_without_stun2(void){ linphone_core_manager_destroy(pauline); } +#if 0 +static void call_with_ice_stun_not_responding(void){ + LinphoneCoreManager * marie = linphone_core_manager_new( "marie_rc"); + LinphoneCoreManager *pauline = linphone_core_manager_new(transport_supported(LinphoneTransportTls) ? "pauline_rc" : "pauline_tcp_rc"); + + /*set dummy stun servers*/ + linphone_core_set_stun_server(marie->lc, "belledonne-communications.com:443"); + linphone_core_set_stun_server(pauline->lc, "belledonne-communications.com:443"); + /*we expect ICE to continue without stun candidates*/ + _call_with_ice_base(marie, pauline, TRUE, TRUE, TRUE, FALSE); + + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); +} +#endif + static void call_with_zrtp_configured_calling_side(void) { LinphoneCoreManager* marie = linphone_core_manager_new("marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new(transport_supported(LinphoneTransportTls) ? "pauline_rc" : "pauline_tcp_rc"); @@ -6339,6 +6390,7 @@ end: test_t call_tests[] = { TEST_NO_TAG("Early declined call", early_declined_call), TEST_NO_TAG("Call declined", call_declined), + TEST_NO_TAG("Call declined on timeout",call_declined_on_timeout), TEST_NO_TAG("Call declined with error", call_declined_with_error), TEST_NO_TAG("Cancelled call", cancelled_call), TEST_NO_TAG("Early cancelled call", early_cancelled_call), @@ -6478,6 +6530,7 @@ test_t call_tests[] = { TEST_ONE_TAG("Call with ICE with default candidate not stun", call_with_ice_with_default_candidate_not_stun, "ICE"), TEST_ONE_TAG("Call with ICE without stun server", call_with_ice_without_stun, "ICE"), TEST_ONE_TAG("Call with ICE without stun server one side", call_with_ice_without_stun2, "ICE"), + /*TEST_ONE_TAG("Call with ICE and stun server not responding", call_with_ice_stun_not_responding, "ICE"),*/ TEST_NO_TAG("Call with ZRTP configured calling side only", call_with_zrtp_configured_calling_side), TEST_NO_TAG("Call with ZRTP configured receiver side only", call_with_zrtp_configured_callee_side), TEST_NO_TAG("Call from plain RTP to ZRTP mandatory should be silent", call_from_plain_rtp_to_zrtp), diff --git a/tester/call_video_tester.c b/tester/call_video_tester.c index cf55ad1f1..d74fbd2f8 100644 --- a/tester/call_video_tester.c +++ b/tester/call_video_tester.c @@ -2008,6 +2008,70 @@ static void video_call_with_thin_congestion(void){ linphone_core_manager_destroy(pauline); } +static void on_tmmbr_received(LinphoneCall *call, int stream_index, int tmmbr) { + if (stream_index == _linphone_call_get_main_video_stream_index(call)) { + stats* stat = get_stats(linphone_call_get_core(call)); + stat->tmmbr_received_from_cb = tmmbr; + } +} + +static void call_created(LinphoneCore *lc, LinphoneCall *call) { + LinphoneCallCbs *cbs = linphone_factory_create_call_cbs(linphone_factory_get()); + linphone_call_cbs_set_tmmbr_received(cbs, on_tmmbr_received); + linphone_call_add_callbacks(call, cbs); + linphone_call_cbs_unref(cbs); +} + +/* + * This test simulates a higher bandwith available from marie than expected. + * The stream from pauline to marie is not under test. + * It checks that after a few seconds marie received a TMMBR with the approximate value corresponding to the new bandwidth. + * +**/ +static void video_call_with_high_bandwidth_available(void) { + LinphoneCoreManager *marie = linphone_core_manager_new("marie_rc"); + LinphoneCoreManager *pauline = linphone_core_manager_new("pauline_rc"); + LinphoneVideoPolicy pol = {0}; + OrtpNetworkSimulatorParams simparams = { 0 }; + LinphoneCoreCbs *core_cbs = linphone_factory_create_core_cbs(linphone_factory_get()); + + linphone_core_set_video_device(marie->lc, "Mire: Mire (synthetic moving picture)"); + linphone_core_enable_video_capture(marie->lc, TRUE); + linphone_core_enable_video_display(marie->lc, TRUE); + linphone_core_enable_video_capture(pauline->lc, TRUE); + linphone_core_enable_video_display(pauline->lc, TRUE); + + pol.automatically_accept = TRUE; + pol.automatically_initiate = TRUE; + linphone_core_set_video_policy(marie->lc, &pol); + linphone_core_set_video_policy(pauline->lc, &pol); + + linphone_core_set_preferred_video_size_by_name(marie->lc, "vga"); + simparams.mode = OrtpNetworkSimulatorOutbound; + simparams.enabled = TRUE; + simparams.max_bandwidth = 1000000; + simparams.max_buffer_size = (int)simparams.max_bandwidth; + simparams.latency = 60; + + linphone_core_set_network_simulator_params(marie->lc, &simparams); + + linphone_core_cbs_set_call_created(core_cbs, call_created); + linphone_core_add_callbacks(marie->lc, core_cbs); + + if (BC_ASSERT_TRUE(call(marie, pauline))){ + /*wait a little in order to have traffic*/ + BC_ASSERT_TRUE(wait_for_until(marie->lc, pauline->lc, NULL, 5, 50000)); + + BC_ASSERT_GREATER((float)marie->stat.last_tmmbr_value_received, 870000.f, float, "%f"); + BC_ASSERT_LOWER((float)marie->stat.last_tmmbr_value_received, 1150000.f, float, "%f"); + + end_call(marie, pauline); + } + linphone_core_cbs_unref(core_cbs); + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); +} + test_t call_video_tests[] = { #ifdef VIDEO_ENABLED TEST_NO_TAG("Call paused resumed with video", call_paused_resumed_with_video), @@ -2073,10 +2137,11 @@ test_t call_video_tests[] = { TEST_NO_TAG("Classic video entry phone setup", classic_video_entry_phone_setup), TEST_NO_TAG("Incoming REINVITE with invalid SDP in ACK", incoming_reinvite_with_invalid_ack_sdp), TEST_NO_TAG("Outgoing REINVITE with invalid SDP in ACK", outgoing_reinvite_with_invalid_ack_sdp), -#endif TEST_NO_TAG("Video call with no audio and no video codec", video_call_with_no_audio_and_no_video_codec), TEST_NO_TAG("Call with early media and no SDP in 200 Ok with video", call_with_early_media_and_no_sdp_in_200_with_video), - TEST_NO_TAG("Video call with thin congestion", video_call_with_thin_congestion) + TEST_NO_TAG("Video call with thin congestion", video_call_with_thin_congestion), + TEST_NO_TAG("Video call with high bandwidth available", video_call_with_high_bandwidth_available) +#endif }; test_suite_t call_video_test_suite = {"Video Call", NULL, NULL, liblinphone_tester_before_each, liblinphone_tester_after_each, diff --git a/tester/certificates/client/cert2.pem b/tester/certificates/client/cert2.pem index dc359d851..bec3c3668 100644 --- a/tester/certificates/client/cert2.pem +++ b/tester/certificates/client/cert2.pem @@ -1,35 +1,35 @@ Certificate: Data: Version: 3 (0x2) - Serial Number: 13 (0xd) + Serial Number: 16 (0x10) Signature Algorithm: sha256WithRSAEncryption Issuer: C=FR, ST=Some-State, L=Grenoble, O=Belledonne Communications, OU=LAB, CN=Jehan Monnier/emailAddress=jehan.monnier@belledonne-communications.com Validity - Not Before: Nov 17 11:09:48 2016 GMT - Not After : Nov 17 11:09:48 2017 GMT - Subject: C=FR, ST=Some-State, L=Lorien, O=Internet Widgits Pty Ltd, CN=sip:galadrielle@sip.example.org + Not Before: Mar 21 16:35:56 2018 GMT + Not After : Mar 18 16:35:56 2028 GMT + Subject: C=FR, ST=Lorien, O=Elfes, CN=sip:galadrielle@sip.example.org Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (2048 bit) Modulus: - 00:ae:3c:ab:f2:34:4b:dd:3e:96:b4:0f:76:61:5f: - 59:dd:d0:93:6f:05:04:a2:2e:f7:f5:2f:65:35:02: - f5:6f:ed:dd:46:bb:72:3e:7c:47:b5:37:15:1d:1d: - 90:a7:dc:0f:bf:cc:a8:58:43:86:fb:b8:c7:7e:13: - 7f:05:09:47:6b:bf:a1:d1:76:7d:7a:d3:09:3a:46: - 78:22:08:49:cd:02:8d:80:10:ee:d1:18:3c:e4:df: - 50:be:05:80:88:56:c3:d4:36:2c:05:5d:57:07:9a: - 4a:13:99:7f:46:d9:0b:dd:81:51:29:bd:8e:3a:55: - b2:33:f2:e6:3e:1c:ce:f9:2f:80:68:ca:5a:78:c5: - e1:27:4a:b4:0b:65:9b:24:ee:df:8c:16:f0:74:dc: - fe:a5:9f:52:5a:a1:f9:09:1d:47:00:d9:8a:84:72: - e2:19:7b:cb:cd:62:b3:44:e3:4f:cf:9b:1c:a1:bc: - 70:d3:e0:10:8b:f2:51:28:91:84:61:92:56:03:3a: - 2c:bf:11:8d:b6:4b:c8:4f:1c:e7:75:54:b9:cd:f3: - d5:be:6b:af:6e:9f:ca:77:45:44:5c:55:6a:23:49: - e0:52:fc:30:3d:a9:a8:66:f1:d8:d0:a8:5b:97:3c: - a7:de:70:db:7b:85:c1:f5:8e:54:3c:f8:0f:3a:9f: - 36:2d + 00:e5:0b:bd:b3:f7:e7:c2:1f:27:40:1f:57:7f:0b: + 47:67:08:54:aa:6f:78:f9:02:32:10:fa:0c:fd:4a: + 0f:3c:a3:f2:34:d0:60:93:1c:c5:fe:25:a5:66:7f: + 02:11:23:cc:98:b2:34:3e:1b:31:8a:f4:9d:d6:89: + a6:41:d5:8f:fc:db:24:0f:61:af:e2:15:7b:71:d7: + 10:3e:25:ea:a4:dd:f6:c5:6b:ac:b8:a8:f5:34:fe: + a2:7e:fc:8e:b5:99:55:2a:74:c4:55:3f:9a:ae:a0: + 62:b3:03:50:aa:39:dd:8c:62:22:ac:3d:0d:60:d0: + da:49:0d:31:79:01:e4:59:9b:54:d8:78:e6:90:3d: + 8a:d6:1b:6c:1f:4e:4e:d7:76:a2:16:e7:37:d8:c8: + cf:33:95:b9:eb:fe:e0:b5:10:6f:b6:0d:31:a5:d4: + d5:86:07:1b:05:43:f1:42:0b:f7:ba:20:96:45:48: + ec:6f:c9:29:8c:fe:15:55:d8:26:f2:20:c6:db:37: + 67:e6:42:de:4b:66:fa:68:a1:4c:6f:39:01:56:92: + 5c:7d:68:2d:58:85:4a:67:a5:03:cb:c5:1d:22:7c: + 00:24:cd:49:3b:68:af:1a:0f:cf:ed:09:9f:74:7a: + 54:69:6c:2e:bf:72:76:46:b0:26:24:14:e0:74:8e: + 0e:23 Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Basic Constraints: @@ -37,38 +37,37 @@ Certificate: Netscape Comment: OpenSSL Generated Certificate X509v3 Subject Key Identifier: - 33:D0:36:5B:62:9B:1C:4D:31:47:9E:C0:91:41:E3:AE:29:61:AB:DB + 22:E0:9B:5C:30:1A:B0:02:A3:88:63:73:16:01:60:E1:16:1A:8A:AD X509v3 Authority Key Identifier: keyid:06:5F:5D:C7:16:AF:62:F8:2D:6E:71:03:88:A0:D6:1D:2B:04:7F:BA Signature Algorithm: sha256WithRSAEncryption - ba:a1:0a:7e:8e:a6:1e:e8:3d:5f:da:28:a6:57:3e:cb:50:79: - 06:8f:19:1b:df:b0:d2:e6:12:1f:ef:a2:bd:de:40:07:e2:5d: - 3d:64:41:34:10:24:3c:85:62:8e:69:0c:99:89:b7:ce:a4:f6: - 08:6d:37:8a:51:98:bd:46:b7:1b:dd:b2:ba:f7:f4:2f:47:d5: - 74:3f:c5:fe:95:60:b3:42:51:4f:d1:ac:ed:a4:c6:f6:16:f3: - 49:b6:8d:64:7f:76:e1:95:5e:ef:eb:46:4b:d7:a5:59:1d:0d: - ba:c5:07:5f:c3:db:2e:40:aa:6e:34:0c:1a:1d:4b:72:e3:ac: - 61:b5 + 12:78:7f:19:e2:38:d1:aa:9d:fe:c7:30:fb:00:00:ce:ce:28: + 66:fc:fe:d2:fb:3c:f8:af:68:83:11:96:30:ab:97:f9:f0:cd: + 09:67:12:4f:9f:97:ad:39:1c:a2:d2:f2:8c:38:71:be:1f:0a: + c8:12:93:8b:42:d7:1a:3a:29:6e:01:08:50:44:a3:cc:09:39: + 63:90:5f:20:09:70:a8:70:ed:91:1e:78:1b:f3:5c:2d:84:a8: + 6e:71:ff:36:0b:fa:b5:26:63:8b:d4:80:43:f0:4a:89:86:d5: + 37:3b:23:c2:2b:40:14:04:e6:67:5e:a5:61:68:8e:03:b7:4c: + cd:16 -----BEGIN CERTIFICATE----- -MIIDsjCCAxugAwIBAgIBDTANBgkqhkiG9w0BAQsFADCBuzELMAkGA1UEBhMCRlIx +MIIDiTCCAvKgAwIBAgIBEDANBgkqhkiG9w0BAQsFADCBuzELMAkGA1UEBhMCRlIx EzARBgNVBAgMClNvbWUtU3RhdGUxETAPBgNVBAcMCEdyZW5vYmxlMSIwIAYDVQQK DBlCZWxsZWRvbm5lIENvbW11bmljYXRpb25zMQwwCgYDVQQLDANMQUIxFjAUBgNV BAMMDUplaGFuIE1vbm5pZXIxOjA4BgkqhkiG9w0BCQEWK2plaGFuLm1vbm5pZXJA -YmVsbGVkb25uZS1jb21tdW5pY2F0aW9ucy5jb20wHhcNMTYxMTE3MTEwOTQ4WhcN -MTcxMTE3MTEwOTQ4WjCBgDELMAkGA1UEBhMCRlIxEzARBgNVBAgMClNvbWUtU3Rh -dGUxDzANBgNVBAcMBkxvcmllbjEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ -dHkgTHRkMSgwJgYDVQQDDB9zaXA6Z2FsYWRyaWVsbGVAc2lwLmV4YW1wbGUub3Jn -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArjyr8jRL3T6WtA92YV9Z -3dCTbwUEoi739S9lNQL1b+3dRrtyPnxHtTcVHR2Qp9wPv8yoWEOG+7jHfhN/BQlH -a7+h0XZ9etMJOkZ4IghJzQKNgBDu0Rg85N9QvgWAiFbD1DYsBV1XB5pKE5l/RtkL -3YFRKb2OOlWyM/LmPhzO+S+AaMpaeMXhJ0q0C2WbJO7fjBbwdNz+pZ9SWqH5CR1H -ANmKhHLiGXvLzWKzRONPz5scobxw0+AQi/JRKJGEYZJWAzosvxGNtkvITxzndVS5 -zfPVvmuvbp/Kd0VEXFVqI0ngUvwwPamoZvHY0Khblzyn3nDbe4XB9Y5UPPgPOp82 -LQIDAQABo3sweTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdl -bmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUM9A2W2KbHE0xR57AkUHjrilh -q9swHwYDVR0jBBgwFoAUBl9dxxavYvgtbnEDiKDWHSsEf7owDQYJKoZIhvcNAQEL -BQADgYEAuqEKfo6mHug9X9ooplc+y1B5Bo8ZG9+w0uYSH++ivd5AB+JdPWRBNBAk -PIVijmkMmYm3zqT2CG03ilGYvUa3G92yuvf0L0fVdD/F/pVgs0JRT9Gs7aTG9hbz -SbaNZH924ZVe7+tGS9elWR0NusUHX8PbLkCqbjQMGh1LcuOsYbU= +YmVsbGVkb25uZS1jb21tdW5pY2F0aW9ucy5jb20wHhcNMTgwMzIxMTYzNTU2WhcN +MjgwMzE4MTYzNTU2WjBYMQswCQYDVQQGEwJGUjEPMA0GA1UECAwGTG9yaWVuMQ4w +DAYDVQQKDAVFbGZlczEoMCYGA1UEAwwfc2lwOmdhbGFkcmllbGxlQHNpcC5leGFt +cGxlLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOULvbP358If +J0AfV38LR2cIVKpvePkCMhD6DP1KDzyj8jTQYJMcxf4lpWZ/AhEjzJiyND4bMYr0 +ndaJpkHVj/zbJA9hr+IVe3HXED4l6qTd9sVrrLio9TT+on78jrWZVSp0xFU/mq6g +YrMDUKo53YxiIqw9DWDQ2kkNMXkB5FmbVNh45pA9itYbbB9OTtd2ohbnN9jIzzOV +uev+4LUQb7YNMaXU1YYHGwVD8UIL97oglkVI7G/JKYz+FVXYJvIgxts3Z+ZC3ktm ++mihTG85AVaSXH1oLViFSmelA8vFHSJ8ACTNSTtorxoPz+0Jn3R6VGlsLr9ydkaw +JiQU4HSODiMCAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3Bl +blNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFCLgm1wwGrACo4hj +cxYBYOEWGoqtMB8GA1UdIwQYMBaAFAZfXccWr2L4LW5xA4ig1h0rBH+6MA0GCSqG +SIb3DQEBCwUAA4GBABJ4fxniONGqnf7HMPsAAM7OKGb8/tL7PPivaIMRljCrl/nw +zQlnEk+fl605HKLS8ow4cb4fCsgSk4tC1xo6KW4BCFBEo8wJOWOQXyAJcKhw7ZEe +eBvzXC2EqG5x/zYL+rUmY4vUgEPwSomG1Tc7I8IrQBQE5mdepWFojgO3TM0W -----END CERTIFICATE----- diff --git a/tester/certificates/client/key2.pem b/tester/certificates/client/key2.pem index d6eab8da5..e3b6c9551 100644 --- a/tester/certificates/client/key2.pem +++ b/tester/certificates/client/key2.pem @@ -1,28 +1,28 @@ -----BEGIN PRIVATE KEY----- -MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCuPKvyNEvdPpa0 -D3ZhX1nd0JNvBQSiLvf1L2U1AvVv7d1Gu3I+fEe1NxUdHZCn3A+/zKhYQ4b7uMd+ -E38FCUdrv6HRdn160wk6RngiCEnNAo2AEO7RGDzk31C+BYCIVsPUNiwFXVcHmkoT -mX9G2QvdgVEpvY46VbIz8uY+HM75L4Boylp4xeEnSrQLZZsk7t+MFvB03P6ln1Ja -ofkJHUcA2YqEcuIZe8vNYrNE40/PmxyhvHDT4BCL8lEokYRhklYDOiy/EY22S8hP -HOd1VLnN89W+a69un8p3RURcVWojSeBS/DA9qahm8djQqFuXPKfecNt7hcH1jlQ8 -+A86nzYtAgMBAAECggEAHyf8O0A8vKA/hI0rRvgs8qwkYPrNvE6XykEiYNtZlh07 -rzU/lYrVq8LgxKcPweRo8IwhIj9Y+NQu4A2ObhEds1e+EN2WTItGICSPwM4onD8z -nE3q1nr2EJsaLhB/zmFtfRn+vyrUsChXzK9rAfk31PEV2VfrAeVnC0EJCNxP6mDX -gAjTNN/+Elqzr8Cr7aofthaMnCWnI6JBJ0MCqaozDBreyfGkaFC+RkRxUpZQerqN -tvcurKn0C/Q5ZcfIugvnEFa4nL/V4s+j4Kv1SWgvfi2z4eR7wyiZVT+mStMiHvg5 -JCLNli4GtFyhYzsTqUnd3S2t0unEdaFLEzJakHGjQQKBgQDdjw9UN354QS2Aiqoe -Gu5e9nc3gi3e/dHmPyk4jKPC/cqrQ3AVrXILLjU/FHpT7OrkwoQNvI0qG39r1Akq -hnztTqDw0HVskuWJmPmUxfdl6DIOUln7pEX4yZMreDwdEjxx/oZzbu7bhU3k7zNV -zKv54deN78AmtVI5KzrEdvKfnQKBgQDJUnAtvDeuwE44XUU0mBoH3XdLULLaVeAl -4vovM/8U283+wiBkASXamFimboBKe34TGH/v10hmKxBHyPCgl9ps6o9iFbPRNzOB -kmGrTTojSOJ6u9EXvQ+wTYjzl2n/RlivIsOZRC0YXmk3n+mRPa0TGwnpxH13cEFV -RnEUnYdT0QKBgBZXw/L5Oa7E2+LXmPo6OwmmjzUw0pFnRVCT1ANY43bZgyOsRFRb -TmHkQghfd0qZXMK+/vQnrJCvfzUPh/Ea6ORBhqdiTkUpty4eGCUxpZZISSv6kAp5 -cXj6UvYSRPWljiTsxwBDEqFemxFYMfQYFMu5Q7STlewRYv5S5rVDTYpdAoGAG77I -xwTRh7vpC8uO5hiwPbU/45lTjNOY+J+3axn3ZaCFWz7Vx/KAjQfB7+36sEkkru0J -dLxuteXpcHs47mj/KVOKPzJOfd7lsk3COCGEiahZziBkSKk9qEaHQUr0yMGhJ0Hb -QxwqOtmIFqprPiEJ4UAwtY7m27cUyfPTUcwEAoECgYBEoCn8kmRXuBoDVNPK1IPh -vQcD0RDdtGhOrM36Pmmbky6oS37c3AV4sXOhw7aTYs4GejpeH0tX7F0hiwaZ/SqG -WxliyHCpUxpl+LsGzdfqCa9nEPn4B27/jFYHVCiSheOfVEwjGavkO+VIZbuHXAP4 -V8rXqdmFIbiVb43P6yoMhg== +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDlC72z9+fCHydA +H1d/C0dnCFSqb3j5AjIQ+gz9Sg88o/I00GCTHMX+JaVmfwIRI8yYsjQ+GzGK9J3W +iaZB1Y/82yQPYa/iFXtx1xA+Jeqk3fbFa6y4qPU0/qJ+/I61mVUqdMRVP5quoGKz +A1CqOd2MYiKsPQ1g0NpJDTF5AeRZm1TYeOaQPYrWG2wfTk7XdqIW5zfYyM8zlbnr +/uC1EG+2DTGl1NWGBxsFQ/FCC/e6IJZFSOxvySmM/hVV2CbyIMbbN2fmQt5LZvpo +oUxvOQFWklx9aC1YhUpnpQPLxR0ifAAkzUk7aK8aD8/tCZ90elRpbC6/cnZGsCYk +FOB0jg4jAgMBAAECggEBAJ5dZNfHQ23b3maehP+pS8A4aVnCY1FALF/ClDKY/zn9 +XR0ZKnzs+xSC8P6SOFqjdvXo2OhMIxAhm/RXDiYcxEafOiqMb3CRS93lRizCSJ+f +fNz5Wt3+rDPtD2tfskhrcRA+1fTfWlL9P+DoHODly4Ih5DlUqShUn2i8/4TcQweU +aM/Kii6s5lf76nqEd4ztzseyn1f3sklxFHI1p0+mzk57YghTdaD/qJiFV0Lxf6jw +SLb5eMmo+BRucV9Rl7jXlhJW00pTIc38ssRdH2XvHw5h51kv9CBRQFSmdnGrzj1/ +D2l+C5gxLIGshbEoAaFDJWZi2v01wWCpe7N0GXxl4HECgYEA9SNHCpWIehydIra1 +h0tBWOxv9gac7OiW64bDd5op6XYXNTwI8GIGX3VsV+avDhPWTptI0+cCOAgBqPyi +LCwT3HrXTyzRcJctlwHRZIUkzlr26Z+5WUJK0+bdCWiOCE31+pS6qX9bjreq1pL6 +C9Gfl3m4FXCICXCGSSutKBT8FRkCgYEA7zHsqzvBmJS8T57lNPBCqmpFI6viR5Vu +qza6zlJLSP7OTlWBiQ+coq4YmsAv2pF+i9j4VDizf77nF2tkNcC/2CJk70oXxj/Y +ahci9M5xnX9WyPn9x53clepT+XXUQ8yW/OvjKUc5gut1g5OZFsjGmWFk/EkzJLSz +p5lXMJl4iJsCgYA+5FQfpQmkup6d/15HXclgNRjsd/ne1jWSK7sOfmDuYrvFjqeE +dMHJz+iCDM8wv2omNLTUmNn64iL65gX9azmVQXbn+0mop0CtE2xTa81rm+7pNW9q +NRXZk8t11HtMKiRHq8zQG7qzvO95qa+5RIi7ZiESbxKXyWTKdQgx1mBuUQKBgHy1 +gdhRIoGj4n58sLImJgvltkB/6E08KuQXd9QEcf4P445R5GSKgDcNIATm+MwzGVBe +gjKfEW8kICZEto2T/jH41Lkx3y1csj+16mLKk8/yyVOli1wdARokfz3L4iyrKXma +nugxm1mX28ALH0ES0wC7F8S1gXW8xQI3346WJZLLAoGBALXykdDntTZ6RG5ahvQx +rLTr95sLxyujG2oyn7hatPcAfvqFatx2m+FiYVDw5x511XHJ49oFsxQ/+kiZrojJ +eKPvsy2gP8EievsEtK5+YDSkZQtZjzP65lOHd8g/0AmQGmO7RQ456GoG4qAhRc7+ +YwPlbSK0lTSXxcloSc2WW6ak -----END PRIVATE KEY----- diff --git a/tester/certificates/cn/openssl-cn.cnf b/tester/certificates/cn/openssl-cn.cnf index 908f6ed4c..fcfe5ef5f 100644 --- a/tester/certificates/cn/openssl-cn.cnf +++ b/tester/certificates/cn/openssl-cn.cnf @@ -70,7 +70,7 @@ cert_opt = ca_default # Certificate field options # crlnumber must also be commented out to leave a V1 CRL. # crl_extensions = crl_ext -default_days = 365 # how long to certify for +default_days = 3650 # how long to certify for default_crl_days= 30 # how long before next CRL default_md = default # use public key default MD preserve = no # keep passed DN ordering diff --git a/tester/conference-event-tester.cpp b/tester/conference-event-tester.cpp index 8f531974f..ecde1d4ab 100644 --- a/tester/conference-event-tester.cpp +++ b/tester/conference-event-tester.cpp @@ -1023,7 +1023,7 @@ void send_admined_notify() { BC_ASSERT_TRUE(!tester->participants.find(bobAddr.asString())->second); BC_ASSERT_TRUE(tester->participants.find(aliceAddr.asString())->second); - notify = localHandlerPrivate->createNotifyParticipantAdmined(bobAddr, true); + notify = localHandlerPrivate->createNotifyParticipantAdminStatusChanged(bobAddr, true); tester->handler->notifyReceived(notify); BC_ASSERT_EQUAL(tester->participants.size(), 2, int, "%d"); @@ -1078,7 +1078,7 @@ void send_unadmined_notify() { BC_ASSERT_TRUE(!tester->participants.find(bobAddr.asString())->second); BC_ASSERT_TRUE(tester->participants.find(aliceAddr.asString())->second); - notify = localHandlerPrivate->createNotifyParticipantAdmined(aliceAddr, false); + notify = localHandlerPrivate->createNotifyParticipantAdminStatusChanged(aliceAddr, false); tester->handler->notifyReceived(notify); BC_ASSERT_EQUAL(tester->participants.size(), 2, int, "%d"); diff --git a/tester/cpim-tester.cpp b/tester/cpim-tester.cpp index a98ca133d..e803ae33a 100644 --- a/tester/cpim-tester.cpp +++ b/tester/cpim-tester.cpp @@ -402,7 +402,6 @@ static int fake_im_encryption_engine_process_outgoing_message_cb(LinphoneImEncry static void cpim_chat_message_modifier_base(bool_t use_multipart) { LinphoneCoreManager* marie = linphone_core_manager_new("marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_tcp_rc"); - LpConfig *config = linphone_core_get_config(marie->lc); // We use a fake encryption engine just to check the internal content type during the sending/receiving process LinphoneImEncryptionEngine *marie_imee = linphone_im_encryption_engine_new(); @@ -414,8 +413,6 @@ static void cpim_chat_message_modifier_base(bool_t use_multipart) { linphone_core_set_im_encryption_engine(marie->lc, marie_imee); linphone_core_set_im_encryption_engine(pauline->lc, pauline_imee); - lp_config_set_int(config, "sip", "use_cpim", 1); - IdentityAddress paulineAddress(linphone_address_as_string_uri_only(pauline->identity)); shared_ptr marieRoom = marie->lc->cppPtr->getOrCreateBasicChatRoom(paulineAddress); marieRoom->allowCpim(true); diff --git a/tester/db/friends.db b/tester/db/friends.db new file mode 100644 index 000000000..20fd191ea Binary files /dev/null and b/tester/db/friends.db differ diff --git a/tester/db/linphone.db b/tester/db/linphone.db index 8e7f0f8a8..a3be0fceb 100644 Binary files a/tester/db/linphone.db and b/tester/db/linphone.db differ diff --git a/tester/db/messages.db b/tester/db/messages.db index 01ad041d3..2eb613810 100644 Binary files a/tester/db/messages.db and b/tester/db/messages.db differ diff --git a/tester/flexisip_tester.c b/tester/flexisip_tester.c index 11e80bab4..19bdbe9d7 100644 --- a/tester/flexisip_tester.c +++ b/tester/flexisip_tester.c @@ -1257,9 +1257,10 @@ static void tls_client_auth_try_register(const char *identity, bool_t with_good_ }else{ BC_ASSERT_TRUE(wait_for(lcm->lc, NULL, &lcm->stat.number_of_LinphoneRegistrationFailed, 1)); BC_ASSERT_EQUAL(lcm->stat.number_of_LinphoneRegistrationOk,0, int, "%d"); - /*we should expect 2 "auth_requested": one for the TLS certificate, another one because the server rejects the REGISTER with 401.*/ + /*we should expect at least 2 "auth_requested": one for the TLS certificate, another one because the server rejects the REGISTER with 401, + with eventually MD5 + SHA256 challenge*/ /*If the certificate isn't recognized at all, the connection will not happen and no SIP response will be received from server.*/ - if (with_good_cert) BC_ASSERT_EQUAL(lcm->stat.number_of_auth_info_requested,2, int, "%d"); + if (with_good_cert) BC_ASSERT_GREATER(lcm->stat.number_of_auth_info_requested,2, int, "%d"); else BC_ASSERT_EQUAL(lcm->stat.number_of_auth_info_requested,1, int, "%d"); } @@ -1388,7 +1389,7 @@ static void on_refer_received(SalOp *op, const SalAddress *refer_to) { Sal *sal = sal_op_get_sal(op); LinphoneCoreManager *receiver = (LinphoneCoreManager*)sal_get_user_pointer(sal); receiver->stat.number_of_LinphoneCallRefered++; - sal_release_op(op); + } void resend_refer_other_devices(void) { diff --git a/tester/group_chat_tester.c b/tester/group_chat_tester.c index 59cba3be5..bc9bb7159 100644 --- a/tester/group_chat_tester.c +++ b/tester/group_chat_tester.c @@ -107,7 +107,7 @@ static void chat_room_subject_changed (LinphoneChatRoom *cr, const LinphoneEvent static void core_chat_room_state_changed (LinphoneCore *core, LinphoneChatRoom *cr, LinphoneChatRoomState state) { if (state == LinphoneChatRoomStateInstantiated) { - LinphoneChatRoomCbs *cbs = linphone_chat_room_get_callbacks(cr); + LinphoneChatRoomCbs *cbs = linphone_factory_create_chat_room_cbs(linphone_factory_get()); linphone_chat_room_cbs_set_is_composing_received(cbs, chat_room_is_composing_received); linphone_chat_room_cbs_set_participant_added(cbs, chat_room_participant_added); linphone_chat_room_cbs_set_participant_admin_status_changed(cbs, chat_room_participant_admin_status_changed); @@ -115,6 +115,8 @@ static void core_chat_room_state_changed (LinphoneCore *core, LinphoneChatRoom * linphone_chat_room_cbs_set_state_changed(cbs, chat_room_state_changed); linphone_chat_room_cbs_set_subject_changed(cbs, chat_room_subject_changed); linphone_chat_room_cbs_set_participant_device_added(cbs, chat_room_participant_device_added); + linphone_chat_room_add_callbacks(cr, cbs); + linphone_chat_room_cbs_unref(cbs); } } @@ -132,9 +134,9 @@ static void configure_core_for_conference (LinphoneCore *core, const char* usern bctbx_free(newIdentity); linphone_core_enable_conference_server(core, server); char *factoryUri = linphone_address_as_string(factoryAddr); - linphone_core_set_conference_factory_uri(core, factoryUri); + LinphoneProxyConfig *proxy = linphone_core_get_default_proxy_config(core); + linphone_proxy_config_set_conference_factory_uri(proxy, factoryUri); bctbx_free(factoryUri); - linphone_config_set_int(linphone_core_get_config(core), "sip", "use_cpim", 1); linphone_core_set_linphone_specs(core, "groupchat"); } @@ -143,6 +145,8 @@ static void _configure_core_for_conference (LinphoneCoreManager *lcm, LinphoneAd } static void _configure_core_for_callbacks(LinphoneCoreManager *lcm, LinphoneCoreCbs *cbs) { + // Remove is-composing callback from the core, we use our own on the chat room + linphone_core_cbs_set_is_composing_received(lcm->cbs, NULL); linphone_core_add_callbacks(lcm->lc, cbs); linphone_core_set_user_data(lcm->lc, lcm); } @@ -151,11 +155,12 @@ static void _start_core(LinphoneCoreManager *lcm) { linphone_core_manager_start(lcm, TRUE); } -static void _send_message(LinphoneChatRoom *chatRoom, const char *message) { +static LinphoneChatMessage *_send_message(LinphoneChatRoom *chatRoom, const char *message) { LinphoneChatMessage *msg = linphone_chat_room_create_message(chatRoom, message); LinphoneChatMessageCbs *msgCbs = linphone_chat_message_get_callbacks(msg); linphone_chat_message_cbs_set_msg_state_changed(msgCbs, liblinphone_tester_chat_message_msg_state_changed); linphone_chat_message_send(msg); + return msg; } static void _send_file_plus_text(LinphoneChatRoom* cr, const char *sendFilepath, const char *text) { @@ -177,8 +182,9 @@ static void _send_file_plus_text(LinphoneChatRoom* cr, const char *sendFilepath, linphone_chat_message_cbs_set_file_transfer_send(cbs, tester_file_transfer_send); linphone_chat_message_cbs_set_msg_state_changed(cbs,liblinphone_tester_chat_message_msg_state_changed); linphone_chat_message_cbs_set_file_transfer_progress_indication(cbs, file_transfer_progress_indication); - linphone_chat_room_send_chat_message_2(cr, msg); + linphone_chat_room_send_chat_message(cr, msg); linphone_content_unref(content); + linphone_chat_message_unref(msg); } static void _send_file(LinphoneChatRoom* cr, const char *sendFilepath) { @@ -226,25 +232,19 @@ static void _receive_file_plus_text(bctbx_list_t *coresList, LinphoneCoreManager // Configure list of core manager for conference and add the listener static bctbx_list_t * init_core_for_conference(bctbx_list_t *coreManagerList) { - bctbx_list_t *coresList = NULL; LinphoneAddress *factoryAddr = linphone_address_new(sFactoryUri); - bctbx_list_for_each2(coreManagerList, (void (*)(void *, void*))_configure_core_for_conference, (void*) factoryAddr); + bctbx_list_for_each2(coreManagerList, (void (*)(void *, void *))_configure_core_for_conference, (void *) factoryAddr); linphone_address_unref(factoryAddr); LinphoneCoreCbs *cbs = linphone_factory_create_core_cbs(linphone_factory_get()); linphone_core_cbs_set_chat_room_state_changed(cbs, core_chat_room_state_changed); - bctbx_list_for_each2(coreManagerList, (void (*)(void *, void*))_configure_core_for_callbacks, (void*) cbs); + bctbx_list_for_each2(coreManagerList, (void (*)(void *, void *))_configure_core_for_callbacks, (void *) cbs); linphone_core_cbs_unref(cbs); - bctbx_list_t *iterator = coreManagerList; - while(iterator != bctbx_list_last_elem(coreManagerList)) { - if (!coresList) { - coresList = bctbx_list_new(((LinphoneCoreManager*)(bctbx_list_get_data(iterator)))->lc); - } else { - bctbx_list_append(coresList, ((LinphoneCoreManager*)(bctbx_list_get_data(iterator)))->lc); - } - iterator = bctbx_list_next(iterator); - } - bctbx_list_append(coresList, ((LinphoneCoreManager*)(bctbx_list_get_data(iterator)))->lc); + + bctbx_list_t *coresList = NULL; + bctbx_list_t *item = coreManagerList; + for (item = coreManagerList; item; item = bctbx_list_next(item)) + coresList = bctbx_list_append(coresList, ((LinphoneCoreManager *)(bctbx_list_get_data(item)))->lc); return coresList; } @@ -265,7 +265,8 @@ static LinphoneChatRoom * check_creation_chat_room_client_side(bctbx_list_t *lcs BC_ASSERT_EQUAL(linphone_chat_room_get_nb_participants(chatRoom), participantNumber, int, "%d"); LinphoneParticipant *participant = linphone_chat_room_get_me(chatRoom); BC_ASSERT_PTR_NOT_NULL(participant); - BC_ASSERT(isAdmin == linphone_participant_is_admin(participant)); + if (!(linphone_chat_room_get_capabilities(chatRoom) & LinphoneChatRoomCapabilitiesOneToOne)) + BC_ASSERT(isAdmin == linphone_participant_is_admin(participant)); BC_ASSERT_STRING_EQUAL(linphone_chat_room_get_subject(chatRoom), subject); } return chatRoom; @@ -311,6 +312,7 @@ static void group_chat_room_creation_server (void) { coresManagerList = bctbx_list_append(coresManagerList, laure); 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))); @@ -326,10 +328,10 @@ static void group_chat_room_creation_server (void) { 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); + LinphoneChatRoom *paulineCr = check_creation_chat_room_client_side(coresList, pauline, &initialPaulineStats, confAddr, initialSubject, 2, FALSE); // Check that the chat room is correctly created on Laure's side and that the participants are added - LinphoneChatRoom *laureCr = check_creation_chat_room_client_side(coresList, laure, &initialLaureStats, confAddr, initialSubject, 2, 0); + LinphoneChatRoom *laureCr = check_creation_chat_room_client_side(coresList, laure, &initialLaureStats, confAddr, initialSubject, 2, FALSE); // Pauline tries to change the subject but is not admin so it fails const char *newSubject = "Let's go drink a beer"; @@ -367,7 +369,7 @@ static void group_chat_room_creation_server (void) { participantsAddresses = NULL; // Check that the chat room is correctly created on Chloe's side and that she was added everywhere - LinphoneChatRoom *chloeCr = check_creation_chat_room_client_side(coresList, chloe, &initialChloeStats, confAddr, newSubject, 3, 0); + LinphoneChatRoom *chloeCr = check_creation_chat_room_client_side(coresList, chloe, &initialChloeStats, confAddr, newSubject, 3, FALSE); BC_ASSERT_TRUE(wait_for_list(coresList, &marie->stat.number_of_participants_added, initialMarieStats.number_of_participants_added + 1, 10000)); BC_ASSERT_TRUE(wait_for_list(coresList, &pauline->stat.number_of_participants_added, initialPaulineStats.number_of_participants_added + 1, 10000)); BC_ASSERT_TRUE(wait_for_list(coresList, &laure->stat.number_of_participants_added, initialLaureStats.number_of_participants_added + 1, 10000)); @@ -393,8 +395,8 @@ static void group_chat_room_creation_server (void) { BC_ASSERT_TRUE(wait_for_list(coresList, &marie->stat.number_of_LinphoneIsComposingActiveReceived, initialMarieStats.number_of_LinphoneIsComposingActiveReceived + 1, 10000)); BC_ASSERT_TRUE(wait_for_list(coresList, &pauline->stat.number_of_LinphoneIsComposingActiveReceived, initialPaulineStats.number_of_LinphoneIsComposingActiveReceived + 1, 10000)); BC_ASSERT_TRUE(wait_for_list(coresList, &laure->stat.number_of_LinphoneIsComposingActiveReceived, initialLaureStats.number_of_LinphoneIsComposingActiveReceived + 1, 10000)); - const char *chloeMessage = "Hello"; - _send_message(chloeCr, chloeMessage); + const char *chloeTextMessage = "Hello"; + LinphoneChatMessage *chloeMessage = _send_message(chloeCr, chloeTextMessage); BC_ASSERT_TRUE(wait_for_list(coresList, &chloe->stat.number_of_LinphoneMessageDelivered, initialChloeStats.number_of_LinphoneMessageDelivered + 1, 10000)); 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)); @@ -402,7 +404,8 @@ static void group_chat_room_creation_server (void) { BC_ASSERT_TRUE(wait_for_list(coresList, &marie->stat.number_of_LinphoneIsComposingIdleReceived, initialMarieStats.number_of_LinphoneIsComposingIdleReceived + 1, 10000)); BC_ASSERT_TRUE(wait_for_list(coresList, &pauline->stat.number_of_LinphoneIsComposingIdleReceived, initialPaulineStats.number_of_LinphoneIsComposingIdleReceived + 1, 10000)); BC_ASSERT_TRUE(wait_for_list(coresList, &laure->stat.number_of_LinphoneIsComposingIdleReceived, initialLaureStats.number_of_LinphoneIsComposingIdleReceived + 1, 10000)); - BC_ASSERT_STRING_EQUAL(linphone_chat_message_get_text(marie->stat.last_received_chat_message), chloeMessage); + BC_ASSERT_STRING_EQUAL(linphone_chat_message_get_text(marie->stat.last_received_chat_message), chloeTextMessage); + linphone_chat_message_unref(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(marie->stat.last_received_chat_message))); linphone_address_unref(chloeAddr); @@ -451,6 +454,123 @@ static void group_chat_room_creation_server (void) { linphone_core_manager_delete_chat_room(pauline, paulineCr, coresList); linphone_core_manager_delete_chat_room(chloe, chloeCr, coresList); + BC_ASSERT_EQUAL(linphone_core_get_call_history_size(marie->lc), 0, int,"%i"); + BC_ASSERT_EQUAL(linphone_core_get_call_history_size(laure->lc), 0, int,"%i"); + BC_ASSERT_EQUAL(linphone_core_get_call_history_size(pauline->lc), 0, int,"%i"); + BC_ASSERT_EQUAL(linphone_core_get_call_history_size(chloe->lc), 0, int,"%i"); + + bctbx_list_free(coresList); + bctbx_list_free(coresManagerList); + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); + linphone_core_manager_destroy(laure); + linphone_core_manager_destroy(chloe); +} + +static void group_chat_room_add_participant (void) { + LinphoneCoreManager *marie = linphone_core_manager_create("marie_rc"); + LinphoneCoreManager *pauline = linphone_core_manager_create("pauline_rc"); + LinphoneCoreManager *laure = linphone_core_manager_create("laure_tcp_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, laure); + coresManagerList = bctbx_list_append(coresManagerList, chloe); + bctbx_list_t *coresList = init_core_for_conference(coresManagerList); + linphone_core_set_linphone_specs(chloe->lc, ""); // Disable group chat for Chloe + + start_core_for_conference(coresManagerList); + participantsAddresses = bctbx_list_append(participantsAddresses, linphone_address_new(linphone_core_get_identity(marie->lc))); + participantsAddresses = bctbx_list_append(participantsAddresses, linphone_address_new(linphone_core_get_identity(laure->lc))); + stats initialMarieStats = marie->stat; + stats initialPaulineStats = pauline->stat; + stats initialLaureStats = laure->stat; + stats initialChloeStats = chloe->stat; + + // Pauline creates a new group chat room + const char *initialSubject = "Colleagues"; + LinphoneChatRoom *paulineCr = create_chat_room_client_side(coresList, pauline, &initialPaulineStats, participantsAddresses, initialSubject, -1); + LinphoneAddress *confAddr = linphone_address_clone(linphone_chat_room_get_conference_address(paulineCr)); + + // Check that the chat room is correctly created on Pauline's side and that the participants are added + LinphoneChatRoom *marieCr = check_creation_chat_room_client_side(coresList, marie, &initialMarieStats, confAddr, initialSubject, 2, FALSE); + + // Check that the chat room is correctly created on Laure's side and that the participants are added + LinphoneChatRoom *laureCr = check_creation_chat_room_client_side(coresList, laure, &initialLaureStats, confAddr, initialSubject, 2, FALSE); + + // To simulate dialog removal for Pauline + linphone_core_set_network_reachable(pauline->lc, FALSE); + LinphoneAddress *paulineAddr = linphone_address_clone(linphone_chat_room_get_peer_address(paulineCr)); + coresList = bctbx_list_remove(coresList, pauline->lc); + linphone_core_manager_reinit(pauline); + bctbx_list_t *tmpCoresManagerList = bctbx_list_append(NULL, pauline); + bctbx_list_t *tmpCoresList = init_core_for_conference(tmpCoresManagerList); + bctbx_list_free(tmpCoresManagerList); + coresList = bctbx_list_concat(coresList, tmpCoresList); + linphone_core_manager_start(pauline, TRUE); + paulineCr = linphone_core_get_chat_room(pauline->lc, paulineAddr); + linphone_address_unref(paulineAddr); + + // Pauline adds Chloe to the chat room + participantsAddresses = NULL; + participantsAddresses = bctbx_list_append(participantsAddresses, linphone_address_new(linphone_core_get_identity(chloe->lc))); + linphone_chat_room_add_participants(paulineCr, participantsAddresses); + bctbx_list_free_with_data(participantsAddresses, (bctbx_list_free_func)linphone_address_unref); + participantsAddresses = NULL; + + // Refused by server because group chat disabled for Chloe + BC_ASSERT_FALSE(wait_for_list(coresList, &marie->stat.number_of_participants_added, initialMarieStats.number_of_participants_added + 1, 1000)); + BC_ASSERT_FALSE(wait_for_list(coresList, &pauline->stat.number_of_participants_added, initialPaulineStats.number_of_participants_added + 1, 1000)); + BC_ASSERT_FALSE(wait_for_list(coresList, &laure->stat.number_of_participants_added, initialLaureStats.number_of_participants_added + 1, 1000)); + BC_ASSERT_EQUAL(linphone_chat_room_get_nb_participants(marieCr), 2, int, "%d"); + BC_ASSERT_EQUAL(linphone_chat_room_get_nb_participants(paulineCr), 2, int, "%d"); + BC_ASSERT_EQUAL(linphone_chat_room_get_nb_participants(laureCr), 2, int, "%d"); + + // Pauline begins composing a message + linphone_chat_room_compose(paulineCr); + BC_ASSERT_TRUE(wait_for_list(coresList, &marie->stat.number_of_LinphoneIsComposingActiveReceived, initialMarieStats.number_of_LinphoneIsComposingActiveReceived + 1, 10000)); + BC_ASSERT_TRUE(wait_for_list(coresList, &laure->stat.number_of_LinphoneIsComposingActiveReceived, initialPaulineStats.number_of_LinphoneIsComposingActiveReceived + 1, 10000)); + + // Now, Chloe is upgrading to group chat client + linphone_core_set_network_reachable(chloe->lc, FALSE); + coresList = bctbx_list_remove(coresList, chloe->lc); + linphone_core_manager_reinit(chloe); + tmpCoresManagerList = bctbx_list_append(NULL, chloe); + tmpCoresList = init_core_for_conference(tmpCoresManagerList); + bctbx_list_free(tmpCoresManagerList); + coresList = bctbx_list_concat(coresList, tmpCoresList); + linphone_core_manager_start(chloe, TRUE); + + // Pauline adds Chloe to the chat room + participantsAddresses = NULL; + participantsAddresses = bctbx_list_append(participantsAddresses, linphone_address_new(linphone_core_get_identity(chloe->lc))); + linphone_chat_room_add_participants(paulineCr, participantsAddresses); + bctbx_list_free_with_data(participantsAddresses, (bctbx_list_free_func)linphone_address_unref); + participantsAddresses = NULL; + + // Check that the chat room is correctly created on Chloe's side and that she was added everywhere + LinphoneChatRoom *chloeCr = check_creation_chat_room_client_side(coresList, chloe, &initialChloeStats, confAddr, initialSubject, 3, FALSE); + BC_ASSERT_TRUE(wait_for_list(coresList, &marie->stat.number_of_participants_added, initialMarieStats.number_of_participants_added + 1, 10000)); + BC_ASSERT_TRUE(wait_for_list(coresList, &pauline->stat.number_of_participants_added, initialPaulineStats.number_of_participants_added + 1, 10000)); + BC_ASSERT_TRUE(wait_for_list(coresList, &laure->stat.number_of_participants_added, initialLaureStats.number_of_participants_added + 1, 10000)); + BC_ASSERT_EQUAL(linphone_chat_room_get_nb_participants(marieCr), 3, int, "%d"); + BC_ASSERT_EQUAL(linphone_chat_room_get_nb_participants(paulineCr), 3, int, "%d"); + BC_ASSERT_EQUAL(linphone_chat_room_get_nb_participants(laureCr), 3, int, "%d"); + + // Clean db from chat room + linphone_core_manager_delete_chat_room(marie, marieCr, coresList); + linphone_core_manager_delete_chat_room(laure, laureCr, coresList); + linphone_core_manager_delete_chat_room(pauline, paulineCr, coresList); + linphone_core_manager_delete_chat_room(chloe, chloeCr, coresList); + + BC_ASSERT_EQUAL(linphone_core_get_call_history_size(marie->lc), 0, int,"%i"); + BC_ASSERT_EQUAL(linphone_core_get_call_history_size(laure->lc), 0, int,"%i"); + BC_ASSERT_EQUAL(linphone_core_get_call_history_size(pauline->lc), 0, int,"%i"); + BC_ASSERT_EQUAL(linphone_core_get_call_history_size(chloe->lc), 0, int,"%i"); + + linphone_address_unref(confAddr); bctbx_list_free(coresList); bctbx_list_free(coresManagerList); linphone_core_manager_destroy(marie); @@ -530,7 +650,7 @@ static void group_chat_room_message (bool_t encrypt) { linphone_im_encryption_engine_cbs_set_process_incoming_message(marie_cbs, im_encryption_engine_process_incoming_message_cb); linphone_im_encryption_engine_cbs_set_process_incoming_message(pauline_cbs, im_encryption_engine_process_incoming_message_cb); linphone_im_encryption_engine_cbs_set_process_incoming_message(chloe_cbs, im_encryption_engine_process_incoming_message_cb); - + linphone_core_set_im_encryption_engine(marie->lc, marie_imee); linphone_core_set_im_encryption_engine(pauline->lc, pauline_imee); linphone_core_set_im_encryption_engine(chloe->lc, chloe_imee); @@ -542,17 +662,17 @@ static void group_chat_room_message (bool_t encrypt) { 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); + LinphoneChatRoom *paulineCr = check_creation_chat_room_client_side(coresList, pauline, &initialPaulineStats, confAddr, initialSubject, 2, FALSE); // 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); + LinphoneChatRoom *chloeCr = check_creation_chat_room_client_side(coresList, chloe, &initialChloeStats, confAddr, initialSubject, 2, FALSE); // Chloe begins composing a message linphone_chat_room_compose(chloeCr); BC_ASSERT_TRUE(wait_for_list(coresList, &marie->stat.number_of_LinphoneIsComposingActiveReceived, initialMarieStats.number_of_LinphoneIsComposingActiveReceived + 1, 10000)); BC_ASSERT_TRUE(wait_for_list(coresList, &pauline->stat.number_of_LinphoneIsComposingActiveReceived, initialPaulineStats.number_of_LinphoneIsComposingActiveReceived + 1, 10000)); - const char *chloeMessage = "Hello"; - _send_message(chloeCr, chloeMessage); + const char *chloeTextMessage = "Hello"; + LinphoneChatMessage *chloeMessage = _send_message(chloeCr, chloeTextMessage); 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)); BC_ASSERT_TRUE(wait_for_list(coresList, &marie->stat.number_of_LinphoneIsComposingIdleReceived, initialMarieStats.number_of_LinphoneIsComposingIdleReceived + 1, 10000)); @@ -561,7 +681,8 @@ static void group_chat_room_message (bool_t encrypt) { if (!BC_ASSERT_PTR_NOT_NULL(marieLastMsg)) goto end; - BC_ASSERT_STRING_EQUAL(linphone_chat_message_get_text(marieLastMsg), chloeMessage); + BC_ASSERT_STRING_EQUAL(linphone_chat_message_get_text(marieLastMsg), chloeTextMessage); + linphone_chat_message_unref(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); @@ -570,8 +691,8 @@ static void group_chat_room_message (bool_t encrypt) { linphone_chat_room_compose(paulineCr); BC_ASSERT_TRUE(wait_for_list(coresList, &marie->stat.number_of_LinphoneIsComposingActiveReceived, initialMarieStats.number_of_LinphoneIsComposingActiveReceived + 1, 10000)); BC_ASSERT_TRUE(wait_for_list(coresList, &chloe->stat.number_of_LinphoneIsComposingActiveReceived, initialChloeStats.number_of_LinphoneIsComposingActiveReceived + 1, 10000)); - const char *paulineMessage = "Héllö Dàrling"; - _send_message(paulineCr, paulineMessage); + const char *paulineTextMessage = "Héllö Dàrling"; + LinphoneChatMessage *paulineMessage = _send_message(paulineCr, paulineTextMessage); 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, &chloe->stat.number_of_LinphoneMessageReceived, initialChloeStats.number_of_LinphoneMessageReceived + 1, 10000)); BC_ASSERT_TRUE(wait_for_list(coresList, &marie->stat.number_of_LinphoneIsComposingIdleReceived, initialMarieStats.number_of_LinphoneIsComposingIdleReceived + 1, 10000)); @@ -580,7 +701,8 @@ static void group_chat_room_message (bool_t encrypt) { if (!BC_ASSERT_PTR_NOT_NULL(marieLastMsg)) goto end; - BC_ASSERT_STRING_EQUAL(linphone_chat_message_get_text(marieLastMsg), paulineMessage); + BC_ASSERT_STRING_EQUAL(linphone_chat_message_get_text(marieLastMsg), paulineTextMessage); + linphone_chat_message_unref(paulineMessage); LinphoneAddress *paulineAddr = linphone_address_new(linphone_core_get_identity(pauline->lc)); BC_ASSERT_TRUE(linphone_address_weak_equal(paulineAddr, linphone_chat_message_get_from_address(marieLastMsg))); linphone_address_unref(paulineAddr); @@ -636,13 +758,13 @@ static void group_chat_room_invite_multi_register_account (void) { const LinphoneAddress *confAddr = linphone_chat_room_get_conference_address(marieCr); // Check that the chat room is correctly created on Pauline1's side and that the participants are added - LinphoneChatRoom *paulineCr = check_creation_chat_room_client_side(coresList, pauline1, &initialPauline1Stats, confAddr, initialSubject, 2, 0); + LinphoneChatRoom *paulineCr = check_creation_chat_room_client_side(coresList, pauline1, &initialPauline1Stats, confAddr, initialSubject, 2, FALSE); // Check that the chat room is correctly created on Pauline2's side and that the participants are added - LinphoneChatRoom *paulineCr2 = check_creation_chat_room_client_side(coresList, pauline2, &initialPauline2Stats, confAddr, initialSubject, 2, 0); + LinphoneChatRoom *paulineCr2 = check_creation_chat_room_client_side(coresList, pauline2, &initialPauline2Stats, confAddr, initialSubject, 2, FALSE); // Check that the chat room is correctly created on Laure's side and that the participants are added - LinphoneChatRoom *laureCr = check_creation_chat_room_client_side(coresList, laure, &initialLaureStats, confAddr, initialSubject, 2, 0); + LinphoneChatRoom *laureCr = check_creation_chat_room_client_side(coresList, laure, &initialLaureStats, confAddr, initialSubject, 2, FALSE); // Clean db from chat room linphone_core_manager_delete_chat_room(marie, marieCr, coresList); @@ -682,10 +804,10 @@ static void group_chat_room_add_admin (void) { 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); + LinphoneChatRoom *paulineCr = check_creation_chat_room_client_side(coresList, pauline, &initialPaulineStats, confAddr, initialSubject, 2, FALSE); // Check that the chat room is correctly created on Laure's side and that the participants are added - LinphoneChatRoom *laureCr = check_creation_chat_room_client_side(coresList, laure, &initialLaureStats, confAddr, initialSubject, 2, 0); + LinphoneChatRoom *laureCr = check_creation_chat_room_client_side(coresList, laure, &initialLaureStats, confAddr, initialSubject, 2, FALSE); // Marie designates Pauline as admin LinphoneAddress *paulineAddr = linphone_address_new(linphone_core_get_identity(pauline->lc)); @@ -734,10 +856,10 @@ static void group_chat_room_add_admin_lately_notified (void) { 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); + LinphoneChatRoom *paulineCr = check_creation_chat_room_client_side(coresList, pauline, &initialPaulineStats, confAddr, initialSubject, 2, FALSE); // Check that the chat room is correctly created on Laure's side and that the participants are added - LinphoneChatRoom *laureCr = check_creation_chat_room_client_side(coresList, laure, &initialLaureStats, confAddr, initialSubject, 2, 0); + LinphoneChatRoom *laureCr = check_creation_chat_room_client_side(coresList, laure, &initialLaureStats, confAddr, initialSubject, 2, FALSE); //simulate pauline has disapeared linphone_core_set_network_reachable(pauline->lc, FALSE); @@ -795,10 +917,10 @@ static void group_chat_room_add_admin_non_admin (void) { 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); + LinphoneChatRoom *paulineCr = check_creation_chat_room_client_side(coresList, pauline, &initialPaulineStats, confAddr, initialSubject, 2, FALSE); // Check that the chat room is correctly created on Laure's side and that the participants are added - LinphoneChatRoom *laureCr = check_creation_chat_room_client_side(coresList, laure, &initialLaureStats, confAddr, initialSubject, 2, 0); + LinphoneChatRoom *laureCr = check_creation_chat_room_client_side(coresList, laure, &initialLaureStats, confAddr, initialSubject, 2, FALSE); // Pauline designates Laure as admin LinphoneAddress *laureAddr = linphone_address_new(linphone_core_get_identity(laure->lc)); @@ -844,10 +966,10 @@ static void group_chat_room_remove_admin (void) { 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); + LinphoneChatRoom *paulineCr = check_creation_chat_room_client_side(coresList, pauline, &initialPaulineStats, confAddr, initialSubject, 2, FALSE); // Check that the chat room is correctly created on Laure's side and that the participants are added - LinphoneChatRoom *laureCr = check_creation_chat_room_client_side(coresList, laure, &initialLaureStats, confAddr, initialSubject, 2, 0); + LinphoneChatRoom *laureCr = check_creation_chat_room_client_side(coresList, laure, &initialLaureStats, confAddr, initialSubject, 2, FALSE); // Marie designates Pauline as admin LinphoneAddress *paulineAddr = linphone_address_new(linphone_core_get_identity(pauline->lc)); @@ -883,6 +1005,55 @@ static void group_chat_room_remove_admin (void) { linphone_core_manager_destroy(laure); } +static void group_chat_room_admin_creator_leaves_the_room (void) { + LinphoneCoreManager *marie = linphone_core_manager_create("marie_rc"); + LinphoneCoreManager *pauline = linphone_core_manager_create("pauline_rc"); + LinphoneCoreManager *laure = linphone_core_manager_create("laure_tcp_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, laure); + 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(laure->lc))); + stats initialMarieStats = marie->stat; + stats initialPaulineStats = pauline->stat; + stats initialLaureStats = laure->stat; + + // 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, FALSE); + + // Check that the chat room is correctly created on Laure's side and that the participants are added + LinphoneChatRoom *laureCr = check_creation_chat_room_client_side(coresList, laure, &initialLaureStats, confAddr, initialSubject, 2, FALSE); + + // Marie leaves the room + linphone_chat_room_leave(marieCr); + BC_ASSERT_TRUE(wait_for_list(coresList, &marie->stat.number_of_LinphoneChatRoomStateTerminated, initialMarieStats.number_of_LinphoneChatRoomStateTerminated + 1, 3000)); + BC_ASSERT_TRUE(wait_for_list(coresList, &pauline->stat.number_of_participants_removed, initialPaulineStats.number_of_participants_removed + 1, 1000)); + BC_ASSERT_TRUE(wait_for_list(coresList, &laure->stat.number_of_participants_removed, initialLaureStats.number_of_participants_removed + 1, 1000)); + BC_ASSERT_TRUE(wait_for_list(coresList, &pauline->stat.number_of_participant_admin_statuses_changed, initialPaulineStats.number_of_participant_admin_statuses_changed + 1, 1000)); + BC_ASSERT_TRUE(wait_for_list(coresList, &laure->stat.number_of_participant_admin_statuses_changed, initialLaureStats.number_of_participant_admin_statuses_changed + 1, 1000)); + BC_ASSERT_TRUE(linphone_participant_is_admin(linphone_chat_room_get_me(laureCr))); + + // Clean db from chat room + linphone_core_manager_delete_chat_room(marie, marieCr, coresList); + linphone_core_manager_delete_chat_room(laure, laureCr, 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(laure); +} static void group_chat_room_change_subject (void) { LinphoneCoreManager *marie = linphone_core_manager_create("marie_rc"); @@ -909,10 +1080,10 @@ static void group_chat_room_change_subject (void) { 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); + LinphoneChatRoom *paulineCr = check_creation_chat_room_client_side(coresList, pauline, &initialPaulineStats, confAddr, initialSubject, 2, FALSE); // Check that the chat room is correctly created on Laure's side and that the participants are added - LinphoneChatRoom *laureCr = check_creation_chat_room_client_side(coresList, laure, &initialLaureStats, confAddr, initialSubject, 2, 0); + LinphoneChatRoom *laureCr = check_creation_chat_room_client_side(coresList, laure, &initialLaureStats, confAddr, initialSubject, 2, FALSE); // Marie now changes the subject linphone_chat_room_set_subject(marieCr, newSubject); @@ -960,10 +1131,10 @@ static void group_chat_room_change_subject_non_admin (void) { 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); + LinphoneChatRoom *paulineCr = check_creation_chat_room_client_side(coresList, pauline, &initialPaulineStats, confAddr, initialSubject, 2, FALSE); // Check that the chat room is correctly created on Laure's side and that the participants are added - LinphoneChatRoom *laureCr = check_creation_chat_room_client_side(coresList, laure, &initialLaureStats, confAddr, initialSubject, 2, 0); + LinphoneChatRoom *laureCr = check_creation_chat_room_client_side(coresList, laure, &initialLaureStats, confAddr, initialSubject, 2, FALSE); // Marie now changes the subject linphone_chat_room_set_subject(paulineCr, newSubject); @@ -1010,10 +1181,10 @@ static void group_chat_room_remove_participant (void) { 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); + LinphoneChatRoom *paulineCr = check_creation_chat_room_client_side(coresList, pauline, &initialPaulineStats, confAddr, initialSubject, 2, FALSE); // Check that the chat room is correctly created on Laure's side and that the participants are added - LinphoneChatRoom *laureCr = check_creation_chat_room_client_side(coresList, laure, &initialLaureStats, confAddr, initialSubject, 2, 0); + LinphoneChatRoom *laureCr = check_creation_chat_room_client_side(coresList, laure, &initialLaureStats, confAddr, initialSubject, 2, FALSE); // Marie removes Laure from the chat room LinphoneAddress *laureAddr = linphone_address_new(linphone_core_get_identity(laure->lc)); @@ -1061,10 +1232,10 @@ static void group_chat_room_send_message_with_participant_removed (void) { 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); + LinphoneChatRoom *paulineCr = check_creation_chat_room_client_side(coresList, pauline, &initialPaulineStats, confAddr, initialSubject, 2, FALSE); // Check that the chat room is correctly created on Laure's side and that the participants are added - LinphoneChatRoom *laureCr = check_creation_chat_room_client_side(coresList, laure, &initialLaureStats, confAddr, initialSubject, 2, 0); + LinphoneChatRoom *laureCr = check_creation_chat_room_client_side(coresList, laure, &initialLaureStats, confAddr, initialSubject, 2, FALSE); // Marie removes Laure from the chat room LinphoneAddress *laureAddr = linphone_address_new(linphone_core_get_identity(laure->lc)); @@ -1079,8 +1250,9 @@ static void group_chat_room_send_message_with_participant_removed (void) { BC_ASSERT_TRUE(wait_for_list(coresList, &pauline->stat.number_of_participants_removed, initialPaulineStats.number_of_participants_removed + 1, 1000)); // Laure try to send a message with the chat room where she was removed - const char *laureMessage = "Hello"; - _send_message(laureCr, laureMessage); + const char *laureTextMessage = "Hello"; + LinphoneChatMessage *laureMessage = _send_message(laureCr, laureTextMessage); + linphone_chat_message_unref(laureMessage); BC_ASSERT_TRUE(wait_for_list(coresList, &laure->stat.number_of_LinphoneMessageReceived, initialMarieStats.number_of_LinphoneMessageDelivered, 10000)); BC_ASSERT_TRUE(wait_for_list(coresList, &marie->stat.number_of_LinphoneMessageReceived, initialMarieStats.number_of_LinphoneMessageReceived, 10000)); @@ -1125,10 +1297,10 @@ static void group_chat_room_leave (void) { 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); + LinphoneChatRoom *paulineCr = check_creation_chat_room_client_side(coresList, pauline, &initialPaulineStats, confAddr, initialSubject, 2, FALSE); // Check that the chat room is correctly created on Laure's side and that the participants are added - LinphoneChatRoom *laureCr = check_creation_chat_room_client_side(coresList, laure, &initialLaureStats, confAddr, initialSubject, 2, 0); + LinphoneChatRoom *laureCr = check_creation_chat_room_client_side(coresList, laure, &initialLaureStats, confAddr, initialSubject, 2, FALSE); linphone_chat_room_leave(paulineCr); BC_ASSERT_TRUE(wait_for_list(coresList, &pauline->stat.number_of_LinphoneChatRoomStateTerminated, initialPaulineStats.number_of_LinphoneChatRoomStateTerminated + 1, 3000)); @@ -1173,10 +1345,10 @@ static void group_chat_room_come_back_after_disconnection (void) { 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); + LinphoneChatRoom *paulineCr = check_creation_chat_room_client_side(coresList, pauline, &initialPaulineStats, confAddr, initialSubject, 2, FALSE); // Check that the chat room is correctly created on Laure's side and that the participants are added - LinphoneChatRoom *laureCr = check_creation_chat_room_client_side(coresList, laure, &initialLaureStats, confAddr, initialSubject, 2, 0); + LinphoneChatRoom *laureCr = check_creation_chat_room_client_side(coresList, laure, &initialLaureStats, confAddr, initialSubject, 2, FALSE); linphone_core_set_network_reachable(marie->lc, FALSE); @@ -1216,7 +1388,7 @@ static void group_chat_room_create_room_with_disconnected_friends_base (bool_t i int dummy = 0; LinphoneChatRoom *paulineCr = NULL; LinphoneChatRoom *laureCr = NULL; - + coresManagerList = bctbx_list_append(coresManagerList, marie); coresManagerList = bctbx_list_append(coresManagerList, pauline); coresManagerList = bctbx_list_append(coresManagerList, laure); @@ -1249,7 +1421,7 @@ static void group_chat_room_create_room_with_disconnected_friends_base (bool_t i linphone_chat_message_send(msg); linphone_chat_message_unref(msg); } - + wait_for_list(coresList, &dummy, 1, 4000); // Reconnect pauline and laure @@ -1257,24 +1429,28 @@ static void group_chat_room_create_room_with_disconnected_friends_base (bool_t i linphone_core_set_network_reachable(laure->lc, TRUE); // Check that the chat room is correctly created on Pauline's side and that the participants are added - paulineCr = check_creation_chat_room_client_side(coresList, pauline, &initialPaulineStats, confAddr, initialSubject, 2, 0); + paulineCr = check_creation_chat_room_client_side(coresList, pauline, &initialPaulineStats, confAddr, initialSubject, 2, FALSE); if (!BC_ASSERT_PTR_NOT_NULL(paulineCr)) goto end; // Check that the chat room is correctly created on Laure's side and that the participants are added - laureCr = check_creation_chat_room_client_side(coresList, laure, &initialLaureStats, confAddr, initialSubject, 2, 0); + laureCr = check_creation_chat_room_client_side(coresList, laure, &initialLaureStats, confAddr, initialSubject, 2, FALSE); if (!BC_ASSERT_PTR_NOT_NULL(laureCr)) goto end; if (initial_message) { if (BC_ASSERT_TRUE(wait_for_list(coresList, &pauline->stat.number_of_LinphoneMessageReceived, 1, 10000))) { LinphoneChatMessage *msg = linphone_chat_room_get_last_message_in_history(paulineCr); - if (BC_ASSERT_PTR_NOT_NULL(msg)) + if (BC_ASSERT_PTR_NOT_NULL(msg)) { BC_ASSERT_STRING_EQUAL(linphone_chat_message_get_text(msg), "Salut"); + linphone_chat_message_unref(msg); + } } if (BC_ASSERT_TRUE(wait_for_list(coresList, &laure->stat.number_of_LinphoneMessageReceived, 1, 10000))) { LinphoneChatMessage *msg = linphone_chat_room_get_last_message_in_history(laureCr); - if (BC_ASSERT_PTR_NOT_NULL(msg)) + if (BC_ASSERT_PTR_NOT_NULL(msg)) { BC_ASSERT_STRING_EQUAL(linphone_chat_message_get_text(msg), "Salut"); + linphone_chat_message_unref(msg); + } } } @@ -1299,7 +1475,7 @@ static void group_chat_room_create_room_with_disconnected_friends_and_initial_me group_chat_room_create_room_with_disconnected_friends_base(TRUE); } -static void group_chat_room_reinvited_after_removed (void) { +static void group_chat_room_reinvited_after_removed_base (bool_t offline_when_removed, bool_t offline_when_reinvited) { LinphoneCoreManager *marie = linphone_core_manager_create("marie_rc"); LinphoneCoreManager *pauline = linphone_core_manager_create("pauline_rc"); LinphoneCoreManager *laure = linphone_core_manager_create("laure_tcp_rc"); @@ -1315,6 +1491,7 @@ static void group_chat_room_reinvited_after_removed (void) { stats initialMarieStats = marie->stat; stats initialPaulineStats = pauline->stat; stats initialLaureStats = laure->stat; + char *savedLaureUuid = NULL; // Marie creates a new group chat room const char *initialSubject = "Colleagues"; @@ -1323,42 +1500,81 @@ static void group_chat_room_reinvited_after_removed (void) { 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); + LinphoneChatRoom *paulineCr = check_creation_chat_room_client_side(coresList, pauline, &initialPaulineStats, confAddr, initialSubject, 2, FALSE); // Check that the chat room is correctly created on Laure's side and that the participants are added - LinphoneChatRoom *laureCr = check_creation_chat_room_client_side(coresList, laure, &initialLaureStats, confAddr, initialSubject, 2, 0); + LinphoneChatRoom *laureCr = check_creation_chat_room_client_side(coresList, laure, &initialLaureStats, confAddr, initialSubject, 2, FALSE); + + LinphoneAddress *laureAddr = linphone_address_new(linphone_core_get_identity(laure->lc)); + if (offline_when_removed) { + savedLaureUuid = bctbx_strdup(lp_config_get_string(linphone_core_get_config(laure->lc), "misc", "uuid", NULL)); + coresList = bctbx_list_remove(coresList, laure->lc); + coresManagerList = bctbx_list_remove(coresManagerList, laure); + linphone_core_set_network_reachable(laure->lc, FALSE); + linphone_core_manager_stop(laure); + } // Marie removes Laure from the chat room - LinphoneAddress *laureAddr = linphone_address_new(linphone_core_get_identity(laure->lc)); LinphoneParticipant *laureParticipant = linphone_chat_room_find_participant(marieCr, laureAddr); - linphone_address_unref(laureAddr); BC_ASSERT_PTR_NOT_NULL(laureParticipant); linphone_chat_room_remove_participant(marieCr, laureParticipant); - BC_ASSERT_TRUE(wait_for_list(coresList, &laure->stat.number_of_LinphoneChatRoomStateTerminated, initialLaureStats.number_of_LinphoneChatRoomStateTerminated + 1, 3000)); BC_ASSERT_TRUE(wait_for_list(coresList, &marie->stat.number_of_participants_removed, initialMarieStats.number_of_participants_removed + 1, 1000)); BC_ASSERT_TRUE(wait_for_list(coresList, &pauline->stat.number_of_participants_removed, initialPaulineStats.number_of_participants_removed + 1, 1000)); + if (offline_when_removed && !offline_when_reinvited) { + linphone_core_manager_configure(laure); + lp_config_set_string(linphone_core_get_config(laure->lc), "misc", "uuid", savedLaureUuid); + bctbx_free(savedLaureUuid); + bctbx_list_t *tmpCoresManagerList = bctbx_list_append(NULL, laure); + bctbx_list_t *tmpCoresList = init_core_for_conference(tmpCoresManagerList); + bctbx_list_free(tmpCoresManagerList); + initialLaureStats = laure->stat; + linphone_core_manager_start(laure, TRUE); + coresList = bctbx_list_concat(coresList, tmpCoresList); + coresManagerList = bctbx_list_append(coresManagerList, laure); + } + if (!offline_when_reinvited) + BC_ASSERT_TRUE(wait_for_list(coresList, &laure->stat.number_of_LinphoneChatRoomStateTerminated, initialLaureStats.number_of_LinphoneChatRoomStateTerminated + 1, 3000)); + // Marie adds Laure to the chat room - participantsAddresses = bctbx_list_append(participantsAddresses, linphone_address_new(linphone_core_get_identity(laure->lc))); + participantsAddresses = bctbx_list_append(participantsAddresses, laureAddr); linphone_chat_room_add_participants(marieCr, participantsAddresses); bctbx_list_free_with_data(participantsAddresses, (bctbx_list_free_func)linphone_address_unref); participantsAddresses = NULL; - BC_ASSERT_TRUE(wait_for_list(coresList, &laure->stat.number_of_LinphoneChatRoomStateCreationPending, initialLaureStats.number_of_LinphoneChatRoomStateCreationPending + 2, 5000)); - BC_ASSERT_TRUE(wait_for_list(coresList, &laure->stat.number_of_LinphoneChatRoomStateCreated, initialLaureStats.number_of_LinphoneChatRoomStateCreated + 2, 5000)); BC_ASSERT_TRUE(wait_for_list(coresList, &marie->stat.number_of_participants_added, initialMarieStats.number_of_participants_added + 1, 1000)); BC_ASSERT_TRUE(wait_for_list(coresList, &pauline->stat.number_of_participants_added, initialPaulineStats.number_of_participants_added + 1, 1000)); + if (offline_when_reinvited) { + linphone_core_manager_configure(laure); + lp_config_set_string(linphone_core_get_config(laure->lc), "misc", "uuid", savedLaureUuid); + bctbx_free(savedLaureUuid); + bctbx_list_t *tmpCoresManagerList = bctbx_list_append(NULL, laure); + bctbx_list_t *tmpCoresList = init_core_for_conference(tmpCoresManagerList); + bctbx_list_free(tmpCoresManagerList); + initialLaureStats = laure->stat; + linphone_core_manager_start(laure, TRUE); + coresList = bctbx_list_concat(coresList, tmpCoresList); + coresManagerList = bctbx_list_append(coresManagerList, laure); + BC_ASSERT_TRUE(wait_for_list(coresList, &laure->stat.number_of_LinphoneChatRoomStateCreationPending, initialLaureStats.number_of_LinphoneChatRoomStateCreationPending + 1, 5000)); + BC_ASSERT_TRUE(wait_for_list(coresList, &laure->stat.number_of_LinphoneChatRoomStateCreated, initialLaureStats.number_of_LinphoneChatRoomStateCreated + 2, 5000)); + } else { + BC_ASSERT_TRUE(wait_for_list(coresList, &laure->stat.number_of_LinphoneChatRoomStateCreationPending, initialLaureStats.number_of_LinphoneChatRoomStateCreationPending + 1, 5000)); + BC_ASSERT_TRUE(wait_for_list(coresList, &laure->stat.number_of_LinphoneChatRoomStateCreated, initialLaureStats.number_of_LinphoneChatRoomStateCreated + 1, 5000)); + } BC_ASSERT_EQUAL(linphone_chat_room_get_nb_participants(marieCr), 2, int, "%d"); BC_ASSERT_EQUAL(linphone_chat_room_get_nb_participants(paulineCr), 2, int, "%d"); - laureAddr = linphone_address_new(linphone_core_get_device_identity(laure->lc)); + char *laureIdentity = linphone_core_get_device_identity(laure->lc); + laureAddr = linphone_address_new(laureIdentity); + bctbx_free(laureIdentity); LinphoneChatRoom *newLaureCr = linphone_core_find_chat_room(laure->lc, confAddr, laureAddr); linphone_address_unref(laureAddr); - BC_ASSERT_PTR_EQUAL(newLaureCr, laureCr); + if (!offline_when_removed) + BC_ASSERT_PTR_EQUAL(newLaureCr, laureCr); BC_ASSERT_EQUAL(linphone_chat_room_get_nb_participants(newLaureCr), 2, int, "%d"); BC_ASSERT_STRING_EQUAL(linphone_chat_room_get_subject(newLaureCr), initialSubject); // Clean db from chat room linphone_core_manager_delete_chat_room(marie, marieCr, coresList); - linphone_core_manager_delete_chat_room(laure, laureCr, coresList); + linphone_core_manager_delete_chat_room(laure, newLaureCr, coresList); linphone_core_manager_delete_chat_room(pauline, paulineCr, coresList); bctbx_list_free(coresList); @@ -1368,6 +1584,104 @@ static void group_chat_room_reinvited_after_removed (void) { linphone_core_manager_destroy(laure); } +static void group_chat_room_reinvited_after_removed (void) { + group_chat_room_reinvited_after_removed_base(FALSE, FALSE); +} + +static void group_chat_room_reinvited_after_removed_while_offline (void) { + group_chat_room_reinvited_after_removed_base(TRUE, FALSE); +} + +static void group_chat_room_reinvited_after_removed_while_offline_2 (void) { + group_chat_room_reinvited_after_removed_base(TRUE, TRUE); +} + +static void group_chat_room_reinvited_after_removed_with_several_devices (void) { + LinphoneCoreManager *marie1 = linphone_core_manager_create("marie_rc"); + LinphoneCoreManager *marie2 = linphone_core_manager_create("marie_rc"); + LinphoneCoreManager *pauline1 = linphone_core_manager_create("pauline_rc"); + LinphoneCoreManager *pauline2 = linphone_core_manager_create("pauline_rc"); + LinphoneCoreManager *laure = linphone_core_manager_create("laure_tcp_rc"); + bctbx_list_t *coresManagerList = NULL; + bctbx_list_t *participantsAddresses = NULL; + coresManagerList = bctbx_list_append(coresManagerList, marie1); + coresManagerList = bctbx_list_append(coresManagerList, marie2); + coresManagerList = bctbx_list_append(coresManagerList, pauline1); + coresManagerList = bctbx_list_append(coresManagerList, pauline2); + coresManagerList = bctbx_list_append(coresManagerList, laure); + 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(pauline1->lc))); + participantsAddresses = bctbx_list_append(participantsAddresses, linphone_address_new(linphone_core_get_identity(laure->lc))); + stats initialMarie1Stats = marie1->stat; + stats initialMarie2Stats = marie2->stat; + stats initialPauline1Stats = pauline1->stat; + stats initialPauline2Stats = pauline2->stat; + stats initialLaureStats = laure->stat; + + // Marie2 is initially offline + linphone_core_set_network_reachable(marie2->lc, FALSE); + + // Marie creates a new group chat room + const char *initialSubject = "Colleagues"; + LinphoneChatRoom *marie1Cr = create_chat_room_client_side(coresList, marie1, &initialMarie1Stats, participantsAddresses, initialSubject, -1); + participantsAddresses = NULL; + const LinphoneAddress *confAddr = linphone_chat_room_get_conference_address(marie1Cr); + + // Check that the chat room is correctly created on Pauline1 and Pauline2's sides and that the participants are added + LinphoneChatRoom *pauline1Cr = check_creation_chat_room_client_side(coresList, pauline1, &initialPauline1Stats, confAddr, initialSubject, 2, FALSE); + LinphoneChatRoom *pauline2Cr = check_creation_chat_room_client_side(coresList, pauline2, &initialPauline2Stats, confAddr, initialSubject, 2, FALSE); + + // Check that the chat room is correctly created on Laure's side and that the participants are added + LinphoneChatRoom *laureCr = check_creation_chat_room_client_side(coresList, laure, &initialLaureStats, confAddr, initialSubject, 2, FALSE); + + // Marie1 removes Pauline from the chat room while Pauline2 is offline + linphone_core_set_network_reachable(pauline2->lc, FALSE); + LinphoneAddress *paulineAddr = linphone_address_new(linphone_core_get_identity(pauline1->lc)); + LinphoneParticipant *paulineParticipant = linphone_chat_room_find_participant(marie1Cr, paulineAddr); + BC_ASSERT_PTR_NOT_NULL(paulineParticipant); + linphone_chat_room_remove_participant(marie1Cr, paulineParticipant); + BC_ASSERT_TRUE(wait_for_list(coresList, &marie1->stat.number_of_participants_removed, initialMarie1Stats.number_of_participants_removed + 1, 1000)); + BC_ASSERT_TRUE(wait_for_list(coresList, &laure->stat.number_of_participants_removed, initialLaureStats.number_of_participants_removed + 1, 1000)); + BC_ASSERT_TRUE(wait_for_list(coresList, &pauline1->stat.number_of_LinphoneChatRoomStateTerminated, initialPauline1Stats.number_of_LinphoneChatRoomStateTerminated + 1, 3000)); + + // Marie2 comes online, check that pauline is not notified as still being in the chat room + linphone_core_set_network_reachable(marie2->lc, TRUE); + LinphoneChatRoom *marie2Cr = check_creation_chat_room_client_side(coresList, marie2, &initialMarie2Stats, confAddr, initialSubject, 1, TRUE); + + // Marie2 adds Pauline back to the chat room + initialPauline1Stats = pauline1->stat; + participantsAddresses = bctbx_list_append(participantsAddresses, paulineAddr); + linphone_chat_room_add_participants(marie2Cr, participantsAddresses); + bctbx_list_free_with_data(participantsAddresses, (bctbx_list_free_func)linphone_address_unref); + participantsAddresses = NULL; + BC_ASSERT_TRUE(wait_for_list(coresList, &marie1->stat.number_of_participants_added, initialMarie1Stats.number_of_participants_added + 1, 1000)); + BC_ASSERT_TRUE(wait_for_list(coresList, &marie2->stat.number_of_participants_added, initialMarie2Stats.number_of_participants_added + 1, 1000)); + BC_ASSERT_TRUE(wait_for_list(coresList, &laure->stat.number_of_participants_added, initialLaureStats.number_of_participants_added + 1, 1000)); + BC_ASSERT_EQUAL(linphone_chat_room_get_nb_participants(marie1Cr), 2, int, "%d"); + BC_ASSERT_EQUAL(linphone_chat_room_get_nb_participants(marie2Cr), 2, int, "%d"); + BC_ASSERT_EQUAL(linphone_chat_room_get_nb_participants(laureCr), 2, int, "%d"); + LinphoneChatRoom *newPauline1Cr = check_creation_chat_room_client_side(coresList, pauline1, &initialPauline1Stats, confAddr, initialSubject, 2, FALSE); + BC_ASSERT_PTR_EQUAL(newPauline1Cr, pauline1Cr); + BC_ASSERT_EQUAL(linphone_chat_room_get_nb_participants(newPauline1Cr), 2, int, "%d"); + BC_ASSERT_STRING_EQUAL(linphone_chat_room_get_subject(newPauline1Cr), initialSubject); + + // Clean db from chat room + linphone_core_manager_delete_chat_room(marie1, marie1Cr, coresList); + linphone_core_delete_chat_room(marie2->lc, marie2Cr); + linphone_core_manager_delete_chat_room(pauline1, newPauline1Cr, coresList); + linphone_core_delete_chat_room(pauline2->lc, pauline2Cr); + linphone_core_manager_delete_chat_room(laure, laureCr, coresList); + + bctbx_list_free(coresList); + bctbx_list_free(coresManagerList); + linphone_core_manager_destroy(marie1); + linphone_core_manager_destroy(marie2); + linphone_core_manager_destroy(pauline1); + linphone_core_manager_destroy(pauline2); + linphone_core_manager_destroy(laure); +} + static void group_chat_room_notify_after_disconnection (void) { LinphoneCoreManager *marie = linphone_core_manager_create("marie_rc"); LinphoneCoreManager *pauline = linphone_core_manager_create("pauline_rc"); @@ -1506,14 +1820,14 @@ static void group_chat_room_send_refer_to_all_devices (void) { const LinphoneAddress *confAddr = linphone_chat_room_get_conference_address(marieCr); // Check that the chat room is correctly created on second Marie's device - LinphoneChatRoom *marieCr2 = check_creation_chat_room_client_side(coresList, marie2, &initialMarie2Stats, confAddr, initialSubject, 2, 1); + LinphoneChatRoom *marieCr2 = check_creation_chat_room_client_side(coresList, marie2, &initialMarie2Stats, confAddr, initialSubject, 2, TRUE); // 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, pauline1, &initialPauline1Stats, confAddr, initialSubject, 2, 0); - LinphoneChatRoom *paulineCr2 = check_creation_chat_room_client_side(coresList, pauline2, &initialPauline2Stats, confAddr, initialSubject, 2, 0); + LinphoneChatRoom *paulineCr = check_creation_chat_room_client_side(coresList, pauline1, &initialPauline1Stats, confAddr, initialSubject, 2, FALSE); + LinphoneChatRoom *paulineCr2 = check_creation_chat_room_client_side(coresList, pauline2, &initialPauline2Stats, confAddr, initialSubject, 2, FALSE); // Check that the chat room is correctly created on Laure's side and that the participants are added - LinphoneChatRoom *laureCr = check_creation_chat_room_client_side(coresList, laure, &initialLaureStats, confAddr, initialSubject, 2, 0); + LinphoneChatRoom *laureCr = check_creation_chat_room_client_side(coresList, laure, &initialLaureStats, confAddr, initialSubject, 2, FALSE); // Check that added Marie's device didn't change her admin status LinphoneAddress *marieAddr = linphone_address_new(linphone_core_get_identity(marie1->lc)); @@ -1536,7 +1850,7 @@ static void group_chat_room_send_refer_to_all_devices (void) { marieParticipant = linphone_chat_room_find_participant(laureCr, marieAddr); if(BC_ASSERT_PTR_NOT_NULL(marieParticipant)) BC_ASSERT_TRUE(linphone_participant_is_admin(marieParticipant)); - + linphone_address_unref(marieAddr); // Marie removes Laure from the chat room @@ -1599,11 +1913,11 @@ static void group_chat_room_add_device (void) { 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, pauline1, &initialPauline1Stats, confAddr, initialSubject, 2, 0); - LinphoneChatRoom *paulineCr2 = check_creation_chat_room_client_side(coresList, pauline2, &initialPauline2Stats, confAddr, initialSubject, 2, 0); + LinphoneChatRoom *paulineCr = check_creation_chat_room_client_side(coresList, pauline1, &initialPauline1Stats, confAddr, initialSubject, 2, FALSE); + LinphoneChatRoom *paulineCr2 = check_creation_chat_room_client_side(coresList, pauline2, &initialPauline2Stats, confAddr, initialSubject, 2, FALSE); // Check that the chat room is correctly created on Laure's side and that the participants are added - LinphoneChatRoom *laureCr = check_creation_chat_room_client_side(coresList, laure, &initialLaureStats, confAddr, initialSubject, 2, 0); + LinphoneChatRoom *laureCr = check_creation_chat_room_client_side(coresList, laure, &initialLaureStats, confAddr, initialSubject, 2, FALSE); // Marie adds a new device LinphoneCoreManager *marie2 = linphone_core_manager_create("marie_rc"); @@ -1619,7 +1933,7 @@ static void group_chat_room_add_device (void) { _start_core(marie2); stats initialMarie2Stats = marie2->stat; // Check that the chat room is correctly created on second Marie's device - LinphoneChatRoom *marieCr2 = check_creation_chat_room_client_side(coresList, marie2, &initialMarie2Stats, confAddr, initialSubject, 2, 1); + LinphoneChatRoom *marieCr2 = check_creation_chat_room_client_side(coresList, marie2, &initialMarie2Stats, confAddr, initialSubject, 2, TRUE); BC_ASSERT_TRUE(wait_for_list(coresList, &marie1->stat.number_of_participant_devices_added, initialMarie1Stats.number_of_participant_devices_added + 1, 1000)); BC_ASSERT_TRUE(wait_for_list(coresList, &pauline1->stat.number_of_participant_devices_added, initialMarie1Stats.number_of_participant_devices_added + 1, 1000)); BC_ASSERT_TRUE(wait_for_list(coresList, &pauline2->stat.number_of_participant_devices_added, initialMarie1Stats.number_of_participant_devices_added + 1, 1000)); @@ -1746,10 +2060,7 @@ static void multiple_is_composing_notification(void) { linphone_chat_room_compose(paulineCr); linphone_chat_room_compose(marieCr); - BC_ASSERT_TRUE(wait_for_list(coresList, &marie->stat.number_of_LinphoneIsComposingActiveReceived, 2, 1000)); // + 1 BC_ASSERT_TRUE(wait_for_list(coresList, &laure->stat.number_of_LinphoneIsComposingActiveReceived, 3, 1000)); // + 2 - BC_ASSERT_TRUE(wait_for_list(coresList, &pauline->stat.number_of_LinphoneIsComposingActiveReceived, 1, 1000)); - // Laure side composing_addresses = linphone_chat_room_get_composing_addresses(laureCr); BC_ASSERT_EQUAL(bctbx_list_size(composing_addresses), 2, int, "%i"); @@ -1764,6 +2075,7 @@ static void multiple_is_composing_notification(void) { } // Marie side + BC_ASSERT_TRUE(wait_for_list(coresList, &marie->stat.number_of_LinphoneIsComposingActiveReceived, 2, 1000)); // + 1 composing_addresses = linphone_chat_room_get_composing_addresses(marieCr); BC_ASSERT_EQUAL(bctbx_list_size(composing_addresses), 1, int, "%i"); if (bctbx_list_size(composing_addresses) == 1) { @@ -1772,6 +2084,7 @@ static void multiple_is_composing_notification(void) { } // Pauline side + BC_ASSERT_TRUE(wait_for_list(coresList, &pauline->stat.number_of_LinphoneIsComposingActiveReceived, 1, 2000)); composing_addresses = linphone_chat_room_get_composing_addresses(paulineCr); BC_ASSERT_EQUAL(bctbx_list_size(composing_addresses), 1, int, "%i"); if (bctbx_list_size(composing_addresses) == 1) { @@ -1833,9 +2146,10 @@ static void group_chat_room_fallback_to_basic_chat_room (void) { bctbx_list_free_with_data(participantsAddresses, (bctbx_list_free_func)linphone_address_unref); participantsAddresses = NULL; - // Send a message and check that a basic chat room is create on Pauline's side + // Send a message and check that a basic chat room is created on Pauline's side LinphoneChatMessage *msg = linphone_chat_room_create_message(marieCr, "Hey Pauline!"); linphone_chat_message_send(msg); + linphone_chat_message_unref(msg); BC_ASSERT_TRUE(wait_for_list(coresList, &pauline->stat.number_of_LinphoneMessageReceived, initialPaulineStats.number_of_LinphoneMessageReceived + 1, 1000)); BC_ASSERT_PTR_NOT_NULL(pauline->stat.last_received_chat_message); if (pauline->stat.last_received_chat_message) @@ -1952,9 +2266,10 @@ static void group_chat_room_migrate_from_basic_chat_room (void) { LinphoneAddress *paulineAddr = linphone_address_new(linphone_core_get_identity(pauline->lc)); LinphoneChatRoom *marieCr = linphone_core_get_chat_room(marie->lc, paulineAddr); - // Send a message and check that a basic chat room is create on Pauline's side + // Send a message and check that a basic chat room is created on Pauline's side LinphoneChatMessage *msg = linphone_chat_room_create_message(marieCr, "Hey Pauline!"); linphone_chat_message_send(msg); + linphone_chat_message_unref(msg); BC_ASSERT_TRUE(wait_for_list(coresList, &pauline->stat.number_of_LinphoneMessageReceived, initialPaulineStats.number_of_LinphoneMessageReceived + 1, 1000)); BC_ASSERT_PTR_NOT_NULL(pauline->stat.last_received_chat_message); if (pauline->stat.last_received_chat_message) @@ -1967,11 +2282,12 @@ static void group_chat_room_migrate_from_basic_chat_room (void) { // Enable chat room migration and restart core for Marie _linphone_chat_room_enable_migration(marieCr, TRUE); coresList = bctbx_list_remove(coresList, marie->lc); - linphone_core_manager_restart(marie, TRUE); + linphone_core_manager_reinit(marie); bctbx_list_t *tmpCoresManagerList = bctbx_list_append(NULL, marie); - init_core_for_conference(tmpCoresManagerList); + bctbx_list_t *tmpCoresList = init_core_for_conference(tmpCoresManagerList); bctbx_list_free(tmpCoresManagerList); - coresList = bctbx_list_append(coresList, marie->lc); + coresList = bctbx_list_concat(coresList, tmpCoresList); + linphone_core_manager_start(marie, TRUE); // Send a new message to initiate chat room migration marieCr = linphone_core_get_chat_room(marie->lc, paulineAddr); @@ -1982,6 +2298,7 @@ static void group_chat_room_migrate_from_basic_chat_room (void) { BC_ASSERT_EQUAL(linphone_chat_room_get_capabilities(marieCr), LinphoneChatRoomCapabilitiesBasic | LinphoneChatRoomCapabilitiesProxy | LinphoneChatRoomCapabilitiesMigratable | LinphoneChatRoomCapabilitiesOneToOne, int, "%d"); msg = linphone_chat_room_create_message(marieCr, "Did you migrate?"); linphone_chat_message_send(msg); + linphone_chat_message_unref(msg); BC_ASSERT_TRUE(wait_for_list(coresList, &marie->stat.number_of_LinphoneChatRoomStateCreationPending, initialMarieStats.number_of_LinphoneChatRoomStateCreationPending + 1, 10000)); BC_ASSERT_TRUE(wait_for_list(coresList, &marie->stat.number_of_LinphoneChatRoomStateCreated, initialMarieStats.number_of_LinphoneChatRoomStateCreated + 1, 10000)); BC_ASSERT_TRUE(linphone_chat_room_get_capabilities(marieCr) & LinphoneChatRoomCapabilitiesConference); @@ -1997,21 +2314,27 @@ static void group_chat_room_migrate_from_basic_chat_room (void) { msg = linphone_chat_room_create_message(marieCr, "Let's go drink a beer"); linphone_chat_message_send(msg); + linphone_chat_message_unref(msg); BC_ASSERT_TRUE(wait_for_list(coresList, &pauline->stat.number_of_LinphoneMessageReceived, initialPaulineStats.number_of_LinphoneMessageReceived + 2, 1000)); BC_ASSERT_EQUAL(linphone_chat_room_get_history_size(marieCr), 3, int, "%d"); BC_ASSERT_EQUAL(linphone_chat_room_get_history_size(paulineCr), 3, int, "%d"); msg = linphone_chat_room_create_message(paulineCr, "Let's go drink mineral water instead"); linphone_chat_message_send(msg); + linphone_chat_message_unref(msg); BC_ASSERT_TRUE(wait_for_list(coresList, &marie->stat.number_of_LinphoneMessageReceived, initialMarieStats.number_of_LinphoneMessageReceived + 1, 1000)); BC_ASSERT_EQUAL(linphone_chat_room_get_history_size(marieCr), 4, int, "%d"); BC_ASSERT_EQUAL(linphone_chat_room_get_history_size(paulineCr), 4, int, "%d"); + + BC_ASSERT_EQUAL((int)bctbx_list_size(linphone_core_get_chat_rooms(marie->lc)), 1, int, "%d"); + BC_ASSERT_EQUAL((int)bctbx_list_size(linphone_core_get_chat_rooms(pauline->lc)), 1, int, "%d"); } // Clean db from chat room linphone_core_manager_delete_chat_room(marie, marieCr, coresList); linphone_core_manager_delete_chat_room(pauline, paulineCr, coresList); + linphone_address_unref(paulineAddr); bctbx_list_free(coresList); bctbx_list_free(coresManagerList); linphone_core_manager_destroy(marie); @@ -2049,7 +2372,7 @@ static void group_chat_room_migrate_from_basic_to_client_fail (void) { bctbx_list_free_with_data(participantsAddresses, (bctbx_list_free_func)linphone_address_unref); participantsAddresses = NULL; - // Send a message and check that a basic chat room is create on Pauline's side + // Send a message and check that a basic chat room is created on Pauline's side LinphoneChatMessage *msg = linphone_chat_room_create_message(marieCr, "Hey Pauline!"); linphone_chat_message_send(msg); BC_ASSERT_TRUE(wait_for_list(coresList, &pauline->stat.number_of_LinphoneMessageReceived, initialPaulineStats.number_of_LinphoneMessageReceived + 1, 1000)); @@ -2064,11 +2387,12 @@ static void group_chat_room_migrate_from_basic_to_client_fail (void) { // Enable chat room migration and restart core for Marie _linphone_chat_room_enable_migration(marieCr, TRUE); coresList = bctbx_list_remove(coresList, marie->lc); - linphone_core_manager_restart(marie, TRUE); + linphone_core_manager_reinit(marie); bctbx_list_t *tmpCoresManagerList = bctbx_list_append(NULL, marie); init_core_for_conference(tmpCoresManagerList); bctbx_list_free(tmpCoresManagerList); coresList = bctbx_list_append(coresList, marie->lc); + linphone_core_manager_start(marie, TRUE); // Send a new message to initiate chat room migration LinphoneAddress *paulineAddr = linphone_address_new(linphone_core_get_identity(pauline->lc)); @@ -2152,9 +2476,10 @@ static void group_chat_donot_room_migrate_from_basic_chat_room (void) { LinphoneAddress *paulineAddr = linphone_address_new(linphone_core_get_identity(pauline->lc)); LinphoneChatRoom *marieCr = linphone_core_get_chat_room(marie->lc, paulineAddr); - // Send a message and check that a basic chat room is create on Pauline's side + // Send a message and check that a basic chat room is created on Pauline's side LinphoneChatMessage *msg = linphone_chat_room_create_message(marieCr, "Hey Pauline!"); linphone_chat_message_send(msg); + linphone_chat_message_unref(msg); BC_ASSERT_TRUE(wait_for_list(coresList, &pauline->stat.number_of_LinphoneMessageReceived, initialPaulineStats.number_of_LinphoneMessageReceived + 1, 1000)); BC_ASSERT_PTR_NOT_NULL(pauline->stat.last_received_chat_message); if (pauline->stat.last_received_chat_message) @@ -2181,6 +2506,7 @@ static void group_chat_donot_room_migrate_from_basic_chat_room (void) { BC_ASSERT_EQUAL(linphone_chat_room_get_capabilities(marieCr), LinphoneChatRoomCapabilitiesBasic | LinphoneChatRoomCapabilitiesOneToOne, int, "%d"); msg = linphone_chat_room_create_message(marieCr, "Did you migrate?"); linphone_chat_message_send(msg); + linphone_chat_message_unref(msg); BC_ASSERT_FALSE(wait_for_list(coresList, &marie->stat.number_of_LinphoneChatRoomStateCreationPending, initialMarieStats.number_of_LinphoneChatRoomStateCreationPending + 1, 10000)); BC_ASSERT_FALSE(wait_for_list(coresList, &marie->stat.number_of_LinphoneChatRoomStateCreated, initialMarieStats.number_of_LinphoneChatRoomStateCreated + 1, 10000)); BC_ASSERT_TRUE(linphone_chat_room_get_capabilities(marieCr) & LinphoneChatRoomCapabilitiesBasic); @@ -2196,12 +2522,14 @@ static void group_chat_donot_room_migrate_from_basic_chat_room (void) { msg = linphone_chat_room_create_message(marieCr, "Let's go drink a beer"); linphone_chat_message_send(msg); + linphone_chat_message_unref(msg); BC_ASSERT_TRUE(wait_for_list(coresList, &pauline->stat.number_of_LinphoneMessageReceived, initialPaulineStats.number_of_LinphoneMessageReceived + 2, 1000)); BC_ASSERT_EQUAL(linphone_chat_room_get_history_size(marieCr), 3, int, "%d"); BC_ASSERT_EQUAL(linphone_chat_room_get_history_size(paulineCr), 3, int, "%d"); msg = linphone_chat_room_create_message(paulineCr, "Let's go drink mineral water instead"); linphone_chat_message_send(msg); + linphone_chat_message_unref(msg); BC_ASSERT_TRUE(wait_for_list(coresList, &marie->stat.number_of_LinphoneMessageReceived, initialMarieStats.number_of_LinphoneMessageReceived + 1, 1000)); BC_ASSERT_EQUAL(linphone_chat_room_get_history_size(marieCr), 4, int, "%d"); BC_ASSERT_EQUAL(linphone_chat_room_get_history_size(paulineCr), 4, int, "%d"); @@ -2211,6 +2539,7 @@ static void group_chat_donot_room_migrate_from_basic_chat_room (void) { linphone_core_delete_chat_room(marie->lc, marieCr); linphone_core_delete_chat_room(pauline->lc, paulineCr); + linphone_address_unref(paulineAddr); bctbx_list_free(coresList); bctbx_list_free(coresManagerList); linphone_core_manager_destroy(marie); @@ -2252,10 +2581,10 @@ static void group_chat_room_send_file_with_or_without_text (bool_t with_text) { 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); + LinphoneChatRoom *paulineCr = check_creation_chat_room_client_side(coresList, pauline, &initialPaulineStats, confAddr, initialSubject, 2, FALSE); // 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); + LinphoneChatRoom *chloeCr = check_creation_chat_room_client_side(coresList, chloe, &initialChloeStats, confAddr, initialSubject, 2, FALSE); // Sending file if (with_text) { @@ -2279,6 +2608,8 @@ static void group_chat_room_send_file_with_or_without_text (bool_t with_text) { 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); + remove(receivePaulineFilepath); + remove(receiveChloeFilepath); bc_free(sendFilepath); bc_free(receivePaulineFilepath); bc_free(receiveChloeFilepath); @@ -2319,15 +2650,16 @@ static void group_chat_room_unique_one_to_one_chat_room (void) { LinphoneAddress *confAddr = linphone_address_clone(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, 1, 0); + LinphoneChatRoom *paulineCr = check_creation_chat_room_client_side(coresList, pauline, &initialPaulineStats, confAddr, initialSubject, 1, FALSE); BC_ASSERT_TRUE(linphone_chat_room_get_capabilities(paulineCr) & LinphoneChatRoomCapabilitiesOneToOne); // Marie sends a message - const char *message = "Hello"; - _send_message(marieCr, message); + const char *textMessage = "Hello"; + LinphoneChatMessage *message = _send_message(marieCr, textMessage); BC_ASSERT_TRUE(wait_for_list(coresList, &marie->stat.number_of_LinphoneMessageDelivered, initialMarieStats.number_of_LinphoneMessageDelivered + 1, 10000)); BC_ASSERT_TRUE(wait_for_list(coresList, &pauline->stat.number_of_LinphoneMessageReceived, initialPaulineStats.number_of_LinphoneMessageReceived + 1, 10000)); - BC_ASSERT_STRING_EQUAL(linphone_chat_message_get_text(pauline->stat.last_received_chat_message), message); + BC_ASSERT_STRING_EQUAL(linphone_chat_message_get_text(pauline->stat.last_received_chat_message), textMessage); + linphone_chat_message_unref(message); // Marie deletes the chat room linphone_core_manager_delete_chat_room(marie, marieCr, coresList); @@ -2341,11 +2673,12 @@ static void group_chat_room_unique_one_to_one_chat_room (void) { marieCr = create_chat_room_client_side(coresList, marie, &initialMarieStats, participantsAddresses, initialSubject, -1); // Marie sends a new message - message = "Hey again"; - _send_message(marieCr, message); + textMessage = "Hey again"; + message = _send_message(marieCr, textMessage); BC_ASSERT_TRUE(wait_for_list(coresList, &marie->stat.number_of_LinphoneMessageDelivered, initialMarieStats.number_of_LinphoneMessageDelivered + 1, 10000)); BC_ASSERT_TRUE(wait_for_list(coresList, &pauline->stat.number_of_LinphoneMessageReceived, initialPaulineStats.number_of_LinphoneMessageReceived + 1, 10000)); - BC_ASSERT_STRING_EQUAL(linphone_chat_message_get_text(pauline->stat.last_received_chat_message), message); + BC_ASSERT_STRING_EQUAL(linphone_chat_message_get_text(pauline->stat.last_received_chat_message), textMessage); + linphone_chat_message_unref(message); // Check that the created address is the same as before const LinphoneAddress *newConfAddr = linphone_chat_room_get_conference_address(marieCr); @@ -2383,32 +2716,31 @@ static void group_chat_room_unique_one_to_one_chat_room_recreated_from_message_b LinphoneAddress *confAddr = linphone_address_clone(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, 1, 0); + LinphoneChatRoom *paulineCr = check_creation_chat_room_client_side(coresList, pauline, &initialPaulineStats, confAddr, initialSubject, 1, FALSE); BC_ASSERT_TRUE(linphone_chat_room_get_capabilities(paulineCr) & LinphoneChatRoomCapabilitiesOneToOne); // Marie sends a message - const char *message = "Hello"; - _send_message(marieCr, message); + const char *textMessage = "Hello"; + LinphoneChatMessage *message = _send_message(marieCr, textMessage); BC_ASSERT_TRUE(wait_for_list(coresList, &marie->stat.number_of_LinphoneMessageDelivered, initialMarieStats.number_of_LinphoneMessageDelivered + 1, 10000)); BC_ASSERT_TRUE(wait_for_list(coresList, &pauline->stat.number_of_LinphoneMessageReceived, initialPaulineStats.number_of_LinphoneMessageReceived + 1, 10000)); - BC_ASSERT_STRING_EQUAL(linphone_chat_message_get_text(pauline->stat.last_received_chat_message), message); + BC_ASSERT_STRING_EQUAL(linphone_chat_message_get_text(pauline->stat.last_received_chat_message), textMessage); + linphone_chat_message_unref(message); if (with_app_restart) { - //to simulate dialog removal + // To simulate dialog removal + LinphoneAddress *marieAddr = linphone_address_clone(linphone_chat_room_get_peer_address(marieCr)); linphone_core_set_network_reachable(marie->lc, FALSE); - //linphone_core_set_network_reachable(marie->lc, TRUE); - coresList=bctbx_list_remove(coresList, marie->lc); - linphone_core_manager_reinit(marie,TRUE); + coresList = bctbx_list_remove(coresList, marie->lc); + linphone_core_manager_reinit(marie); bctbx_list_t *tmpCoresManagerList = bctbx_list_append(NULL, marie); - init_core_for_conference(tmpCoresManagerList); + bctbx_list_t *tmpCoresList = init_core_for_conference(tmpCoresManagerList); bctbx_list_free(tmpCoresManagerList); - coresList = bctbx_list_append(coresList, marie->lc); - linphone_core_manager_start(marie,TRUE); - wait_for_list(coresList, 0, 1, 2000); - LinphoneChatRoom *oldMarieCr = marieCr; - marieCr = linphone_core_get_chat_room(marie->lc, linphone_chat_room_get_peer_address(oldMarieCr)); + coresList = bctbx_list_concat(coresList, tmpCoresList); + linphone_core_manager_start(marie, TRUE); + marieCr = linphone_core_get_chat_room(marie->lc, marieAddr); + linphone_address_unref(marieAddr); } - // Marie deletes the chat room linphone_core_manager_delete_chat_room(marie, marieCr, coresList); @@ -2419,21 +2751,28 @@ static void group_chat_room_unique_one_to_one_chat_room_recreated_from_message_b initialMarieStats = marie->stat; initialPaulineStats = pauline->stat; - // Marie sends a new message - message = "Hey you"; - _send_message(paulineCr, message); + // Pauline sends a new message + textMessage = "Hey you"; + message = _send_message(paulineCr, textMessage); BC_ASSERT_TRUE(wait_for_list(coresList, &pauline->stat.number_of_LinphoneMessageDelivered, initialPaulineStats.number_of_LinphoneMessageDelivered + 1, 10000)); BC_ASSERT_TRUE(wait_for_list(coresList, &marie->stat.number_of_LinphoneMessageReceived, initialMarieStats.number_of_LinphoneMessageReceived + 1, 10000)); - BC_ASSERT_STRING_EQUAL(linphone_chat_message_get_text(marie->stat.last_received_chat_message), message); + BC_ASSERT_STRING_EQUAL(linphone_chat_message_get_text(marie->stat.last_received_chat_message), textMessage); + linphone_chat_message_unref(message); // Check that the chat room has been correctly recreated on Marie's side - marieCr = check_creation_chat_room_client_side(coresList, marie, &initialMarieStats, confAddr, initialSubject, 1, 0); + marieCr = check_creation_chat_room_client_side(coresList, marie, &initialMarieStats, confAddr, initialSubject, 1, FALSE); BC_ASSERT_TRUE(linphone_chat_room_get_capabilities(paulineCr) & LinphoneChatRoomCapabilitiesOneToOne); // Clean db from chat room linphone_core_manager_delete_chat_room(marie, marieCr, coresList); linphone_core_manager_delete_chat_room(pauline, paulineCr, coresList); + wait_for_list(coresList, 0, 1, 2000); + BC_ASSERT_EQUAL(linphone_core_get_call_history_size(marie->lc), 0, int,"%i"); + BC_ASSERT_EQUAL(linphone_core_get_call_history_size(pauline->lc), 0, int,"%i"); + BC_ASSERT_PTR_NULL(linphone_core_get_call_logs(marie->lc)); + BC_ASSERT_PTR_NULL(linphone_core_get_call_logs(pauline->lc)); + linphone_address_unref(confAddr); bctbx_list_free(coresList); bctbx_list_free(coresManagerList); @@ -2449,6 +2788,176 @@ static void group_chat_room_unique_one_to_one_chat_room_recreated_from_message_w group_chat_room_unique_one_to_one_chat_room_recreated_from_message_base(TRUE); } +static void group_chat_room_unique_one_to_one_chat_room_recreated_from_message_2 (void) { + LinphoneCoreManager *marie = linphone_core_manager_create("marie_rc"); + LinphoneCoreManager *pauline = linphone_core_manager_create("pauline_rc"); + /*create a second device for marie, but it is inactive after registration in this test*/ + LinphoneCoreManager *marie2 = linphone_core_manager_create("marie_rc"); + /*Create a seconde device for pauline, but again inactivate after registration*/ + LinphoneCoreManager *pauline2 = linphone_core_manager_create("marie_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, marie2); + coresManagerList = bctbx_list_append(coresManagerList, pauline2); + 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))); + stats initialMarieStats = marie->stat; + stats initialPaulineStats = pauline->stat; + + linphone_core_set_network_reachable(marie2->lc, FALSE); + linphone_core_set_network_reachable(pauline2->lc, FALSE); + + // Marie creates a new group chat room + const char *initialSubject = "Pauline"; + LinphoneChatRoom *marieCr = create_chat_room_client_side(coresList, marie, &initialMarieStats, participantsAddresses, initialSubject, -1); + BC_ASSERT_TRUE(linphone_chat_room_get_capabilities(marieCr) & LinphoneChatRoomCapabilitiesOneToOne); + + LinphoneAddress *confAddr = linphone_address_clone(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, 1, FALSE); + BC_ASSERT_TRUE(linphone_chat_room_get_capabilities(paulineCr) & LinphoneChatRoomCapabilitiesOneToOne); + + // Marie sends a message + const char *textMessage = "Hello"; + LinphoneChatMessage *message = _send_message(marieCr, textMessage); + BC_ASSERT_TRUE(wait_for_list(coresList, &marie->stat.number_of_LinphoneMessageDelivered, initialMarieStats.number_of_LinphoneMessageDelivered + 1, 10000)); + BC_ASSERT_TRUE(wait_for_list(coresList, &pauline->stat.number_of_LinphoneMessageReceived, initialPaulineStats.number_of_LinphoneMessageReceived + 1, 10000)); + BC_ASSERT_STRING_EQUAL(linphone_chat_message_get_text(pauline->stat.last_received_chat_message), textMessage); + linphone_chat_message_unref(message); + + // Marie deletes the chat room + linphone_core_manager_delete_chat_room(marie, marieCr, coresList); + wait_for_list(coresList, 0, 1, 2000); + BC_ASSERT_EQUAL(pauline->stat.number_of_participants_removed, initialPaulineStats.number_of_participants_removed, int, "%d"); + + // Marie sends a new message + participantsAddresses = NULL; + participantsAddresses = bctbx_list_append(participantsAddresses, linphone_address_new(linphone_core_get_identity(pauline->lc))); + initialMarieStats = marie->stat; + initialPaulineStats = pauline->stat; + marieCr = create_chat_room_client_side(coresList, marie, &initialMarieStats, participantsAddresses, initialSubject, -1); + + // Check that the chat room has been correctly recreated on Marie's side + marieCr = check_creation_chat_room_client_side(coresList, marie, &initialMarieStats, confAddr, initialSubject, 1, FALSE); + if (BC_ASSERT_PTR_NOT_NULL(marieCr)){ + BC_ASSERT_TRUE(linphone_chat_room_get_capabilities(marieCr) & LinphoneChatRoomCapabilitiesOneToOne); + textMessage = "Hey you"; + message = _send_message(marieCr, textMessage); + BC_ASSERT_TRUE(wait_for_list(coresList, &marie->stat.number_of_LinphoneMessageDelivered, initialMarieStats.number_of_LinphoneMessageDelivered + 1, 10000)); + BC_ASSERT_TRUE(wait_for_list(coresList, &pauline->stat.number_of_LinphoneMessageReceived, initialPaulineStats.number_of_LinphoneMessageReceived + 1, 10000)); + BC_ASSERT_STRING_EQUAL(linphone_chat_message_get_text(pauline->stat.last_received_chat_message), textMessage); + linphone_chat_message_unref(message); + + // Clean db from chat room + linphone_core_manager_delete_chat_room(marie, marieCr, coresList); + } + linphone_core_manager_delete_chat_room(pauline, paulineCr, coresList); + + wait_for_list(coresList, 0, 1, 2000); + BC_ASSERT_EQUAL(linphone_core_get_call_history_size(marie->lc), 0, int,"%i"); + BC_ASSERT_EQUAL(linphone_core_get_call_history_size(pauline->lc), 0, int,"%i"); + BC_ASSERT_PTR_NULL(linphone_core_get_call_logs(marie->lc)); + BC_ASSERT_PTR_NULL(linphone_core_get_call_logs(pauline->lc)); + + linphone_address_unref(confAddr); + bctbx_list_free(coresList); + bctbx_list_free(coresManagerList); + linphone_core_set_network_reachable(marie2->lc, TRUE); + linphone_core_set_network_reachable(pauline2->lc, TRUE); + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); + linphone_core_manager_destroy(marie2); + linphone_core_manager_destroy(pauline2); +} + +static void group_chat_room_join_one_to_one_chat_room_with_a_new_device (void) { + LinphoneCoreManager *marie1 = linphone_core_manager_create("marie_rc"); + LinphoneCoreManager *pauline = linphone_core_manager_create("pauline_rc"); + bctbx_list_t *coresManagerList = NULL; + coresManagerList = bctbx_list_append(coresManagerList, marie1); + coresManagerList = bctbx_list_append(coresManagerList, pauline); + bctbx_list_t *coresList = init_core_for_conference(coresManagerList); + start_core_for_conference(coresManagerList); + bctbx_list_t *participantsAddresses = bctbx_list_append(NULL, linphone_address_new(linphone_core_get_identity(pauline->lc))); + stats initialMarie1Stats = marie1->stat; + stats initialPaulineStats = pauline->stat; + + // Marie1 creates a new one-to-one chat room with Pauline + const char *initialSubject = "Pauline"; + LinphoneChatRoom *marie1Cr = create_chat_room_client_side(coresList, marie1, &initialMarie1Stats, participantsAddresses, initialSubject, -1); + BC_ASSERT_TRUE(linphone_chat_room_get_capabilities(marie1Cr) & LinphoneChatRoomCapabilitiesOneToOne); + + LinphoneAddress *confAddr = linphone_address_clone(linphone_chat_room_get_conference_address(marie1Cr)); + + // 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, 1, FALSE); + BC_ASSERT_TRUE(linphone_chat_room_get_capabilities(paulineCr) & LinphoneChatRoomCapabilitiesOneToOne); + + // Marie1 sends a message + const char *textMessage = "Hello"; + LinphoneChatMessage *message = _send_message(marie1Cr, textMessage); + BC_ASSERT_TRUE(wait_for_list(coresList, &marie1->stat.number_of_LinphoneMessageDelivered, initialMarie1Stats.number_of_LinphoneMessageDelivered + 1, 10000)); + BC_ASSERT_TRUE(wait_for_list(coresList, &pauline->stat.number_of_LinphoneMessageReceived, initialPaulineStats.number_of_LinphoneMessageReceived + 1, 10000)); + BC_ASSERT_STRING_EQUAL(linphone_chat_message_get_text(pauline->stat.last_received_chat_message), textMessage); + linphone_chat_message_unref(message); + + // Pauline answers to the previous message + textMessage = "Hey. How are you?"; + message = _send_message(paulineCr, textMessage); + BC_ASSERT_TRUE(wait_for_list(coresList, &pauline->stat.number_of_LinphoneMessageDelivered, initialPaulineStats.number_of_LinphoneMessageDelivered + 1, 10000)); + BC_ASSERT_TRUE(wait_for_list(coresList, &marie1->stat.number_of_LinphoneMessageReceived, initialMarie1Stats.number_of_LinphoneMessageReceived + 1, 10000)); + BC_ASSERT_STRING_EQUAL(linphone_chat_message_get_text(marie1->stat.last_received_chat_message), textMessage); + linphone_chat_message_unref(message); + + // Simulate an uninstall of the application on Marie's side + linphone_core_set_network_reachable(marie1->lc, FALSE); + coresManagerList = bctbx_list_remove(coresManagerList, marie1); + coresList = bctbx_list_remove(coresList, marie1->lc); + LinphoneCoreManager *marie2 = linphone_core_manager_create("marie_rc"); + bctbx_list_t *newCoresManagerList = bctbx_list_append(NULL, marie2); + bctbx_list_t *newCoresList = init_core_for_conference(newCoresManagerList); + start_core_for_conference(newCoresManagerList); + coresManagerList = bctbx_list_concat(coresManagerList, newCoresManagerList); + coresList = bctbx_list_concat(coresList, newCoresList); + + // Marie2 gets the one-to-one chat room with Pauline + stats initialMarie2Stats = marie2->stat; + LinphoneChatRoom *marie2Cr = check_creation_chat_room_client_side(coresList, marie2, &initialMarie2Stats, confAddr, initialSubject, 1, FALSE); + BC_ASSERT_TRUE(linphone_chat_room_get_capabilities(marie2Cr) & LinphoneChatRoomCapabilitiesOneToOne); + + // Marie2 sends a new message + textMessage = "Fine and you?"; + message = _send_message(marie2Cr, textMessage); + BC_ASSERT_TRUE(wait_for_list(coresList, &marie2->stat.number_of_LinphoneMessageDelivered, initialMarie2Stats.number_of_LinphoneMessageDelivered + 1, 10000)); + BC_ASSERT_TRUE(wait_for_list(coresList, &pauline->stat.number_of_LinphoneMessageReceived, initialPaulineStats.number_of_LinphoneMessageReceived + 2, 10000)); + BC_ASSERT_STRING_EQUAL(linphone_chat_message_get_text(pauline->stat.last_received_chat_message), textMessage); + linphone_chat_message_unref(message); + + // Pauline answers to the previous message + textMessage = "Perfect!"; + message = _send_message(paulineCr, textMessage); + BC_ASSERT_TRUE(wait_for_list(coresList, &pauline->stat.number_of_LinphoneMessageDelivered, initialPaulineStats.number_of_LinphoneMessageDelivered + 2, 10000)); + BC_ASSERT_TRUE(wait_for_list(coresList, &marie2->stat.number_of_LinphoneMessageReceived, initialMarie2Stats.number_of_LinphoneMessageReceived + 1, 10000)); + BC_ASSERT_STRING_EQUAL(linphone_chat_message_get_text(marie2->stat.last_received_chat_message), textMessage); + linphone_chat_message_unref(message); + + // Clean db from chat room + linphone_core_manager_delete_chat_room(marie2, marie2Cr, coresList); + linphone_core_delete_chat_room(marie1->lc, marie1Cr); + linphone_core_manager_delete_chat_room(pauline, paulineCr, coresList); + + linphone_address_unref(confAddr); + bctbx_list_free(coresList); + bctbx_list_free(coresManagerList); + linphone_core_manager_destroy(marie2); + linphone_core_manager_destroy(marie1); + linphone_core_manager_destroy(pauline); +} + static void group_chat_room_new_unique_one_to_one_chat_room_after_both_participants_left (void) { LinphoneCoreManager *marie = linphone_core_manager_create("marie_rc"); LinphoneCoreManager *pauline = linphone_core_manager_create("pauline_rc"); @@ -2470,7 +2979,7 @@ static void group_chat_room_new_unique_one_to_one_chat_room_after_both_participa LinphoneAddress *firstConfAddr = linphone_address_clone(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, firstConfAddr, initialSubject, 1, 0); + LinphoneChatRoom *paulineCr = check_creation_chat_room_client_side(coresList, pauline, &initialPaulineStats, firstConfAddr, initialSubject, 1, FALSE); BC_ASSERT_TRUE(linphone_chat_room_get_capabilities(paulineCr) & LinphoneChatRoomCapabilitiesOneToOne); // Both participants delete the chat room @@ -2488,7 +2997,7 @@ static void group_chat_room_new_unique_one_to_one_chat_room_after_both_participa LinphoneAddress *secondConfAddr = linphone_address_clone(linphone_chat_room_get_conference_address(marieCr)); // Check that the chat room has been correctly recreated on Marie's side - paulineCr = check_creation_chat_room_client_side(coresList, pauline, &initialPaulineStats, secondConfAddr, initialSubject, 1, 0); + paulineCr = check_creation_chat_room_client_side(coresList, pauline, &initialPaulineStats, secondConfAddr, initialSubject, 1, FALSE); BC_ASSERT_TRUE(linphone_chat_room_get_capabilities(paulineCr) & LinphoneChatRoomCapabilitiesOneToOne); BC_ASSERT_FALSE(linphone_address_equal(firstConfAddr, secondConfAddr)); @@ -2505,41 +3014,269 @@ 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)); + linphone_config_set_bool(linphone_core_get_config(marie->lc), "misc", "enable_simple_group_chat_message_state", FALSE); + linphone_config_set_bool(linphone_core_get_config(pauline->lc), "misc", "enable_simple_group_chat_message_state", FALSE); + linphone_config_set_bool(linphone_core_get_config(chloe->lc), "misc", "enable_simple_group_chat_message_state", FALSE); + + // 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, FALSE); + + // 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, FALSE); + + // Chloe begins composing a message + const char *chloeTextMessage = "Hello"; + LinphoneChatMessage *chloeMessage = _send_message(chloeCr, chloeTextMessage); + 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), chloeTextMessage); + 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)); + + linphone_chat_message_unref(chloeMessage); + +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); +} + +static void find_one_to_one_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; + // Only to be used in linphone_core_find_one_to_one_chatroom(...); + const LinphoneAddress *marieAddr = linphone_proxy_config_get_contact(linphone_core_get_default_proxy_config(marie->lc)); + const LinphoneAddress *paulineAddr = linphone_proxy_config_get_identity_address(linphone_core_get_default_proxy_config(pauline->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, FALSE); + + // 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, FALSE); + + // Chloe leave the chat room + linphone_chat_room_leave(chloeCr); + BC_ASSERT_TRUE(wait_for_list(coresList, &chloe->stat.number_of_LinphoneChatRoomStateTerminationPending, initialChloeStats.number_of_LinphoneChatRoomStateTerminationPending + 1, 100)); + BC_ASSERT_TRUE(wait_for_list(coresList, &chloe->stat.number_of_LinphoneChatRoomStateTerminated, initialChloeStats.number_of_LinphoneChatRoomStateTerminated + 1, 10000)); + BC_ASSERT_TRUE(wait_for_list(coresList, &pauline->stat.number_of_participants_removed, initialPaulineStats.number_of_participants_removed + 1, 1000)); + BC_ASSERT_TRUE(wait_for_list(coresList, &marie->stat.number_of_participants_removed, initialMarieStats.number_of_participants_removed + 1, 1000)); + + LinphoneChatRoom *oneToOneChatRoom = linphone_core_find_one_to_one_chat_room(marie->lc, marieAddr, paulineAddr); + BC_ASSERT_PTR_NULL(oneToOneChatRoom); + + // Marie create a one to one chat room with Pauline + participantsAddresses = NULL; + participantsAddresses = bctbx_list_append(participantsAddresses, linphone_address_new(linphone_core_get_identity(pauline->lc))); + initialMarieStats = marie->stat; + initialPaulineStats = pauline->stat; + LinphoneChatRoom *marieOneToOneCr = create_chat_room_client_side(coresList, marie, &initialMarieStats, participantsAddresses, "one to one", -1); + confAddr = linphone_chat_room_get_conference_address(marieOneToOneCr); + LinphoneChatRoom *paulineOneToOneCr = check_creation_chat_room_client_side(coresList, pauline, &initialPaulineStats, confAddr, "one to one", 1, FALSE); + + // Check it's the same chat room + oneToOneChatRoom = linphone_core_find_one_to_one_chat_room(marie->lc, marieAddr, paulineAddr); + BC_ASSERT_PTR_NOT_NULL(oneToOneChatRoom); + BC_ASSERT_PTR_EQUAL(oneToOneChatRoom, marieOneToOneCr); + + // Clean the db + linphone_core_manager_delete_chat_room(marie, marieOneToOneCr, coresList); + linphone_core_manager_delete_chat_room(pauline, paulineOneToOneCr, coresList); + + // Check cleaning went well + oneToOneChatRoom = linphone_core_find_one_to_one_chat_room(marie->lc, marieAddr, paulineAddr); + BC_ASSERT_PTR_NULL(oneToOneChatRoom); + + // Create a basic chat room + marieOneToOneCr = linphone_core_get_chat_room(marie->lc, paulineAddr); + + // Check it's the same chat room + oneToOneChatRoom = linphone_core_find_one_to_one_chat_room(marie->lc, marieAddr, paulineAddr); + BC_ASSERT_PTR_NOT_NULL(oneToOneChatRoom); + BC_ASSERT_PTR_EQUAL(oneToOneChatRoom, marieOneToOneCr); + + // Clean db from chat room + linphone_core_manager_delete_chat_room(marie, marieOneToOneCr, coresList); + 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); +} + +static void group_chat_room_new_device_after_creation (void) { + LinphoneCoreManager *marie1 = linphone_core_manager_create("marie_rc"); + LinphoneCoreManager *pauline1 = linphone_core_manager_create("pauline_rc"); + LinphoneCoreManager *pauline2 = linphone_core_manager_create("pauline_rc"); + LinphoneCoreManager *laure = linphone_core_manager_create("laure_tcp_rc"); + bctbx_list_t *coresManagerList = NULL; + bctbx_list_t *participantsAddresses = NULL; + coresManagerList = bctbx_list_append(coresManagerList, marie1); + coresManagerList = bctbx_list_append(coresManagerList, pauline1); + coresManagerList = bctbx_list_append(coresManagerList, pauline2); + coresManagerList = bctbx_list_append(coresManagerList, laure); + 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(pauline1->lc))); + participantsAddresses = bctbx_list_append(participantsAddresses, linphone_address_new(linphone_core_get_identity(laure->lc))); + stats initialMarie1Stats = marie1->stat; + stats initialPauline1Stats = pauline1->stat; + stats initialPauline2Stats = pauline2->stat; + stats initialLaureStats = laure->stat; + + // Marie creates a new group chat room + const char *initialSubject = "Colleagues"; + LinphoneChatRoom *marie1Cr = create_chat_room_client_side(coresList, marie1, &initialMarie1Stats, participantsAddresses, initialSubject, -1); + participantsAddresses = NULL; + const LinphoneAddress *confAddr = linphone_chat_room_get_conference_address(marie1Cr); + + // Check that the chat room is correctly created on Pauline1 and Pauline2's sides and that the participants are added + LinphoneChatRoom *pauline1Cr = check_creation_chat_room_client_side(coresList, pauline1, &initialPauline1Stats, confAddr, initialSubject, 2, FALSE); + LinphoneChatRoom *pauline2Cr = check_creation_chat_room_client_side(coresList, pauline2, &initialPauline2Stats, confAddr, initialSubject, 2, FALSE); + + // Check that the chat room is correctly created on Laure's side and that the participants are added + LinphoneChatRoom *laureCr = check_creation_chat_room_client_side(coresList, laure, &initialLaureStats, confAddr, initialSubject, 2, FALSE); + + // Marie adds a new device + LinphoneCoreManager *marie2 = linphone_core_manager_create("marie_rc"); + stats initialMarie2Stats = marie2->stat; + bctbx_list_t *tmpCoresManagerList = bctbx_list_append(NULL, marie2); + bctbx_list_t *tmpCoresList = init_core_for_conference(tmpCoresManagerList); + coresList = bctbx_list_concat(coresList, tmpCoresList); + start_core_for_conference(tmpCoresManagerList); + bctbx_list_free(tmpCoresManagerList); + LinphoneChatRoom *marie2Cr = check_creation_chat_room_client_side(coresList, marie2, &initialMarie2Stats, confAddr, initialSubject, 2, TRUE); + + // Clean db from chat room + linphone_core_manager_delete_chat_room(marie1, marie1Cr, coresList); + linphone_core_delete_chat_room(marie2->lc, marie2Cr); + linphone_core_manager_delete_chat_room(pauline1, pauline1Cr, coresList); + linphone_core_delete_chat_room(pauline2->lc, pauline2Cr); + linphone_core_manager_delete_chat_room(laure, laureCr, coresList); + + bctbx_list_free(coresList); + bctbx_list_free(coresManagerList); + linphone_core_manager_destroy(marie1); + linphone_core_manager_destroy(marie2); + linphone_core_manager_destroy(pauline1); + linphone_core_manager_destroy(pauline2); + linphone_core_manager_destroy(laure); +} + 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"), - TEST_TWO_TAGS("Send encrypted message", group_chat_room_send_message_encrypted, "Server", "LeaksMemory"), - TEST_TWO_TAGS("Send invite on a multi register account", group_chat_room_invite_multi_register_account, "Server", "LeaksMemory"), - TEST_TWO_TAGS("Add admin", group_chat_room_add_admin, "Server", "LeaksMemory"), - TEST_TWO_TAGS("Add admin lately notified", group_chat_room_add_admin_lately_notified, "Server", "LeaksMemory"), - TEST_TWO_TAGS("Add admin with a non admin", group_chat_room_add_admin_non_admin, "Server", "LeaksMemory"), - TEST_TWO_TAGS("Remove admin", group_chat_room_remove_admin, "Server", "LeaksMemory"), - TEST_TWO_TAGS("Change subject", group_chat_room_change_subject, "Server", "LeaksMemory"), - TEST_TWO_TAGS("Change subject with a non admin", group_chat_room_change_subject_non_admin, "Server", "LeaksMemory"), - TEST_TWO_TAGS("Remove participant", group_chat_room_remove_participant, "Server", "LeaksMemory"), - TEST_TWO_TAGS("Send message with a participant removed", group_chat_room_send_message_with_participant_removed, "Server", "LeaksMemory"), - TEST_TWO_TAGS("Leave group chat room", group_chat_room_leave, "Server", "LeaksMemory"), - TEST_TWO_TAGS("Come back on a group chat room after a disconnection", group_chat_room_come_back_after_disconnection, "Server", "LeaksMemory"), - TEST_TWO_TAGS("Create chat room with disconnected friends", group_chat_room_create_room_with_disconnected_friends, "Server", "LeaksMemory"), - TEST_TWO_TAGS("Create chat room with disconnected friends and initial message", group_chat_room_create_room_with_disconnected_friends_and_initial_message, "Server", "LeaksMemory"), - TEST_TWO_TAGS("Reinvited after removed from group chat room", group_chat_room_reinvited_after_removed, "Server", "LeaksMemory"), - TEST_TWO_TAGS("Notify after disconnection", group_chat_room_notify_after_disconnection, "Server", "LeaksMemory"), - TEST_TWO_TAGS("Send refer to all participants devices", group_chat_room_send_refer_to_all_devices, "Server", "LeaksMemory"), + TEST_NO_TAG("Group chat room creation server", group_chat_room_creation_server), + TEST_ONE_TAG("Add participant", group_chat_room_add_participant, "LeaksMemory"), + TEST_NO_TAG("Send message", group_chat_room_send_message), + TEST_NO_TAG("Send encrypted message", group_chat_room_send_message_encrypted), + TEST_NO_TAG("Send invite on a multi register account", group_chat_room_invite_multi_register_account), + TEST_NO_TAG("Add admin", group_chat_room_add_admin), + TEST_NO_TAG("Add admin lately notified", group_chat_room_add_admin_lately_notified), + TEST_NO_TAG("Add admin with a non admin", group_chat_room_add_admin_non_admin), + TEST_NO_TAG("Remove admin", group_chat_room_remove_admin), + TEST_NO_TAG("Admin creator leaves the room", group_chat_room_admin_creator_leaves_the_room), + TEST_NO_TAG("Change subject", group_chat_room_change_subject), + TEST_NO_TAG("Change subject with a non admin", group_chat_room_change_subject_non_admin), + TEST_NO_TAG("Remove participant", group_chat_room_remove_participant), + TEST_NO_TAG("Send message with a participant removed", group_chat_room_send_message_with_participant_removed), + TEST_NO_TAG("Leave group chat room", group_chat_room_leave), + TEST_NO_TAG("Come back on a group chat room after a disconnection", group_chat_room_come_back_after_disconnection), + TEST_NO_TAG("Create chat room with disconnected friends", group_chat_room_create_room_with_disconnected_friends), + TEST_ONE_TAG("Create chat room with disconnected friends and initial message", group_chat_room_create_room_with_disconnected_friends_and_initial_message, "LeaksMemory"), + TEST_NO_TAG("Reinvited after removed from group chat room", group_chat_room_reinvited_after_removed), + TEST_ONE_TAG("Reinvited after removed from group chat room while offline", group_chat_room_reinvited_after_removed_while_offline, "LeaksMemory"), + TEST_ONE_TAG("Reinvited after removed from group chat room while offline 2", group_chat_room_reinvited_after_removed_while_offline_2, "LeaksMemory"), + TEST_ONE_TAG("Reinvited after removed from group chat room with several devices", group_chat_room_reinvited_after_removed_with_several_devices, "LeaksMemory"), + TEST_NO_TAG("Notify after disconnection", group_chat_room_notify_after_disconnection), + TEST_NO_TAG("Send refer to all participants devices", group_chat_room_send_refer_to_all_devices), // TODO: Use when we support adding a new device in created conf - //TEST_TWO_TAGS("Admin add device and doesn't lose admin status", group_chat_room_add_device, "Server", "LeaksMemory"), - TEST_TWO_TAGS("Send multiple is composing", multiple_is_composing_notification, "Server", "LeaksMemory"), - TEST_TWO_TAGS("Fallback to basic chat room", group_chat_room_fallback_to_basic_chat_room, "Server", "LeaksMemory"), - TEST_TWO_TAGS("Group chat room creation fails if invited participants don't support it", group_chat_room_creation_fails_if_invited_participants_dont_support_it, "Server", "LeaksMemory"), - TEST_TWO_TAGS("Group chat room creation succesful if at least one invited participant supports it", group_chat_room_creation_successful_if_at_least_one_invited_participant_supports_it, "Server", "LeaksMemory"), - TEST_TWO_TAGS("Migrate basic chat room to client group chat room", group_chat_room_migrate_from_basic_chat_room, "Server", "LeaksMemory"), - TEST_TWO_TAGS("Migrate basic chat room to client group chat room failure", group_chat_room_migrate_from_basic_to_client_fail, "Server", "LeaksMemory"), - TEST_TWO_TAGS("Migrate basic chat room to client group chat room not needed", group_chat_donot_room_migrate_from_basic_chat_room, "Server", "LeaksMemory"), - TEST_TWO_TAGS("Send file", group_chat_room_send_file, "Server", "LeaksMemory"), - TEST_TWO_TAGS("Send file + text", group_chat_room_send_file_plus_text, "Server", "LeaksMemory"), - 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_ONE_TAGS("Admin add device and doesn't lose admin status", group_chat_room_add_device, "LeaksMemory"), + TEST_NO_TAG("Send multiple is composing", multiple_is_composing_notification), + TEST_ONE_TAG("Fallback to basic chat room", group_chat_room_fallback_to_basic_chat_room, "LeaksMemory"), + TEST_NO_TAG("Group chat room creation fails if invited participants don't support it", group_chat_room_creation_fails_if_invited_participants_dont_support_it), + TEST_NO_TAG("Group chat room creation successful if at least one invited participant supports it", group_chat_room_creation_successful_if_at_least_one_invited_participant_supports_it), + TEST_ONE_TAG("Migrate basic chat room to client group chat room", group_chat_room_migrate_from_basic_chat_room, "LeaksMemory"), + TEST_ONE_TAG("Migrate basic chat room to client group chat room failure", group_chat_room_migrate_from_basic_to_client_fail, "LeaksMemory"), + TEST_NO_TAG("Migrate basic chat room to client group chat room not needed", group_chat_donot_room_migrate_from_basic_chat_room), + TEST_NO_TAG("Send file", group_chat_room_send_file), + TEST_NO_TAG("Send file + text", group_chat_room_send_file_plus_text), + TEST_NO_TAG("Unique one-to-one chatroom", group_chat_room_unique_one_to_one_chat_room), + TEST_NO_TAG("Unique one-to-one chatroom recreated from message", group_chat_room_unique_one_to_one_chat_room_recreated_from_message), + TEST_ONE_TAG("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, "LeaksMemory"), + TEST_NO_TAG("Join one-to-one chat room with a new device", group_chat_room_join_one_to_one_chat_room_with_a_new_device), + TEST_NO_TAG("New unique one-to-one chatroom after both participants left", group_chat_room_new_unique_one_to_one_chat_room_after_both_participants_left), + TEST_NO_TAG("Unique one-to-one chatroom re-created from the party that deleted it, with inactive devices", group_chat_room_unique_one_to_one_chat_room_recreated_from_message_2), + TEST_NO_TAG("IMDN for group chat room", imdn_for_group_chat_room), + TEST_NO_TAG("Find one to one chat room", find_one_to_one_chat_room), + TEST_NO_TAG("New device after group chat room creation", group_chat_room_new_device_after_creation) }; test_suite_t group_chat_test_suite = { diff --git a/tester/liblinphone_tester.h b/tester/liblinphone_tester.h index 050462630..9824c2084 100644 --- a/tester/liblinphone_tester.h +++ b/tester/liblinphone_tester.h @@ -283,6 +283,7 @@ typedef struct _stats { int number_of_rtcp_generic_nack; int number_of_tmmbr_received; int last_tmmbr_value_received; + int tmmbr_received_from_cb; int number_of_participants_added; int number_of_participant_admin_statuses_changed; @@ -322,6 +323,7 @@ typedef struct _LinphoneCallTestParams { void liblinphone_tester_add_suites(void); void linphone_core_manager_init(LinphoneCoreManager *mgr, const char* rc_file, const char* phone_alias); +void linphone_core_manager_configure (LinphoneCoreManager *mgr); void linphone_core_manager_start(LinphoneCoreManager *mgr, bool_t check_for_proxies); LinphoneCoreManager* linphone_core_manager_create2(const char* rc_file, const char* phone_alias); LinphoneCoreManager* linphone_core_manager_create(const char* rc_file); @@ -329,7 +331,7 @@ LinphoneCoreManager* linphone_core_manager_new3(const char* rc_file, bool_t chec LinphoneCoreManager* linphone_core_manager_new2(const char* rc_file, bool_t check_for_proxies); LinphoneCoreManager* linphone_core_manager_new(const char* rc_file); void linphone_core_manager_stop(LinphoneCoreManager *mgr); -void linphone_core_manager_reinit(LinphoneCoreManager *mgr, bool_t check_for_proxies); +void linphone_core_manager_reinit(LinphoneCoreManager *mgr); void linphone_core_manager_restart(LinphoneCoreManager *mgr, bool_t check_for_proxies); void linphone_core_manager_uninit(LinphoneCoreManager *mgr); void linphone_core_manager_wait_for_stun_resolution(LinphoneCoreManager *mgr); diff --git a/tester/log_collection_tester.c b/tester/log_collection_tester.c index 6027e7d13..b81d43449 100644 --- a/tester/log_collection_tester.c +++ b/tester/log_collection_tester.c @@ -28,9 +28,14 @@ #include #endif +#ifdef __ANDROID__ +#include "android/api-level.h" +#endif /*getline is POSIX 2008, not available on many systems.*/ -#if (defined(__ANDROID__) && !defined(__LP64__)) || defined(_WIN32) || defined(__QNX__) + +/*It is declared since NDK14 unified headers, that can be detected by the presence of __ANDROID_API_O__ define*/ +#if (defined(__ANDROID__) && __ANDROID_API__ <= 16) || defined(_WIN32) || defined(__QNX__) /* This code is public domain -- Will Hartung 4/9/09 */ static ssize_t getline(char **lineptr, size_t *n, FILE *stream) { char *bufptr = NULL; diff --git a/tester/message_tester.c b/tester/message_tester.c index cc62e98c3..396f4b9c4 100644 --- a/tester/message_tester.c +++ b/tester/message_tester.c @@ -503,9 +503,13 @@ void transfer_message_base2(LinphoneCoreManager* marie, LinphoneCoreManager* pau } else { BC_ASSERT_TRUE(wait_for_until(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneMessageReceivedWithFile,1, 60000)); if (marie->stat.last_received_chat_message) { + LinphoneChatRoom *marie_room = linphone_core_get_chat_room(marie->lc, pauline->identity); + linphone_chat_room_mark_as_read(marie_room); + // We shoudln't get displayed IMDN until file has been downloaded + BC_ASSERT_FALSE(wait_for_until(pauline->lc,marie->lc,&pauline->stat.number_of_LinphoneMessageDisplayed,1, 5000)); + LinphoneChatMessage *recv_msg; if (download_from_history) { - LinphoneChatRoom *marie_room = linphone_core_get_chat_room(marie->lc, pauline->identity); msg_list = linphone_chat_room_get_history(marie_room,1); BC_ASSERT_PTR_NOT_NULL(msg_list); if (!msg_list) goto end; @@ -529,11 +533,13 @@ void transfer_message_base2(LinphoneCoreManager* marie, LinphoneCoreManager* pau belle_http_provider_set_recv_error(linphone_core_get_http_provider(marie->lc), -1); BC_ASSERT_TRUE(wait_for_until(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneMessageNotDelivered,1, 10000)); belle_http_provider_set_recv_error(linphone_core_get_http_provider(marie->lc), 0); + BC_ASSERT_FALSE(wait_for_until(pauline->lc,marie->lc,&pauline->stat.number_of_LinphoneMessageDisplayed,1, 5000)); } else { /* wait for a long time in case the DNS SRV resolution takes times - it should be immediate though */ if (BC_ASSERT_TRUE(wait_for_until(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneFileTransferDownloadSuccessful,1,55000))) { compare_files(send_filepath, receive_filepath); } + BC_ASSERT_TRUE(wait_for_until(pauline->lc,marie->lc,&pauline->stat.number_of_LinphoneMessageDisplayed,1, 5000)); } } BC_ASSERT_EQUAL(pauline->stat.number_of_LinphoneMessageInProgress,2, int, "%d"); //sent twice because of file transfer @@ -548,41 +554,48 @@ end: } void transfer_message_base(bool_t upload_error, bool_t download_error, bool_t use_file_body_handler_in_upload, - bool_t use_file_body_handler_in_download, bool_t download_from_history) { + bool_t use_file_body_handler_in_download, bool_t download_from_history, bool_t enable_imdn) { if (transport_supported(LinphoneTransportTls)) { LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_tcp_rc"); + + if (enable_imdn) { + lp_config_set_int(linphone_core_get_config(pauline->lc), "sip", "deliver_imdn", 1); + lp_config_set_int(linphone_core_get_config(marie->lc), "sip", "deliver_imdn", 1); + 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)); + } transfer_message_base2(marie,pauline,upload_error,download_error, use_file_body_handler_in_upload, use_file_body_handler_in_download, download_from_history); linphone_core_manager_destroy(pauline); linphone_core_manager_destroy(marie); } } static void transfer_message(void) { - transfer_message_base(FALSE, FALSE, FALSE, FALSE, FALSE); + transfer_message_base(FALSE, FALSE, FALSE, FALSE, FALSE, TRUE); } static void transfer_message_2(void) { - transfer_message_base(FALSE, FALSE, TRUE, FALSE, FALSE); + transfer_message_base(FALSE, FALSE, TRUE, FALSE, FALSE, TRUE); } static void transfer_message_3(void) { - transfer_message_base(FALSE, FALSE, FALSE, TRUE, FALSE); + transfer_message_base(FALSE, FALSE, FALSE, TRUE, FALSE, TRUE); } static void transfer_message_4(void) { - transfer_message_base(FALSE, FALSE, TRUE, TRUE, FALSE); + transfer_message_base(FALSE, FALSE, TRUE, TRUE, FALSE, TRUE); } static void transfer_message_from_history(void) { - transfer_message_base(FALSE, FALSE, TRUE, TRUE, TRUE); + transfer_message_base(FALSE, FALSE, TRUE, TRUE, TRUE, TRUE); } static void transfer_message_with_upload_io_error(void) { - transfer_message_base(TRUE, FALSE, FALSE, FALSE, FALSE); + transfer_message_base(TRUE, FALSE, FALSE, FALSE, FALSE, TRUE); } static void transfer_message_with_download_io_error(void) { - transfer_message_base(FALSE, TRUE, FALSE, FALSE, FALSE); + transfer_message_base(FALSE, TRUE, FALSE, FALSE, FALSE, TRUE); } static void transfer_message_upload_cancelled(void) { @@ -729,6 +742,52 @@ static void file_transfer_2_messages_simultaneously(void) { } } +static void file_transfer_external_body_url(bool_t use_file_body_handler_in_download) { + LinphoneCoreManager* marie = linphone_core_manager_new("marie_rc"); + LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_tcp_rc"); + LinphoneChatRoom* chat_room = linphone_core_get_chat_room(marie->lc, pauline->identity); + LinphoneChatMessage* msg = linphone_chat_room_create_message(chat_room, NULL); + LinphoneChatMessageCbs *cbs = linphone_chat_message_get_callbacks(msg); + char *receive_filepath = bc_tester_file("receive_file.dump"); + + linphone_chat_message_set_external_body_url(msg, "https://www.linphone.org/img/linphone-open-source-voip-projectX2.png"); + linphone_chat_message_cbs_set_msg_state_changed(cbs, liblinphone_tester_chat_message_msg_state_changed); + linphone_chat_room_send_chat_message(chat_room, msg); + linphone_chat_message_unref(msg); + + BC_ASSERT_TRUE(wait_for_until(pauline->lc, marie->lc, &pauline->stat.number_of_LinphoneMessageReceivedWithFile, 1, 60000)); + + if (pauline->stat.last_received_chat_message) { + LinphoneChatMessage *recv_msg = pauline->stat.last_received_chat_message; + cbs = linphone_chat_message_get_callbacks(recv_msg); + linphone_chat_message_cbs_set_msg_state_changed(cbs, liblinphone_tester_chat_message_msg_state_changed); + linphone_chat_message_cbs_set_file_transfer_recv(cbs, file_transfer_received); + linphone_chat_message_cbs_set_file_transfer_progress_indication(cbs, file_transfer_progress_indication); + if (use_file_body_handler_in_download) { + /* Remove any previously downloaded file */ + remove(receive_filepath); + linphone_chat_message_set_file_transfer_filepath(recv_msg, receive_filepath); + } + linphone_chat_message_download_file(recv_msg); + + /* wait for a long time in case the DNS SRV resolution takes times - it should be immediate though */ + BC_ASSERT_TRUE(wait_for_until(pauline->lc, marie->lc, &pauline->stat.number_of_LinphoneFileTransferDownloadSuccessful, 1, 55000)); + } + + remove(receive_filepath); + bc_free(receive_filepath); + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); +} + +static void file_transfer_using_external_body_url(void) { + file_transfer_external_body_url(FALSE); +} + +static void file_transfer_using_external_body_url_2(void) { + file_transfer_external_body_url(TRUE); +} + static void text_message_denied(void) { LinphoneCoreManager* marie = linphone_core_manager_new("marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_tcp_rc"); @@ -891,6 +950,7 @@ static void _is_composing_notification(bool_t lime_enabled) { LinphoneCoreManager* marie = linphone_core_manager_new("marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_tcp_rc"); + lp_config_set_int(linphone_core_get_config(marie->lc), "sip", "deliver_imdn", 1); if (lime_enabled) { if (enable_lime_for_message_test(marie, pauline) < 0) goto end; @@ -900,15 +960,42 @@ static void _is_composing_notification(bool_t lime_enabled) { marie_chat_room = linphone_core_get_chat_room(marie->lc, pauline->identity); linphone_core_get_chat_room(marie->lc, pauline->identity); /*make marie create the chatroom with pauline, which is necessary for receiving the is-composing*/ linphone_chat_room_compose(pauline_chat_room); - wait_for_until(pauline->lc, marie->lc, &dummy, 1, 1500); /*just to sleep while iterating*/ - linphone_chat_room_send_message(pauline_chat_room, "Composing a msg"); BC_ASSERT_TRUE(wait_for(pauline->lc, marie->lc, &marie->stat.number_of_LinphoneIsComposingActiveReceived, 1)); composing_addresses = linphone_chat_room_get_composing_addresses(marie_chat_room); BC_ASSERT_GREATER(bctbx_list_size(composing_addresses), 0, int, "%i"); if (bctbx_list_size(composing_addresses) > 0) { LinphoneAddress *addr = (LinphoneAddress *)bctbx_list_get_data(composing_addresses); - BC_ASSERT_STRING_EQUAL(linphone_address_as_string(addr), linphone_address_as_string(pauline->identity)); + char *address_string = linphone_address_as_string(addr); + char *pauline_address = linphone_address_as_string(pauline->identity); + BC_ASSERT_STRING_EQUAL(address_string, pauline_address); + bctbx_free(address_string); + bctbx_free(pauline_address); } + BC_ASSERT_TRUE(wait_for(pauline->lc, marie->lc, &marie->stat.number_of_LinphoneMessageReceived, 1)); + LinphoneChatMessage *is_composing_msg = marie->stat.last_received_chat_message; + if (BC_ASSERT_PTR_NOT_NULL(is_composing_msg)) { + const char *expires = linphone_chat_message_get_custom_header(is_composing_msg, "Expires"); + if (BC_ASSERT_PTR_NOT_NULL(expires)) + BC_ASSERT_STRING_EQUAL(expires, "0"); + + const char *priority = linphone_chat_message_get_custom_header(is_composing_msg, "Priority"); + if (BC_ASSERT_PTR_NOT_NULL(priority)) + BC_ASSERT_STRING_EQUAL(priority, "non-urgent"); + } + wait_for_until(pauline->lc, marie->lc, &dummy, 1, 1500); /*just to sleep while iterating*/ + BC_ASSERT_TRUE(wait_for(pauline->lc, marie->lc, &marie->stat.number_of_LinphoneIsComposingIdleReceived, 1)); + BC_ASSERT_TRUE(wait_for(pauline->lc, marie->lc, &marie->stat.number_of_LinphoneMessageReceived, 2)); + is_composing_msg = marie->stat.last_received_chat_message; + if (BC_ASSERT_PTR_NOT_NULL(is_composing_msg)) { + const char *expires = linphone_chat_message_get_custom_header(is_composing_msg, "Expires"); + if (BC_ASSERT_PTR_NOT_NULL(expires)) + BC_ASSERT_STRING_EQUAL(expires, "0"); + + const char *priority = linphone_chat_message_get_custom_header(is_composing_msg, "Priority"); + if (BC_ASSERT_PTR_NOT_NULL(priority)) + BC_ASSERT_STRING_EQUAL(priority, "non-urgent"); + } + linphone_chat_room_send_message(pauline_chat_room, "Composing a msg"); BC_ASSERT_TRUE(wait_for(pauline->lc, marie->lc, &marie->stat.number_of_LinphoneIsComposingIdleReceived, 2)); composing_addresses = linphone_chat_room_get_composing_addresses(marie_chat_room); BC_ASSERT_EQUAL(bctbx_list_size(composing_addresses), 0, int, "%i"); @@ -933,6 +1020,7 @@ static void is_composing_notification_with_lime(void) { static void _imdn_notifications(bool_t with_lime) { LinphoneCoreManager *marie = linphone_core_manager_new("marie_rc"); LinphoneCoreManager *pauline = linphone_core_manager_new("pauline_tcp_rc"); + lp_config_set_int(linphone_core_get_config(pauline->lc), "sip", "deliver_imdn", 1); LinphoneChatRoom *pauline_chat_room = linphone_core_get_chat_room(pauline->lc, marie->identity); LinphoneChatRoom *marie_chat_room; LinphoneChatMessage *sent_cm; @@ -959,8 +1047,22 @@ static void _imdn_notifications(bool_t with_lime) { BC_ASSERT_PTR_NOT_NULL(received_cm); if (received_cm != NULL) { BC_ASSERT_TRUE(wait_for(pauline->lc, marie->lc, &pauline->stat.number_of_LinphoneMessageDeliveredToUser, 1)); + BC_ASSERT_TRUE(wait_for(pauline->lc, marie->lc, &pauline->stat.number_of_LinphoneMessageReceived, 1)); + LinphoneChatMessage *imdn_message = pauline->stat.last_received_chat_message; + if (BC_ASSERT_PTR_NOT_NULL(imdn_message)) { + const char *priority = linphone_chat_message_get_custom_header(imdn_message, "Priority"); + if (BC_ASSERT_PTR_NOT_NULL(priority)) + BC_ASSERT_STRING_EQUAL(priority, "non-urgent"); + } linphone_chat_room_mark_as_read(marie_chat_room); /* This sends the display notification */ BC_ASSERT_TRUE(wait_for(pauline->lc, marie->lc, &pauline->stat.number_of_LinphoneMessageDisplayed, 1)); + BC_ASSERT_TRUE(wait_for(pauline->lc, marie->lc, &pauline->stat.number_of_LinphoneMessageReceived, 2)); + imdn_message = pauline->stat.last_received_chat_message; + if (BC_ASSERT_PTR_NOT_NULL(imdn_message)) { + const char *priority = linphone_chat_message_get_custom_header(imdn_message, "Priority"); + if (BC_ASSERT_PTR_NOT_NULL(priority)) + BC_ASSERT_STRING_EQUAL(priority, "non-urgent"); + } bctbx_list_free_with_data(history, (bctbx_list_free_func)linphone_chat_message_unref); } } @@ -1467,7 +1569,7 @@ static void lime_unit(void) { * Destination file is truncated if existing. * Return 0 on success, positive value on error. */ -int message_tester_copy_file(const char *from, const char *to) +static int message_tester_copy_file(const char *from, const char *to) { FILE *in, *out; char buf[256]; @@ -1544,6 +1646,8 @@ void crash_during_file_transfer(void) { /* Create a new core and check that the message stored in the saved database is in the not delivered state */ linphone_core_manager_restart(pauline, TRUE); + linphone_core_set_file_transfer_server(pauline->lc, "https://www.linphone.org:444/lft.php"); + //BC_ASSERT_TRUE(wait_for(pauline->lc, pauline->lc, &pauline->stat.number_of_LinphoneRegistrationOk, 1)); chat_room = linphone_core_get_chat_room(pauline->lc, marie->identity); @@ -1553,9 +1657,36 @@ void crash_during_file_transfer(void) { msg_list = linphone_chat_room_get_history(chat_room, 0); LinphoneChatMessage *sent_msg = (LinphoneChatMessage *)bctbx_list_get_data(msg_list); BC_ASSERT_EQUAL((int)linphone_chat_message_get_state(sent_msg), (int)LinphoneChatMessageStateNotDelivered, int, "%d"); + //resend + LinphoneChatMessageCbs *cbs = linphone_chat_message_get_callbacks(sent_msg); + linphone_chat_message_cbs_set_file_transfer_send(cbs, tester_file_transfer_send); + linphone_chat_message_cbs_set_msg_state_changed(cbs,liblinphone_tester_chat_message_msg_state_changed); + linphone_chat_message_cbs_set_file_transfer_progress_indication(cbs, file_transfer_progress_indication); + linphone_chat_message_send(sent_msg); + if (BC_ASSERT_TRUE(wait_for_until(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneMessageReceivedWithFile,1, 60000))) { + linphone_core_manager_stop(marie); + /* Create a new core and check that the message stored in the saved database is in the not delivered state */ + linphone_core_manager_restart(marie, TRUE); + LinphoneChatRoom *marie_room = linphone_core_get_chat_room(marie->lc, pauline->identity); + bctbx_list_t *msg_list_2 = linphone_chat_room_get_history(marie_room,1); + BC_ASSERT_PTR_NOT_NULL(msg_list_2); + LinphoneChatMessage *recv_msg = (LinphoneChatMessage *)msg_list_2->data; + LinphoneChatMessageCbs *cbs = linphone_chat_message_get_callbacks(recv_msg); + linphone_chat_message_cbs_set_msg_state_changed(cbs, liblinphone_tester_chat_message_msg_state_changed); + linphone_chat_message_cbs_set_file_transfer_recv(cbs, file_transfer_received); + linphone_chat_message_cbs_set_file_transfer_progress_indication(cbs, file_transfer_progress_indication); + linphone_chat_message_download_file(recv_msg); + /* wait for a long time in case the DNS SRV resolution takes times - it should be immediate though */ + if (BC_ASSERT_TRUE(wait_for_until(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneFileTransferDownloadSuccessful,1,55000))) { + BC_ASSERT_PTR_NULL(linphone_chat_message_get_external_body_url(recv_msg)); + } + bctbx_list_free_with_data(msg_list_2, (bctbx_list_free_func)linphone_chat_message_unref); + } } + bctbx_list_free_with_data(msg_list, (bctbx_list_free_func)linphone_chat_message_unref); + linphone_core_manager_destroy(pauline); linphone_core_manager_destroy(marie); @@ -1709,6 +1840,7 @@ static void real_time_text(bool_t audio_stream_enabled, bool_t srtp_enabled, boo } linphone_chat_room_send_chat_message(pauline_chat_room, rtt_message); BC_ASSERT_TRUE(wait_for(pauline->lc, marie->lc, &marie->stat.number_of_LinphoneMessageReceived, 1)); + linphone_chat_message_unref(rtt_message); if (sql_storage) { bctbx_list_t *marie_messages = linphone_chat_room_get_history(marie_chat_room, 0); @@ -1827,6 +1959,8 @@ static void real_time_text_conversation(void) { } } + linphone_chat_message_unref(pauline_rtt_message); + linphone_chat_message_unref(marie_rtt_message); reset_counters(&pauline->stat); reset_counters(&marie->stat); pauline_rtt_message = linphone_chat_room_create_message(pauline_chat_room,NULL); @@ -1862,6 +1996,8 @@ static void real_time_text_conversation(void) { BC_ASSERT_STRING_EQUAL(linphone_chat_message_get_text(msg), message2_2); } } + linphone_chat_message_unref(pauline_rtt_message); + linphone_chat_message_unref(marie_rtt_message); } end_call(marie, pauline); linphone_call_params_unref(marie_params); @@ -1978,7 +2114,14 @@ static void real_time_text_message_accented_chars(void) { linphone_chat_room_send_chat_message(pauline_chat_room, rtt_message); BC_ASSERT_TRUE(wait_for(pauline->lc, marie->lc, &marie->stat.number_of_LinphoneMessageReceived, 1)); - BC_ASSERT_EQUAL(strcmp(linphone_chat_message_get_text(marie->stat.last_received_chat_message), "ãæçéîøùÿ"), 0, int, "%i"); + BC_ASSERT_PTR_NOT_NULL(marie->stat.last_received_chat_message); + if (marie->stat.last_received_chat_message) { + const char *text = linphone_chat_message_get_text(marie->stat.last_received_chat_message); + BC_ASSERT_PTR_NOT_NULL(text); + if (text) + BC_ASSERT_STRING_EQUAL(text, "ãæçéîøùÿ"); + } + linphone_chat_message_unref(rtt_message); } end_call(marie, pauline); } @@ -2031,6 +2174,7 @@ static void real_time_text_copy_paste(void) { } linphone_chat_room_send_chat_message(pauline_chat_room, rtt_message); BC_ASSERT_TRUE(wait_for(pauline->lc, marie->lc, &marie->stat.number_of_LinphoneMessageReceived, 1)); + linphone_chat_message_unref(rtt_message); } end_call(marie, pauline); @@ -2044,6 +2188,10 @@ void file_transfer_with_http_proxy(void) { if (transport_supported(LinphoneTransportTls)) { LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_tcp_rc"); + lp_config_set_int(linphone_core_get_config(pauline->lc), "sip", "deliver_imdn", 1); + lp_config_set_int(linphone_core_get_config(marie->lc), "sip", "deliver_imdn", 1); + 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_core_set_http_proxy_host(marie->lc, "sip.linphone.org"); transfer_message_base2(marie,pauline,FALSE,FALSE,FALSE,FALSE,FALSE); linphone_core_manager_destroy(pauline); @@ -2269,6 +2417,72 @@ void im_encryption_engine_b64_async(void) { im_encryption_engine_b64_base(TRUE); } +void unread_message_count(void) { + LinphoneCoreManager* marie = linphone_core_manager_new("marie_rc"); + LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_tcp_rc"); + + text_message_base(marie, pauline); + + BC_ASSERT_PTR_NOT_NULL(marie->stat.last_received_chat_message); + if (marie->stat.last_received_chat_message != NULL) { + LinphoneChatRoom *marie_room = linphone_chat_message_get_chat_room(marie->stat.last_received_chat_message); + BC_ASSERT_EQUAL(1, linphone_chat_room_get_unread_messages_count(marie_room), int, "%d"); + linphone_chat_room_mark_as_read(marie_room); + BC_ASSERT_EQUAL(0, linphone_chat_room_get_unread_messages_count(marie_room), int, "%d"); + } + + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); +} + +static void message_received_callback(LinphoneCore *lc, LinphoneChatRoom *room, LinphoneChatMessage* msg) { + BC_ASSERT_PTR_NOT_NULL(room); + BC_ASSERT_EQUAL(1, linphone_chat_room_get_unread_messages_count(room), int, "%d"); + BC_ASSERT_PTR_NOT_NULL(msg); + if (room != NULL) { + linphone_chat_room_mark_as_read(room); + } + BC_ASSERT_EQUAL(0, linphone_chat_room_get_unread_messages_count(room), int, "%d"); +} + +void unread_message_count_callback(void) { + LinphoneCoreManager* marie = linphone_core_manager_new("marie_rc"); + LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_tcp_rc"); + int dummy = 0; + + LinphoneCoreCbs *cbs = linphone_factory_create_core_cbs(linphone_factory_get()); + linphone_core_cbs_set_message_received(cbs, message_received_callback); + linphone_core_add_callbacks(marie->lc, cbs); + + text_message_base(marie, pauline); + + wait_for_until(pauline->lc, marie->lc, &dummy, 1, 5000); + + linphone_core_cbs_unref(cbs); + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); +} + +static void migration_from_messages_db (void) { + LinphoneCoreManager* marie = linphone_core_manager_new("marie_rc"); + char *src_db = bc_tester_res("db/messages.db"); + char *tmp_db = bc_tester_file("tmp.db"); + + BC_ASSERT_EQUAL(message_tester_copy_file(src_db, tmp_db), 0, int, "%d"); + + // The messages.db has 10000 dummy messages with the very first DB scheme. + // This will test the migration procedure + linphone_core_set_chat_database_path(marie->lc, tmp_db); + + const bctbx_list_t *chatrooms = linphone_core_get_chat_rooms(marie->lc); + BC_ASSERT(bctbx_list_size(chatrooms) > 0); + + linphone_core_manager_destroy(marie); + remove(tmp_db); + bctbx_free(src_db); + bctbx_free(tmp_db); +} + test_t message_tests[] = { TEST_NO_TAG("Text message", text_message), TEST_NO_TAG("Text message with credentials from auth callback", text_message_with_credential_from_auth_callback), @@ -2287,11 +2501,15 @@ test_t message_tests[] = { TEST_NO_TAG("Transfer message upload cancelled", transfer_message_upload_cancelled), TEST_NO_TAG("Transfer message download cancelled", transfer_message_download_cancelled), TEST_NO_TAG("Transfer 2 messages simultaneously", file_transfer_2_messages_simultaneously), + TEST_NO_TAG("Transfer using external body URL", file_transfer_using_external_body_url), + TEST_NO_TAG("Transfer using external body URL 2", file_transfer_using_external_body_url_2), TEST_NO_TAG("Text message denied", text_message_denied), TEST_NO_TAG("IsComposing notification", is_composing_notification), TEST_NO_TAG("IMDN notifications", imdn_notifications), TEST_NO_TAG("IM notification policy", im_notification_policy), #ifdef SQLITE_STORAGE_ENABLED + TEST_NO_TAG("Unread message count", unread_message_count), + TEST_NO_TAG("Unread message count in callback", unread_message_count_callback), TEST_ONE_TAG("IsComposing notification lime", is_composing_notification_with_lime, "LIME"), TEST_ONE_TAG("IMDN notifications with lime", imdn_notifications_with_lime, "LIME"), TEST_ONE_TAG("IM notification policy with lime", im_notification_policy_with_lime, "LIME"), @@ -2343,6 +2561,7 @@ test_t message_tests[] = { TEST_NO_TAG("Crash during file transfer", crash_during_file_transfer), TEST_NO_TAG("Text status after destroying chat room", text_status_after_destroying_chat_room), TEST_NO_TAG("Transfer io error after destroying chatroom", file_transfer_io_error_after_destroying_chatroom), + TEST_NO_TAG("Migration from messages db", migration_from_messages_db) }; static int message_tester_before_suite(void) { diff --git a/tester/multipart-tester.cpp b/tester/multipart-tester.cpp index ad5d731f3..3fdd9073a 100644 --- a/tester/multipart-tester.cpp +++ b/tester/multipart-tester.cpp @@ -50,7 +50,7 @@ static void chat_message_multipart_modifier_base(bool first_file_transfer, bool if (first_file_transfer) { char *send_filepath = bc_tester_res("sounds/sintel_trailer_opus_h264.mkv"); FileContent *content = new FileContent(); - content->setContentType("video/mkv"); + content->setContentType(ContentType("video/mkv")); content->setFilePath(send_filepath); content->setFileName("sintel_trailer_opus_h264.mkv"); marieMessage->addContent(*content); @@ -65,7 +65,7 @@ static void chat_message_multipart_modifier_base(bool first_file_transfer, bool if (second_file_transfer) { char *send_filepath = bc_tester_res("vcards/vcards.vcf"); FileContent *content = new FileContent(); - content->setContentType("file/vcf"); + content->setContentType(ContentType("file/vcf")); content->setFilePath(send_filepath); content->setFileName("vcards.vcf"); marieMessage->addContent(*content); diff --git a/tester/offeranswer_tester.c b/tester/offeranswer_tester.c index b524381d5..5a70a42ba 100644 --- a/tester/offeranswer_tester.c +++ b/tester/offeranswer_tester.c @@ -105,6 +105,42 @@ static void simple_call_with_different_codec_mappings(void) { linphone_core_manager_destroy(pauline); } +static void simple_call_with_fmtps(void){ + LinphoneCoreManager* marie; + LinphoneCoreManager* pauline; + LinphoneCall *pauline_call; + + marie = linphone_core_manager_new( "marie_rc"); + pauline = linphone_core_manager_new( "pauline_tcp_rc"); + + disable_all_audio_codecs_except_one(marie->lc,"pcmu",-1); + disable_all_audio_codecs_except_one(pauline->lc,"pcmu",-1); + + /*marie set a fantasy fmtp to PCMU*/ + linphone_payload_type_set_recv_fmtp(linphone_core_get_payload_type(marie->lc, "PCMU", 8000, -1), "parles-plus-fort=1"); + + BC_ASSERT_TRUE(call(marie,pauline)); + pauline_call=linphone_core_get_current_call(pauline->lc); + BC_ASSERT_PTR_NOT_NULL(pauline_call); + if (pauline_call){ + LinphonePayloadType *pt = linphone_call_params_get_used_audio_payload_type(linphone_call_get_current_params(pauline_call)); + BC_ASSERT_PTR_NOT_NULL(pt); + if (pt){ + BC_ASSERT_STRING_EQUAL(linphone_payload_type_get_send_fmtp(pt),"parles-plus-fort=1"); + } + pt = linphone_call_params_get_used_audio_payload_type(linphone_call_get_current_params(linphone_core_get_current_call(marie->lc))); + BC_ASSERT_PTR_NOT_NULL(pt); + if (pt){ + ms_message("send_fmtp=%s, recv_fmtp=%s", linphone_payload_type_get_send_fmtp(pt), linphone_payload_type_get_recv_fmtp(pt)); + BC_ASSERT_STRING_EQUAL(linphone_payload_type_get_recv_fmtp(pt),"parles-plus-fort=1"); + } + } + + end_call(marie,pauline); + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); +} + static void call_failed_because_of_codecs(void) { LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_tcp_rc"); @@ -492,6 +528,7 @@ static test_t offeranswer_tests[] = { TEST_NO_TAG("Start with no config", start_with_no_config), TEST_NO_TAG("Call failed because of codecs", call_failed_because_of_codecs), TEST_NO_TAG("Simple call with different codec mappings", simple_call_with_different_codec_mappings), + TEST_NO_TAG("Simple call with fmtps", simple_call_with_fmtps), TEST_NO_TAG("AVP to AVP call", avp_to_avp_call), TEST_NO_TAG("AVP to AVPF call", avp_to_avpf_call), TEST_NO_TAG("AVP to SAVP call", avp_to_savp_call), diff --git a/tester/presence_server_tester.c b/tester/presence_server_tester.c index 5f9f0bf7d..62bb8a364 100644 --- a/tester/presence_server_tester.c +++ b/tester/presence_server_tester.c @@ -1571,8 +1571,157 @@ static void extended_notify_sub_unsub_sub2(void) { bctbx_list_free(lcs); } +static void simple_publish_with_expire(int expires) { + LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); + LinphoneProxyConfig* proxy; + LinphonePresenceModel* presence; + LinphoneCoreCbs *cbs = linphone_factory_create_core_cbs(linphone_factory_get()); + + linphone_core_cbs_set_publish_state_changed(cbs, linphone_publish_state_changed); + _linphone_core_add_callbacks(marie->lc, cbs, TRUE); + linphone_core_cbs_unref(cbs); + + proxy = linphone_core_get_default_proxy_config(marie->lc); + linphone_proxy_config_edit(proxy); + if (expires > 0) { + linphone_proxy_config_set_publish_expires(proxy,expires); + } + linphone_proxy_config_enable_publish(proxy,TRUE); + linphone_proxy_config_done(proxy); + + BC_ASSERT_TRUE(wait_for(marie->lc,marie->lc,&marie->stat.number_of_LinphonePublishProgress,1)); + BC_ASSERT_TRUE(wait_for(marie->lc,marie->lc,&marie->stat.number_of_LinphonePublishOk,1)); + + presence = linphone_presence_model_new(); + linphone_presence_model_set_basic_status(presence, LinphonePresenceBasicStatusClosed); + linphone_core_set_presence_model(marie->lc,presence); + linphone_presence_model_unref(presence); + + BC_ASSERT_TRUE(wait_for(marie->lc,marie->lc,&marie->stat.number_of_LinphonePublishProgress,2)); + BC_ASSERT_TRUE(wait_for(marie->lc,marie->lc,&marie->stat.number_of_LinphonePublishOk,2)); + + linphone_proxy_config_edit(proxy); + linphone_proxy_config_done(proxy); + /*make sure no publish is sent*/ + BC_ASSERT_FALSE(wait_for_until(marie->lc,marie->lc,&marie->stat.number_of_LinphonePublishProgress,3,2000)); + + linphone_proxy_config_edit(proxy); + linphone_proxy_config_enable_publish(proxy,FALSE); + linphone_proxy_config_done(proxy); + + + /*fixme PUBLISH state machine is too simple, clear state should only be propagated at API level when 200ok is received*/ + /*BC_ASSERT_TRUE(wait_for(marie->lc,marie->lc,&marie->stat.number_of_LinphonePublishProgress,3));*/ + wait_for_until(marie->lc,marie->lc,NULL,0,2000); + BC_ASSERT_TRUE(wait_for(marie->lc,marie->lc,&marie->stat.number_of_LinphonePublishCleared,1)); + + linphone_proxy_config_edit(proxy); + linphone_proxy_config_enable_publish(proxy,TRUE); + linphone_proxy_config_done(proxy); + BC_ASSERT_TRUE(wait_for(marie->lc,marie->lc,&marie->stat.number_of_LinphonePublishProgress,3)); + BC_ASSERT_TRUE(wait_for(marie->lc,marie->lc,&marie->stat.number_of_LinphonePublishOk,3)); + + linphone_proxy_config_edit(proxy); + linphone_proxy_config_set_publish_expires(proxy, linphone_proxy_config_get_publish_expires(proxy)+1); + linphone_proxy_config_done(proxy); + BC_ASSERT_TRUE(wait_for(marie->lc,marie->lc,&marie->stat.number_of_LinphonePublishProgress,4)); + BC_ASSERT_TRUE(wait_for(marie->lc,marie->lc,&marie->stat.number_of_LinphonePublishOk,4)); + + linphone_core_manager_stop(marie); + BC_ASSERT_EQUAL(marie->stat.number_of_LinphonePublishCleared,3,int,"%i"); /*yes it is 3 because when we change the expires, a new LinphoneEvent is created*/ + BC_ASSERT_EQUAL(marie->stat.number_of_LinphonePublishOk,4,int,"%i"); + linphone_core_manager_destroy(marie); +} + +static void simple_publish(void) { + simple_publish_with_expire(-1); +} + +static void publish_with_expires(void) { + simple_publish_with_expire(2); +} + +static void publish_with_dual_identity(void) { + LinphoneCoreManager* pauline = linphone_core_manager_new("multi_account_rc"); + const bctbx_list_t* proxies; + LinphoneCoreCbs *cbs = linphone_factory_create_core_cbs(linphone_factory_get()); + + linphone_core_cbs_set_publish_state_changed(cbs, linphone_publish_state_changed); + _linphone_core_add_callbacks(pauline->lc, cbs, TRUE); + linphone_core_cbs_unref(cbs); + + for (proxies = linphone_core_get_proxy_config_list(pauline->lc); proxies!=NULL; proxies = proxies->next) { + LinphoneProxyConfig *proxy = (LinphoneProxyConfig *) proxies->data; + linphone_proxy_config_edit(proxy); + linphone_proxy_config_enable_publish(proxy,TRUE); + linphone_proxy_config_done(proxy); + } + + BC_ASSERT_TRUE(wait_for(pauline->lc,pauline->lc,&pauline->stat.number_of_LinphonePublishProgress,4)); + BC_ASSERT_TRUE(wait_for(pauline->lc,pauline->lc,&pauline->stat.number_of_LinphonePublishOk,4)); + + linphone_core_manager_stop(pauline); + BC_ASSERT_EQUAL(pauline->stat.number_of_LinphonePublishCleared,4,int,"%i"); + BC_ASSERT_EQUAL(pauline->stat.number_of_LinphonePublishOk,4,int,"%i"); + linphone_core_manager_destroy(pauline); + +} +static void publish_with_network_state_changes(void) { + LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); + LinphoneCoreManager* pauline = linphone_core_manager_new(transport_supported(LinphoneTransportTls) ? "pauline_rc" : "pauline_tcp_rc"); + LinphoneFriend* marie_as_friend = linphone_core_create_friend_with_address(pauline->lc, get_identity(marie)); + + LinphoneProxyConfig* proxy; + LinphoneCoreCbs *cbs = linphone_factory_create_core_cbs(linphone_factory_get()); + + linphone_core_cbs_set_publish_state_changed(cbs, linphone_publish_state_changed); + _linphone_core_add_callbacks(marie->lc, cbs,TRUE); + linphone_core_cbs_unref(cbs); + + linphone_core_set_user_agent(marie->lc, "full-presence-support", NULL); + linphone_core_set_user_agent(marie->lc, "full-presence-support-bypass", NULL); + + proxy = linphone_core_get_default_proxy_config(marie->lc); + linphone_proxy_config_edit(proxy); + linphone_proxy_config_enable_publish(proxy,TRUE); + linphone_proxy_config_done(proxy); + + BC_ASSERT_TRUE(wait_for(marie->lc,marie->lc,&marie->stat.number_of_LinphonePublishProgress,1)); + BC_ASSERT_TRUE(wait_for(marie->lc,marie->lc,&marie->stat.number_of_LinphonePublishOk,1)); + + linphone_core_set_network_reachable(marie->lc, FALSE); + BC_ASSERT_TRUE(wait_for(marie->lc,marie->lc,&marie->stat.number_of_LinphoneRegistrationNone,1)); + BC_ASSERT_FALSE(wait_for_until(marie->lc,marie->lc,&marie->stat.number_of_LinphonePublishProgress,2,1000)); + BC_ASSERT_EQUAL(marie->stat.number_of_LinphonePublishOk,1,int,"%i"); + BC_ASSERT_EQUAL(marie->stat.number_of_LinphonePublishError,0,int,"%i"); + + linphone_core_set_network_reachable(marie->lc, TRUE); + BC_ASSERT_TRUE(wait_for(marie->lc,marie->lc,&marie->stat.number_of_LinphonePublishProgress,2)); + BC_ASSERT_TRUE(wait_for(marie->lc,marie->lc,&marie->stat.number_of_LinphonePublishOk,2)); + + + + linphone_core_manager_stop(marie); + BC_ASSERT_EQUAL(marie->stat.number_of_LinphonePublishCleared,1,int,"%i"); /*yes it is 3 because when we change the expires, a new LinphoneEvent is created*/ + BC_ASSERT_EQUAL(marie->stat.number_of_LinphonePublishOk,2,int,"%i"); + linphone_core_manager_destroy(marie); + + /*make sure there is no remaining publish caused by network failure*/ + linphone_core_set_user_agent(pauline->lc, "full-presence-support", NULL); + linphone_core_set_user_agent(pauline->lc, "full-presence-support-bypass", NULL); + linphone_core_add_friend(pauline->lc, marie_as_friend); + + BC_ASSERT_TRUE(wait_for(pauline->lc,pauline->lc,&pauline->stat.number_of_LinphonePresenceActivityAway,1)); + linphone_friend_unref(marie_as_friend); + linphone_core_manager_destroy(pauline); + +} test_t presence_server_tests[] = { + TEST_NO_TAG("Simple Publish", simple_publish), + TEST_NO_TAG("Publish with 2 identities", publish_with_dual_identity), + TEST_NO_TAG("Simple Publish with expires", publish_with_expires), + TEST_ONE_TAG("Publish with network state changes", publish_with_network_state_changes, "presence"), TEST_NO_TAG("Simple", simple), TEST_NO_TAG("Fast activity change", fast_activity_change), TEST_NO_TAG("Forked subscribe with late publish", test_forked_subscribe_notify_publish), diff --git a/tester/presence_tester.c b/tester/presence_tester.c index e53db19ed..2eb4c931d 100644 --- a/tester/presence_tester.c +++ b/tester/presence_tester.c @@ -142,101 +142,6 @@ void notify_presence_received_for_uri_or_tel(LinphoneCore *lc, LinphoneFriend *l counters->number_of_NotifyPresenceReceivedForUriOrTel++; } -static void simple_publish_with_expire(int expires) { - LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); - LinphoneProxyConfig* proxy; - LinphonePresenceModel* presence; - LinphoneCoreCbs *cbs = linphone_factory_create_core_cbs(linphone_factory_get()); - - linphone_core_cbs_set_publish_state_changed(cbs, linphone_publish_state_changed); - _linphone_core_add_callbacks(marie->lc, cbs, TRUE); - linphone_core_cbs_unref(cbs); - - proxy = linphone_core_get_default_proxy_config(marie->lc); - linphone_proxy_config_edit(proxy); - if (expires > 0) { - linphone_proxy_config_set_publish_expires(proxy,expires); - } - linphone_proxy_config_enable_publish(proxy,TRUE); - linphone_proxy_config_done(proxy); - - BC_ASSERT_TRUE(wait_for(marie->lc,marie->lc,&marie->stat.number_of_LinphonePublishProgress,1)); - BC_ASSERT_TRUE(wait_for(marie->lc,marie->lc,&marie->stat.number_of_LinphonePublishOk,1)); - - presence = linphone_presence_model_new(); - linphone_presence_model_set_basic_status(presence, LinphonePresenceBasicStatusClosed); - linphone_core_set_presence_model(marie->lc,presence); - linphone_presence_model_unref(presence); - - BC_ASSERT_TRUE(wait_for(marie->lc,marie->lc,&marie->stat.number_of_LinphonePublishProgress,2)); - BC_ASSERT_TRUE(wait_for(marie->lc,marie->lc,&marie->stat.number_of_LinphonePublishOk,2)); - - linphone_proxy_config_edit(proxy); - linphone_proxy_config_done(proxy); - /*make sure no publish is sent*/ - BC_ASSERT_FALSE(wait_for_until(marie->lc,marie->lc,&marie->stat.number_of_LinphonePublishProgress,3,2000)); - - linphone_proxy_config_edit(proxy); - linphone_proxy_config_enable_publish(proxy,FALSE); - linphone_proxy_config_done(proxy); - - - /*fixme PUBLISH state machine is too simple, clear state should only be propagated at API level when 200ok is received*/ - /*BC_ASSERT_TRUE(wait_for(marie->lc,marie->lc,&marie->stat.number_of_LinphonePublishProgress,3));*/ - wait_for_until(marie->lc,marie->lc,NULL,0,2000); - BC_ASSERT_TRUE(wait_for(marie->lc,marie->lc,&marie->stat.number_of_LinphonePublishCleared,1)); - - linphone_proxy_config_edit(proxy); - linphone_proxy_config_enable_publish(proxy,TRUE); - linphone_proxy_config_done(proxy); - BC_ASSERT_TRUE(wait_for(marie->lc,marie->lc,&marie->stat.number_of_LinphonePublishProgress,3)); - BC_ASSERT_TRUE(wait_for(marie->lc,marie->lc,&marie->stat.number_of_LinphonePublishOk,3)); - - linphone_proxy_config_edit(proxy); - linphone_proxy_config_set_publish_expires(proxy, linphone_proxy_config_get_publish_expires(proxy)+1); - linphone_proxy_config_done(proxy); - BC_ASSERT_TRUE(wait_for(marie->lc,marie->lc,&marie->stat.number_of_LinphonePublishProgress,4)); - BC_ASSERT_TRUE(wait_for(marie->lc,marie->lc,&marie->stat.number_of_LinphonePublishOk,4)); - - linphone_core_manager_stop(marie); - BC_ASSERT_EQUAL(marie->stat.number_of_LinphonePublishCleared,3,int,"%i"); /*yes it is 3 because when we change the expires, a new LinphoneEvent is created*/ - BC_ASSERT_EQUAL(marie->stat.number_of_LinphonePublishOk,4,int,"%i"); - linphone_core_manager_destroy(marie); -} - -static void simple_publish(void) { - simple_publish_with_expire(-1); -} - -static void publish_with_expires(void) { - simple_publish_with_expire(2); -} - -static void publish_with_dual_identity(void) { - LinphoneCoreManager* pauline = linphone_core_manager_new("multi_account_rc"); - const bctbx_list_t* proxies; - LinphoneCoreCbs *cbs = linphone_factory_create_core_cbs(linphone_factory_get()); - - linphone_core_cbs_set_publish_state_changed(cbs, linphone_publish_state_changed); - _linphone_core_add_callbacks(pauline->lc, cbs, TRUE); - linphone_core_cbs_unref(cbs); - - for (proxies = linphone_core_get_proxy_config_list(pauline->lc); proxies!=NULL; proxies = proxies->next) { - LinphoneProxyConfig *proxy = (LinphoneProxyConfig *) proxies->data; - linphone_proxy_config_edit(proxy); - linphone_proxy_config_enable_publish(proxy,TRUE); - linphone_proxy_config_done(proxy); - } - - BC_ASSERT_TRUE(wait_for(pauline->lc,pauline->lc,&pauline->stat.number_of_LinphonePublishProgress,4)); - BC_ASSERT_TRUE(wait_for(pauline->lc,pauline->lc,&pauline->stat.number_of_LinphonePublishOk,4)); - - linphone_core_manager_stop(pauline); - BC_ASSERT_EQUAL(pauline->stat.number_of_LinphonePublishCleared,4,int,"%i"); - BC_ASSERT_EQUAL(pauline->stat.number_of_LinphonePublishOk,4,int,"%i"); - linphone_core_manager_destroy(pauline); - -} static bool_t subscribe_to_callee_presence(LinphoneCoreManager* caller_mgr,LinphoneCoreManager* callee_mgr) { stats initial_caller=caller_mgr->stat; stats initial_callee=callee_mgr->stat; @@ -306,40 +211,6 @@ static void subscribe_failure_handle_by_app(void) { linphone_core_manager_destroy(pauline); } -static void publish_with_network_state_changes(void) { - LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); - LinphoneProxyConfig* proxy; - LinphoneCoreCbs *cbs = linphone_factory_create_core_cbs(linphone_factory_get()); - - linphone_core_cbs_set_publish_state_changed(cbs, linphone_publish_state_changed); - _linphone_core_add_callbacks(marie->lc, cbs,TRUE); - linphone_core_cbs_unref(cbs); - - proxy = linphone_core_get_default_proxy_config(marie->lc); - linphone_proxy_config_edit(proxy); - linphone_proxy_config_enable_publish(proxy,TRUE); - linphone_proxy_config_done(proxy); - - BC_ASSERT_TRUE(wait_for(marie->lc,marie->lc,&marie->stat.number_of_LinphonePublishProgress,1)); - BC_ASSERT_TRUE(wait_for(marie->lc,marie->lc,&marie->stat.number_of_LinphonePublishOk,1)); - - linphone_core_set_network_reachable(marie->lc, FALSE); - BC_ASSERT_TRUE(wait_for(marie->lc,marie->lc,&marie->stat.number_of_LinphoneRegistrationNone,1)); - BC_ASSERT_FALSE(wait_for_until(marie->lc,marie->lc,&marie->stat.number_of_LinphonePublishProgress,2,1000)); - BC_ASSERT_EQUAL(marie->stat.number_of_LinphonePublishOk,1,int,"%i"); - BC_ASSERT_EQUAL(marie->stat.number_of_LinphonePublishError,0,int,"%i"); - - linphone_core_set_network_reachable(marie->lc, TRUE); - BC_ASSERT_TRUE(wait_for(marie->lc,marie->lc,&marie->stat.number_of_LinphonePublishProgress,2)); - BC_ASSERT_TRUE(wait_for(marie->lc,marie->lc,&marie->stat.number_of_LinphonePublishOk,2)); - - - linphone_core_manager_stop(marie); - BC_ASSERT_EQUAL(marie->stat.number_of_LinphonePublishCleared,1,int,"%i"); /*yes it is 3 because when we change the expires, a new LinphoneEvent is created*/ - BC_ASSERT_EQUAL(marie->stat.number_of_LinphonePublishOk,2,int,"%i"); - linphone_core_manager_destroy(marie); -} - static void simple_subscribe(void) { LinphoneCoreManager* marie = presence_linphone_core_manager_new("marie"); LinphoneCoreManager* pauline = presence_linphone_core_manager_new("pauline"); @@ -640,14 +511,10 @@ test_t presence_tests[] = { TEST_ONE_TAG("Simple Subscribe", simple_subscribe,"presence"), TEST_ONE_TAG("Simple Subscribe with early NOTIFY", simple_subscribe_with_early_notify,"presence"), TEST_NO_TAG("Simple Subscribe with friend from rc", simple_subscribe_with_friend_from_rc), - TEST_NO_TAG("Simple Publish", simple_publish), - TEST_NO_TAG("Publish with 2 identities", publish_with_dual_identity), - TEST_NO_TAG("Simple Publish with expires", publish_with_expires), - TEST_ONE_TAG("Publish with network state changes", publish_with_network_state_changes, "presence"), /*TEST_ONE_TAG("Call with presence", call_with_presence, "LeaksMemory"),*/ TEST_NO_TAG("Unsubscribe while subscribing", unsubscribe_while_subscribing), TEST_NO_TAG("Presence information", presence_information), - TEST_NO_TAG("App managed presence failure", subscribe_failure_handle_by_app), + TEST_ONE_TAG("App managed presence failure", subscribe_failure_handle_by_app,"presence"), TEST_NO_TAG("Presence SUBSCRIBE forked", subscribe_presence_forked), TEST_NO_TAG("Presence SUBSCRIBE expired", subscribe_presence_expired), }; diff --git a/tester/proxy_config_tester.c b/tester/proxy_config_tester.c index a2bf63066..a07c0217f 100644 --- a/tester/proxy_config_tester.c +++ b/tester/proxy_config_tester.c @@ -243,12 +243,36 @@ static void load_dynamic_proxy_config(void) { } +static void single_route(void) { + LinphoneCoreManager *marie = linphone_core_manager_new("marie_rc"); + LinphoneProxyConfig *marie_cfg = linphone_core_get_default_proxy_config(marie->lc); + BC_ASSERT_PTR_NOT_NULL(marie_cfg); + + const bctbx_list_t *routes = linphone_proxy_config_get_routes(marie_cfg); + BC_ASSERT_PTR_NOT_NULL(routes); + BC_ASSERT_EQUAL(bctbx_list_size(routes), 1, int, "%d"); + const char *route = (const char *)bctbx_list_get_data(routes); + BC_ASSERT_STRING_EQUAL(linphone_proxy_config_get_route(marie_cfg), "sip:sip.example.org;transport=tcp;lr"); + BC_ASSERT_STRING_EQUAL(route, "sip:sip.example.org;transport=tcp;lr"); + + linphone_proxy_config_set_route(marie_cfg, "sip.linphone.org"); + routes = linphone_proxy_config_get_routes(marie_cfg); + BC_ASSERT_PTR_NOT_NULL(routes); + BC_ASSERT_EQUAL(bctbx_list_size(routes), 1, int, "%d"); + route = (const char *)bctbx_list_get_data(routes); + BC_ASSERT_STRING_EQUAL(linphone_proxy_config_get_route(marie_cfg), "sip:sip.linphone.org"); + BC_ASSERT_STRING_EQUAL(route, "sip:sip.linphone.org"); + + linphone_core_manager_destroy(marie); +} + test_t proxy_config_tests[] = { TEST_NO_TAG("Phone normalization without proxy", phone_normalization_without_proxy), TEST_NO_TAG("Phone normalization with proxy", phone_normalization_with_proxy), TEST_NO_TAG("Phone normalization with dial escape plus", phone_normalization_with_dial_escape_plus), TEST_NO_TAG("SIP URI normalization", sip_uri_normalization), - TEST_NO_TAG("Load new default value for proxy config", load_dynamic_proxy_config) + TEST_NO_TAG("Load new default value for proxy config", load_dynamic_proxy_config), + TEST_NO_TAG("Single route", single_route) }; test_suite_t proxy_config_test_suite = {"Proxy config", NULL, NULL, liblinphone_tester_before_each, liblinphone_tester_after_each, diff --git a/tester/rcfiles/marie_rc b/tester/rcfiles/marie_rc index 282cc9cc0..ce4c4877f 100644 --- a/tester/rcfiles/marie_rc +++ b/tester/rcfiles/marie_rc @@ -28,7 +28,6 @@ pol=accept subscribe=0 [misc] -conference_factory_uri=sip:conference-factory@conf.example.org enable_basic_to_client_group_chat_room_migration=1 basic_to_client_group_chat_room_migration_timer=10 diff --git a/tester/setup_tester.c b/tester/setup_tester.c index 16209aeda..b10705004 100644 --- a/tester/setup_tester.c +++ b/tester/setup_tester.c @@ -17,12 +17,94 @@ */ -#include "linphone/core.h" #include "liblinphone_tester.h" -#include "tester_utils.h" +#include "linphone/core.h" +#include "linphone/friend.h" +#include "linphone/friendlist.h" #include "linphone/lpconfig.h" +#include "linphone/api/c-magic-search.h" #include "tester_utils.h" +#define S_SIZE_FRIEND 12 +static const unsigned int sSizeFriend = S_SIZE_FRIEND; +static const char *sFriends[S_SIZE_FRIEND] = { + "sip:charu@sip.test.org",//0 + "sip:charette@sip.example.org",//1 + "sip:allo@sip.example.org",//2 + "sip:hello@sip.example.org",//3 + "sip:hello@sip.test.org",//4 + "sip:marie@sip.example.org",//5 + "sip:laura@sip.example.org",//6 + "sip:loic@sip.example.org",//7 + "sip:laure@sip.test.org",//8 + "sip:loic@sip.test.org",//9 + "sip:+111223344@sip.example.org",//10 + "sip:+33655667788@sip.example.org"//11 +}; + +static void _create_friends_from_tab(LinphoneCore *lc, LinphoneFriendList *list, const char *friends[], const unsigned int size) { + unsigned int i; + for (i = 0 ; i < size ; i++) { + LinphoneFriend *fr = linphone_core_create_friend_with_address(lc, friends[i]); + linphone_friend_list_add_friend(list, fr); + } +} + +static void _remove_friends_from_list(LinphoneFriendList *list, const char *friends[], const unsigned int size) { + unsigned int i; + for (i = 0 ; i < size ; i++) { + LinphoneFriend *fr = linphone_friend_list_find_friend_by_uri(list, friends[i]); + if (fr) linphone_friend_list_remove_friend(list, fr); + } +} + +static void _check_friend_result_list(LinphoneCore *lc, const bctbx_list_t *resultList, const unsigned int index, const char* uri, const char* phone) { + if (index >= bctbx_list_size(resultList)) { + ms_error("Attempt to access result to an outbound index"); + return; + } + const LinphoneSearchResult *sr = bctbx_list_nth_data(resultList, index); + const LinphoneFriend *lf = linphone_search_result_get_friend(sr); + if (lf) { + const LinphoneAddress *la = linphone_friend_get_address(lf); + if (la) { + char* fa = linphone_address_as_string(la); + BC_ASSERT_STRING_EQUAL(fa , uri); + free(fa); + return; + } else if (phone) { + const LinphonePresenceModel *presence = linphone_friend_get_presence_model_for_uri_or_tel(lf, phone); + if (BC_ASSERT_PTR_NOT_NULL(presence)) { + char *contact = linphone_presence_model_get_contact(presence); + BC_ASSERT_STRING_EQUAL(contact, uri); + free(contact); + return; + } + } + } else { + const bctbx_list_t *callLog = linphone_core_get_call_logs(lc); + const bctbx_list_t *f; + for (f = callLog ; f != NULL ; f = bctbx_list_next(f)) { + LinphoneCallLog *log = (LinphoneCallLog*)(f->data); + const LinphoneAddress *addr = (linphone_call_log_get_dir(log) == LinphoneCallIncoming) ? + linphone_call_log_get_from_address(log) : linphone_call_log_get_to_address(log); + if (addr) { + char *addrUri = linphone_address_as_string_uri_only(addr); + if (addrUri && strcmp(addrUri, uri) == 0) { + return; + } + if (addrUri) free(addrUri); + } + } + } + BC_ASSERT(FALSE); + ms_error("Address NULL and Presence NULL"); +} + +static void _create_call_log(LinphoneCore *lc, LinphoneAddress *addrFrom, LinphoneAddress *addrTo) { + linphone_core_create_call_log(lc, addrFrom, addrTo, LinphoneCallOutgoing, 100, time(NULL), time(NULL), LinphoneCallSuccess, FALSE, 1.0); +} + static void linphone_version_test(void){ const char *version=linphone_core_get_version(); /*make sure the git version is always included in the version number*/ @@ -393,6 +475,402 @@ static void custom_tones_setup(void){ linphone_core_manager_destroy(mgr); } +static void search_friend_all_domains(void) { + LinphoneMagicSearch *magicSearch = NULL; + bctbx_list_t *resultList = NULL; + LinphoneCoreManager* manager = linphone_core_manager_new2("empty_rc", FALSE); + LinphoneFriendList *lfl = linphone_core_get_default_friend_list(manager->lc); + + _create_friends_from_tab(manager->lc, lfl, sFriends, sSizeFriend); + + magicSearch = linphone_magic_search_new(manager->lc); + + resultList = linphone_magic_search_get_contact_list_from_filter(magicSearch, "llo", ""); + + if (BC_ASSERT_PTR_NOT_NULL(resultList)) { + BC_ASSERT_EQUAL(bctbx_list_size(resultList), 3, int, "%d"); + _check_friend_result_list(manager->lc, resultList, 0, sFriends[2], NULL);//"sip:allo@sip.example.org" + _check_friend_result_list(manager->lc, resultList, 1, sFriends[3], NULL);//"sip:hello@sip.example.org" + _check_friend_result_list(manager->lc, resultList, 2, sFriends[4], NULL);//"sip:hello@sip.test.org" + bctbx_list_free(resultList); + } + + _remove_friends_from_list(lfl, sFriends, sSizeFriend); + + linphone_magic_search_unref(magicSearch); + linphone_core_manager_destroy(manager); +} + +static void search_friend_one_domain(void) { + LinphoneMagicSearch *magicSearch = NULL; + bctbx_list_t *resultList = NULL; + LinphoneCoreManager* manager = linphone_core_manager_new2("empty_rc", FALSE); + LinphoneFriendList *lfl = linphone_core_get_default_friend_list(manager->lc); + + _create_friends_from_tab(manager->lc, lfl, sFriends, sSizeFriend); + + magicSearch = linphone_magic_search_new(manager->lc); + + resultList = linphone_magic_search_get_contact_list_from_filter(magicSearch, "llo", "sip.example.org"); + + if (BC_ASSERT_PTR_NOT_NULL(resultList)) { + BC_ASSERT_EQUAL(bctbx_list_size(resultList), 2, int, "%d"); + _check_friend_result_list(manager->lc, resultList, 0, sFriends[2], NULL);//"sip:allo@sip.example.org" + _check_friend_result_list(manager->lc, resultList, 1, sFriends[3], NULL);//"sip:hello@sip.example.org" + bctbx_list_free(resultList); + } + + _remove_friends_from_list(lfl, sFriends, sSizeFriend); + + linphone_magic_search_unref(magicSearch); + linphone_core_manager_destroy(manager); +} + +static void search_friend_research_estate(void) { + LinphoneMagicSearch *magicSearch = NULL; + bctbx_list_t *resultList = NULL; + LinphoneCoreManager* manager = linphone_core_manager_new2("empty_rc", FALSE); + LinphoneFriendList *lfl = linphone_core_get_default_friend_list(manager->lc); + + _create_friends_from_tab(manager->lc, lfl, sFriends, sSizeFriend); + + magicSearch = linphone_magic_search_new(manager->lc); + + resultList = linphone_magic_search_get_contact_list_from_filter(magicSearch, "l", ""); + + if (BC_ASSERT_PTR_NOT_NULL(resultList)) { + BC_ASSERT_EQUAL(bctbx_list_size(resultList), 7, int, "%d"); + _check_friend_result_list(manager->lc, resultList, 0, sFriends[6], NULL);//"sip:laura@sip.example.org" + _check_friend_result_list(manager->lc, resultList, 1, sFriends[7], NULL);//"sip:loic@sip.example.org" + _check_friend_result_list(manager->lc, resultList, 2, sFriends[8], NULL);//"sip:laure@sip.test.org" + _check_friend_result_list(manager->lc, resultList, 3, sFriends[9], NULL);//"sip:loic@sip.test.org" + _check_friend_result_list(manager->lc, resultList, 4, sFriends[2], NULL);//"sip:allo@sip.example.org" + _check_friend_result_list(manager->lc, resultList, 5, sFriends[3], NULL);//"sip:hello@sip.example.org" + _check_friend_result_list(manager->lc, resultList, 6, sFriends[4], NULL);//"sip:hello@sip.test.org" + bctbx_list_free(resultList); + } + + resultList = linphone_magic_search_get_contact_list_from_filter(magicSearch, "la", ""); + + if (BC_ASSERT_PTR_NOT_NULL(resultList)) { + BC_ASSERT_EQUAL(bctbx_list_size(resultList), 2, int, "%d"); + _check_friend_result_list(manager->lc, resultList, 0, sFriends[8], NULL);//"sip:laure@sip.test.org" + _check_friend_result_list(manager->lc, resultList, 1, sFriends[6], NULL);//"sip:laura@sip.example.org" + bctbx_list_free(resultList); + } + + _remove_friends_from_list(lfl, sFriends, sSizeFriend); + + linphone_magic_search_unref(magicSearch); + linphone_core_manager_destroy(manager); +} + +static void search_friend_research_estate_reset(void) { + LinphoneMagicSearch *magicSearch = NULL; + bctbx_list_t *resultList = NULL; + LinphoneCoreManager* manager = linphone_core_manager_new2("empty_rc", FALSE); + LinphoneFriendList *lfl = linphone_core_get_default_friend_list(manager->lc); + + _create_friends_from_tab(manager->lc, lfl, sFriends, sSizeFriend); + + magicSearch = linphone_magic_search_new(manager->lc); + + resultList = linphone_magic_search_get_contact_list_from_filter(magicSearch, "la", ""); + + if (BC_ASSERT_PTR_NOT_NULL(resultList)) { + BC_ASSERT_EQUAL(bctbx_list_size(resultList), 2, int, "%d"); + _check_friend_result_list(manager->lc, resultList, 0, sFriends[6], NULL);//"sip:laura@sip.example.org" + _check_friend_result_list(manager->lc, resultList, 1, sFriends[8], NULL);//"sip:laure@sip.test.org" + bctbx_list_free(resultList); + } + + linphone_magic_search_reset_search_cache(magicSearch); + + resultList = linphone_magic_search_get_contact_list_from_filter(magicSearch, "l", ""); + + if (BC_ASSERT_PTR_NOT_NULL(resultList)) { + BC_ASSERT_EQUAL(bctbx_list_size(resultList), 7, int, "%d"); + _check_friend_result_list(manager->lc, resultList, 0, sFriends[6], NULL);//"sip:laura@sip.example.org" + _check_friend_result_list(manager->lc, resultList, 1, sFriends[7], NULL);//"sip:loic@sip.example.org" + _check_friend_result_list(manager->lc, resultList, 2, sFriends[8], NULL);//"sip:laure@sip.test.org" + _check_friend_result_list(manager->lc, resultList, 3, sFriends[9], NULL);//"sip:loic@sip.test.org" + _check_friend_result_list(manager->lc, resultList, 4, sFriends[2], NULL);//"sip:allo@sip.example.org" + _check_friend_result_list(manager->lc, resultList, 5, sFriends[3], NULL);//"sip:hello@sip.example.org" + _check_friend_result_list(manager->lc, resultList, 6, sFriends[4], NULL);//"sip:hello@sip.test.org" + bctbx_list_free(resultList); + } + + _remove_friends_from_list(lfl, sFriends, sSizeFriend); + + linphone_magic_search_unref(magicSearch); + linphone_core_manager_destroy(manager); +} + +static void search_friend_with_phone_number(void) { + LinphoneMagicSearch *magicSearch = NULL; + bctbx_list_t *resultList = NULL; + LinphoneCoreManager* manager = linphone_core_manager_new2("empty_rc", FALSE); + LinphoneFriendList *lfl = linphone_core_get_default_friend_list(manager->lc); + const char* mariePhoneNumber = {"0633556644"}; + + _create_friends_from_tab(manager->lc, lfl, sFriends, sSizeFriend); + + linphone_friend_add_phone_number(linphone_friend_list_find_friend_by_uri(lfl, sFriends[5]), mariePhoneNumber); + + magicSearch = linphone_magic_search_new(manager->lc); + + resultList = linphone_magic_search_get_contact_list_from_filter(magicSearch, "33", ""); + + if (BC_ASSERT_PTR_NOT_NULL(resultList)) { + BC_ASSERT_EQUAL(bctbx_list_size(resultList), 3, int, "%d"); + _check_friend_result_list(manager->lc, resultList, 0, sFriends[11], NULL);//"sip:+111223344@sip.example.org" + _check_friend_result_list(manager->lc, resultList, 1, sFriends[10], NULL);//"sip:+33655667788@sip.example.org" + _check_friend_result_list(manager->lc, resultList, 2, sFriends[5], NULL);//"sip:marie@sip.example.org" + bctbx_list_free(resultList); + } + + _remove_friends_from_list(lfl, sFriends, sSizeFriend); + + linphone_magic_search_unref(magicSearch); + linphone_core_manager_destroy(manager); +} + +static void search_friend_with_presence(void) { + LinphoneMagicSearch *magicSearch = NULL; + bctbx_list_t *resultList = NULL; + LinphoneCoreManager* manager = linphone_core_manager_create("marie_rc"); + LinphoneFriendList *lfl = linphone_core_get_default_friend_list(manager->lc); + const char* chloeSipUri = {"sip:chloe@sip.example.org"}; + const char* chloePhoneNumber = {"0633556644"}; + LinphoneFriend *chloeFriend = linphone_core_create_friend(manager->lc); + LinphonePresenceModel *chloePresence = linphone_core_create_presence_model(manager->lc); + LinphoneProxyConfig *proxy = linphone_core_get_default_proxy_config(manager->lc); + + linphone_proxy_config_edit(proxy); + linphone_proxy_config_set_dial_prefix(proxy, "33"); + linphone_proxy_config_done(proxy); + linphone_core_set_default_proxy(manager->lc, proxy); + + _create_friends_from_tab(manager->lc, lfl, sFriends, sSizeFriend); + linphone_presence_model_set_contact(chloePresence, chloeSipUri); + linphone_friend_add_phone_number(chloeFriend, chloePhoneNumber); + linphone_friend_set_presence_model_for_uri_or_tel(chloeFriend, chloePhoneNumber, chloePresence); + linphone_friend_list_add_friend(lfl, chloeFriend); + + magicSearch = linphone_magic_search_new(manager->lc); + + resultList = linphone_magic_search_get_contact_list_from_filter(magicSearch, "33", ""); + + if (BC_ASSERT_PTR_NOT_NULL(resultList)) { + // 3 + last address from filter "sip:33@sip.example.org" + BC_ASSERT_EQUAL(bctbx_list_size(resultList), 4, int, "%d"); + _check_friend_result_list(manager->lc, resultList, 0, sFriends[11], NULL);//"sip:+111223344@sip.example.org" + _check_friend_result_list(manager->lc, resultList, 1, chloeSipUri, chloePhoneNumber);//"sip:chloe@sip.example.org" + _check_friend_result_list(manager->lc, resultList, 2, sFriends[10], NULL);//"sip:+33655667788@sip.example.org" + bctbx_list_free(resultList); + } + + linphone_magic_search_reset_search_cache(magicSearch); + + resultList = linphone_magic_search_get_contact_list_from_filter(magicSearch, "chloe", ""); + + if (BC_ASSERT_PTR_NOT_NULL(resultList)) { + // 1 + last address from filter "sip:chloe@sip.example.org" + BC_ASSERT_EQUAL(bctbx_list_size(resultList), 2, int, "%d"); + _check_friend_result_list(manager->lc, resultList, 0, chloeSipUri, chloePhoneNumber);//"sip:chloe@sip.example.org" + bctbx_list_free(resultList); + } + + _remove_friends_from_list(lfl, sFriends, sSizeFriend); + { + LinphoneFriend *fr = linphone_friend_list_find_friend_by_uri(lfl, chloeSipUri); + if (fr != NULL) linphone_friend_list_remove_friend(lfl, fr); + if (chloeFriend) linphone_friend_unref(chloeFriend); + } + + linphone_magic_search_unref(magicSearch); + linphone_core_manager_destroy(manager); +} + +static void search_friend_in_call_log(void) { + LinphoneMagicSearch *magicSearch = NULL; + bctbx_list_t *resultList = NULL; + LinphoneCoreManager* manager = linphone_core_manager_new2("empty_rc", FALSE); + LinphoneFriendList *lfl = linphone_core_get_default_friend_list(manager->lc); + const char *chloeSipUri = {"sip:chloe@sip.example.org"}; + const char *benjaminSipUri = {"sip:benjamin@sip.example.org"}; + const char *charlesSipUri = {"sip:charles@sip.test.org"}; + const char *ronanSipUri = {"sip:ronan@sip.example.org"}; + LinphoneAddress *chloeAddress = linphone_address_new(chloeSipUri); + LinphoneAddress *benjaminAddress = linphone_address_new(benjaminSipUri); + LinphoneAddress *charlesAddress = linphone_address_new(charlesSipUri); + LinphoneAddress *ronanAddress = linphone_address_new(ronanSipUri); + + _create_call_log(manager->lc, ronanAddress, chloeAddress); + _create_call_log(manager->lc, ronanAddress, charlesAddress); + _create_call_log(manager->lc, ronanAddress, benjaminAddress); + + _create_friends_from_tab(manager->lc, lfl, sFriends, sSizeFriend); + + magicSearch = linphone_magic_search_new(manager->lc); + + resultList = linphone_magic_search_get_contact_list_from_filter(magicSearch, "ch", ""); + + if (BC_ASSERT_PTR_NOT_NULL(resultList)) { + BC_ASSERT_EQUAL(bctbx_list_size(resultList), 4, int, "%d"); + _check_friend_result_list(manager->lc, resultList, 0, sFriends[0], NULL);//"sip:charu@sip.test.org" + _check_friend_result_list(manager->lc, resultList, 1, sFriends[1], NULL);//"sip:charette@sip.example.org" + _check_friend_result_list(manager->lc, resultList, 2, chloeSipUri, NULL);//"sip:chloe@sip.example.org" + _check_friend_result_list(manager->lc, resultList, 3, charlesSipUri, NULL);//"sip:charles@sip.test.org" + bctbx_list_free(resultList); + } + + linphone_magic_search_reset_search_cache(magicSearch); + + resultList = linphone_magic_search_get_contact_list_from_filter(magicSearch, "ch", "sip.test.org"); + + if (BC_ASSERT_PTR_NOT_NULL(resultList)) { + BC_ASSERT_EQUAL(bctbx_list_size(resultList), 2, int, "%d"); + _check_friend_result_list(manager->lc, resultList, 0, sFriends[0], NULL);//"sip:charu@sip.test.org" + _check_friend_result_list(manager->lc, resultList, 1, charlesSipUri, NULL);//"sip:charles@sip.test.org" + bctbx_list_free(resultList); + } + + _remove_friends_from_list(lfl, sFriends, sSizeFriend); + + if (chloeAddress) linphone_address_unref(chloeAddress); + if (benjaminAddress) linphone_address_unref(benjaminAddress); + if (charlesAddress) linphone_address_unref(charlesAddress); + if (ronanAddress) linphone_address_unref(ronanAddress); + + linphone_magic_search_unref(magicSearch); + linphone_core_manager_destroy(manager); +} + +static void search_friend_last_item_is_filter(void) { + LinphoneMagicSearch *magicSearch = NULL; + bctbx_list_t *resultList = NULL; + LinphoneCoreManager* manager = linphone_core_manager_create("marie_rc"); + LinphoneFriendList *lfl = linphone_core_get_default_friend_list(manager->lc); + + _create_friends_from_tab(manager->lc, lfl, sFriends, sSizeFriend); + + magicSearch = linphone_magic_search_new(manager->lc); + + resultList = linphone_magic_search_get_contact_list_from_filter(magicSearch, "newaddress", ""); + + if (BC_ASSERT_PTR_NOT_NULL(resultList)) { + BC_ASSERT_EQUAL(bctbx_list_size(resultList), 1, int, "%d"); + const LinphoneSearchResult *sr = bctbx_list_nth_data(resultList, 0); + if (BC_ASSERT_PTR_NOT_NULL(sr)) { + const LinphoneAddress *srAddress = linphone_search_result_get_address(sr); + if (BC_ASSERT_PTR_NOT_NULL(srAddress)) { + BC_ASSERT_STRING_EQUAL(linphone_address_get_username(srAddress), "newaddress"); + } + } + bctbx_list_free(resultList); + } + + linphone_magic_search_unref(magicSearch); + linphone_core_manager_destroy(manager); +} + +static void search_friend_with_name(void) { + LinphoneMagicSearch *magicSearch = NULL; + bctbx_list_t *resultList = NULL; + LinphoneCoreManager* manager = linphone_core_manager_new2("empty_rc", FALSE); + LinphoneFriendList *lfl = linphone_core_get_default_friend_list(manager->lc); + const char *stephanie1SipUri = {"sip:toto@sip.example.org"}; + const char *stephanie2SipUri = {"sip:stephanie@sip.example.org"}; + LinphoneFriend *stephanie1Friend = linphone_core_create_friend(manager->lc); + LinphoneFriend *stephanie2Friend = linphone_core_create_friend(manager->lc); + LinphoneVcard *stephanie1Vcard = linphone_factory_create_vcard(linphone_factory_get()); + LinphoneVcard *stephanie2Vcard = linphone_factory_create_vcard(linphone_factory_get()); + const char *stephanie1Name = {"stephanie delarue"}; + const char *stephanie2Name = {"alias delarue"}; + + _create_friends_from_tab(manager->lc, lfl, sFriends, sSizeFriend); + + linphone_vcard_set_full_name(stephanie1Vcard, stephanie1Name); // stephanie delarue + linphone_vcard_set_url(stephanie1Vcard, stephanie1SipUri); //sip:toto@sip.example.org + linphone_vcard_add_sip_address(stephanie1Vcard, stephanie1SipUri); + linphone_friend_set_vcard(stephanie1Friend, stephanie1Vcard); + linphone_core_add_friend(manager->lc, stephanie1Friend); + + linphone_vcard_set_full_name(stephanie2Vcard, stephanie2Name); // alias delarue + linphone_vcard_set_url(stephanie2Vcard, stephanie2SipUri); //sip:stephanie@sip.example.org + linphone_vcard_add_sip_address(stephanie2Vcard, stephanie2SipUri); + linphone_friend_set_vcard(stephanie2Friend, stephanie2Vcard); + linphone_core_add_friend(manager->lc, stephanie2Friend); + + magicSearch = linphone_magic_search_new(manager->lc); + + resultList = linphone_magic_search_get_contact_list_from_filter(magicSearch, "stephanie", ""); + + if (BC_ASSERT_PTR_NOT_NULL(resultList)) { + BC_ASSERT_EQUAL(bctbx_list_size(resultList), 2, int, "%d"); + _check_friend_result_list(manager->lc, resultList, 0, stephanie1SipUri, NULL);//"sip:toto@sip.example.org" + _check_friend_result_list(manager->lc, resultList, 1, stephanie2SipUri, NULL);//"sip:stephanie@sip.example.org" + bctbx_list_free(resultList); + } + + linphone_magic_search_reset_search_cache(magicSearch); + + resultList = linphone_magic_search_get_contact_list_from_filter(magicSearch, "delarue", ""); + + if (BC_ASSERT_PTR_NOT_NULL(resultList)) { + BC_ASSERT_EQUAL(bctbx_list_size(resultList), 2, int, "%d"); + _check_friend_result_list(manager->lc, resultList, 0, stephanie2SipUri, NULL);//"sip:stephanie@sip.example.org" + _check_friend_result_list(manager->lc, resultList, 1, stephanie1SipUri, NULL);//"sip:toto@sip.example.org" + bctbx_list_free(resultList); + } + + _remove_friends_from_list(lfl, sFriends, sSizeFriend); + linphone_friend_list_remove_friend(lfl, stephanie1Friend); + linphone_friend_list_remove_friend(lfl, stephanie2Friend); + if (stephanie1Friend) linphone_friend_unref(stephanie1Friend); + if (stephanie2Friend) linphone_friend_unref(stephanie2Friend); + if (stephanie1Vcard) linphone_vcard_unref(stephanie1Vcard); + if (stephanie2Vcard) linphone_vcard_unref(stephanie2Vcard); + + linphone_magic_search_unref(magicSearch); + linphone_core_manager_destroy(manager); +} + +static void search_friend_large_database(void) { + MSTimeSpec start, current; + LinphoneMagicSearch *magicSearch = NULL; + bctbx_list_t *resultList = NULL; + char *dbPath = bc_tester_res("db/friends.db"); + char searchedFriend[] = {"6295103032641994169"}; + char subBuff[30]; + LinphoneCoreManager* manager = linphone_core_manager_new2("empty_rc", FALSE); + unsigned int i; + + linphone_core_set_friends_database_path(manager->lc, dbPath); + + magicSearch = linphone_magic_search_new(manager->lc); + + for (i = 1; i < sizeof(searchedFriend) ; i++) { + memcpy(subBuff, &searchedFriend, i); + subBuff[i] = '\0'; + liblinphone_tester_clock_start(&start); + resultList = linphone_magic_search_get_contact_list_from_filter(magicSearch, subBuff, ""); + if (BC_ASSERT_PTR_NOT_NULL(resultList)) { + ms_get_cur_time(¤t); + ms_message("Searching time: %lld ms" ,((current.tv_sec - start.tv_sec)*1000LL) + ((current.tv_nsec - start.tv_nsec)/1000000LL)); + + if (BC_ASSERT_PTR_NOT_NULL(resultList)) ms_message("List size: %zu", bctbx_list_size(resultList)); + + bctbx_list_free(resultList); + } + } + + free(dbPath); + linphone_magic_search_unref(magicSearch); + linphone_core_manager_destroy(manager); +} + test_t setup_tests[] = { TEST_NO_TAG("Version check", linphone_version_test), TEST_NO_TAG("Linphone Address", linphone_address_test), @@ -409,7 +887,17 @@ test_t setup_tests[] = { TEST_NO_TAG("Devices reload", devices_reload_test), TEST_NO_TAG("Codec usability", codec_usability_test), TEST_NO_TAG("Codec setup", codec_setup), - TEST_NO_TAG("Custom tones setup", custom_tones_setup) + TEST_NO_TAG("Custom tones setup", custom_tones_setup), + TEST_TWO_TAGS("Search friend from all domains", search_friend_all_domains, "MagicSearch", "LeaksMemory"), + TEST_TWO_TAGS("Search friend from one domain", search_friend_one_domain, "MagicSearch", "LeaksMemory"), + TEST_TWO_TAGS("Multiple looking for friends with the same cache", search_friend_research_estate, "MagicSearch", "LeaksMemory"), + TEST_TWO_TAGS("Multiple looking for friends with cache resetting", search_friend_research_estate_reset, "MagicSearch", "LeaksMemory"), + TEST_TWO_TAGS("Search friend with phone number", search_friend_with_phone_number, "MagicSearch", "LeaksMemory"), + TEST_TWO_TAGS("Search friend and find it with its presence", search_friend_with_presence, "MagicSearch", "LeaksMemory"), + TEST_TWO_TAGS("Search friend in call log", search_friend_in_call_log, "MagicSearch", "LeaksMemory"), + TEST_TWO_TAGS("Search friend last item is the filter", search_friend_last_item_is_filter, "MagicSearch", "LeaksMemory"), + TEST_TWO_TAGS("Search friend with name", search_friend_with_name, "MagicSearch", "LeaksMemory"), + TEST_TWO_TAGS("Search friend in large friends database", search_friend_large_database, "MagicSearch", "LeaksMemory") }; test_suite_t setup_test_suite = {"Setup", NULL, NULL, liblinphone_tester_before_each, liblinphone_tester_after_each, diff --git a/tester/tester.c b/tester/tester.c index 50d3bd6d4..c47cff87f 100644 --- a/tester/tester.c +++ b/tester/tester.c @@ -459,24 +459,23 @@ void linphone_core_manager_stop(LinphoneCoreManager *mgr){ } } -void linphone_core_manager_reinit(LinphoneCoreManager *mgr, bool_t check_for_proxies) { - char* uuid = NULL; +void linphone_core_manager_reinit(LinphoneCoreManager *mgr) { + char *uuid = NULL; if (mgr->lc) { - if (lp_config_get_string(linphone_core_get_config(mgr->lc),"misc","uuid",NULL)) { - uuid = bctbx_strdup(lp_config_get_string(linphone_core_get_config(mgr->lc),"misc","uuid",NULL)); - } + if (lp_config_get_string(linphone_core_get_config(mgr->lc), "misc", "uuid", NULL)) + uuid = bctbx_strdup(lp_config_get_string(linphone_core_get_config(mgr->lc), "misc", "uuid", NULL)); linphone_core_unref(mgr->lc); } linphone_core_manager_configure(mgr); reset_counters(&mgr->stat); - //to make sure gruu is preserved - lp_config_set_string(linphone_core_get_config(mgr->lc),"misc","uuid",uuid); + // Make sure gruu is preserved + lp_config_set_string(linphone_core_get_config(mgr->lc), "misc", "uuid", uuid); if (uuid) - bctbx_free(uuid); + bctbx_free(uuid); } void linphone_core_manager_restart(LinphoneCoreManager *mgr, bool_t check_for_proxies) { - linphone_core_manager_reinit(mgr, check_for_proxies); + linphone_core_manager_reinit(mgr); linphone_core_manager_start(mgr, check_for_proxies); } @@ -518,6 +517,11 @@ void linphone_core_manager_wait_for_stun_resolution(LinphoneCoreManager *mgr) { } void linphone_core_manager_destroy(LinphoneCoreManager* mgr) { + if (mgr->lc && !linphone_core_is_network_reachable(mgr->lc)) { + int previousNbRegistrationOk = mgr->stat.number_of_LinphoneRegistrationOk; + linphone_core_set_network_reachable(mgr->lc, TRUE); + wait_for_until(mgr->lc, NULL, &mgr->stat.number_of_LinphoneRegistrationOk, previousNbRegistrationOk + 1, 2000); + } linphone_core_manager_stop(mgr); linphone_core_manager_uninit(mgr); ms_free(mgr); @@ -838,6 +842,7 @@ bool_t check_ice(LinphoneCoreManager* caller, LinphoneCoreManager* callee, Linph linphone_core_iterate(callee->lc); linphone_call_stats_unref(stats1); linphone_call_stats_unref(stats2); + stats1 = stats2 = NULL; } ms_usleep(20000); } while (!liblinphone_tester_clock_elapsed(&ts,10000)); @@ -866,6 +871,7 @@ bool_t check_ice(LinphoneCoreManager* caller, LinphoneCoreManager* callee, Linph linphone_core_iterate(callee->lc); linphone_call_stats_unref(stats1); linphone_call_stats_unref(stats2); + stats1 = stats2 = NULL; } ms_usleep(20000); } while (!liblinphone_tester_clock_elapsed(&ts,10000)); diff --git a/tester/vcard_tester.c b/tester/vcard_tester.c index be14d2007..664ed68e6 100644 --- a/tester/vcard_tester.c +++ b/tester/vcard_tester.c @@ -795,9 +795,9 @@ static void carddav_integration(void) { linphone_friend_done(lf); BC_ASSERT_EQUAL((unsigned int)bctbx_list_size(linphone_friend_list_get_dirty_friends_to_update(linphone_friend_get_friend_list(lf))), 1, unsigned int, "%u"); - ms_free(stats); linphone_friend_list_unref(lfl); linphone_core_manager_destroy(manager); + ms_free(stats); } static void carddav_clean(void) { // This is to ensure the content of the test addressbook is in the correct state for the following tests diff --git a/wrappers/cpp/class_impl.mustache b/wrappers/cpp/class_impl.mustache index e98acedfa..3002c3c9c 100644 --- a/wrappers/cpp/class_impl.mustache +++ b/wrappers/cpp/class_impl.mustache @@ -38,7 +38,7 @@ static {{{returnType}}} {{{cbName}}}({{{declArgs}}}) { {{#ismultilistenable}} {{{cListenerName}}} *cbs = {{{currentCallbacksGetter}}}({{{firstArgName}}}); - std::list > &listeners = *(std::list > *){{{userDataGetter}}}(cbs); + std::list > listeners = *(std::list > *){{{userDataGetter}}}(cbs); for(auto it=listeners.begin(); it!=listeners.end(); it++) { std::shared_ptr<{{{cppListenerName}}}> listener = std::static_pointer_cast<{{{cppListenerName}}},Listener>(*it); {{{cppMethodCallingLine}}}; diff --git a/wrappers/java/classes/tools/AndroidPlatformHelper.java b/wrappers/java/classes/tools/AndroidPlatformHelper.java index 7e07f009b..1c20b94c6 100644 --- a/wrappers/java/classes/tools/AndroidPlatformHelper.java +++ b/wrappers/java/classes/tools/AndroidPlatformHelper.java @@ -171,19 +171,28 @@ public class AndroidPlatformHelper { mWakeLock.acquire(); } - public void releaseCpuLock(){ + public void releaseCpuLock() { Log.i("releaseCpuLock()"); mWakeLock.release(); } + private int getResourceIdentifierFromName(String name) { + int resId = mResources.getIdentifier(name, "raw", mContext.getPackageName()); + if (resId == 0) { + Log.d("App doesn't seem to embed resource " + name + "in it's res/raw/ directory, use linphone's instead"); + resId = mResources.getIdentifier(name, "raw", "org.linphone"); + } + return resId; + } + private void copyAssetsFromPackage() throws IOException { - copyIfNotExist(mResources.getIdentifier("org.linphone:raw/notes_of_the_optimistic", null, null), mRingSoundFile); - copyIfNotExist(mResources.getIdentifier("org.linphone:raw/ringback", null, null), mRingbackSoundFile); - copyIfNotExist(mResources.getIdentifier("org.linphone:raw/hold", null, null), mPauseSoundFile); - copyIfNotExist(mResources.getIdentifier("org.linphone:raw/incoming_chat", null, null), mErrorToneFile); - copyIfNotExist(mResources.getIdentifier("org.linphone:raw/cpim_grammar", null, null), mGrammarCpimFile); - copyIfNotExist(mResources.getIdentifier("org.linphone:raw/vcard_grammar", null, null), mGrammarVcardFile); - copyIfNotExist(mResources.getIdentifier("org.linphone:raw/rootca", null, null), mLinphoneRootCaFile); + copyIfNotExist(getResourceIdentifierFromName("notes_of_the_optimistic"), mRingSoundFile); + copyIfNotExist(getResourceIdentifierFromName("ringback"), mRingbackSoundFile); + copyIfNotExist(getResourceIdentifierFromName("hold"), mPauseSoundFile); + copyIfNotExist(getResourceIdentifierFromName("incoming_chat"), mErrorToneFile); + copyIfNotExist(getResourceIdentifierFromName("cpim_grammar"), mGrammarCpimFile); + copyIfNotExist(getResourceIdentifierFromName("vcard_grammar"), mGrammarVcardFile); + copyIfNotExist(getResourceIdentifierFromName("rootca"), mLinphoneRootCaFile); } public void copyIfNotExist(int ressourceId, String target) throws IOException { @@ -193,9 +202,9 @@ public class AndroidPlatformHelper { } } - public void copyFromPackage(int ressourceId, String target) throws IOException{ - FileOutputStream lOutputStream = mContext.openFileOutput (target, 0); + public void copyFromPackage(int ressourceId, String target) throws IOException { InputStream lInputStream = mResources.openRawResource(ressourceId); + FileOutputStream lOutputStream = mContext.openFileOutput (target, 0); int readByte; byte[] buff = new byte[8048]; while (( readByte = lInputStream.read(buff)) != -1) { diff --git a/wrappers/java/jni.mustache b/wrappers/java/jni.mustache index 52152d754..e226d5c68 100644 --- a/wrappers/java/jni.mustache +++ b/wrappers/java/jni.mustache @@ -357,27 +357,47 @@ static {{return}} {{callbackName}}({{params}}) { {{#interfaces}} {{#isSingleListener}} void Java_{{jniPackage}}{{className}}Impl_setListener(JNIEnv* env, jobject thiz, jlong ptr, jobject jlistener) { + {{classCName}} *cptr = ({{classCName}}*)ptr; + {{classCName}}Cbs *cbs = {{cPrefix}}_get_callbacks(cptr); + if (jlistener == NULL) { + jobject listener = (jobject) {{cPrefix}}_cbs_get_user_data(cbs); + {{cPrefix}}_cbs_set_user_data(cbs, NULL); + if (listener != NULL) { + env->DeleteGlobalRef(listener); + } + } else { + jobject listener = (jobject) {{cPrefix}}_cbs_get_user_data(cbs); + if (listener == NULL) { + listener = env->NewGlobalRef(jlistener); + } else { + if (env->IsSameObject(listener, jlistener)) { + return; + } else { + env->DeleteGlobalRef(listener); + listener = env->NewGlobalRef(jlistener); + } + } + {{cPrefix}}_cbs_set_user_data(cbs, listener); + {{#callbacksList}} + {{cPrefix}}_cbs_set_{{callback}}(cbs, {{callbackName}}); + {{/callbacksList}} + } +} {{/isSingleListener}} + {{#isMultiListener}} void Java_{{jniPackage}}{{className}}Impl_addListener(JNIEnv* env, jobject thiz, jlong ptr, jobject jlistener) { -{{/isMultiListener}} + if (jlistener == NULL) return; {{classCName}} *cptr = ({{classCName}}*)ptr; jobject listener = env->NewGlobalRef(jlistener); - {{#isSingleListener}} - {{classCName}}Cbs *cbs = {{cPrefix}}_get_callbacks(cptr); - {{/isSingleListener}} - {{#isMultiListener}} {{classCName}}Cbs *cbs = linphone_factory_create_{{factoryName}}_cbs(NULL); - {{/isMultiListener}} {{cPrefix}}_cbs_set_user_data(cbs, listener); {{#callbacksList}} {{cPrefix}}_cbs_set_{{callback}}(cbs, {{callbackName}}); {{/callbacksList}} - {{#isMultiListener}} {{cPrefix}}_add_callbacks(cptr, cbs); - {{/isMultiListener}} + {{cPrefix}}_cbs_unref(cbs); } -{{#isMultiListener}} void Java_{{jniPackage}}{{className}}Impl_removeListener(JNIEnv* env, jobject thiz, jlong ptr, jobject jlistener) { {{classCName}} *cptr = ({{classCName}}*)ptr; @@ -385,14 +405,16 @@ void Java_{{jniPackage}}{{className}}Impl_removeListener(JNIEnv* env, jobject th bctbx_list_t *it; for (it = (bctbx_list_t *)cbs_list; it != NULL; it = it->next) { {{classCName}}Cbs *cbs = ({{classCName}}Cbs *)it->data; - if ({{cPrefix}}_cbs_get_user_data(cbs) == jlistener) { + jobject listener = (jobject) {{cPrefix}}_cbs_get_user_data(cbs); + if (env->IsSameObject(listener, jlistener)) { + {{cPrefix}}_cbs_set_user_data(cbs, NULL); {{cPrefix}}_remove_callbacks(cptr, cbs); + env->DeleteGlobalRef(listener); break; } } } {{/isMultiListener}} - {{/interfaces}} //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////