diff --git a/.gitignore b/.gitignore index c34ffd5d9..b262a4641 100644 --- a/.gitignore +++ b/.gitignore @@ -103,3 +103,5 @@ tester/stereo-record.wav git-clang-format.diff *.log .bc_tester_utils.tmp +tools/lp-test-ecc +tools/lp-sendmsg diff --git a/CMakeLists.txt b/CMakeLists.txt index f4f45d39b..69759f76b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,13 +21,13 @@ ############################################################################ cmake_minimum_required(VERSION 3.0) -project(LINPHONE C CXX) +project(linphone VERSION 3.9.1 LANGUAGES C CXX) -set(LINPHONE_MAJOR_VERSION "3") -set(LINPHONE_MINOR_VERSION "9") -set(LINPHONE_MICRO_VERSION "1") -set(LINPHONE_VERSION "${LINPHONE_MAJOR_VERSION}.${LINPHONE_MINOR_VERSION}.${LINPHONE_MICRO_VERSION}") +set(LINPHONE_MAJOR_VERSION ${PROJECT_VERSION_MAJOR}) +set(LINPHONE_MINOR_VERSION ${PROJECT_VERSION_MINOR}) +set(LINPHONE_MICRO_VERSION ${PROJECT_VERSION_PATCH}) +set(LINPHONE_VERSION ${PROJECT_VERSION}) set(LINPHONE_SO_VERSION "8") file(GLOB LINPHONE_PO_FILES RELATIVE "${CMAKE_CURRENT_LIST_DIR}/po" "${CMAKE_CURRENT_LIST_DIR}/po/*.po") @@ -36,7 +36,8 @@ string(REPLACE ";" " " LINPHONE_ALL_LANGS "${LINPHONE_ALL_LANGS_LIST}") include(CMakeDependentOption) -option(ENABLE_STATIC "Build static library (default is shared library)." NO) +option(ENABLE_SHARED "Build shared library." YES) +option(ENABLE_STATIC "Build static library." YES) option(ENABLE_CONSOLE_UI "Turn on or off compilation of console interface." YES) option(ENABLE_DATE "Use build date in internal version number." NO) option(ENABLE_DOC "Enable documentation generation with Doxygen." YES) @@ -44,7 +45,7 @@ option(ENABLE_GTK_UI "Turn on or off compilation of gtk interface." YES) option(ENABLE_LDAP "Enable LDAP support." NO) option(ENABLE_LIME "Enable Instant Messaging Encryption." NO) option(ENABLE_MSG_STORAGE "Turn on compilation of message storage." YES) -cmake_dependent_option(ENABLE_NOTIFY "Enable libnotify support." YES "ENABLE_GTK_UI" NO) +cmake_dependent_option(ENABLE_NOTIFY "Enable libnotify support." YES "ENABLE_GTK_UI;NOT APPLE" NO) option(ENABLE_RELATIVE_PREFIX "Find resources relatively to the installation directory." NO) option(ENABLE_STRICT "Build with strict compile options." YES) option(ENABLE_TOOLS "Turn on or off compilation of tools." YES) @@ -57,6 +58,8 @@ cmake_dependent_option(ENABLE_ASSISTANT "Turn on assistant compiling." YES "ENAB option(ENABLE_DEBUG_LOGS "Turn on or off debug level logs." NO) option(ENABLE_NLS "Build with internationalisation support" YES) option(ENABLE_CALL_LOGS_STORAGE "Turn on compilation of call logs storage." YES) +option(ENABLE_FRIENDS_SQL_STORAGE "Turn on compilation of friends sql storage." YES) +option(ENABLE_VCARD "Turn on compilation of vcard4 support." YES) macro(apply_compile_flags SOURCE_FILES) @@ -79,6 +82,12 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") include(CheckSymbolExists) include(CMakePushCheckState) +include(GNUInstallDirs) + +if(NOT CMAKE_INSTALL_RPATH AND CMAKE_INSTALL_PREFIX) + set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_FULL_LIBDIR}) + message(STATUS "Setting install rpath to ${CMAKE_INSTALL_RPATH}") +endif() set(MSVC_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include/MSVC") if(MSVC) @@ -91,26 +100,15 @@ endif() if(LINPHONE_BUILDER_GROUP_EXTERNAL_SOURCE_PATH_BUILDERS) include("${EP_bellesip_CONFIG_DIR}/BelleSIPConfig.cmake") include("${EP_ms2_CONFIG_DIR}/Mediastreamer2Config.cmake") + set(BcToolbox_FIND_COMPONENTS tester) + include("${EP_bctoolbox_CONFIG_DIR}/BcToolboxConfig.cmake") else() find_package(BelleSIP REQUIRED) find_package(Mediastreamer2 REQUIRED) + find_package(BcToolbox REQUIRED OPTIONAL_COMPONENTS tester) endif() find_package(XML2 REQUIRED) find_package(Zlib) -if(ENABLE_UNIT_TESTS) - find_package(CUnit) - if(CUNIT_FOUND) - cmake_push_check_state(RESET) - list(APPEND CMAKE_REQUIRED_INCLUDES ${CUNIT_INCLUDE_DIRS}) - list(APPEND CMAKE_REQUIRED_LIBRARIES ${CUNIT_LIBRARIES}) - check_symbol_exists("CU_get_suite" "CUnit/CUnit.h" HAVE_CU_GET_SUITE) - check_symbol_exists("CU_curses_run_tests" "CUnit/CUnit.h" HAVE_CU_CURSES) - cmake_pop_check_state() - else() - message(WARNING "Could not find the cunit library!") - set(ENABLE_UNIT_TESTS OFF CACHE BOOL "Enable compilation of unit tests." FORCE) - endif() -endif() if(ENABLE_TUNNEL) if(LINPHONE_BUILDER_GROUP_EXTERNAL_SOURCE_PATH_BUILDERS) include("${EP_tunnel_CONFIG_DIR}/TunnelConfig.cmake") @@ -163,9 +161,25 @@ endif() if(ENABLE_CALL_LOGS_STORAGE) find_package(Sqlite3 REQUIRED) endif() +if (ENABLE_FRIENDS_SQL_STORAGE) + find_package(Sqlite3 REQUIRED) +endif() if(ENABLE_LIME) set(HAVE_LIME 1) endif() +if (ENABLE_VCARD) + if(LINPHONE_BUILDER_GROUP_EXTERNAL_SOURCE_PATH_BUILDERS) + include("${EP_belcard_CONFIG_DIR}/BelcardConfig.cmake") + else() + find_package(Belcard) + endif() + if(NOT BELCARD_FOUND) + message(WARNING "Could not find the belcard library!") + set(ENABLE_VCARD OFF CACHE BOOL "Enable vcard support." FORCE) + else() + add_definitions(-DVCARD_ENABLED) + endif() +endif() if(UNIX AND NOT APPLE) include(CheckIncludeFiles) @@ -181,10 +195,14 @@ include_directories( ${CMAKE_CURRENT_BINARY_DIR}/coreapi/ ${BELLESIP_INCLUDE_DIRS} ${MEDIASTREAMER2_INCLUDE_DIRS} + ${BCTOOLBOX_CORE_INCLUDE_DIRS} ) if(ENABLE_TUNNEL) include_directories(${TUNNEL_INCLUDE_DIRS}) endif() +if (ENABLE_VCARD) + include_directories(${BELCARD_INCLUDE_DIRS}) +endif() include_directories(${XML2_INCLUDE_DIRS}) @@ -200,6 +218,9 @@ if(SQLITE3_FOUND) if(ENABLE_CALL_LOGS_STORAGE) add_definitions("-DCALL_LOGS_STORAGE_ENABLED") endif() + if(ENABLE_FRIENDS_SQL_STORAGE) + add_definitions("-DFRIENDS_SQL_STORAGE_ENABLED") + endif() endif() if(INTL_FOUND) set(HAVE_INTL 1) @@ -213,9 +234,13 @@ add_definitions("-DIN_LINPHONE") if(ENABLE_DEBUG_LOGS) add_definitions("-DDEBUG") endif() +if(ANDROID) + add_definitions("-DANDROID") +endif() set(STRICT_OPTIONS_CPP ) set(STRICT_OPTIONS_C ) +set(STRICT_OPTIONS_CXX ) set(STRICT_OPTIONS_OBJC ) if(NOT MSVC) list(APPEND STRICT_OPTIONS_CPP "-Wall" "-Wuninitialized" "-Wno-error=deprecated-declarations") @@ -230,6 +255,9 @@ if(NOT MSVC) list(APPEND STRICT_OPTIONS_CPP "-Werror" "-fno-strict-aliasing") endif() endif() +if(CMAKE_SYSTEM_NAME STREQUAL "WindowsStore") + list(APPEND STRICT_OPTIONS_CPP "/wd4996") +endif() if(STRICT_OPTIONS_CPP) list(REMOVE_DUPLICATES STRICT_OPTIONS_CPP) endif() @@ -244,13 +272,13 @@ if(ENABLE_RELATIVE_PREFIX) else() set(LINPHONE_DATA_DIR "${CMAKE_INSTALL_PREFIX}") endif() -set(LINPHONE_PLUGINS_DIR "${LINPHONE_DATA_DIR}/lib/liblinphone/plugins") +set(LINPHONE_PLUGINS_DIR "${LINPHONE_DATA_DIR}/${CMAKE_INSTALL_LIBDIR}/liblinphone/plugins") if(WIN32) set(LINPHONE_CONFIG_DIR "Linphone") endif() -set(PACKAGE_LOCALE_DIR "${LINPHONE_DATA_DIR}/share/locale") -set(PACKAGE_DATA_DIR "${LINPHONE_DATA_DIR}/share") -set(PACKAGE_SOUND_DIR "${LINPHONE_DATA_DIR}/share/sounds/linphone") +set(PACKAGE_LOCALE_DIR "${LINPHONE_DATA_DIR}/${CMAKE_INSTALL_DATADIR}/locale") +set(PACKAGE_DATA_DIR "${LINPHONE_DATA_DIR}/${CMAKE_INSTALL_DATADIR}") +set(PACKAGE_SOUND_DIR "${LINPHONE_DATA_DIR}/${CMAKE_INSTALL_DATADIR}/sounds/linphone") set(PACKAGE_RING_DIR "${PACKAGE_SOUND_DIR}/rings") set(PACKAGE_FREEDESKTOP_DIR "${PACKAGE_DATA_DIR}/applications") configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h) @@ -270,6 +298,7 @@ else() endif() +add_subdirectory(java) add_subdirectory(coreapi) add_subdirectory(share) if(ENABLE_CONSOLE_UI) @@ -283,7 +312,7 @@ endif() if(ENABLE_TOOLS) add_subdirectory(tools) endif() -if(ENABLE_UNIT_TESTS) +if(ENABLE_UNIT_TESTS AND BCTOOLBOX_TESTER_FOUND) add_subdirectory(tester) endif() @@ -302,13 +331,13 @@ configure_file(cmake/LinphoneConfig.cmake.in @ONLY ) -set(ConfigPackageLocation lib/cmake/Linphone) +set(CONFIG_PACKAGE_LOCATION "${CMAKE_INSTALL_DATADIR}/Linphone/cmake") install(EXPORT ${EXPORT_TARGETS_NAME}Targets FILE LinphoneTargets.cmake - DESTINATION ${ConfigPackageLocation} + DESTINATION ${CONFIG_PACKAGE_LOCATION} ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/LinphoneConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/LinphoneConfigVersion.cmake" - DESTINATION ${ConfigPackageLocation} + DESTINATION ${CONFIG_PACKAGE_LOCATION} ) diff --git a/Makefile.am b/Makefile.am index c3b462465..168c5492c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -243,7 +243,7 @@ pkg: $(MACAPPNAME) cp ${srcdir}/pixmaps/linphone.png ./packaging pkgbuild --install-location /Applications --scripts ${srcdir}/build/macos/pkg-scripts --component $(MACAPPNAME) ./packaging/linphone.pkg productbuild --resources . --distribution ${srcdir}/build/macos/pkg-distribution.xml --package-path ./packaging $(MACAPPPKG) - + signed-pkg: pkg mv $(MACAPPPKG) $(MACAPPPKG).tmp productsign --sign "$(BUNDLE_SIGNING_ID)" $(MACAPPPKG).tmp $(MACAPPPKG) @@ -258,7 +258,7 @@ clean-local: discovery: touch specs.c $(CC) --include $(top_builddir)/config.h \ - $(TUNNEL_CFLAGS) $(CFLAGS) $(MEDIASTREAMER2_CFLAGS) $(ORTP_CFLAGS) $(SIPSTACK_CFLAGS) $(CUNIT_CFLAGS) -E -P -v -dD specs.c + $(TUNNEL_CFLAGS) $(CFLAGS) $(MEDIASTREAMER2_CFLAGS) $(ORTP_CFLAGS) $(SIPSTACK_CFLAGS) $(BCTOOLBOXTESTER_CFLAGS) -E -P -v -dD specs.c .PHONY: $(MACAPPNAME) pkg diff --git a/README b/README index 7e0b97524..7b54bdd42 100644 --- a/README +++ b/README @@ -11,6 +11,7 @@ This is Linphone, a free (GPL) video softphone based on the SIP protocol. - belle-sip>=1.3.0 - speex>=1.2.0 (including libspeexdsp part) - libxml2 + - bctoolbox + if you want the gtk/glade interface: - libgtk >=2.16.0 diff --git a/build/android/Android.mk b/build/android/Android.mk index 96fe57e16..705ddc2ab 100755 --- a/build/android/Android.mk +++ b/build/android/Android.mk @@ -45,14 +45,16 @@ LOCAL_SRC_FILES := \ callbacks.c \ call_log.c \ call_params.c \ + carddav.c \ chat.c \ chat_file_transfer.c \ - conference.c \ + conference.cc \ content.c \ ec-calibrator.c \ enum.c \ event.c \ friend.c \ + friendlist.c \ info.c \ linphonecall.c \ linphonecore.c \ @@ -76,7 +78,8 @@ LOCAL_SRC_FILES := \ xml2lpc.c \ xml.c \ xmlrpc.c \ - vtables.c + vtables.c \ + ringtoneplayer.c ifndef LIBLINPHONE_VERSION LIBLINPHONE_VERSION = "Devel" @@ -121,11 +124,12 @@ LOCAL_C_INCLUDES += \ $(LOCAL_PATH)/../oRTP/include \ $(LOCAL_PATH)/../mediastreamer2/include \ $(LOCAL_PATH)/../mediastreamer2/src/audiofilters/ \ + $(LOCAL_PATH)/../../bctoolbox/include \ $(LOCAL_PATH)/../../belle-sip/include \ $(LOCAL_PATH)/../../../gen \ $(LOCAL_PATH)/../../externals/libxml2/include \ $(LOCAL_PATH)/../../externals/build/libxml2 \ - $(LOCAL_PATH)/../../externals/polarssl/include + $(LOCAL_PATH)/../../externals/polarssl/include \ LOCAL_LDLIBS += -llog -ldl -lz @@ -134,6 +138,7 @@ LOCAL_STATIC_LIBRARIES := \ libmediastreamer2 \ libortp \ libbellesip \ + libbctoolbox \ libgsm \ liblpxml2 @@ -180,33 +185,52 @@ LOCAL_CFLAGS += -DHAVE_CODEC2 LOCAL_STATIC_LIBRARIES += libcodec2 libmscodec2 endif -ifneq ($(BUILD_WEBRTC_AECM)$(BUILD_WEBRTC_ISAC),00) +ifneq ($(BUILD_WEBRTC_AECM)$(BUILD_WEBRTC_ISAC)$(BUILD_ILBC),000) LOCAL_CFLAGS += -DHAVE_WEBRTC LOCAL_STATIC_LIBRARIES += libmswebrtc endif + ifneq ($(BUILD_WEBRTC_AECM),0) LOCAL_STATIC_LIBRARIES += \ - libwebrtc_aecm \ + libwebrtc_aecm +ifeq ($(TARGET_ARCH_ABI), armeabi-v7a) +LOCAL_STATIC_LIBRARIES += \ + libwebrtc_aecm_neon +endif +endif + + +ifneq ($(BUILD_WEBRTC_ISAC),0) +LOCAL_STATIC_LIBRARIES += \ + libwebrtc_isacfix +ifeq ($(TARGET_ARCH_ABI), armeabi-v7a) +LOCAL_STATIC_LIBRARIES += \ + libwebrtc_isacfix_neon +endif +endif + +ifneq ($(BUILD_ILBC),0) +LOCAL_STATIC_LIBRARIES += \ + libwebrtc_ilbc +endif + + +ifneq ($(BUILD_WEBRTC_AECM)$(BUILD_WEBRTC_ISAC)$(BUILD_ILBC),000) + +LOCAL_STATIC_LIBRARIES += \ + libwebrtc_apm_utility \ + libwebrtc_system_wrappers \ libwebrtc_apm_utility \ libwebrtc_spl \ libwebrtc_system_wrappers ifeq ($(TARGET_ARCH_ABI), armeabi-v7a) LOCAL_STATIC_LIBRARIES += \ - libwebrtc_aecm_neon \ - libwebrtc_spl_neon -endif -endif -ifneq ($(BUILD_WEBRTC_ISAC),0) -LOCAL_STATIC_LIBRARIES += \ - libwebrtc_isacfix \ - libwebrtc_spl -ifeq ($(TARGET_ARCH_ABI), armeabi-v7a) -LOCAL_STATIC_LIBRARIES += \ - libwebrtc_isacfix_neon \ libwebrtc_spl_neon endif + endif + ifeq ($(BUILD_G729),1) LOCAL_CFLAGS += -DHAVE_G729 LOCAL_STATIC_LIBRARIES += libbcg729 libmsbcg729 @@ -238,6 +262,10 @@ ifeq ($(BUILD_SRTP), 1) LOCAL_C_INCLUDES += $(SRTP_C_INCLUDE) endif +ifeq ($(BUILD_VCARD),1) + LOCAL_C_INCLUDES += $(VCARD_C_INCLUDE) +endif + ifeq ($(BUILD_ILBC), 1) ifneq ($(TARGET_ARCH_ABI),armeabi) LOCAL_CFLAGS += -DHAVE_ILBC=1 @@ -259,8 +287,16 @@ ifeq ($(BUILD_SRTP),1) LOCAL_STATIC_LIBRARIES += libsrtp endif +ifeq ($(BUILD_VCARD),1) + LOCAL_CFLAGS += -DVCARD_ENABLED + LOCAL_SRC_FILES += vcard.cc + LOCAL_STATIC_LIBRARIES += libbelr libbelcard +else + LOCAL_SRC_FILES += vcard_stubs.c +endif + ifeq ($(BUILD_SQLITE),1) -LOCAL_CFLAGS += -DMSG_STORAGE_ENABLED -DCALL_LOGS_STORAGE_ENABLED +LOCAL_CFLAGS += -DMSG_STORAGE_ENABLED -DCALL_LOGS_STORAGE_ENABLED -DFRIENDS_SQL_STORAGE_ENABLED LOCAL_STATIC_LIBRARIES += liblinsqlite LOCAL_C_INCLUDES += \ $(LOCAL_PATH)/../../externals/sqlite3/ @@ -282,7 +318,7 @@ LOCAL_MODULE_FILENAME := liblinphone-$(TARGET_ARCH_ABI) include $(BUILD_SHARED_LIBRARY) -LOCAL_CPPFLAGS=$(LOCAL_CFLAGS) +LOCAL_CPPFLAGS += $(LOCAL_CFLAGS) LOCAL_CFLAGS += -Wdeclaration-after-statement LOCAL_LDFLAGS := -Wl,-soname,$(LOCAL_MODULE_FILENAME).so diff --git a/build/android/liblinphone_tester.mk b/build/android/liblinphone_tester.mk index 8395df144..118aaeb88 100644 --- a/build/android/liblinphone_tester.mk +++ b/build/android/liblinphone_tester.mk @@ -1,7 +1,6 @@ LOCAL_PATH := $(call my-dir)/../../tester common_SRC_FILES := \ - common/bc_tester_utils.c \ accountmanager.c \ call_tester.c \ dtmf_tester.c \ @@ -24,14 +23,15 @@ common_SRC_FILES := \ tunnel_tester.c \ upnp_tester.c \ multicast_call_tester.c \ + vcard_tester.c \ + complex_sip_call_tester.c \ common_C_INCLUDES += \ $(LOCAL_PATH) \ $(LOCAL_PATH)/../include \ $(LOCAL_PATH)/../coreapi \ $(LOCAL_PATH)/../oRTP/include \ - $(LOCAL_PATH)/../mediastreamer2/include \ - $(LOCAL_PATH)/common + $(LOCAL_PATH)/../mediastreamer2/include include $(CLEAR_VARS) @@ -47,6 +47,8 @@ ifeq ($(BUILD_MATROSKA), 1) LOCAL_CFLAGS += -DHAVE_MATROSKA -DHAVE_ZLIB endif +LOCAL_STATIC_LIBRARIES := bctoolbox_tester + LOCAL_SHARED_LIBRARIES := cunit liblinphone include $(BUILD_SHARED_LIBRARY) diff --git a/build/macos/linphone.bundle b/build/macos/linphone.bundle index 7d70bf8b2..19793fe78 100644 --- a/build/macos/linphone.bundle +++ b/build/macos/linphone.bundle @@ -187,12 +187,12 @@ ${project}/../../gtk/gtkrc.mac - - ${prefix:linphone}/share/sounds/linphone/rings/oldphone.wav + + ${prefix:linphone}/share/sounds/linphone/rings/oldphone-mono.wav - - ${prefix:linphone}/share/sounds/linphone/rings/toy-mono.wav + + ${prefix:linphone}/share/sounds/linphone/toy-mono.wav diff --git a/build/windows10/liblinphone-tester/liblinphone-tester.csproj b/build/windows10/liblinphone-tester/liblinphone-tester.csproj index ab018cc05..7cf0be904 100644 --- a/build/windows10/liblinphone-tester/liblinphone-tester.csproj +++ b/build/windows10/liblinphone-tester/liblinphone-tester.csproj @@ -277,6 +277,12 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + @@ -309,7 +315,8 @@ XCopy /I /Y $(ProjectDir)..\..\..\tester\certificates\altname $(ProjectDir)Asset XCopy /I /Y $(ProjectDir)..\..\..\tester\certificates\cn $(ProjectDir)Assets\certificates\cn XCopy /I /Y $(ProjectDir)..\..\..\tester\images $(ProjectDir)Assets\images XCopy /I /Y $(ProjectDir)..\..\..\tester\rcfiles $(ProjectDir)Assets\rcfiles -XCopy /I /Y $(ProjectDir)..\..\..\tester\sounds $(ProjectDir)Assets\sounds +XCopy /I /Y $(ProjectDir)..\..\..\tester\sounds $(ProjectDir)Assets\sounds +XCopy /I /Y $(ProjectDir)..\..\..\tester\common $(ProjectDir)Assets\common - \ No newline at end of file + diff --git a/cmake/FindCUnit.cmake b/cmake/FindCUnit.cmake deleted file mode 100644 index 10c1a10b6..000000000 --- a/cmake/FindCUnit.cmake +++ /dev/null @@ -1,58 +0,0 @@ -############################################################################ -# FindCUnit.txt -# Copyright (C) 2015 Belledonne Communications, Grenoble France -# -############################################################################ -# -# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -# -############################################################################ -# -# - Find the CUnit include file and library -# -# CUNIT_FOUND - system has CUnit -# CUNIT_INCLUDE_DIRS - the CUnit include directory -# CUNIT_LIBRARIES - The libraries needed to use CUnit - -include(CheckIncludeFile) -include(CheckLibraryExists) - -set(_CUNIT_ROOT_PATHS - ${CMAKE_INSTALL_PREFIX} -) - -find_path(CUNIT_INCLUDE_DIRS - NAMES CUnit/CUnit.h - HINTS _CUNIT_ROOT_PATHS - PATH_SUFFIXES include -) - -if(CUNIT_INCLUDE_DIRS) - set(HAVE_CUNIT_CUNIT_H 1) -endif() - -find_library(CUNIT_LIBRARIES - NAMES cunit - HINTS ${_CUNIT_ROOT_PATHS} - PATH_SUFFIXES bin lib -) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(CUnit - DEFAULT_MSG - CUNIT_INCLUDE_DIRS CUNIT_LIBRARIES -) - -mark_as_advanced(CUNIT_INCLUDE_DIRS CUNIT_LIBRARIES) diff --git a/cmake/FindZlib.cmake b/cmake/FindZlib.cmake index 17b2d1e39..f29d2a8db 100644 --- a/cmake/FindZlib.cmake +++ b/cmake/FindZlib.cmake @@ -26,10 +26,6 @@ # ZLIB_INCLUDE_DIRS - the zlib include directory # ZLIB_LIBRARIES - The libraries needed to use zlib -set(_ZLIB_ROOT_PATHS - ${CMAKE_INSTALL_PREFIX} -) - find_path(ZLIB_INCLUDE_DIRS NAMES zlib.h HINTS _ZLIB_ROOT_PATHS @@ -41,20 +37,13 @@ if(ZLIB_INCLUDE_DIRS) endif() if(ENABLE_STATIC) - if(IOS OR QNX) - set(_ZLIB_STATIC_NAMES z) - else() - set(_ZLIB_STATIC_NAMES zstatic zlibstatic zlibstaticd) - endif() find_library(ZLIB_LIBRARIES - NAMES ${_ZLIB_STATIC_NAMES} - HINTS ${_ZLIB_ROOT_PATHS} + NAMES zstatic zlibstatic zlibstaticd z PATH_SUFFIXES bin lib ) else() find_library(ZLIB_LIBRARIES NAMES z zlib zlibd - HINTS ${_ZLIB_ROOT_PATHS} PATH_SUFFIXES bin lib ) endif() diff --git a/cmake/LinphoneConfig.cmake.in b/cmake/LinphoneConfig.cmake.in index 9bcd9dfa4..8c9ef3944 100644 --- a/cmake/LinphoneConfig.cmake.in +++ b/cmake/LinphoneConfig.cmake.in @@ -36,12 +36,27 @@ endif() find_package(Mediastreamer2 REQUIRED) find_package(BelleSIP REQUIRED) if(@ENABLE_TUNNEL@) - find_package(Tunnel) + if(LINPHONE_BUILDER_GROUP_EXTERNAL_SOURCE_PATH_BUILDERS) + include("${EP_tunnel_CONFIG_DIR}/TunnelConfig.cmake") + else() + find_package(Tunnel) + endif() +endif() +if(@ENABLE_VCARD@) + if(LINPHONE_BUILDER_GROUP_EXTERNAL_SOURCE_PATH_BUILDERS) + include("${EP_belcard_CONFIG_DIR}/BelcardConfig.cmake") + else() + find_package(Belcard) + endif() endif() get_filename_component(LINPHONE_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) set(LINPHONE_INCLUDE_DIRS "${LINPHONE_CMAKE_DIR}/../../../include") -set(LINPHONE_LIBRARIES linphone) +if(@ENABLE_SHARED@) + set(LINPHONE_LIBRARIES linphone) +else() + set(LINPHONE_LIBRARIES linphone-static) +endif() set(LINPHONE_LDFLAGS @LINK_FLAGS@) list(APPEND LINPHONE_INCLUDE_DIRS ${MEDIASTREAMER2_INCLUDE_DIRS} ${BELLESIP_INCLUDE_DIRS}) list(APPEND LINPHONE_LIBRARIES ${MEDIASTREAMER2_LIBRARIES} ${BELLESIP_LIBRARIES}) @@ -51,4 +66,8 @@ if(TUNNEL_FOUND) list(APPEND LINPHONE_INCLUDE_DIRS ${TUNNEL_INCLUDE_DIRS}) list(APPEND LINPHONE_LIBRARIES ${TUNNEL_LIBRARIES}) endif() +if(BELCARD_FOUND) + list(APPEND LINPHONE_INCLUDE_DIRS ${BELCARD_INCLUDE_DIRS}) + list(APPEND LINPHONE_LIBRARIES ${BELCARD_LIBRARIES}) +endif() set(LINPHONE_FOUND 1) diff --git a/configure.ac b/configure.ac index ffd399813..aefc30f2d 100644 --- a/configure.ac +++ b/configure.ac @@ -17,7 +17,7 @@ if test "$LINPHONE_EXTRA_VERSION" != "" ;then LINPHONE_VERSION=$LINPHONE_VERSION.${LINPHONE_EXTRA_VERSION} fi -LIBLINPHONE_SO_CURRENT=8 dnl increment this number when you add/change/remove an interface +LIBLINPHONE_SO_CURRENT=9 dnl increment this number when you add/change/remove an interface LIBLINPHONE_SO_REVISION=0 dnl increment this number when you change source code, without changing interfaces; set to 0 when incrementing CURRENT LIBLINPHONE_SO_AGE=0 dnl increment this number when you add an interface, set to 0 if you remove an interface @@ -39,6 +39,7 @@ AC_CONFIG_MACRO_DIR([m4]) dnl do not put anythingelse before AC_PROG_CC unless checking if macro still work for clang AC_PROG_CXX(["xcrun clang++" g++]) AC_PROG_CC(["xcrun clang" gcc]) +AC_PROG_OBJC(["xcrun clang" gcc]) gl_LD_OUTPUT_DEF @@ -595,6 +596,8 @@ AC_ARG_WITH(ffmpeg, [ ffmpegdir=/usr ] ) +AM_CONDITIONAL([BUILD_MACOS], [test "x$build_macos" = "xyes"]) + if test "$video" = "true"; then if test "$enable_x11" = "true"; then @@ -877,6 +880,40 @@ if test x$enable_tunnel = xtrue; then AC_DEFINE(TUNNEL_ENABLED,1,[Tells tunnel extension is built-in]) fi +AC_ARG_ENABLE(vcard, + [AS_HELP_STRING([--enable-vcard=[yes/no]], [Turn on compilation of vcard (default=auto)])], + [case "${enableval}" in + yes) enable_vcard=true ;; + no) enable_vcard=false ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-vcard) ;; + esac], + [enable_vcard=auto] +) + +if test x$enable_vcard != xfalse; then + PKG_CHECK_MODULES(BELCARD, belcard, [found_vcard=yes],[found_vcard=no]) + if test "$found_vcard" = "no"; then + dnl Check the lib presence in case the PKG-CONFIG version is not found + AC_LANG_CPLUSPLUS + AC_CHECK_LIB(belcard, main, [BELCARD_LIBS+=" -lbelr -lbelcard"; found_vcard=yes], [foo=bar]) + AC_LANG_C + fi + if test "$found_vcard" = "yes"; then + BELCARD_CFLAGS+=" -DVCARD_ENABLED" + enable_vcard=true + else + if test x$enable_vcard = xtrue; then + AC_MSG_ERROR([belcard, required for vcard support, not found]) + fi + enable_vcard=false + fi + + AC_SUBST(BELCARD_CFLAGS) + AC_SUBST(BELCARD_LIBS) +fi + +AM_CONDITIONAL(BUILD_VCARD, test x$enable_vcard = xtrue) + AC_ARG_ENABLE(msg-storage, [AS_HELP_STRING([--enable-msg-storage=[yes/no]], [Turn on compilation of message storage (default=auto)])], [case "${enableval}" in @@ -947,6 +984,41 @@ fi AM_CONDITIONAL(BUILD_CALL_LOGS_STORAGE, test x$enable_call_logs_storage = xtrue) +AC_ARG_ENABLE(friends-db-storage, + [AS_HELP_STRING([--enable-friends-db-storage=[yes/no]], [Turn on compilation of friends database storage (default=auto)])], + [case "${enableval}" in + yes) enable_friends_db_storage=true ;; + no) enable_friends_db_storage=false ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-friends-db-storage) ;; + esac], + [enable_friends_db_storage=auto] +) + +if test x$enable_friends_db_storage != xfalse; then + PKG_CHECK_MODULES(SQLITE3,[sqlite3 >= 3.6.0],[found_sqlite=yes],[found_sqlite=no]) + if test "$found_sqlite" = "no"; then + dnl Check the lib presence in case the PKG-CONFIG version is not found + AC_CHECK_LIB(sqlite3, sqlite3_open, [SQLITE3_LIBS+=" -lsqlite3 "; found_sqlite=yes], [foo=bar]) + fi + if test "$found_sqlite" = "yes"; then + SQLITE3_CFLAGS+=" -DFRIENDS_SQL_STORAGE_ENABLED" + if test "$build_macos" = "yes" -o "$ios_found" = "yes"; then + SQLITE3_LIBS+=" -liconv" + fi + enable_friends_db_storage=true + else + if test x$enable_friends_db_storage = xtrue; then + AC_MSG_ERROR([sqlite3, required for friends database storage, not found]) + fi + enable_friends_db_storage=false + fi + + AC_SUBST(SQLITE3_CFLAGS) + AC_SUBST(SQLITE3_LIBS) +fi + +AM_CONDITIONAL(BUILD_FRIENDS_DB_STORAGE, test x$enable_friends_db_storage = xtrue) + PKG_CHECK_MODULES(BELLESIP, [belle-sip >= 1.4.0]) SIPSTACK_CFLAGS="$BELLESIP_CFLAGS" @@ -997,44 +1069,13 @@ AC_ARG_ENABLE(tests, ) AM_CONDITIONAL(ENABLE_TESTS, test x$tests_enabled = xyes) -PKG_CHECK_MODULES(CUNIT, cunit, [found_cunit=yes],[found_cunit=no]) +PKG_CHECK_MODULES(BCTOOLBOXTESTER, bctoolbox-tester, [found_pkg_config_bctoolboxtester=yes],[found_pkg_config_bctoolboxtester=no]) -if test "$found_cunit" = "no" ; then - AC_CHECK_HEADERS(CUnit/CUnit.h, - [ - AC_CHECK_LIB(cunit,CU_add_suite,[ - found_cunit=yes - CUNIT_LIBS+=" -lcunit" - ]) - - ]) +AM_CONDITIONAL([ENABLE_TESTS], [test x$found_pkg_config_bctoolboxtester = xyes && test x$tests_enabled != xfalse]) +if test "$found_pkg_config_bctoolboxtester" = "no" ; then + AC_MSG_WARN([Could not find bctoolbox-tester wrapper, tests are not compiled.]) fi -case "$target_os" in - *darwin*) - #hack for macport - CUNIT_LIBS+=" -lncurses" - ;; -esac -AM_CONDITIONAL([BUILD_CUNIT_TESTS], [test x$found_cunit = xyes && test x$tests_enabled != xfalse]) -if test "$found_cunit" = "no" ; then - AC_MSG_WARN([Could not find cunit framework, tests are not compiled.]) -else - AC_CHECK_LIB(cunit,CU_get_suite,[ - AC_DEFINE(HAVE_CU_GET_SUITE,1,[defined when CU_get_suite is available]) - ],[foo=bar],[$CUNIT_LIBS]) - - AC_CHECK_LIB(cunit,CU_curses_run_tests,[ - AC_DEFINE(HAVE_CU_CURSES,1,[defined when CU_curses_run_tests is available]) - ],[foo=bar],[$CUNIT_LIBS]) -fi - -case "$target_os" in - *linux*) - # Eliminate -lstdc++ addition to postdeps for cross compiles. - postdeps_CXX=`echo " $postdeps_CXX " | sed 's, -lstdc++ ,,g'` - ;; -esac dnl ################################################## @@ -1106,7 +1147,9 @@ printf "* %-30s %s\n" "Account assistant" $build_wizard printf "* %-30s %s\n" "Console interface" $console_ui printf "* %-30s %s\n" "Tools" $build_tools printf "* %-30s %s\n" "Message storage" $enable_msg_storage -printf "* %-30s %s\n" "Call logs storage" $enable_call_logs_storage +printf "* %-30s %s\n" "Call logs storage" $enable_call_logs_storage +printf "* %-30s %s\n" "Friends db storage" $enable_friends_db_storage +printf "* %-30s %s\n" "VCard support" $enable_vcard printf "* %-30s %s\n" "IM encryption" $lime printf "* %-30s %s\n" "uPnP support" $build_upnp printf "* %-30s %s\n" "LDAP support" $enable_ldap diff --git a/console/CMakeLists.txt b/console/CMakeLists.txt index 4b7f4df24..3ec2b9e44 100644 --- a/console/CMakeLists.txt +++ b/console/CMakeLists.txt @@ -47,8 +47,8 @@ if(WIN32) endif() install(TARGETS ${INSTALL_TARGETS} - RUNTIME DESTINATION bin - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE ) diff --git a/console/commands.c b/console/commands.c index f2c316325..095ab3f18 100644 --- a/console/commands.c +++ b/console/commands.c @@ -35,7 +35,7 @@ #include "linphonec.h" #include "lpconfig.h" -#ifndef WIN32 +#ifndef _WIN32 #include #include #endif @@ -118,7 +118,7 @@ static void linphonec_friend_display(LinphoneFriend *fr); static int linphonec_friend_list(LinphoneCore *lc, char *arg); static void linphonec_display_command_help(LPC_COMMAND *cmd); static int linphonec_friend_call(LinphoneCore *lc, unsigned int num); -#ifndef WIN32 +#ifndef _WIN32 static int linphonec_friend_add(LinphoneCore *lc, const char *name, const char *addr); #endif static int linphonec_friend_delete(LinphoneCore *lc, int num); @@ -287,10 +287,10 @@ static LPC_COMMAND advanced_commands[] = { "'codec list' : list audio codecs\n" "'codec enable ' : enable available audio codec\n" "'codec disable ' : disable audio codec" }, - { "vcodec", lpc_cmd_vcodec, "Video codec configuration", - "'vcodec list' : list video codecs\n" - "'vcodec enable ' : enable available video codec\n" - "'vcodec disable ' : disable video codec" }, + { "vcodec", lpc_cmd_vcodec, "Video codec configuration", + "'vcodec list' : list video codecs\n" + "'vcodec enable ' : enable available video codec\n" + "'vcodec disable ' : disable video codec" }, { "ec", lpc_cmd_echocancellation, "Echo cancellation", "'ec on [] [] []' : turn EC on with given delay, tail length and framesize\n" "'ec off' : turn echo cancellation (EC) off\n" @@ -367,7 +367,8 @@ static LPC_COMMAND advanced_commands[] = { "'ringback disable'\t: Disable playing of ringback tone to callers\n" }, { "redirect", lpc_cmd_redirect, "Redirect an incoming call", - "'redirect '\t: Redirect all pending incoming calls to the \n" + "'redirect '\t: Redirect the specified call to the \n" + "'redirect all '\t: Redirect all pending incoming calls to the \n" }, { "zrtp-set-verified", lpc_cmd_zrtp_verified,"Set ZRTP SAS verified.", "'Set ZRTP SAS verified'\n" @@ -414,7 +415,8 @@ linphonec_parse_command_line(LinphoneCore *lc, char *cl) { while ( isdigit(*cl) || *cl == '#' || *cl == '*' ) { - linphone_core_send_dtmf(lc, *cl); + if (linphone_core_get_current_call(lc)) + linphone_call_send_dtmf(linphone_core_get_current_call(lc), *cl); linphone_core_play_dtmf (lc,*cl,100); ms_sleep(1); // be nice ++cl; @@ -739,17 +741,37 @@ lpc_cmd_redirect(LinphoneCore *lc, char *args){ linphonec_out("No active calls.\n"); return 1; } - while(elem!=NULL){ - LinphoneCall *call=(LinphoneCall*)elem->data; - if (linphone_call_get_state(call)==LinphoneCallIncomingReceived){ - linphone_core_redirect_call(lc,call,args); - didit=1; - /*as the redirection closes the call, we need to re-check the call list that is invalidated.*/ - elem=linphone_core_get_calls(lc); - }else elem=elem->next; - } - if (didit==0){ - linphonec_out("There is no pending incoming call to redirect."); + if (strncmp(args, "all ", 4) == 0) { + while(elem!=NULL){ + LinphoneCall *call=(LinphoneCall*)elem->data; + if (linphone_call_get_state(call)==LinphoneCallIncomingReceived){ + if (linphone_core_redirect_call(lc,call,args+4) != 0) { + linphonec_out("Could not redirect call.\n"); + elem=elem->next; + } else { + didit=1; + /*as the redirection closes the call, we need to re-check the call list that is invalidated.*/ + elem=linphone_core_get_calls(lc); + } + }else elem=elem->next; + } + if (didit==0){ + linphonec_out("There is no pending incoming call to redirect.\n"); + } + } else { + char space; + long id; + int charRead; + if ( sscanf(args, "%li%c%n", &id, &space, &charRead) == 2 && space == ' ') { + LinphoneCall * call = linphonec_get_call(id); + if ( call != NULL ) { + if (linphone_call_get_state(call)!=LinphoneCallIncomingReceived) { + linphonec_out("The state of the call is not incoming, can't be redirected.\n"); + } else if (linphone_core_redirect_call(lc,call,args+charRead) != 0) { + linphonec_out("Could not redirect call.\n"); + } + } + } else return 0; } return 1; } @@ -927,7 +949,7 @@ lpc_cmd_firewall(LinphoneCore *lc, char *args) return 1; } -#ifndef WIN32 +#ifndef _WIN32 /* Helper function for processing freind names */ static int lpc_friend_name(char **args, char **name) @@ -1012,7 +1034,7 @@ lpc_cmd_friend(LinphoneCore *lc, char *args) } else if ( !strncmp(args, "add", 3) ) { -#ifndef WIN32 +#ifndef _WIN32 char *name; char addr[80]; char *addr_p = addr; @@ -1040,7 +1062,7 @@ lpc_cmd_friend(LinphoneCore *lc, char *args) linphonec_friend_add(lc, name, addr); #else LinphoneFriend *new_friend; - new_friend = linphone_friend_new_with_address(args); + new_friend = linphone_core_create_friend_with_address(lc, args); linphone_core_add_friend(lc, new_friend); #endif return 1; @@ -1242,12 +1264,16 @@ static int lpc_cmd_soundcard(LinphoneCore *lc, char *args) if (strcmp(arg1, "show")==0) { - linphonec_out("Ringer device: %s\n", - linphone_core_get_ringer_device(lc)); - linphonec_out("Playback device: %s\n", - linphone_core_get_playback_device(lc)); - linphonec_out("Capture device: %s\n", - linphone_core_get_capture_device(lc)); + if (linphone_core_get_use_files(lc)) { + linphonec_out("Using files.\n"); + } else { + linphonec_out("Ringer device: %s\n", + linphone_core_get_ringer_device(lc)); + linphonec_out("Playback device: %s\n", + linphone_core_get_playback_device(lc)); + linphonec_out("Capture device: %s\n", + linphone_core_get_capture_device(lc)); + } return 1; } @@ -1260,6 +1286,8 @@ static int lpc_cmd_soundcard(LinphoneCore *lc, char *args) return 1; } + linphone_core_use_files(lc,FALSE); + dev=linphone_core_get_sound_devices(lc); index=atoi(arg2); /* FIXME: handle not-a-number */ for(i=0;dev[i]!=NULL;i++) @@ -1275,6 +1303,7 @@ static int lpc_cmd_soundcard(LinphoneCore *lc, char *args) linphonec_out("No such sound device\n"); return 1; } + if (strcmp(arg1, "capture")==0) { const char *devname=linphone_core_get_capture_device(lc); @@ -1857,7 +1886,7 @@ linphonec_friend_call(LinphoneCore *lc, unsigned int num) return 1; } -#ifndef WIN32 +#ifndef _WIN32 static int linphonec_friend_add(LinphoneCore *lc, const char *name, const char *addr) { @@ -1866,7 +1895,7 @@ linphonec_friend_add(LinphoneCore *lc, const char *name, const char *addr) char url[PATH_MAX]; snprintf(url, PATH_MAX, "%s <%s>", name, addr); - newFriend = linphone_friend_new_with_address(url); + newFriend = linphone_core_create_friend_with_address(lc, url); linphone_core_add_friend(lc, newFriend); return 0; } @@ -1917,8 +1946,7 @@ static int lpc_cmd_register(LinphoneCore *lc, char *args){ if (!args) { /* it means that you want to register the default proxy */ - LinphoneProxyConfig *cfg=NULL; - linphone_core_get_default_proxy(lc,&cfg); + LinphoneProxyConfig *cfg = linphone_core_get_default_proxy_config(lc); if (cfg) { if(!linphone_proxy_config_is_registered(cfg)) { @@ -1965,8 +1993,7 @@ static int lpc_cmd_register(LinphoneCore *lc, char *args){ } static int lpc_cmd_unregister(LinphoneCore *lc, char *args){ - LinphoneProxyConfig *cfg=NULL; - linphone_core_get_default_proxy(lc,&cfg); + LinphoneProxyConfig *cfg = linphone_core_get_default_proxy_config(lc); if (cfg && linphone_proxy_config_is_registered(cfg)) { linphone_proxy_config_edit(cfg); linphone_proxy_config_enable_register(cfg,FALSE); @@ -1994,7 +2021,7 @@ static int lpc_cmd_status(LinphoneCore *lc, char *args) LinphoneProxyConfig *cfg; if ( ! args ) return 0; - linphone_core_get_default_proxy(lc,&cfg); + cfg = linphone_core_get_default_proxy_config(lc); if (strstr(args,"register")) { if (cfg) @@ -2110,7 +2137,7 @@ static int lpc_cmd_param(LinphoneCore *lc, char *args) } static int lpc_cmd_speak(LinphoneCore *lc, char *args){ -#ifndef WIN32 +#ifndef _WIN32 char voice[64]; char *sentence; char cl[128]; @@ -2336,12 +2363,12 @@ static int lpc_cmd_echolimiter(LinphoneCore *lc, char *args){ static int lpc_cmd_mute_mic(LinphoneCore *lc, char *args) { - linphone_core_mute_mic(lc, 1); + linphone_core_enable_mic(lc, 0); return 1; } static int lpc_cmd_unmute_mic(LinphoneCore *lc, char *args){ - linphone_core_mute_mic(lc, 0); + linphone_core_enable_mic(lc, 1); return 1; } @@ -2452,7 +2479,7 @@ static void lpc_display_call_states(LinphoneCore *lc){ const char *flag; bool_t in_conference; call=(LinphoneCall*)elem->data; - in_conference=linphone_call_params_get_local_conference_mode(linphone_call_get_current_params(call)); + in_conference=(linphone_call_get_conference(call) != NULL); tmp=linphone_call_get_remote_address_as_string (call); flag=in_conference ? "conferencing" : ""; flag=linphone_call_has_transfer_pending(call) ? "transfer pending" : flag; diff --git a/console/linphonec.c b/console/linphonec.c index 709940269..07ac95729 100644 --- a/console/linphonec.c +++ b/console/linphonec.c @@ -38,7 +38,7 @@ #include "linphonec.h" -#ifdef WIN32 +#ifdef _WIN32 #include #include #ifndef _WIN32_WCE @@ -102,7 +102,7 @@ static int linphonec_main_loop (LinphoneCore * opm); static int linphonec_idle_call (void); #ifdef HAVE_READLINE static int linphonec_initialize_readline(void); -static int linphonec_finish_readline(); +static int linphonec_finish_readline(void); static char **linephonec_readline_completion(const char *text, int start, int end); #endif @@ -439,7 +439,7 @@ static void start_prompt_reader(void){ #if !defined(_WIN32_WCE) static ortp_pipe_t create_server_socket(void){ char path[128]; -#ifndef WIN32 +#ifndef _WIN32 snprintf(path,sizeof(path)-1,"linphonec-%i",getuid()); #else { @@ -459,7 +459,7 @@ static void *pipe_thread(void*p){ if (server_sock==ORTP_PIPE_INVALID) return NULL; while(pipe_reader_run){ while(client_sock!=ORTP_PIPE_INVALID){ /*sleep until the last command is finished*/ -#ifndef WIN32 +#ifndef _WIN32 usleep(20000); #else Sleep(20); @@ -537,7 +537,7 @@ char *linphonec_readline(char *prompt){ } ms_mutex_unlock(&prompt_mutex); linphonec_idle_call(); -#ifdef WIN32 +#ifdef _WIN32 { MSG msg; Sleep(20); diff --git a/console/shell.c b/console/shell.c index 7fbccb4c3..850446ab7 100644 --- a/console/shell.c +++ b/console/shell.c @@ -25,7 +25,7 @@ #include #include -#ifdef WIN32 +#ifdef _WIN32 #include #include #include @@ -82,7 +82,7 @@ static int send_command(const char *command, char *reply, int reply_len, int pri int i; int err; char path[128]; -#ifndef WIN32 +#ifndef _WIN32 snprintf(path,sizeof(path)-1,"linphonec-%i",getuid()); #else { @@ -130,7 +130,7 @@ static void print_usage(void){ exit(-1); } -#ifdef WIN32 +#ifdef _WIN32 static char *argv_to_line(int argc, char *argv[]) { int i; int line_length; @@ -157,7 +157,7 @@ static char *argv_to_line(int argc, char *argv[]) { #define MAX_ARGS 10 -#ifndef WIN32 +#ifndef _WIN32 static void spawn_linphonec(int argc, char *argv[]){ char * args[MAX_ARGS]; int i,j; diff --git a/console/sipomatic.c b/console/sipomatic.c index e7a1c6a88..92f4fe572 100644 --- a/console/sipomatic.c +++ b/console/sipomatic.c @@ -76,7 +76,7 @@ void call_accept(Call *call) sdp_context_t *ctx; PayloadType *payload; char *hellofile; - static int call_count=0; + static int call_count=0; char record_file[250]; osip_message_t *msg=NULL; sprintf(record_file,"/tmp/sipomatic%i.wav",call_count); @@ -105,7 +105,7 @@ void call_accept(Call *call) #ifdef VIDEO_ENABLED if (call->video.remoteport!=0){ video_stream_send_only_start(call->video_stream,call->profile, - call->video.remaddr,call->video.remoteport,call->video.remoteport+1,call->video.pt, 60, + call->video.remaddr,call->video.remoteport,call->video.remoteport+1,call->video.pt, 60, ms_web_cam_manager_get_default_cam(ms_web_cam_manager_get())); } #endif @@ -124,7 +124,7 @@ PayloadType * sipomatic_payload_is_supported(sdp_payload_t *payload,RtpProfile * localpt=payload->pt; ms_warning("payload has no rtpmap."); } - + if (localpt>=0){ /* this payload is supported in our local rtp profile, so add it to the dialog rtp profile */ @@ -160,7 +160,7 @@ int sipomatic_accept_audio_offer(sdp_context_t *ctx,sdp_payload_t *payload) Call *call=(Call*)sdp_context_get_user_pointer(ctx); PayloadType *supported; struct stream_params *params=&call->audio; - + /* see if this codec is supported in our local rtp profile*/ supported=sipomatic_payload_is_supported(payload,&av_profile,call->profile); if (supported==NULL) { @@ -191,7 +191,7 @@ int sipomatic_accept_video_offer(sdp_context_t *ctx,sdp_payload_t *payload) Call *call=(Call*)sdp_context_get_user_pointer(ctx); PayloadType *supported; struct stream_params *params=&call->video; - + /* see if this codec is supported in our local rtp profile*/ supported=sipomatic_payload_is_supported(payload,&av_profile,call->profile); if (supported==NULL) { @@ -221,9 +221,9 @@ void sipomatic_init(Sipomatic *obj, char *url, bool_t ipv6) { osip_uri_t *uri=NULL; int port=5064; - + obj->ipv6=ipv6; - + if (url==NULL){ url=getenv("SIPOMATIC_URL"); if (url==NULL){ @@ -237,7 +237,7 @@ void sipomatic_init(Sipomatic *obj, char *url, bool_t ipv6) if (uri->port!=NULL) port=atoi(uri->port); }else{ ms_warning("Invalid identity uri:%s",url); - } + } } ms_message("Starting using url %s",url); ms_mutex_init(&obj->lock,NULL); @@ -324,7 +324,7 @@ Call * call_new(Sipomatic *root, eXosip_event_t *ev) int status; sdp_message_t *sdp; sdp_context_t *sdpc; - + sdp=eXosip_get_sdp_info(ev->request); sdpc=sdp_handler_create_context(&sipomatic_sdp_handler,NULL,"sipomatic",NULL); obj=ms_new0(Call,1); @@ -334,7 +334,7 @@ Call * call_new(Sipomatic *root, eXosip_event_t *ev) sdpans=sdp_context_get_answer(sdpc,sdp); if (sdpans!=NULL){ eXosip_call_send_answer(ev->tid,180,NULL); - + }else{ status=sdp_context_get_status(sdpc); eXosip_call_send_answer(ev->tid,status,NULL); @@ -409,7 +409,7 @@ int main(int argc, char *argv[]) char *url=NULL; bool_t ipv6=FALSE; int i; - + for(i=1;i 0) { + sal_end_background_task(mLongRunningTaskId); + mLongRunningTaskId = 0; + } UdpMirrorClientList::iterator it; for (it = mUdpMirrorClients.begin(); it != mUdpMirrorClients.end();) { UdpMirrorClient& s=*it++; @@ -89,6 +92,7 @@ RtpTransport *TunnelManager::createRtpTransport(int port){ t->t_close=sCloseRtpTransport; t->t_destroy=sDestroyRtpTransport; t->data=socket; + ms_message("Creating tunnel RTP transport for local virtual port %i", port); return t; } @@ -134,7 +138,8 @@ TunnelManager::TunnelManager(LinphoneCore* lc) : mTunnelizeSipPackets(true), mTunnelClient(NULL), mHttpProxyPort(0), - mVTable(NULL) + mVTable(NULL), + mLongRunningTaskId(0) { linphone_core_add_iterate_hook(mCore,(LinphoneCoreIterateHook)sOnIterate,this); mTransportFactories.audio_rtcp_func=sCreateRtpTransport; @@ -152,17 +157,22 @@ TunnelManager::TunnelManager(LinphoneCore* lc) : } TunnelManager::~TunnelManager(){ + if (mLongRunningTaskId > 0) { + sal_end_background_task(mLongRunningTaskId); + mLongRunningTaskId = 0; + } for(UdpMirrorClientList::iterator udpMirror = mUdpMirrorClients.begin(); udpMirror != mUdpMirrorClients.end(); udpMirror++) { udpMirror->stop(); } if(mTunnelClient) delete mTunnelClient; + sal_set_tunnel(mCore->sal,NULL); linphone_core_remove_listener(mCore, mVTable); linphone_core_v_table_destroy(mVTable); } void TunnelManager::doRegistration(){ LinphoneProxyConfig* lProxy; - linphone_core_get_default_proxy(mCore, &lProxy); + lProxy = linphone_core_get_default_proxy_config(mCore); if (lProxy) { ms_message("TunnelManager: New registration"); lProxy->commit = TRUE; @@ -171,7 +181,7 @@ void TunnelManager::doRegistration(){ void TunnelManager::doUnregistration() { LinphoneProxyConfig *lProxy; - linphone_core_get_default_proxy(mCore, &lProxy); + lProxy = linphone_core_get_default_proxy_config(mCore); if(lProxy) { _linphone_proxy_config_unregister(lProxy); } @@ -188,7 +198,7 @@ void TunnelManager::processTunnelEvent(const Event &ev){ _linphone_core_apply_transports(mCore); doRegistration(); } - + } } else { ms_error("TunnelManager: tunnel has been disconnected"); @@ -247,6 +257,10 @@ void TunnelManager::setMode(LinphoneTunnelMode mode) { void TunnelManager::tunnelCallback(bool connected, TunnelManager *zis){ Event ev; + if (zis->mLongRunningTaskId > 0) { + sal_end_background_task(zis->mLongRunningTaskId); + zis->mLongRunningTaskId = 0; + } ev.mType=TunnelEvent; ev.mData.mConnected=connected; zis->postEvent(ev); @@ -323,6 +337,7 @@ void TunnelManager::processUdpMirrorEvent(const Event &ev){ ms_message("TunnelManager: UDP mirror test succeed"); if(mTunnelClient) { if(mTunnelizeSipPackets) doUnregistration(); + sal_set_tunnel(mCore->sal,NULL); delete mTunnelClient; mTunnelClient = NULL; if(mTunnelizeSipPackets) doRegistration(); @@ -333,6 +348,8 @@ void TunnelManager::processUdpMirrorEvent(const Event &ev){ mCurrentUdpMirrorClient++; if (mCurrentUdpMirrorClient !=mUdpMirrorClients.end()) { ms_message("TunnelManager: trying another UDP mirror"); + if (mLongRunningTaskId == 0) + mLongRunningTaskId = sal_begin_background_task("Tunnel auto detect", NULL, NULL); UdpMirrorClient &lUdpMirrorClient=*mCurrentUdpMirrorClient; lUdpMirrorClient.start(TunnelManager::sUdpMirrorClientCallback,(void*)this); } else { @@ -377,6 +394,8 @@ bool TunnelManager::startAutoDetection() { } ms_message("TunnelManager: Starting auto-detection"); mCurrentUdpMirrorClient = mUdpMirrorClients.begin(); + if (mLongRunningTaskId == 0) + mLongRunningTaskId = sal_begin_background_task("Tunnel auto detect", NULL, NULL); UdpMirrorClient &lUdpMirrorClient=*mCurrentUdpMirrorClient; lUdpMirrorClient.start(TunnelManager::sUdpMirrorClientCallback,(void*)this); return true; diff --git a/coreapi/TunnelManager.hh b/coreapi/TunnelManager.hh index f8002ee5d..764c8efda 100644 --- a/coreapi/TunnelManager.hh +++ b/coreapi/TunnelManager.hh @@ -201,6 +201,7 @@ namespace belledonnecomm { Mutex mMutex; std::queue mEvq; char mLocalAddr[64]; + unsigned long mLongRunningTaskId; }; /** diff --git a/coreapi/account_creator.c b/coreapi/account_creator.c index 4646806f0..d1eb954a5 100644 --- a/coreapi/account_creator.c +++ b/coreapi/account_creator.c @@ -130,14 +130,14 @@ void linphone_account_creator_set_user_data(LinphoneAccountCreator *creator, voi } static LinphoneAccountCreatorStatus validate_uri(const char* username, const char* domain, const char* route, const char* display_name) { - LinphoneProxyConfig* proxy = linphone_proxy_config_new(); LinphoneAddress* addr; - - linphone_proxy_config_set_identity(proxy, "sip:user@domain.com"); + LinphoneAccountCreatorStatus status = LinphoneAccountCreatorOK; + LinphoneProxyConfig* proxy = linphone_proxy_config_new(); + linphone_proxy_config_set_identity(proxy, "sip:userame@domain.com"); if (route && linphone_proxy_config_set_route(proxy, route) != 0) { - linphone_proxy_config_destroy(proxy); - return LinphoneAccountCreatorRouteInvalid; + status = LinphoneAccountCreatorRouteInvalid; + goto end; } if (username) { @@ -145,24 +145,39 @@ static LinphoneAccountCreatorStatus validate_uri(const char* username, const cha } else { addr = linphone_address_clone(linphone_proxy_config_get_identity_address(proxy)); } - linphone_proxy_config_destroy(proxy); if (addr == NULL) { - return LinphoneAccountCreatorUsernameInvalid; + status = LinphoneAccountCreatorUsernameInvalid; + goto end; } - if (domain) { - ms_error("TODO: detect invalid domain"); - linphone_address_set_domain(addr, domain); + if (domain && linphone_address_set_domain(addr, domain) != 0) { + status = LinphoneAccountCreatorDomainInvalid; } - if (display_name) { - ms_error("TODO: detect invalid display name"); - linphone_address_set_display_name(addr, display_name); + if (display_name && (!strlen(display_name) || linphone_address_set_display_name(addr, display_name) != 0)) { + status = LinphoneAccountCreatorDisplayNameInvalid; } - linphone_address_unref(addr); - return LinphoneAccountCreatorOK; +end: + linphone_proxy_config_destroy(proxy); + return status; +} + +static char* _get_identity(const LinphoneAccountCreator *creator) { + char *identity = NULL; + if (creator->username && creator->domain) { + //we must escape username + LinphoneProxyConfig* proxy = linphone_proxy_config_new(); + LinphoneAddress* addr; + linphone_proxy_config_set_identity(proxy, "sip:userame@domain.com"); + addr = linphone_proxy_config_normalize_sip_uri(proxy, creator->username); + linphone_address_set_domain(addr, creator->domain); + identity = linphone_address_as_string(addr); + linphone_address_destroy(addr); + linphone_proxy_config_destroy(proxy); + } + return identity; } static bool_t is_matching_regex(const char *entry, const char* regex) { @@ -185,13 +200,16 @@ static bool_t is_matching_regex(const char *entry, const char* regex) { } LinphoneAccountCreatorStatus linphone_account_creator_set_username(LinphoneAccountCreator *creator, const char *username) { - int min_length = lp_config_get_int(creator->core->config, "assistant", "username_min_length", 0); - int fixed_length = lp_config_get_int(creator->core->config, "assistant", "username_length", 0); + int min_length = lp_config_get_int(creator->core->config, "assistant", "username_min_length", -1); + int fixed_length = lp_config_get_int(creator->core->config, "assistant", "username_length", -1); + int max_length = lp_config_get_int(creator->core->config, "assistant", "username_max_length", -1); bool_t use_phone_number = lp_config_get_int(creator->core->config, "assistant", "use_phone_number", 0); const char* regex = lp_config_get_string(creator->core->config, "assistant", "username_regex", 0); LinphoneAccountCreatorStatus status; if (min_length > 0 && strlen(username) < min_length) { return LinphoneAccountCreatorUsernameTooShort; + } else if (max_length > 0 && strlen(username) > max_length) { + return LinphoneAccountCreatorUsernameTooLong; } else if (fixed_length > 0 && strlen(username) != fixed_length) { return LinphoneAccountCreatorUsernameInvalidSize; } else if (use_phone_number && !linphone_proxy_config_is_phone_number(NULL, username)) { @@ -201,7 +219,9 @@ LinphoneAccountCreatorStatus linphone_account_creator_set_username(LinphoneAccou } else if ((status = validate_uri(username, NULL, NULL, NULL)) != LinphoneAccountCreatorOK) { return status; } + set_string(&creator->username, username, TRUE); + return LinphoneAccountCreatorOK; } @@ -210,9 +230,12 @@ const char * linphone_account_creator_get_username(const LinphoneAccountCreator } LinphoneAccountCreatorStatus linphone_account_creator_set_password(LinphoneAccountCreator *creator, const char *password){ - int min_length = lp_config_get_int(creator->core->config, "assistant", "password_min_length", 0); + int min_length = lp_config_get_int(creator->core->config, "assistant", "password_min_length", -1); + int max_length = lp_config_get_int(creator->core->config, "assistant", "password_max_length", -1); if (min_length > 0 && strlen(password) < min_length) { return LinphoneAccountCreatorPasswordTooShort; + } else if (max_length > 0 && strlen(password) > max_length) { + return LinphoneAccountCreatorPasswordTooLong; } set_string(&creator->password, password, FALSE); return LinphoneAccountCreatorOK; @@ -308,15 +331,14 @@ static void _test_existence_cb(LinphoneXmlRpcRequest *request) { LinphoneAccountCreatorStatus linphone_account_creator_test_existence(LinphoneAccountCreator *creator) { LinphoneXmlRpcRequest *request; - char *identity; - - if (!creator->username || !creator->domain) { + char *identity = _get_identity(creator); + if (!identity) { if (creator->callbacks->existence_tested != NULL) { creator->callbacks->existence_tested(creator, LinphoneAccountCreatorReqFailed); } return LinphoneAccountCreatorReqFailed; } - identity = ms_strdup_printf("%s@%s", creator->username, creator->domain); + request = linphone_xml_rpc_request_new_with_args("check_account", LinphoneXmlRpcArgInt, LinphoneXmlRpcArgString, identity, LinphoneXmlRpcArgNone); @@ -342,16 +364,13 @@ static void _test_validation_cb(LinphoneXmlRpcRequest *request) { LinphoneAccountCreatorStatus linphone_account_creator_test_validation(LinphoneAccountCreator *creator) { LinphoneXmlRpcRequest *request; - char *identity; - - if (!creator->username || !creator->domain) { + char *identity = _get_identity(creator); + if (!identity) { if (creator->callbacks->validation_tested != NULL) { creator->callbacks->validation_tested(creator, LinphoneAccountCreatorReqFailed); } return LinphoneAccountCreatorReqFailed; } - - identity = ms_strdup_printf("%s@%s", creator->username, creator->domain); request = linphone_xml_rpc_request_new_with_args("check_account_validated", LinphoneXmlRpcArgInt, LinphoneXmlRpcArgString, identity, LinphoneXmlRpcArgNone); @@ -377,16 +396,15 @@ static void _create_account_cb(LinphoneXmlRpcRequest *request) { LinphoneAccountCreatorStatus linphone_account_creator_create_account(LinphoneAccountCreator *creator) { LinphoneXmlRpcRequest *request; - char *identity; - - if (!creator->username || !creator->domain || !creator->email) { + char *identity = _get_identity(creator); + if (!identity || !creator->email) { if (creator->callbacks->create_account != NULL) { creator->callbacks->create_account(creator, LinphoneAccountCreatorReqFailed); } + if (identity) ms_free(identity); return LinphoneAccountCreatorReqFailed; } - identity = ms_strdup_printf("%s@%s", creator->username, creator->domain); request = linphone_xml_rpc_request_new_with_args("create_account", LinphoneXmlRpcArgInt, LinphoneXmlRpcArgString, identity, LinphoneXmlRpcArgString, creator->password, @@ -404,18 +422,25 @@ LinphoneAccountCreatorStatus linphone_account_creator_create_account(LinphoneAcc LinphoneProxyConfig * linphone_account_creator_configure(const LinphoneAccountCreator *creator) { LinphoneAuthInfo *info; LinphoneProxyConfig *cfg = linphone_core_create_proxy_config(creator->core); - char *identity_str = ms_strdup_printf("sip:%s@%s", creator->username, creator->domain); - LinphoneAddress *identity = linphone_address_new(identity_str); + char *identity_str = _get_identity(creator); + LinphoneAddress *identity = linphone_address_new(identity_str); + char *route = NULL; + char *domain = NULL; + ms_free(identity_str); if (creator->display_name) { linphone_address_set_display_name(identity, creator->display_name); } - - linphone_proxy_config_set_identity(cfg, linphone_address_as_string(identity)); - linphone_proxy_config_set_server_addr(cfg, creator->domain); - linphone_proxy_config_set_route(cfg, creator->route); + if (creator->route) { + route = ms_strdup_printf("%s;transport=%s", creator->route, linphone_transport_to_string(creator->transport)); + } + if (creator->domain) { + domain = ms_strdup_printf("%s;transport=%s", creator->domain, linphone_transport_to_string(creator->transport)); + } + linphone_proxy_config_set_identity_address(cfg, identity); + linphone_proxy_config_set_server_addr(cfg, domain); + linphone_proxy_config_set_route(cfg, route); linphone_proxy_config_enable_publish(cfg, FALSE); linphone_proxy_config_enable_register(cfg, TRUE); - ms_free(identity_str); if (strcmp(creator->domain, "sip.linphone.org") == 0) { linphone_proxy_config_enable_avpf(cfg, TRUE); diff --git a/coreapi/account_creator.h b/coreapi/account_creator.h index fe6c46c3d..94c16670a 100644 --- a/coreapi/account_creator.h +++ b/coreapi/account_creator.h @@ -20,13 +20,12 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #ifndef LINPHONE_ACCOUNT_CREATOR_H_ #define LINPHONE_ACCOUNT_CREATOR_H_ +#include "linphonecore.h" #ifdef __cplusplus extern "C" { #endif -#include "linphonecore.h" - /** * @addtogroup misc * @{ @@ -51,8 +50,10 @@ typedef enum _LinphoneAccountCreatorStatus { LinphoneAccountCreatorEmailInvalid, LinphoneAccountCreatorUsernameInvalid, LinphoneAccountCreatorUsernameTooShort, + LinphoneAccountCreatorUsernameTooLong, LinphoneAccountCreatorUsernameInvalidSize, LinphoneAccountCreatorPasswordTooShort, + LinphoneAccountCreatorPasswordTooLong, LinphoneAccountCreatorDomainInvalid, LinphoneAccountCreatorRouteInvalid, LinphoneAccountCreatorDisplayNameInvalid, diff --git a/coreapi/address.c b/coreapi/address.c index 1299a5351..13fc15d3a 100644 --- a/coreapi/address.c +++ b/coreapi/address.c @@ -89,37 +89,51 @@ const char *linphone_address_get_domain(const LinphoneAddress *u){ /** * Sets the display name. **/ -void linphone_address_set_display_name(LinphoneAddress *u, const char *display_name){ +int linphone_address_set_display_name(LinphoneAddress *u, const char *display_name){ sal_address_set_display_name(u,display_name); + return 0; } /** * Sets the username. **/ -void linphone_address_set_username(LinphoneAddress *uri, const char *username){ +int linphone_address_set_username(LinphoneAddress *uri, const char *username){ sal_address_set_username(uri,username); + return 0; } /** * Sets the domain. **/ -void linphone_address_set_domain(LinphoneAddress *uri, const char *host){ - sal_address_set_domain(uri,host); +int linphone_address_set_domain(LinphoneAddress *uri, const char *host){ + if (host) { + char *identity = ms_strdup_printf("sip:%s", host); + LinphoneAddress* test = linphone_address_new(identity); + ms_free(identity); + if (test) { + sal_address_set_domain(uri,host); + linphone_address_destroy(test); + return 0; + } + } + return -1; } /** * Sets the port number. **/ -void linphone_address_set_port(LinphoneAddress *uri, int port){ +int linphone_address_set_port(LinphoneAddress *uri, int port){ sal_address_set_port(uri,port); + return 0; } /** * Set a transport. **/ -void linphone_address_set_transport(LinphoneAddress *uri, LinphoneTransportType tp){ +int linphone_address_set_transport(LinphoneAddress *uri, LinphoneTransportType tp){ sal_address_set_transport(uri,(SalTransport)tp); + return 0; } /** @@ -129,6 +143,20 @@ LinphoneTransportType linphone_address_get_transport(const LinphoneAddress *uri) return (LinphoneTransportType)sal_address_get_transport(uri); } +/** + * Set the value of the method parameter +**/ +void linphone_address_set_method_param(LinphoneAddress *addr, const char *method) { + sal_address_set_method_param(addr, method); +} + +/** + * Get the value of the method parameter +**/ +const char *linphone_address_get_method_param(const LinphoneAddress *addr) { + return sal_address_get_method_param(addr); +} + /** * Removes address's tags and uri headers so that it is displayable to the user. **/ diff --git a/coreapi/bellesip_sal/sal_address_impl.c b/coreapi/bellesip_sal/sal_address_impl.c index 2546e7850..505ee95e3 100644 --- a/coreapi/bellesip_sal/sal_address_impl.c +++ b/coreapi/bellesip_sal/sal_address_impl.c @@ -114,6 +114,15 @@ const char* sal_address_get_transport_name(const SalAddress* addr){ return NULL; } +const char *sal_address_get_method_param(const SalAddress *addr) { + belle_sip_header_address_t* header_addr = BELLE_SIP_HEADER_ADDRESS(addr); + belle_sip_uri_t* uri = belle_sip_header_address_get_uri(header_addr); + if (uri) { + return belle_sip_uri_get_method_param(uri); + } + return NULL; +} + void sal_address_set_display_name(SalAddress *addr, const char *display_name){ belle_sip_header_address_t* header_addr = BELLE_SIP_HEADER_ADDRESS(addr); belle_sip_header_address_set_displayname(header_addr,display_name); @@ -199,6 +208,11 @@ void sal_address_set_uri_params(SalAddress *addr, const char *params){ belle_sip_parameters_set(parameters,params); } +bool_t sal_address_has_uri_param(SalAddress *addr, const char *name){ + belle_sip_parameters_t* parameters = BELLE_SIP_PARAMETERS(belle_sip_header_address_get_uri(BELLE_SIP_HEADER_ADDRESS(addr))); + return belle_sip_parameters_has_parameter(parameters, name); +} + void sal_address_set_header(SalAddress *addr, const char *header_name, const char *header_value){ belle_sip_uri_set_header(belle_sip_header_address_get_uri(BELLE_SIP_HEADER_ADDRESS(addr)),header_name, header_value); } @@ -213,6 +227,10 @@ void sal_address_set_transport_name(SalAddress* addr,const char *transport){ SAL_ADDRESS_SET(addr,transport_param,transport); } +void sal_address_set_method_param(SalAddress *addr, const char *method) { + SAL_ADDRESS_SET(addr, method_param, method); +} + SalAddress *sal_address_ref(SalAddress *addr){ return (SalAddress*)belle_sip_object_ref(BELLE_SIP_HEADER_ADDRESS(addr)); } diff --git a/coreapi/bellesip_sal/sal_impl.c b/coreapi/bellesip_sal/sal_impl.c index c98dbec7f..131d7656c 100644 --- a/coreapi/bellesip_sal/sal_impl.c +++ b/coreapi/bellesip_sal/sal_impl.c @@ -82,8 +82,8 @@ void _belle_sip_log(belle_sip_log_level lev, const char *fmt, va_list args) { ortp_level=ORTP_DEBUG; break; } - if (ortp_log_level_enabled(ortp_level)){ - ortp_logv(ortp_level,fmt,args); + if (ortp_log_level_enabled("belle-sip", ortp_level)){ + ortp_logv("belle-sip", ortp_level,fmt,args); } } @@ -464,13 +464,13 @@ static void process_auth_requested(void *sal, belle_sip_auth_event_t *event) { sal_auth_info_delete(auth_info); } -Sal * sal_init(){ +Sal * sal_init(MSFactory *factory){ belle_sip_listener_callbacks_t listener_callbacks; Sal * sal=ms_new0(Sal,1); /*belle_sip_object_enable_marshal_check(TRUE);*/ sal->auto_contacts=TRUE; - + sal->factory = factory; /*first create the stack, which initializes the belle-sip object's pool for this thread*/ belle_sip_set_log_handler(_belle_sip_log); sal->stack = belle_sip_stack_new(NULL); @@ -478,6 +478,8 @@ Sal * sal_init(){ sal->user_agent=belle_sip_header_user_agent_new(); #if defined(PACKAGE_NAME) && defined(LIBLINPHONE_VERSION) belle_sip_header_user_agent_add_product(sal->user_agent, PACKAGE_NAME "/" LIBLINPHONE_VERSION); +#else + belle_sip_header_user_agent_add_product(sal->user_agent, "Unknown"); #endif sal_append_stack_string_to_user_agent(sal); belle_sip_object_ref(sal->user_agent); @@ -542,8 +544,8 @@ void sal_set_callbacks(Sal *ctx, const SalCallbacks *cbs){ ctx->callbacks.notify=(SalOnNotify)unimplemented_stub; if (ctx->callbacks.subscribe_received==NULL) ctx->callbacks.subscribe_received=(SalOnSubscribeReceived)unimplemented_stub; - if (ctx->callbacks.subscribe_closed==NULL) - ctx->callbacks.subscribe_closed=(SalOnSubscribeClosed)unimplemented_stub; + if (ctx->callbacks.incoming_subscribe_closed==NULL) + ctx->callbacks.incoming_subscribe_closed=(SalOnIncomingSubscribeClosed)unimplemented_stub; if (ctx->callbacks.parse_presence_requested==NULL) ctx->callbacks.parse_presence_requested=(SalOnParsePresenceRequested)unimplemented_stub; if (ctx->callbacks.convert_presence_to_xml_requested==NULL) @@ -595,6 +597,10 @@ int sal_transport_available(Sal *sal, SalTransport t){ return FALSE; } +bool_t sal_content_encoding_available(Sal *sal, const char *content_encoding) { + return (bool_t)belle_sip_stack_content_encoding_available(sal->stack, content_encoding); +} + static int sal_add_listen_port(Sal *ctx, SalAddress* addr, bool_t is_tunneled){ int result; belle_sip_listening_point_t* lp; @@ -734,13 +740,14 @@ static void set_tls_properties(Sal *ctx){ belle_sip_listening_point_t *lp=belle_sip_provider_get_listening_point(ctx->prov,"TLS"); if (lp){ belle_sip_tls_listening_point_t *tlp=BELLE_SIP_TLS_LISTENING_POINT(lp); - int verify_exceptions=0; - - if (!ctx->tls_verify) verify_exceptions=BELLE_SIP_TLS_LISTENING_POINT_BADCERT_ANY_REASON; - else if (!ctx->tls_verify_cn) verify_exceptions=BELLE_SIP_TLS_LISTENING_POINT_BADCERT_CN_MISMATCH; - - belle_sip_tls_listening_point_set_root_ca(tlp,ctx->root_ca); /*root_ca might be NULL */ - belle_sip_tls_listening_point_set_verify_exceptions(tlp,verify_exceptions); + belle_tls_crypto_config_t *crypto_config = belle_tls_crypto_config_new(); + int verify_exceptions = BELLE_TLS_VERIFY_NONE; + if (!ctx->tls_verify) verify_exceptions = BELLE_TLS_VERIFY_ANY_REASON; + else if (!ctx->tls_verify_cn) verify_exceptions = BELLE_TLS_VERIFY_CN_MISMATCH; + belle_tls_crypto_config_set_verify_exceptions(crypto_config, verify_exceptions); + if (ctx->root_ca != NULL) belle_tls_crypto_config_set_root_ca(crypto_config, ctx->root_ca); + belle_sip_tls_listening_point_set_crypto_config(tlp, crypto_config); + belle_sip_object_unref(crypto_config); } } @@ -816,6 +823,7 @@ bool_t sal_nat_helper_enabled(Sal *sal) { void sal_set_dns_timeout(Sal* sal,int timeout) { belle_sip_stack_set_dns_timeout(sal->stack, timeout); } + int sal_get_dns_timeout(const Sal* sal) { return belle_sip_stack_get_dns_timeout(sal->stack); } @@ -823,12 +831,26 @@ int sal_get_dns_timeout(const Sal* sal) { void sal_set_transport_timeout(Sal* sal,int timeout) { belle_sip_stack_set_transport_timeout(sal->stack, timeout); } + int sal_get_transport_timeout(const Sal* sal) { return belle_sip_stack_get_transport_timeout(sal->stack); } + +void sal_set_dns_servers(Sal *sal, const MSList *servers){ + belle_sip_list_t *l = NULL; + + /*we have to convert the MSList into a belle_sip_list_t first*/ + for (; servers != NULL; servers = servers->next){ + l = belle_sip_list_append(l, servers->data); + } + belle_sip_stack_set_dns_servers(sal->stack, l); + belle_sip_list_free(l); +} + void sal_enable_dns_srv(Sal *sal, bool_t enable) { belle_sip_stack_enable_dns_srv(sal->stack, (unsigned char)enable); } + bool_t sal_dns_srv_enabled(const Sal *sal) { return (bool_t)belle_sip_stack_dns_srv_enabled(sal->stack); } @@ -976,12 +998,11 @@ typedef struct { unsigned char node[6]; } sal_uuid_t; - -int sal_create_uuid(Sal*ctx, char *uuid, size_t len){ +int sal_generate_uuid(char *uuid, size_t len) { sal_uuid_t uuid_struct; int i; int written; - + if (len==0) return -1; /*create an UUID as described in RFC4122, 4.4 */ belle_sip_random_bytes((unsigned char*)&uuid_struct, sizeof(sal_uuid_t)); @@ -1000,10 +1021,17 @@ int sal_create_uuid(Sal*ctx, char *uuid, size_t len){ for (i = 0; i < 6; i++) written+=snprintf(uuid+written,len-written,"%2.2x", uuid_struct.node[i]); uuid[len-1]='\0'; - sal_set_uuid(ctx,uuid); return 0; } +int sal_create_uuid(Sal*ctx, char *uuid, size_t len) { + if (sal_generate_uuid(uuid, len) == 0) { + sal_set_uuid(ctx, uuid); + return 0; + } + return -1; +} + static void make_supported_header(Sal *sal){ MSList *it; char *alltags=NULL; @@ -1018,7 +1046,7 @@ static void make_supported_header(Sal *sal){ const char *tag=(const char*)it->data; size_t taglen=strlen(tag); if (alltags==NULL || (written+taglen+1>=buflen)) alltags=ms_realloc(alltags,(buflen=buflen*2)); - snprintf(alltags+written,buflen-written,it->next ? "%s, " : "%s",tag); + written+=snprintf(alltags+written,buflen-written,it->next ? "%s, " : "%s",tag); } if (alltags){ sal->supported=belle_sip_header_create("Supported",alltags); diff --git a/coreapi/bellesip_sal/sal_impl.h b/coreapi/bellesip_sal/sal_impl.h index bd942c9aa..b9175a7d0 100644 --- a/coreapi/bellesip_sal/sal_impl.h +++ b/coreapi/bellesip_sal/sal_impl.h @@ -25,6 +25,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "belle-sip/belle-sdp.h" struct Sal{ + MSFactory *factory; SalCallbacks callbacks; MSList *pending_auths;/*MSList of SalOp */ belle_sip_stack_t* stack; @@ -166,10 +167,9 @@ belle_sip_response_t *sal_create_response_from_request(Sal *sal, belle_sip_reque void sal_op_assign_recv_headers(SalOp *op, belle_sip_message_t *incoming); -void sal_op_add_body(SalOp *op, belle_sip_message_t *req, const SalBody *body); -bool_t sal_op_get_body(SalOp *op, belle_sip_message_t *msg, SalBody *salbody); +SalBodyHandler * sal_op_get_body_handler(SalOp *op, belle_sip_message_t *msg); -SalReason sal_reason_to_sip_code(SalReason r); +int sal_reason_to_sip_code(SalReason r); void _sal_op_add_custom_headers(SalOp *op, belle_sip_message_t *msg); diff --git a/coreapi/bellesip_sal/sal_op_call.c b/coreapi/bellesip_sal/sal_op_call.c index 076421aad..7ff099f72 100644 --- a/coreapi/bellesip_sal/sal_op_call.c +++ b/coreapi/bellesip_sal/sal_op_call.c @@ -57,13 +57,13 @@ static void sdp_process(SalOp *h){ h->result=sal_media_description_new(); if (h->sdp_offering){ - offer_answer_initiate_outgoing(h->base.local_media,h->base.remote_media,h->result); + offer_answer_initiate_outgoing(h->base.root->factory, h->base.local_media,h->base.remote_media,h->result); }else{ int i; if (h->sdp_answer){ belle_sip_object_unref(h->sdp_answer); } - offer_answer_initiate_incoming(h->base.local_media,h->base.remote_media,h->result,h->base.root->one_matching_codec); + offer_answer_initiate_incoming(h->base.root->factory, h->base.local_media,h->base.remote_media,h->result,h->base.root->one_matching_codec); /*for backward compatibility purpose*/ if(h->cnx_ip_to_0000_if_sendonly_enabled && sal_media_description_has_dir(h->result,SalStreamSendOnly)) { set_addr_to_0000(h->result->addr); @@ -308,7 +308,7 @@ static void call_process_response(void *op_base, const belle_sip_response_event_ if (code >=200 && code<300) { handle_sdp_from_response(op,response); ack=belle_sip_dialog_create_ack(op->dialog,belle_sip_dialog_get_local_seq_number(op->dialog)); - if (ack==NULL) { + if (ack == NULL) { ms_error("This call has been already terminated."); return ; } @@ -317,6 +317,7 @@ static void call_process_response(void *op_base, const belle_sip_response_event_ belle_sip_object_unref(op->sdp_answer); op->sdp_answer=NULL; } + belle_sip_message_add_header(BELLE_SIP_MESSAGE(ack),BELLE_SIP_HEADER(op->base.root->user_agent)); belle_sip_dialog_send_ack(op->dialog,ack); op->base.root->callbacks.call_accepted(op); /*INVITE*/ op->state=SalOpStateActive; @@ -669,15 +670,19 @@ static void process_request_event(void *op_base, const belle_sip_request_event_t } }else{ - SalBody salbody; - if (sal_op_get_body(op,(belle_sip_message_t*)req,&salbody)) { - if (sal_body_has_type(&salbody,"application","dtmf-relay")){ + belle_sip_message_t *msg = BELLE_SIP_MESSAGE(req); + belle_sip_body_handler_t *body_handler = BELLE_SIP_BODY_HANDLER(sal_op_get_body_handler(op, msg)); + if (body_handler) { + belle_sip_header_content_type_t *content_type = belle_sip_message_get_header_by_type(msg, belle_sip_header_content_type_t); + if (content_type + && (strcmp(belle_sip_header_content_type_get_type(content_type), "application") == 0) + && (strcmp(belle_sip_header_content_type_get_subtype(content_type), "dtmf-relay") == 0)) { char tmp[10]; - if (sal_lines_get_value(salbody.data, "Signal",tmp, sizeof(tmp))){ + if (sal_lines_get_value(belle_sip_message_get_body(msg), "Signal",tmp, sizeof(tmp))){ op->base.root->callbacks.dtmf_received(op,tmp[0]); } }else - op->base.root->callbacks.info_received(op,&salbody); + op->base.root->callbacks.info_received(op, (SalBodyHandler *)body_handler); } else { op->base.root->callbacks.info_received(op,NULL); } diff --git a/coreapi/bellesip_sal/sal_op_call_transfer.c b/coreapi/bellesip_sal/sal_op_call_transfer.c index 84e529091..ffc4b945a 100644 --- a/coreapi/bellesip_sal/sal_op_call_transfer.c +++ b/coreapi/bellesip_sal/sal_op_call_transfer.c @@ -40,7 +40,7 @@ static void sal_op_set_referred_by(SalOp* op,belle_sip_header_referred_by_t* ref int sal_call_refer_to(SalOp *op, belle_sip_header_refer_to_t* refer_to, belle_sip_header_referred_by_t* referred_by){ char* tmp; - belle_sip_request_t* req=op->dialog?belle_sip_dialog_create_request(op->dialog,"REFER"):NULL; /*cannot create request if dialog not set yet*/ + belle_sip_request_t* req=op->dialog?belle_sip_dialog_create_request(op->dialog,"REFER"):sal_op_build_request(op, "REFER"); if (!req) { tmp=belle_sip_uri_to_string(belle_sip_header_address_get_uri(BELLE_SIP_HEADER_ADDRESS(refer_to))); ms_error("Cannot refer to [%s] for op [%p]",tmp,op); diff --git a/coreapi/bellesip_sal/sal_op_events.c b/coreapi/bellesip_sal/sal_op_events.c index 8b439eb9b..6a8107d93 100644 --- a/coreapi/bellesip_sal/sal_op_events.c +++ b/coreapi/bellesip_sal/sal_op_events.c @@ -46,11 +46,15 @@ static void subscribe_refresher_listener (belle_sip_refresher_t* refresher if (status_code==200) sss=SalSubscribeActive; else if (status_code==202) sss=SalSubscribePending; set_or_update_dialog(op,belle_sip_transaction_get_dialog(tr)); - } - if (status_code>=200){ - sal_error_info_set(&op->error_info,SalReasonUnknown,status_code,reason_phrase,NULL); op->base.root->callbacks.subscribe_response(op,sss); - }else if (status_code==0){ + } else if (status_code >= 300) { + SalReason reason = SalReasonUnknown; + if (status_code == 503) { /*refresher returns 503 for IO error*/ + reason = SalReasonIOError; + } + sal_error_info_set(&op->error_info,reason,status_code,reason_phrase,NULL); + op->base.root->callbacks.subscribe_response(op,sss); + }if (status_code==0){ op->base.root->callbacks.on_expire(op); } @@ -61,10 +65,20 @@ static void subscribe_process_io_error(void *user_ctx, const belle_sip_io_error_ } static void subscribe_process_dialog_terminated(void *ctx, const belle_sip_dialog_terminated_event_t *event) { + belle_sip_dialog_t *dialog = belle_sip_dialog_terminated_event_get_dialog(event); SalOp* op= (SalOp*)ctx; if (op->dialog) { op->dialog=NULL; + if (!belle_sip_dialog_is_server(dialog) && belle_sip_dialog_terminated_event_is_expired(event)){ + /*notify the app that our subscription is dead*/ + const char *eventname = NULL; + if (op->event){ + eventname = belle_sip_header_get_unparsed_value(op->event); + } + op->base.root->callbacks.notify(op, SalSubscribeTerminated, eventname, NULL); + } sal_op_unref(op); + } } @@ -77,7 +91,7 @@ static void subscribe_process_timeout(void *user_ctx, const belle_sip_timeout_ev static void subscribe_process_transaction_terminated(void *user_ctx, const belle_sip_transaction_terminated_event_t *event) { } -static void handle_notify(SalOp *op, belle_sip_request_t *req, const char *eventname, SalBody * body){ +static void handle_notify(SalOp *op, belle_sip_request_t *req, const char *eventname, SalBodyHandler* body_handler){ SalSubscribeStatus sub_state; belle_sip_header_subscription_state_t* subscription_state_header=belle_sip_message_get_header_by_type(req,belle_sip_header_subscription_state_t); belle_sip_response_t* resp; @@ -89,7 +103,7 @@ static void handle_notify(SalOp *op, belle_sip_request_t *req, const char *event } else sub_state=SalSubscribeActive; sal_op_ref(op); - op->base.root->callbacks.notify(op,sub_state,eventname,body); + op->base.root->callbacks.notify(op,sub_state,eventname,body_handler); resp=sal_op_create_response_from_request(op,req,200); belle_sip_server_transaction_send_response(server_transaction,resp); sal_op_unref(op); @@ -102,7 +116,7 @@ static void subscribe_process_request_event(void *op_base, const belle_sip_reque belle_sip_dialog_state_t dialog_state; belle_sip_header_expires_t* expires = belle_sip_message_get_header_by_type(req,belle_sip_header_expires_t); belle_sip_header_t *event_header; - SalBody body; + belle_sip_body_handler_t *body_handler; belle_sip_response_t* resp; const char *eventname=NULL; const char *method=belle_sip_request_get_method(req); @@ -112,12 +126,13 @@ static void subscribe_process_request_event(void *op_base, const belle_sip_reque op->pending_server_trans=server_transaction; event_header=belle_sip_message_get_header((belle_sip_message_t*)req,"Event"); - sal_op_get_body(op,(belle_sip_message_t*)req,&body); + body_handler = BELLE_SIP_BODY_HANDLER(sal_op_get_body_handler(op, BELLE_SIP_MESSAGE(req))); if (event_header==NULL){ ms_warning("No event header in incoming SUBSCRIBE."); resp=sal_op_create_response_from_request(op,req,400); belle_sip_server_transaction_send_response(server_transaction,resp); + if (!op->dialog) sal_op_release(op); return; } if (op->event==NULL) { @@ -128,11 +143,17 @@ static void subscribe_process_request_event(void *op_base, const belle_sip_reque if (!op->dialog) { if (strcmp(method,"SUBSCRIBE")==0){ - op->dialog=belle_sip_provider_create_dialog(op->base.root->prov,BELLE_SIP_TRANSACTION(server_transaction)); + op->dialog = belle_sip_provider_create_dialog(op->base.root->prov,BELLE_SIP_TRANSACTION(server_transaction)); + if (!op->dialog){ + resp=sal_op_create_response_from_request(op,req,481); + belle_sip_server_transaction_send_response(server_transaction,resp); + sal_op_release(op); + return; + } belle_sip_dialog_set_application_data(op->dialog, sal_op_ref(op)); ms_message("new incoming subscription from [%s] to [%s]",sal_op_get_from(op),sal_op_get_to(op)); }else{ /*this is a NOTIFY*/ - handle_notify(op,req,eventname,&body); + handle_notify(op, req, eventname, (SalBodyHandler *)body_handler); return; } } @@ -140,7 +161,10 @@ static void subscribe_process_request_event(void *op_base, const belle_sip_reque switch(dialog_state) { case BELLE_SIP_DIALOG_NULL: { - op->base.root->callbacks.subscribe_received(op,eventname,body.type ? &body : NULL); + const char *type = NULL; + belle_sip_header_content_type_t *content_type = belle_sip_message_get_header_by_type(BELLE_SIP_MESSAGE(req), belle_sip_header_content_type_t); + if (content_type) type = belle_sip_header_content_type_get_type(content_type); + op->base.root->callbacks.subscribe_received(op, eventname, type ? (SalBodyHandler *)body_handler : NULL); break; } case BELLE_SIP_DIALOG_EARLY: @@ -149,7 +173,7 @@ static void subscribe_process_request_event(void *op_base, const belle_sip_reque case BELLE_SIP_DIALOG_CONFIRMED: if (strcmp("NOTIFY",method)==0) { - handle_notify(op,req,eventname,&body); + handle_notify(op, req, eventname, (SalBodyHandler *)body_handler); } else if (strcmp("SUBSCRIBE",method)==0) { /*either a refresh of an unsubscribe*/ if (expires && belle_sip_header_expires_get_expires(expires)>0) { @@ -159,7 +183,7 @@ static void subscribe_process_request_event(void *op_base, const belle_sip_reque ms_message("Unsubscribe received from [%s]",sal_op_get_from(op)); resp=sal_op_create_response_from_request(op,req,200); belle_sip_server_transaction_send_response(server_transaction,resp); - op->base.root->callbacks.subscribe_closed(op); + op->base.root->callbacks.incoming_subscribe_closed(op); } } break; @@ -171,6 +195,18 @@ static void subscribe_process_request_event(void *op_base, const belle_sip_reque static belle_sip_listener_callbacks_t op_subscribe_callbacks={ 0 }; +/*Invoke when sal_op_release is called by upper layer*/ +static void sal_op_release_cb(struct SalOpBase* op_base) { + SalOp *op =(SalOp*)op_base; + if(op->refresher) { + belle_sip_refresher_stop(op->refresher); + belle_sip_object_unref(op->refresher); + op->refresher=NULL; + set_or_update_dialog(op,NULL); /*only if we have refresher. else dialog terminated event will remove association*/ + } + +} + void sal_op_subscribe_fill_cbs(SalOp*op) { if (op_subscribe_callbacks.process_io_error==NULL){ op_subscribe_callbacks.process_io_error=subscribe_process_io_error; @@ -182,10 +218,11 @@ void sal_op_subscribe_fill_cbs(SalOp*op) { } op->callbacks=&op_subscribe_callbacks; op->type=SalOpSubscribe; + op->base.release_cb=sal_op_release_cb; } -int sal_subscribe(SalOp *op, const char *from, const char *to, const char *eventname, int expires, const SalBody *body){ +int sal_subscribe(SalOp *op, const char *from, const char *to, const char *eventname, int expires, const SalBodyHandler *body_handler){ belle_sip_request_t *req=NULL; if (from) @@ -195,7 +232,6 @@ int sal_subscribe(SalOp *op, const char *from, const char *to, const char *event if (!op->dialog){ sal_op_subscribe_fill_cbs(op); - /*???sal_exosip_fix_route(op); make sure to ha ;lr*/ req=sal_op_build_request(op,"SUBSCRIBE"); if( req == NULL ) { return -1; @@ -207,13 +243,13 @@ int sal_subscribe(SalOp *op, const char *from, const char *to, const char *event } belle_sip_message_add_header(BELLE_SIP_MESSAGE(req),op->event); belle_sip_message_add_header(BELLE_SIP_MESSAGE(req),BELLE_SIP_HEADER(belle_sip_header_expires_create(expires))); - sal_op_add_body(op,(belle_sip_message_t*)req,body); + belle_sip_message_set_body_handler(BELLE_SIP_MESSAGE(req), BELLE_SIP_BODY_HANDLER(body_handler)); return sal_op_send_and_create_refresher(op,req,expires,subscribe_refresher_listener); }else if (op->refresher){ const belle_sip_transaction_t *tr=(const belle_sip_transaction_t*) belle_sip_refresher_get_transaction(op->refresher); belle_sip_request_t *last_req=belle_sip_transaction_get_request(tr); /* modify last request to update body*/ - sal_op_add_body(op,(belle_sip_message_t*)last_req,body); + belle_sip_message_set_body_handler(BELLE_SIP_MESSAGE(last_req), BELLE_SIP_BODY_HANDLER(body_handler)); return belle_sip_refresher_refresh(op->refresher,expires); } ms_warning("sal_subscribe(): no dialog and no refresher ?"); @@ -224,7 +260,7 @@ int sal_unsubscribe(SalOp *op){ if (op->refresher){ const belle_sip_transaction_t *tr=(const belle_sip_transaction_t*) belle_sip_refresher_get_transaction(op->refresher); belle_sip_request_t *last_req=belle_sip_transaction_get_request(tr); - sal_op_add_body(op,(belle_sip_message_t*)last_req,NULL); + belle_sip_message_set_body(BELLE_SIP_MESSAGE(last_req), NULL, 0); belle_sip_refresher_refresh(op->refresher,0); return 0; } @@ -247,7 +283,7 @@ int sal_subscribe_decline(SalOp *op, SalReason reason){ return 0; } -int sal_notify(SalOp *op, const SalBody *body){ +int sal_notify(SalOp *op, const SalBodyHandler *body_handler){ belle_sip_request_t* notify; if (!op->dialog) return -1; @@ -258,8 +294,7 @@ int sal_notify(SalOp *op, const SalBody *body){ belle_sip_message_add_header(BELLE_SIP_MESSAGE(notify) ,BELLE_SIP_HEADER(belle_sip_header_subscription_state_create(BELLE_SIP_SUBSCRIPTION_STATE_ACTIVE,600))); - - sal_op_add_body(op,(belle_sip_message_t*)notify, body); + belle_sip_message_set_body_handler(BELLE_SIP_MESSAGE(notify), BELLE_SIP_BODY_HANDLER(body_handler)); return sal_op_send_request(op,notify); } diff --git a/coreapi/bellesip_sal/sal_op_impl.c b/coreapi/bellesip_sal/sal_op_impl.c index 5a55d2900..b58be0f2b 100644 --- a/coreapi/bellesip_sal/sal_op_impl.c +++ b/coreapi/bellesip_sal/sal_op_impl.c @@ -35,6 +35,8 @@ void sal_op_release(SalOp *op){ if (op->state!=SalOpStateTerminating) op->state=SalOpStateTerminated; sal_op_set_user_pointer(op,NULL);/*mandatory because releasing op doesn't not mean freeing op. Make sure back pointer will not be used later*/ + if (op->base.release_cb) + op->base.release_cb(&op->base); if (op->refresher) { belle_sip_refresher_stop(op->refresher); } @@ -163,7 +165,7 @@ belle_sip_request_t* sal_op_build_request(SalOp *op,const char* method) { ms_error("No To: address, cannot build request"); return NULL; } - + to_uri = belle_sip_header_address_get_uri(BELLE_SIP_HEADER_ADDRESS(to_address)); if( to_uri == NULL ){ ms_error("To: address is invalid, cannot build request"); @@ -393,7 +395,7 @@ int sal_op_send_request(SalOp* op, belle_sip_request_t* request) { return _sal_op_send_request_with_contact(op, request,need_contact); } -SalReason sal_reason_to_sip_code(SalReason r){ +int sal_reason_to_sip_code(SalReason r){ int ret=500; switch(r){ case SalReasonNone: @@ -518,14 +520,14 @@ SalReason _sal_reason_from_sip_code(int code) { return SalReasonNotImplemented; case 502: return SalReasonBadGateway; + case 503: + return SalReasonServiceUnavailable; case 504: return SalReasonServerTimeout; case 600: return SalReasonDoNotDisturb; case 603: return SalReasonDeclined; - case 503: - return SalReasonServiceUnavailable; default: return SalReasonUnknown; } @@ -701,55 +703,21 @@ void sal_op_assign_recv_headers(SalOp *op, belle_sip_message_t *incoming){ const char *sal_op_get_remote_contact(const SalOp *op){ /* * remote contact is filled in process_response - * return sal_custom_header_find(op->base.recv_custom_headers,"Contact"); */ return op->base.remote_contact; } -void sal_op_add_body(SalOp *op, belle_sip_message_t *req, const SalBody *body){ - belle_sip_message_remove_header((belle_sip_message_t*)req,"Content-type"); - belle_sip_message_remove_header((belle_sip_message_t*)req,"Content-length"); - belle_sip_message_remove_header((belle_sip_message_t*)req,"Content-encoding"); - belle_sip_message_set_body((belle_sip_message_t*)req,NULL,0); - if (body && body->type && body->subtype && body->data){ - belle_sip_message_add_header((belle_sip_message_t*)req, - (belle_sip_header_t*)belle_sip_header_content_type_create(body->type,body->subtype)); - belle_sip_message_add_header((belle_sip_message_t*)req, - (belle_sip_header_t*)belle_sip_header_content_length_create(body->size)); - belle_sip_message_set_body((belle_sip_message_t*)req,(const char*)body->data,body->size); - if (body->encoding){ - belle_sip_message_add_header((belle_sip_message_t*)req,(belle_sip_header_t*) - belle_sip_header_create("Content-encoding",body->encoding)); - } +SalBodyHandler * sal_op_get_body_handler(SalOp *op, belle_sip_message_t *msg) { + belle_sip_body_handler_t *body_handler = belle_sip_message_get_body_handler(msg); + if (body_handler != NULL) { + belle_sip_header_content_type_t *content_type = belle_sip_message_get_header_by_type(msg, belle_sip_header_content_type_t); + belle_sip_header_content_length_t *content_length = belle_sip_message_get_header_by_type(msg, belle_sip_header_content_length_t); + belle_sip_header_t *content_encoding = belle_sip_message_get_header(msg, "Content-Encoding"); + if (content_type != NULL) belle_sip_body_handler_add_header(body_handler, BELLE_SIP_HEADER(content_type)); + if (content_length != NULL) belle_sip_body_handler_add_header(body_handler, BELLE_SIP_HEADER(content_length)); + if (content_encoding != NULL) belle_sip_body_handler_add_header(body_handler, content_encoding); } -} - - -bool_t sal_op_get_body(SalOp *op, belle_sip_message_t *msg, SalBody *salbody){ - const char *body = NULL; - belle_sip_header_content_type_t *content_type; - belle_sip_header_content_length_t *clen=NULL; - belle_sip_header_t *content_encoding; - - content_type=belle_sip_message_get_header_by_type(msg,belle_sip_header_content_type_t); - if (content_type){ - body=belle_sip_message_get_body(msg); - clen=belle_sip_message_get_header_by_type(msg,belle_sip_header_content_length_t); - } - content_encoding=belle_sip_message_get_header(msg,"Content-encoding"); - - memset(salbody,0,sizeof(SalBody)); - - if (content_type && body && clen) { - salbody->type=belle_sip_header_content_type_get_type(content_type); - salbody->subtype=belle_sip_header_content_type_get_subtype(content_type); - salbody->data=body; - salbody->size=belle_sip_header_content_length_get_content_length(clen); - if (content_encoding) - salbody->encoding=belle_sip_header_get_unparsed_value(content_encoding); - return TRUE; - } - return FALSE; + return (SalBodyHandler *)body_handler; } void sal_op_set_privacy(SalOp* op,SalPrivacyMask privacy) { @@ -823,3 +791,11 @@ bool_t sal_op_cnx_ip_to_0000_if_sendonly_enabled(SalOp *op) { bool_t sal_op_is_forked_of(const SalOp *op1, const SalOp *op2){ return op1->base.call_id && op2->base.call_id && strcmp(op1->base.call_id, op2->base.call_id) == 0; } +int sal_op_refresh(SalOp *op) { + if (op->refresher) { + belle_sip_refresher_refresh(op->refresher,belle_sip_refresher_get_expires(op->refresher)); + return 0; + } + ms_warning("sal_refresh on op [%p] of type [%s] no refresher",op,sal_op_type_to_string(op->type)); + return -1; +} diff --git a/coreapi/bellesip_sal/sal_op_info.c b/coreapi/bellesip_sal/sal_op_info.c index 3cffb8e55..3820c95ee 100644 --- a/coreapi/bellesip_sal/sal_op_info.c +++ b/coreapi/bellesip_sal/sal_op_info.c @@ -19,12 +19,12 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "sal_impl.h" -int sal_send_info(SalOp *op, const char *from, const char *to, const SalBody *body){ +int sal_send_info(SalOp *op, const char *from, const char *to, const SalBodyHandler *body_handler){ if (op->dialog){ belle_sip_request_t *req; belle_sip_dialog_enable_pending_trans_checking(op->dialog,op->base.root->pending_trans_checking); req=belle_sip_dialog_create_queued_request(op->dialog,"INFO"); - sal_op_add_body(op,(belle_sip_message_t*)req,body); + belle_sip_message_set_body_handler(BELLE_SIP_MESSAGE(req), BELLE_SIP_BODY_HANDLER(body_handler)); return sal_op_send_request(op,req); } return -1; diff --git a/coreapi/bellesip_sal/sal_op_message.c b/coreapi/bellesip_sal/sal_op_message.c index 921f8c0a6..baf69a9eb 100644 --- a/coreapi/bellesip_sal/sal_op_message.c +++ b/coreapi/bellesip_sal/sal_op_message.c @@ -110,122 +110,127 @@ void sal_process_incoming_message(SalOp *op,const belle_sip_request_event_t *eve from_header=belle_sip_message_get_header_by_type(BELLE_SIP_MESSAGE(req),belle_sip_header_from_t); content_type=belle_sip_message_get_header_by_type(BELLE_SIP_MESSAGE(req),belle_sip_header_content_type_t); - /* check if we have a xml/cipher message to be decrypted */ - if (content_type && (cipher_xml=is_cipher_xml(content_type))) { - /* access the zrtp cache to get keys needed to decipher the message */ - LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(op)); - FILE *CACHEFD = fopen(lc->zrtp_secrets_cache, "rb+"); - if (CACHEFD == NULL) { - ms_warning("Unable to access ZRTP ZID cache to decrypt message"); - goto error; - } else { - size_t cacheSize; - char *cacheString; - int retval; - xmlDocPtr cacheXml; - - cacheString=ms_load_file_content(CACHEFD, &cacheSize); - if (!cacheString){ - ms_warning("Unable to load content of ZRTP ZID cache to decrypt message"); - goto error; - } - cacheString[cacheSize] = '\0'; - cacheSize += 1; - fclose(CACHEFD); - cacheXml = xmlParseDoc((xmlChar*)cacheString); - ms_free(cacheString); - retval = lime_decryptMultipartMessage(cacheXml, (uint8_t *)belle_sip_message_get_body(BELLE_SIP_MESSAGE(req)), &decryptedMessage); - if (retval != 0) { - ms_warning("Unable to decrypt message, reason : %s - op [%p]", lime_error_code_to_string(retval), op); - free(decryptedMessage); - xmlFreeDoc(cacheXml); - errcode = 488; + + if (content_type){ + + /* check if we have a xml/cipher message to be decrypted */ + if ((cipher_xml=is_cipher_xml(content_type))) { + /* access the zrtp cache to get keys needed to decipher the message */ + LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(op)); + FILE *CACHEFD = NULL; + if (lc->zrtp_secrets_cache != NULL) CACHEFD = fopen(lc->zrtp_secrets_cache, "rb+"); + if (CACHEFD == NULL) { + ms_warning("Unable to access ZRTP ZID cache to decrypt message"); goto error; } else { - /* dump updated cache to a string */ - xmlChar *xmlStringOutput; - int xmlStringLength; - xmlDocDumpFormatMemoryEnc(cacheXml, &xmlStringOutput, &xmlStringLength, "UTF-8", 0); - /* write it to the cache file */ - CACHEFD = fopen(lc->zrtp_secrets_cache, "wb+"); - if (fwrite(xmlStringOutput, 1, xmlStringLength, CACHEFD)<=0){ - ms_warning("Fail to write cache"); + size_t cacheSize; + char *cacheString; + int retval; + xmlDocPtr cacheXml; + + cacheString=ms_load_file_content(CACHEFD, &cacheSize); + if (!cacheString){ + ms_warning("Unable to load content of ZRTP ZID cache to decrypt message"); + goto error; } - xmlFree(xmlStringOutput); + cacheString[cacheSize] = '\0'; + cacheSize += 1; fclose(CACHEFD); + cacheXml = xmlParseDoc((xmlChar*)cacheString); + ms_free(cacheString); + retval = lime_decryptMultipartMessage(cacheXml, (uint8_t *)belle_sip_message_get_body(BELLE_SIP_MESSAGE(req)), &decryptedMessage); + if (retval != 0) { + ms_warning("Unable to decrypt message, reason : %s - op [%p]", lime_error_code_to_string(retval), op); + free(decryptedMessage); + xmlFreeDoc(cacheXml); + errcode = 488; + goto error; + } else { + /* dump updated cache to a string */ + xmlChar *xmlStringOutput; + int xmlStringLength; + xmlDocDumpFormatMemoryEnc(cacheXml, &xmlStringOutput, &xmlStringLength, "UTF-8", 0); + /* write it to the cache file */ + CACHEFD = fopen(lc->zrtp_secrets_cache, "wb+"); + if (fwrite(xmlStringOutput, 1, xmlStringLength, CACHEFD)<=0){ + ms_warning("Fail to write cache"); + } + xmlFree(xmlStringOutput); + fclose(CACHEFD); + } + + xmlFreeDoc(cacheXml); } - xmlFreeDoc(cacheXml); } - - } - - rcs_filetransfer=is_rcs_filetransfer(content_type); - if (content_type && ((plain_text=is_plain_text(content_type)) - || (external_body=is_external_body(content_type)) - || (decryptedMessage!=NULL) - || rcs_filetransfer)) { - SalMessage salmsg; - char message_id[256]={0}; + external_body=is_external_body(content_type); + plain_text=is_plain_text(content_type); + rcs_filetransfer = is_rcs_filetransfer(content_type); - if (op->pending_server_trans) belle_sip_object_unref(op->pending_server_trans); - op->pending_server_trans=server_transaction; - belle_sip_object_ref(op->pending_server_trans); - - address=belle_sip_header_address_create(belle_sip_header_address_get_displayname(BELLE_SIP_HEADER_ADDRESS(from_header)) - ,belle_sip_header_address_get_uri(BELLE_SIP_HEADER_ADDRESS(from_header))); - from=belle_sip_object_to_string(BELLE_SIP_OBJECT(address)); - snprintf(message_id,sizeof(message_id)-1,"%s%i" - ,belle_sip_header_call_id_get_call_id(call_id) - ,belle_sip_header_cseq_get_seq_number(cseq)); - salmsg.from=from; - /* if we just deciphered a message, use the deciphered part(which can be a rcs xml body pointing to the file to retreive from server)*/ - if (cipher_xml) { - salmsg.text = (char *)decryptedMessage; - } else { /* message body wasn't ciphered */ - salmsg.text=(plain_text||rcs_filetransfer)?belle_sip_message_get_body(BELLE_SIP_MESSAGE(req)):NULL; - } - salmsg.url=NULL; - salmsg.content_type = NULL; - if (rcs_filetransfer) { /* if we have a rcs file transfer, set the type, message body (stored in salmsg.text) contains all needed information to retrieve the file */ - salmsg.content_type = "application/vnd.gsma.rcs-ft-http+xml"; - } - if (external_body && belle_sip_parameters_get_parameter(BELLE_SIP_PARAMETERS(content_type),"URL")) { - size_t url_length=strlen(belle_sip_parameters_get_parameter(BELLE_SIP_PARAMETERS(content_type),"URL")); - salmsg.url = ms_strdup(belle_sip_parameters_get_parameter(BELLE_SIP_PARAMETERS(content_type),"URL")+1); /* skip first "*/ - ((char*)salmsg.url)[url_length-2]='\0'; /*remove trailing "*/ - } - salmsg.message_id=message_id; - salmsg.time=date ? belle_sip_header_date_get_time(date) : time(NULL); - op->base.root->callbacks.text_received(op,&salmsg); + if (external_body || plain_text || rcs_filetransfer || decryptedMessage!=NULL) { + SalMessage salmsg; + char message_id[256]={0}; + + if (op->pending_server_trans) belle_sip_object_unref(op->pending_server_trans); + op->pending_server_trans=server_transaction; + belle_sip_object_ref(op->pending_server_trans); + + address=belle_sip_header_address_create(belle_sip_header_address_get_displayname(BELLE_SIP_HEADER_ADDRESS(from_header)) + ,belle_sip_header_address_get_uri(BELLE_SIP_HEADER_ADDRESS(from_header))); + from=belle_sip_object_to_string(BELLE_SIP_OBJECT(address)); + snprintf(message_id,sizeof(message_id)-1,"%s%i" + ,belle_sip_header_call_id_get_call_id(call_id) + ,belle_sip_header_cseq_get_seq_number(cseq)); + salmsg.from=from; + /* if we just deciphered a message, use the deciphered part(which can be a rcs xml body pointing to the file to retreive from server)*/ + if (cipher_xml) { + salmsg.text = (char *)decryptedMessage; + } else { /* message body wasn't ciphered */ + salmsg.text=(plain_text||rcs_filetransfer)?belle_sip_message_get_body(BELLE_SIP_MESSAGE(req)):NULL; + } + salmsg.url=NULL; + salmsg.content_type = NULL; + if (rcs_filetransfer) { /* if we have a rcs file transfer, set the type, message body (stored in salmsg.text) contains all needed information to retrieve the file */ + salmsg.content_type = "application/vnd.gsma.rcs-ft-http+xml"; + } + if (external_body && belle_sip_parameters_get_parameter(BELLE_SIP_PARAMETERS(content_type),"URL")) { + size_t url_length=strlen(belle_sip_parameters_get_parameter(BELLE_SIP_PARAMETERS(content_type),"URL")); + salmsg.url = ms_strdup(belle_sip_parameters_get_parameter(BELLE_SIP_PARAMETERS(content_type),"URL")+1); /* skip first "*/ + ((char*)salmsg.url)[url_length-2]='\0'; /*remove trailing "*/ + } + salmsg.message_id=message_id; + salmsg.time=date ? belle_sip_header_date_get_time(date) : time(NULL); + op->base.root->callbacks.text_received(op,&salmsg); - free(decryptedMessage); - belle_sip_object_unref(address); - belle_sip_free(from); - if (salmsg.url) ms_free((char*)salmsg.url); - } else if (content_type && is_im_iscomposing(content_type)) { - SalIsComposing saliscomposing; - address=belle_sip_header_address_create(belle_sip_header_address_get_displayname(BELLE_SIP_HEADER_ADDRESS(from_header)) - ,belle_sip_header_address_get_uri(BELLE_SIP_HEADER_ADDRESS(from_header))); - from=belle_sip_object_to_string(BELLE_SIP_OBJECT(address)); - saliscomposing.from=from; - saliscomposing.text=belle_sip_message_get_body(BELLE_SIP_MESSAGE(req)); - op->base.root->callbacks.is_composing_received(op,&saliscomposing); - resp = belle_sip_response_create_from_request(req,200); - belle_sip_server_transaction_send_response(server_transaction,resp); - belle_sip_object_unref(address); - belle_sip_free(from); - } else { - ms_error("Unsupported MESSAGE (content-type not recognized)"); - resp = belle_sip_response_create_from_request(req,415); - add_message_accept((belle_sip_message_t*)resp); - belle_sip_server_transaction_send_response(server_transaction,resp); - sal_op_release(op); - return; + free(decryptedMessage); + belle_sip_object_unref(address); + belle_sip_free(from); + if (salmsg.url) ms_free((char*)salmsg.url); + } else if (is_im_iscomposing(content_type)) { + SalIsComposing saliscomposing; + address=belle_sip_header_address_create(belle_sip_header_address_get_displayname(BELLE_SIP_HEADER_ADDRESS(from_header)) + ,belle_sip_header_address_get_uri(BELLE_SIP_HEADER_ADDRESS(from_header))); + from=belle_sip_object_to_string(BELLE_SIP_OBJECT(address)); + saliscomposing.from=from; + saliscomposing.text=belle_sip_message_get_body(BELLE_SIP_MESSAGE(req)); + op->base.root->callbacks.is_composing_received(op,&saliscomposing); + resp = belle_sip_response_create_from_request(req,200); + belle_sip_server_transaction_send_response(server_transaction,resp); + belle_sip_object_unref(address); + belle_sip_free(from); + }else{ + ms_error("Unsupported MESSAGE (content-type not recognized)"); + errcode = 415; + goto error; + } + }else { + ms_error("Unsupported MESSAGE (no Content-Type)"); + goto error; } return; error: resp = belle_sip_response_create_from_request(req, errcode); + add_message_accept((belle_sip_message_t*)resp); belle_sip_server_transaction_send_response(server_transaction,resp); sal_op_release(op); } @@ -239,8 +244,9 @@ int sal_message_send(SalOp *op, const char *from, const char *to, const char* co belle_sip_request_t* req; char content_type_raw[256]; size_t content_length = msg?strlen(msg):0; - time_t curtime=time(NULL); + time_t curtime = ms_time(NULL); uint8_t *multipartEncryptedMessage = NULL; + const char *body; int retval; if (op->dialog){ @@ -321,7 +327,11 @@ int sal_message_send(SalOp *op, const char *from, const char *to, const char* co belle_sip_message_add_header(BELLE_SIP_MESSAGE(req),BELLE_SIP_HEADER(belle_sip_header_content_type_parse(content_type_raw))); belle_sip_message_add_header(BELLE_SIP_MESSAGE(req),BELLE_SIP_HEADER(belle_sip_header_content_length_create(content_length))); belle_sip_message_add_header(BELLE_SIP_MESSAGE(req),BELLE_SIP_HEADER(belle_sip_header_date_create_from_time(&curtime))); - belle_sip_message_set_body(BELLE_SIP_MESSAGE(req),(multipartEncryptedMessage==NULL)?msg:(const char *)multipartEncryptedMessage,content_length); + body = (multipartEncryptedMessage==NULL) ? msg : (char*) multipartEncryptedMessage; + if (body){ + /*don't call set_body() with null argument because it resets content type and content length*/ + belle_sip_message_set_body(BELLE_SIP_MESSAGE(req), body, content_length); + } retval = sal_op_send_request(op,req); free(multipartEncryptedMessage); diff --git a/coreapi/bellesip_sal/sal_op_presence.c b/coreapi/bellesip_sal/sal_op_presence.c index f5ec79ad8..6e9f0e6d2 100644 --- a/coreapi/bellesip_sal/sal_op_presence.c +++ b/coreapi/bellesip_sal/sal_op_presence.c @@ -52,15 +52,13 @@ static void presence_process_io_error(void *user_ctx, const belle_sip_io_error_e static void presence_process_dialog_terminated(void *ctx, const belle_sip_dialog_terminated_event_t *event) { SalOp* op= (SalOp*)ctx; - if (op->dialog) { - if (belle_sip_dialog_is_server(op->dialog)){ + if (op->dialog && belle_sip_dialog_is_server(op->dialog)) { ms_message("Incoming subscribtion from [%s] terminated",sal_op_get_from(op)); if (!op->op_released){ op->base.root->callbacks.subscribe_presence_closed(op, sal_op_get_from(op)); } - } - set_or_update_dialog(op, NULL); - } + set_or_update_dialog(op, NULL); + }/* else client dialog is managed by refresher*/ } static void presence_refresher_listener(belle_sip_refresher_t* refresher, void* user_pointer, unsigned int status_code, const char* reason_phrase){ @@ -135,13 +133,6 @@ static void presence_response_event(void *op_base, const belle_sip_response_even } break; } - case BELLE_SIP_DIALOG_TERMINATED: - if (op->refresher) { - belle_sip_refresher_stop(op->refresher); - belle_sip_object_unref(op->refresher); - op->refresher=NULL; - } - break; default: { ms_error("presence op [%p] receive answer [%i] not implemented",op,code); } @@ -245,6 +236,12 @@ static void presence_process_request_event(void *op_base, const belle_sip_reques if (!op->dialog) { if (strcmp(method,"SUBSCRIBE")==0){ belle_sip_dialog_t *dialog = belle_sip_provider_create_dialog(op->base.root->prov,BELLE_SIP_TRANSACTION(server_transaction)); + if (!dialog){ + resp=sal_op_create_response_from_request(op,req,481); + belle_sip_server_transaction_send_response(server_transaction,resp); + sal_op_release(op); + return; + } set_or_update_dialog(op, dialog); ms_message("new incoming subscription from [%s] to [%s]",sal_op_get_from(op),sal_op_get_to(op)); }else{ /* this is a NOTIFY */ @@ -282,6 +279,17 @@ static void presence_process_request_event(void *op_base, const belle_sip_reques static belle_sip_listener_callbacks_t op_presence_callbacks={0}; +/*Invoke when sal_op_release is called by upper layer*/ +static void sal_op_release_cb(struct SalOpBase* op_base) { + SalOp *op =(SalOp*)op_base; + if(op->refresher) { + belle_sip_refresher_stop(op->refresher); + belle_sip_object_unref(op->refresher); + op->refresher=NULL; + set_or_update_dialog(op,NULL); /*only if we have refresher. else dialog terminated event will remove association*/ + } + +} void sal_op_presence_fill_cbs(SalOp*op) { if (op_presence_callbacks.process_request_event==NULL){ op_presence_callbacks.process_io_error=presence_process_io_error; @@ -293,6 +301,7 @@ void sal_op_presence_fill_cbs(SalOp*op) { } op->callbacks=&op_presence_callbacks; op->type=SalOpPresence; + op->base.release_cb=sal_op_release_cb; } @@ -365,6 +374,7 @@ int sal_notify_presence(SalOp *op, SalPresenceModel *presence){ int sal_notify_presence_close(SalOp *op){ belle_sip_request_t* notify=NULL; + int status; if (sal_op_check_dialog_state(op)) { return -1; } @@ -374,7 +384,9 @@ int sal_notify_presence_close(SalOp *op){ sal_add_presence_info(op,BELLE_SIP_MESSAGE(notify),NULL); /*FIXME, what about expires ??*/ belle_sip_message_add_header(BELLE_SIP_MESSAGE(notify) ,BELLE_SIP_HEADER(belle_sip_header_subscription_state_create(BELLE_SIP_SUBSCRIPTION_STATE_TERMINATED,-1))); - return sal_op_send_request(op,notify); + status = sal_op_send_request(op,notify); + set_or_update_dialog(op,NULL); /*because we may be chalanged for the notify, so we must release dialog right now*/ + return status; } diff --git a/coreapi/bellesip_sal/sal_op_publish.c b/coreapi/bellesip_sal/sal_op_publish.c index 17ef8e8db..a525a9998 100644 --- a/coreapi/bellesip_sal/sal_op_publish.c +++ b/coreapi/bellesip_sal/sal_op_publish.c @@ -25,17 +25,17 @@ static void publish_refresher_listener (belle_sip_refresher_t* refresher ,const char* reason_phrase) { SalOp* op = (SalOp*)user_pointer; const belle_sip_client_transaction_t* last_publish_trans=belle_sip_refresher_get_transaction(op->refresher); - belle_sip_request_t* last_publish=belle_sip_transaction_get_request(BELLE_SIP_TRANSACTION(last_publish_trans)); belle_sip_response_t *response=belle_sip_transaction_get_response(BELLE_SIP_TRANSACTION(last_publish_trans)); - /*belle_sip_response_t* response=belle_sip_transaction_get_response(BELLE_SIP_TRANSACTION(belle_sip_refresher_get_transaction(refresher)));*/ ms_message("Publish refresher [%i] reason [%s] for proxy [%s]",status_code,reason_phrase?reason_phrase:"none",sal_op_get_proxy(op)); - if (status_code==412){ - /*resubmit the request after removing the SIP-If-Match*/ - belle_sip_message_remove_header((belle_sip_message_t*)last_publish,"SIP-If-Match"); - belle_sip_refresher_refresh(op->refresher,BELLE_SIP_REFRESHER_REUSE_EXPIRES); - }else if (status_code==0){ + if (status_code==0){ op->base.root->callbacks.on_expire(op); }else if (status_code>=200){ + belle_sip_header_t *sip_etag; + const char *sip_etag_string = NULL; + if (response && (sip_etag = belle_sip_message_get_header(BELLE_SIP_MESSAGE(response), "SIP-ETag"))) { + sip_etag_string = belle_sip_header_get_unparsed_value(sip_etag); + } + sal_op_set_entity_tag(op, sip_etag_string); sal_error_info_set(&op->error_info,SalReasonUnknown,status_code,reason_phrase,NULL); sal_op_assign_recv_headers(op,(belle_sip_message_t*)response); op->base.root->callbacks.on_publish_response(op); @@ -60,43 +60,7 @@ void sal_op_publish_fill_cbs(SalOp *op) { op->type=SalOpPublish; } -/* - * Sending a publish with 0 expires removes the event state and such request shall not contain a body. - * See RFC3903, section 4.5 - */ - -/*presence publish */ -int sal_publish_presence(SalOp *op, const char *from, const char *to, int expires, SalPresenceModel *presence){ - belle_sip_request_t *req=NULL; - if(!op->refresher || !belle_sip_refresher_get_transaction(op->refresher)) { - if (from) - sal_op_set_from(op,from); - if (to) - sal_op_set_to(op,to); - - op->type=SalOpPublish; - req=sal_op_build_request(op,"PUBLISH"); - - if( req == NULL ){ - return -1; - } - - if (sal_op_get_contact_address(op)){ - belle_sip_message_add_header(BELLE_SIP_MESSAGE(req),BELLE_SIP_HEADER(sal_op_create_contact(op))); - } - belle_sip_message_add_header(BELLE_SIP_MESSAGE(req),belle_sip_header_create("Event","presence")); - sal_add_presence_info(op,BELLE_SIP_MESSAGE(req),presence); - return sal_op_send_and_create_refresher(op,req,expires,publish_refresher_listener); - } else { - /*update presence status*/ - const belle_sip_client_transaction_t* last_publish_trans=belle_sip_refresher_get_transaction(op->refresher); - belle_sip_request_t* last_publish=belle_sip_transaction_get_request(BELLE_SIP_TRANSACTION(last_publish_trans)); - sal_add_presence_info(op,BELLE_SIP_MESSAGE(last_publish),expires!=0 ? presence : NULL); - return belle_sip_refresher_refresh(op->refresher,expires); - } -} - -int sal_publish(SalOp *op, const char *from, const char *to, const char *eventname, int expires, const SalBody *body){ +int sal_publish(SalOp *op, const char *from, const char *to, const char *eventname, int expires, const SalBodyHandler *body_handler){ belle_sip_request_t *req=NULL; if(!op->refresher || !belle_sip_refresher_get_transaction(op->refresher)) { if (from) @@ -109,12 +73,16 @@ int sal_publish(SalOp *op, const char *from, const char *to, const char *eventna if( req == NULL ){ return -1; } - + + if (sal_op_get_entity_tag(op)) { + belle_sip_message_add_header(BELLE_SIP_MESSAGE(req),belle_sip_header_create("SIP-If-Match", sal_op_get_entity_tag(op))); + } + if (sal_op_get_contact_address(op)){ belle_sip_message_add_header(BELLE_SIP_MESSAGE(req),BELLE_SIP_HEADER(sal_op_create_contact(op))); } belle_sip_message_add_header(BELLE_SIP_MESSAGE(req),belle_sip_header_create("Event",eventname)); - sal_op_add_body(op,BELLE_SIP_MESSAGE(req),body); + belle_sip_message_set_body_handler(BELLE_SIP_MESSAGE(req), BELLE_SIP_BODY_HANDLER(body_handler)); if (expires!=-1) return sal_op_send_and_create_refresher(op,req,expires,publish_refresher_listener); else return sal_op_send_request(op,req); @@ -123,7 +91,22 @@ int sal_publish(SalOp *op, const char *from, const char *to, const char *eventna const belle_sip_client_transaction_t* last_publish_trans=belle_sip_refresher_get_transaction(op->refresher); belle_sip_request_t* last_publish=belle_sip_transaction_get_request(BELLE_SIP_TRANSACTION(last_publish_trans)); /*update body*/ - sal_op_add_body(op,BELLE_SIP_MESSAGE(last_publish),expires!=0 ? body : NULL); + if (expires == 0) { + belle_sip_message_set_body(BELLE_SIP_MESSAGE(last_publish), NULL, 0); + } else { + belle_sip_message_set_body_handler(BELLE_SIP_MESSAGE(last_publish), BELLE_SIP_BODY_HANDLER(body_handler)); + } return belle_sip_refresher_refresh(op->refresher,expires==-1 ? BELLE_SIP_REFRESHER_REUSE_EXPIRES : expires); } } + +int sal_op_unpublish(SalOp *op){ + if (op->refresher){ + const belle_sip_transaction_t *tr=(const belle_sip_transaction_t*) belle_sip_refresher_get_transaction(op->refresher); + belle_sip_request_t *last_req=belle_sip_transaction_get_request(tr); + belle_sip_message_set_body(BELLE_SIP_MESSAGE(last_req), NULL, 0); + belle_sip_refresher_refresh(op->refresher,0); + return 0; + } + return -1; +} diff --git a/coreapi/bellesip_sal/sal_sdp.c b/coreapi/bellesip_sal/sal_sdp.c index 3d5934f1c..80cfb032b 100644 --- a/coreapi/bellesip_sal/sal_sdp.c +++ b/coreapi/bellesip_sal/sal_sdp.c @@ -1,3 +1,4 @@ + /* linphone Copyright (C) 2012 Belledonne Communications, Grenoble, France @@ -125,7 +126,7 @@ static void add_rtcp_fb_attributes(belle_sdp_media_description_t *media_desc, co uint16_t trr_int = 0; general_trr_int = is_rtcp_fb_trr_int_the_same_for_all_payloads(stream, &trr_int); - if (general_trr_int == TRUE) { + if (general_trr_int == TRUE && trr_int != 0) { add_rtcp_fb_trr_int_attribute(media_desc, -1, trr_int); } if (stream->rtcp_fb.generic_nack_enabled == TRUE) { @@ -143,7 +144,7 @@ static void add_rtcp_fb_attributes(belle_sdp_media_description_t *media_desc, co avpf_params = payload_type_get_avpf_params(pt); /* Add trr-int if not set generally. */ - if (general_trr_int != TRUE) { + if (general_trr_int != TRUE && trr_int != 0) { add_rtcp_fb_trr_int_attribute(media_desc, payload_type_get_number(pt), avpf_params.trr_interval); } @@ -319,7 +320,7 @@ static void stream_description_to_sdp ( belle_sdp_session_description_t *session belle_sdp_media_description_add_attribute(media_desc,belle_sdp_attribute_create ("rtcp",buffer)); } } - if (stream->ice_completed == TRUE) { + if (stream->set_nortpproxy == TRUE) { belle_sdp_media_description_add_attribute(media_desc,belle_sdp_attribute_create ("nortpproxy","yes")); } if (stream->ice_mismatch == TRUE) { @@ -335,7 +336,7 @@ static void stream_description_to_sdp ( belle_sdp_session_description_t *session } } - if ((rtp_port != 0) && sal_stream_description_has_avpf(stream)) { + if ((rtp_port != 0) && (sal_stream_description_has_avpf(stream) || sal_stream_description_has_implicit_avpf(stream))) { add_rtcp_fb_attributes(media_desc, md, stream); } @@ -422,7 +423,7 @@ belle_sdp_session_description_t * media_description_to_sdp ( const SalMediaDescr belle_sdp_session_description_set_bandwidth ( session_desc,"AS",desc->bandwidth ); } - if (desc->ice_completed == TRUE) belle_sdp_session_description_add_attribute(session_desc, belle_sdp_attribute_create("nortpproxy","yes")); + if (desc->set_nortpproxy == TRUE) belle_sdp_session_description_add_attribute(session_desc, belle_sdp_attribute_create("nortpproxy","yes")); if (desc->ice_pwd[0] != '\0') belle_sdp_session_description_add_attribute(session_desc, belle_sdp_attribute_create("ice-pwd",desc->ice_pwd)); if (desc->ice_ufrag[0] != '\0') belle_sdp_session_description_add_attribute(session_desc, belle_sdp_attribute_create("ice-ufrag",desc->ice_ufrag)); @@ -629,14 +630,15 @@ static void apply_rtcp_fb_attribute_to_payload(belle_sdp_rtcp_fb_attribute_t *fb payload_type_set_avpf_params(pt, avpf_params); } -static void sdp_parse_rtcp_fb_parameters(belle_sdp_media_description_t *media_desc, SalStreamDescription *stream) { +static bool_t sdp_parse_rtcp_fb_parameters(belle_sdp_media_description_t *media_desc, SalStreamDescription *stream) { belle_sip_list_t *it; belle_sdp_attribute_t *attribute; belle_sdp_rtcp_fb_attribute_t *fb_attribute; MSList *pt_it; PayloadType *pt; int8_t pt_num; - + bool_t retval = FALSE; + /* Handle rtcp-fb attributes that concern all payload types. */ for (it = belle_sdp_media_description_get_attributes(media_desc); it != NULL; it = it->next) { attribute = BELLE_SDP_ATTRIBUTE(it->data); @@ -646,6 +648,7 @@ static void sdp_parse_rtcp_fb_parameters(belle_sdp_media_description_t *media_de for (pt_it = stream->payloads; pt_it != NULL; pt_it = pt_it->next) { pt = (PayloadType *)pt_it->data; apply_rtcp_fb_attribute_to_payload(fb_attribute, stream, pt); + retval = TRUE; } } } @@ -659,12 +662,14 @@ static void sdp_parse_rtcp_fb_parameters(belle_sdp_media_description_t *media_de pt_num = belle_sdp_rtcp_fb_attribute_get_id(fb_attribute); for (pt_it = stream->payloads; pt_it != NULL; pt_it = pt_it->next) { pt = (PayloadType *)pt_it->data; + retval = TRUE; if (payload_type_get_number(pt) == (int)pt_num) { apply_rtcp_fb_attribute_to_payload(fb_attribute, stream, pt); } } } } + return retval; } static void sal_init_rtcp_xr_description(OrtpRtcpXrConfiguration *config) { @@ -727,6 +732,7 @@ static SalStreamDescription * sdp_to_stream_description(SalMediaDescription *md, belle_sip_list_t *custom_attribute_it; const char* value; const char *mtype,*proto; + bool_t has_avpf_attributes; stream=&md->streams[md->nb_streams]; media=belle_sdp_media_description_get_media ( media_desc ); @@ -830,12 +836,17 @@ static SalStreamDescription * sdp_to_stream_description(SalMediaDescription *md, /* Get ICE candidate attributes if any */ sdp_parse_media_ice_parameters(media_desc, stream); - + + has_avpf_attributes = sdp_parse_rtcp_fb_parameters(media_desc, stream); + /* Get RTCP-FB attributes if any */ if (sal_stream_description_has_avpf(stream)) { enable_avpf_for_stream(stream); - sdp_parse_rtcp_fb_parameters(media_desc, stream); } + else if (has_avpf_attributes ){ + + stream->implicit_rtcp_fb = TRUE; + } /* Get RTCP-XR attributes if any */ stream->rtcp_xr = md->rtcp_xr; // Use session parameters if no stream parameters are defined diff --git a/coreapi/buffer.h b/coreapi/buffer.h index 0d22e9ad2..492629353 100644 --- a/coreapi/buffer.h +++ b/coreapi/buffer.h @@ -144,4 +144,4 @@ LINPHONE_PUBLIC bool_t linphone_buffer_is_empty(const LinphoneBuffer *buffer); } #endif -#endif /* LINPHONE_CONTENT_H_ */ +#endif /* LINPHONE_BUFFER_H_ */ diff --git a/coreapi/call_log.c b/coreapi/call_log.c index 2bf8d5b9f..8de0712ef 100644 --- a/coreapi/call_log.c +++ b/coreapi/call_log.c @@ -248,6 +248,10 @@ bool_t linphone_call_log_video_enabled(LinphoneCallLog *cl) { return cl->video_enabled; } +bool_t linphone_call_log_was_conference(LinphoneCallLog *cl) { + return cl->was_conference; +} + /******************************************************************************* * Reference and user data handling functions * @@ -345,7 +349,7 @@ static void linphone_create_table(sqlite3* db) { } } -void linphone_update_call_log_table(sqlite3* db) { +static void linphone_update_call_log_table(sqlite3* db) { char* errmsg=NULL; int ret; @@ -419,6 +423,9 @@ static int create_call_log(void *data, int argc, char **argv, char **colName) { unsigned int storage_id = atoi(argv[0]); from = linphone_address_new(argv[1]); to = linphone_address_new(argv[2]); + + if (from == NULL || to == NULL) goto error; + dir = (LinphoneCallDir) atoi(argv[3]); log = linphone_call_log_new(dir, from, to); @@ -441,11 +448,20 @@ static int create_call_log(void *data, int argc, char **argv, char **colName) { } *list = ms_list_append(*list, log); - + return 0; + +error: + if (from){ + linphone_address_destroy(from); + } + if (to){ + linphone_address_destroy(to); + } + ms_error("Bad call log at storage_id %u", storage_id); return 0; } -void linphone_sql_request_call_log(sqlite3 *db, const char *stmt, MSList **list) { +static void linphone_sql_request_call_log(sqlite3 *db, const char *stmt, MSList **list) { char* errmsg = NULL; int ret; ret = sqlite3_exec(db, stmt, create_call_log, list, &errmsg); @@ -455,7 +471,7 @@ void linphone_sql_request_call_log(sqlite3 *db, const char *stmt, MSList **list) } } -int linphone_sql_request_generic(sqlite3* db, const char *stmt) { +static int linphone_sql_request_generic(sqlite3* db, const char *stmt) { char* errmsg = NULL; int ret; ret = sqlite3_exec(db, stmt, NULL, NULL, &errmsg); diff --git a/coreapi/call_log.h b/coreapi/call_log.h index 6a3ec8dab..8cae77d24 100644 --- a/coreapi/call_log.h +++ b/coreapi/call_log.h @@ -179,6 +179,13 @@ LINPHONE_PUBLIC bool_t linphone_call_log_video_enabled(LinphoneCallLog *cl); **/ LINPHONE_PUBLIC char * linphone_call_log_to_str(LinphoneCallLog *cl); +/** + * Tells whether that call was a call to a conference server + * @param[in] cl #LinphoneCallLog object + * @return TRUE if the call was a call to a conference server + */ +LINPHONE_PUBLIC bool_t linphone_call_log_was_conference(LinphoneCallLog *cl); + /******************************************************************************* * Reference and user data handling functions * diff --git a/coreapi/call_params.h b/coreapi/call_params.h index e3e6d1ee4..e536280a4 100644 --- a/coreapi/call_params.h +++ b/coreapi/call_params.h @@ -125,6 +125,10 @@ LINPHONE_PUBLIC const char *linphone_call_params_get_custom_header(const Linphon /** * Tell whether the call is part of the locally managed conference. + * @warning If a conference server is used to manage conferences, + * that function does not return TRUE even if the conference is running.
+ * If you want to test whether the conference is running, you should test + * whether linphone_core_get_conference() return a non-null pointer. * @param[in] cp LinphoneCallParams object * @return A boolean value telling whether the call is part of the locally managed conference. **/ diff --git a/coreapi/callbacks.c b/coreapi/callbacks.c index a0035d2ed..1e14854a9 100644 --- a/coreapi/callbacks.c +++ b/coreapi/callbacks.c @@ -187,7 +187,7 @@ void linphone_core_update_streams(LinphoneCore *lc, LinphoneCall *call, SalMedia ms_message("Media ip type has changed, destroying sessions context on call [%p]",call); ms_media_stream_sessions_uninit(&call->sessions[call->main_audio_stream_index]); ms_media_stream_sessions_uninit(&call->sessions[call->main_video_stream_index]); - if (call->params->realtimetext_enabled) ms_media_stream_sessions_uninit(&call->sessions[call->main_text_stream_index]); + ms_media_stream_sessions_uninit(&call->sessions[call->main_text_stream_index]); } linphone_call_init_media_streams (call); } @@ -254,27 +254,19 @@ static void call_received(SalOp *h){ if (linphone_presence_model_get_basic_status(lc->presence_model) == LinphonePresenceBasicStatusClosed) { LinphonePresenceActivity *activity = linphone_presence_model_get_activity(lc->presence_model); switch (linphone_presence_activity_get_type(activity)) { - case LinphonePresenceActivityBusy: - sal_call_decline(h,SalReasonBusy,NULL); - break; - case LinphonePresenceActivityAppointment: - case LinphonePresenceActivityMeeting: - case LinphonePresenceActivityOffline: - case LinphonePresenceActivityWorship: - sal_call_decline(h,SalReasonTemporarilyUnavailable,NULL); - break; case LinphonePresenceActivityPermanentAbsence: alt_contact = linphone_presence_model_get_contact(lc->presence_model); if (alt_contact != NULL) { sal_call_decline(h,SalReasonRedirect,alt_contact); ms_free(alt_contact); + sal_op_release(h); + return; } break; default: + /*nothing special to be done*/ break; } - sal_op_release(h); - return; } if (!linphone_core_can_we_add_call(lc)){/*busy*/ @@ -344,7 +336,7 @@ static void call_received(SalOp *h){ call->bg_task_id=sal_begin_background_task("liblinphone call notification", NULL, NULL); - if ((linphone_core_get_firewall_policy(lc) == LinphonePolicyUseIce) && (call->ice_session != NULL)) { + if (call->defer_notify_incoming) { /* Defer ringing until the end of the ICE candidates gathering process. */ ms_message("Defer ringing to gather ICE candidates"); return; @@ -405,7 +397,7 @@ static void start_remote_ring(LinphoneCore *lc, LinphoneCall *call) { if (call->audiostream) audio_stream_unprepare_sound(call->audiostream); if( lc->sound_conf.remote_ring ){ - lc->ringstream=ring_start(lc->sound_conf.remote_ring,2000,ringcard); + lc->ringstream=ring_start(lc->factory, lc->sound_conf.remote_ring,2000,ringcard); } } } @@ -503,10 +495,7 @@ static void process_call_accepted(LinphoneCore *lc, LinphoneCall *call, SalOp *o if (call->params->internal_call_update) call->params->internal_call_update = FALSE; - /* Handle remote ICE attributes if any. */ - if (call->ice_session != NULL && rmd) { - linphone_call_update_ice_from_remote_media_description(call, rmd); - } + #ifdef BUILD_UPNP if (call->upnp_session != NULL && rmd) { linphone_core_update_upnp_from_remote_media_description(call, rmd); @@ -522,6 +511,12 @@ static void process_call_accepted(LinphoneCore *lc, LinphoneCall *call, SalOp *o md = NULL; } if (md){ /*there is a valid SDP in the response, either offer or answer, and we're able to start/update the streams*/ + + /* Handle remote ICE attributes if any. */ + if (call->ice_session != NULL && rmd) { + linphone_call_update_ice_from_remote_media_description(call, rmd, FALSE); + } + switch (call->state){ case LinphoneCallResuming: linphone_core_notify_display_status(lc,_("Call resumed.")); @@ -791,7 +786,7 @@ static void call_terminated(SalOp *op, const char *from){ linphone_core_start_refered_call(lc,call,NULL); } //we stop the call only if we have this current call or if we are in call - if (lc->ringstream!=NULL && ( (ms_list_size(lc->calls) == 1) || linphone_core_in_call(lc) )) { + if ((ms_list_size(lc->calls) == 1) || linphone_core_in_call(lc)) { linphone_core_stop_ringing(lc); } linphone_call_stop_media_streams(call); @@ -1035,10 +1030,11 @@ static void register_failure(SalOp *op){ } else { linphone_proxy_config_set_state(cfg,LinphoneRegistrationFailed,details); } - if (cfg->publish_op){ + if (cfg->long_term_event){ /*prevent publish to be sent now until registration gets successful*/ - sal_op_release(cfg->publish_op); - cfg->publish_op=NULL; + linphone_event_terminate(cfg->long_term_event); + linphone_event_unref(cfg->long_term_event); + cfg->long_term_event=NULL; cfg->send_publish=cfg->publish; } } @@ -1065,7 +1061,15 @@ static void dtmf_received(SalOp *op, char dtmf){ static void refer_received(Sal *sal, SalOp *op, const char *referto){ LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal); LinphoneCall *call=(LinphoneCall*)sal_op_get_user_pointer(op); - if (call){ + LinphoneAddress *refer_to_addr = linphone_address_new(referto); + char method[20] = ""; + + if(refer_to_addr) { + const char *tmp = linphone_address_get_method_param(refer_to_addr); + if(tmp) strncpy(method, tmp, sizeof(method)); + linphone_address_destroy(refer_to_addr); + } + if (call && (strlen(method) == 0 || strcmp(method, "INVITE") == 0)) { if (call->refer_to!=NULL){ ms_free(call->refer_to); } @@ -1122,11 +1126,18 @@ static void is_composing_received(SalOp *op, const SalIsComposing *is_composing) } static void parse_presence_requested(SalOp *op, const char *content_type, const char *content_subtype, const char *body, SalPresenceModel **result) { - linphone_notify_parse_presence(op, content_type, content_subtype, body, result); + linphone_notify_parse_presence(content_type, content_subtype, body, result); } static void convert_presence_to_xml_requested(SalOp *op, SalPresenceModel *presence, const char *contact, char **content) { - linphone_notify_convert_presence_to_xml(op, presence, contact, content); + /*for backward compatibility because still used by notify. No loguer used for publish*/ + + if(linphone_presence_model_get_presentity((LinphonePresenceModel*)presence) == NULL) { + LinphoneAddress * presentity = linphone_address_new(contact); + linphone_presence_model_set_presentity((LinphonePresenceModel*)presence, presentity); + linphone_address_unref(presentity); + } + *content = linphone_presence_model_to_xml((LinphonePresenceModel*)presence); } static void notify_presence(SalOp *op, SalSubscribeStatus ss, SalPresenceModel *model, const char *msg){ @@ -1276,9 +1287,9 @@ static void text_delivery_update(SalOp *op, SalTextDeliveryStatus status){ } } -static void info_received(SalOp *op, const SalBody *body){ +static void info_received(SalOp *op, SalBodyHandler *body_handler){ LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(op)); - linphone_core_notify_info_message(lc,op,body); + linphone_core_notify_info_message(lc,op,body_handler); } static void subscribe_response(SalOp *op, SalSubscribeStatus status){ @@ -1292,14 +1303,14 @@ static void subscribe_response(SalOp *op, SalSubscribeStatus status){ }else if (status==SalSubscribePending){ linphone_event_set_state(lev,LinphoneSubscriptionPending); }else{ - if (lev->subscription_state==LinphoneSubscriptionActive && ei->reason==SalReasonIOError){ + if (lev->subscription_state==LinphoneSubscriptionActive && (ei->reason==SalReasonIOError || ei->reason == SalReasonNoMatch)){ linphone_event_set_state(lev,LinphoneSubscriptionOutgoingProgress); } else linphone_event_set_state(lev,LinphoneSubscriptionError); } } -static void notify(SalOp *op, SalSubscribeStatus st, const char *eventname, const SalBody *body){ +static void notify(SalOp *op, SalSubscribeStatus st, const char *eventname, SalBodyHandler *body_handler){ LinphoneEvent *lev=(LinphoneEvent*)sal_op_get_user_pointer(op); LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(op)); @@ -1308,15 +1319,18 @@ static void notify(SalOp *op, SalSubscribeStatus st, const char *eventname, cons lev=linphone_event_new_with_out_of_dialog_op(lc,op,LinphoneSubscriptionOutgoing,eventname); } { - LinphoneContent *ct=linphone_content_from_sal_body(body); - if (ct) linphone_core_notify_notify_received(lc,lev,eventname,ct); + LinphoneContent *ct=linphone_content_from_sal_body_handler(body_handler); + if (ct) { + linphone_core_notify_notify_received(lc,lev,eventname,ct); + linphone_content_unref(ct); + } } if (st!=SalSubscribeNone){ linphone_event_set_state(lev,linphone_subscription_state_from_sal(st)); } } -static void subscribe_received(SalOp *op, const char *eventname, const SalBody *body){ +static void subscribe_received(SalOp *op, const char *eventname, const SalBodyHandler *body_handler){ LinphoneEvent *lev=(LinphoneEvent*)sal_op_get_user_pointer(op); LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(op)); @@ -1329,7 +1343,7 @@ static void subscribe_received(SalOp *op, const char *eventname, const SalBody * } -static void subscribe_closed(SalOp *op){ +static void incoming_subscribe_closed(SalOp *op){ LinphoneEvent *lev=(LinphoneEvent*)sal_op_get_user_pointer(op); linphone_event_set_state(lev,LinphoneSubscriptionTerminated); @@ -1386,7 +1400,7 @@ SalCallbacks linphone_sal_callbacks={ is_composing_received, notify_refer, subscribe_received, - subscribe_closed, + incoming_subscribe_closed, subscribe_response, notify, subscribe_presence_received, diff --git a/coreapi/carddav.c b/coreapi/carddav.c new file mode 100644 index 000000000..6bfcb1d8e --- /dev/null +++ b/coreapi/carddav.c @@ -0,0 +1,676 @@ +/* +carddav.c +Copyright (C) 2015 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "linphonecore.h" +#include "private.h" + +LinphoneCardDavContext* linphone_carddav_context_new(LinphoneFriendList *lfl) { + LinphoneCardDavContext *carddav_context = NULL; + + if (!lfl || !lfl->uri) { + return NULL; + } + +#ifdef VCARD_ENABLED + carddav_context = (LinphoneCardDavContext *)ms_new0(LinphoneCardDavContext, 1); + carddav_context->friend_list = linphone_friend_list_ref(lfl); +#else + ms_error("vCard isn't available (maybe it wasn't compiled), can't do CardDAV sync"); +#endif + return carddav_context; +} + +void linphone_carddav_context_destroy(LinphoneCardDavContext *cdc) { + if (cdc) { + if (cdc->friend_list) { + linphone_friend_list_unref(cdc->friend_list); + cdc->friend_list = NULL; + } + ms_free(cdc); + } +} + +void linphone_carddav_set_user_data(LinphoneCardDavContext *cdc, void *ud) { + cdc->user_data = ud; +} + +void* linphone_carddav_get_user_data(LinphoneCardDavContext *cdc) { + return cdc->user_data; +} + +void linphone_carddav_synchronize(LinphoneCardDavContext *cdc) { + cdc->ctag = cdc->friend_list->revision; + linphone_carddav_get_current_ctag(cdc); +} + +static void linphone_carddav_sync_done(LinphoneCardDavContext *cdc, bool_t success, const char *msg) { + if (success) { + ms_debug("CardDAV sync successful, saving new cTag: %i", cdc->ctag); + linphone_friend_list_update_revision(cdc->friend_list, cdc->ctag); + } else { + ms_error("CardDAV sync failure: %s", msg); + } + + if (cdc->sync_done_cb) { + cdc->sync_done_cb(cdc, success, msg); + } +} + +static int find_matching_friend(LinphoneFriend *lf1, LinphoneFriend *lf2) { + LinphoneVcard *lvc1 = linphone_friend_get_vcard(lf1); + LinphoneVcard *lvc2 = linphone_friend_get_vcard(lf2); + const char *uid1 = NULL, *uid2 = NULL; + if (!lvc1 || !lvc2) { + return 1; + } + uid1 = linphone_vcard_get_uid(lvc1); + uid2 = linphone_vcard_get_uid(lvc2); + if (!uid1 || !uid2) { + return 1; + } + return strcmp(uid1, uid2); +} + +static void linphone_carddav_vcards_pulled(LinphoneCardDavContext *cdc, MSList *vCards) { + if (vCards != NULL && ms_list_size(vCards) > 0) { + MSList *friends = cdc->friend_list->friends; + while (vCards) { + LinphoneCardDavResponse *vCard = (LinphoneCardDavResponse *)vCards->data; + if (vCard) { + LinphoneVcard *lvc = linphone_vcard_new_from_vcard4_buffer(vCard->vcard); + LinphoneFriend *lf = NULL; + MSList *local_friend = NULL; + + if (lvc) { + // Compute downloaded vCards' URL and save it (+ eTag) + char *vCard_name = strrchr(vCard->url, '/'); + char full_url[300]; + snprintf(full_url, sizeof(full_url), "%s%s", cdc->friend_list->uri, vCard_name); + linphone_vcard_set_url(lvc, full_url); + linphone_vcard_set_etag(lvc, vCard->etag); + ms_debug("Downloaded vCard etag/url are %s and %s", vCard->etag, full_url); + } + lf = linphone_friend_new_from_vcard(lvc); + local_friend = ms_list_find_custom(friends, (int (*)(const void*, const void*))find_matching_friend, lf); + + if (local_friend) { + LinphoneFriend *lf2 = (LinphoneFriend *)local_friend->data; + lf->storage_id = lf2->storage_id; + lf->pol = lf2->pol; + lf->subscribe = lf2->subscribe; + lf->refkey = ms_strdup(lf2->refkey); + lf->presence_received = lf2->presence_received; + lf->lc = lf2->lc; + lf->friend_list = lf2->friend_list; + + if (cdc->contact_updated_cb) { + ms_debug("Contact updated: %s", linphone_friend_get_name(lf)); + cdc->contact_updated_cb(cdc, lf, lf2); + } + } else { + if (cdc->contact_created_cb) { + ms_debug("Contact created: %s", linphone_friend_get_name(lf)); + cdc->contact_created_cb(cdc, lf); + } + } + linphone_friend_unref(lf); + } + vCards = ms_list_next(vCards); + } + } + ms_list_free(vCards); + linphone_carddav_sync_done(cdc, TRUE, NULL); +} + +static MSList* parse_vcards_from_xml_response(const char *body) { + MSList *result = NULL; + xmlparsing_context_t *xml_ctx = linphone_xmlparsing_context_new(); + xmlSetGenericErrorFunc(xml_ctx, linphone_xmlparsing_genericxml_error); + xml_ctx->doc = xmlReadDoc((const unsigned char*)body, 0, NULL, 0); + if (xml_ctx->doc != NULL) { + if (linphone_create_xml_xpath_context(xml_ctx) < 0) goto end; + linphone_xml_xpath_context_init_carddav_ns(xml_ctx); + { + xmlXPathObjectPtr responses = linphone_get_xml_xpath_object_for_node_list(xml_ctx, "/d:multistatus/d:response"); + if (responses != NULL && responses->nodesetval != NULL) { + xmlNodeSetPtr responses_nodes = responses->nodesetval; + if (responses_nodes->nodeNr >= 1) { + int i; + for (i = 0; i < responses_nodes->nodeNr; i++) { + xmlNodePtr response_node = responses_nodes->nodeTab[i]; + xml_ctx->xpath_ctx->node = response_node; + { + char *etag = linphone_get_xml_text_content(xml_ctx, "d:propstat/d:prop/d:getetag"); + char *url = linphone_get_xml_text_content(xml_ctx, "d:href"); + char *vcard = linphone_get_xml_text_content(xml_ctx, "d:propstat/d:prop/card:address-data"); + LinphoneCardDavResponse *response = ms_new0(LinphoneCardDavResponse, 1); + response->etag = ms_strdup(etag); + response->url = ms_strdup(url); + response->vcard = ms_strdup(vcard); + result = ms_list_append(result, response); + ms_debug("Added vCard object with eTag %s, URL %s and vCard %s", etag, url, vcard); + } + } + } + xmlXPathFreeObject(responses); + } + } + } +end: + linphone_xmlparsing_context_destroy(xml_ctx); + return result; +} + +static int find_matching_vcard(LinphoneCardDavResponse *response, LinphoneFriend *lf) { + if (!response->url || !lf || !lf->vcard || !linphone_vcard_get_url(lf->vcard)) { + return 1; + } + return strcmp(response->url, linphone_vcard_get_url(lf->vcard)); +} + +static void linphone_carddav_vcards_fetched(LinphoneCardDavContext *cdc, MSList *vCards) { + if (vCards != NULL && ms_list_size(vCards) > 0) { + MSList *friends = cdc->friend_list->friends; + MSList *friends_to_remove = NULL; + MSList *temp_list = NULL; + + while (friends) { + LinphoneFriend *lf = (LinphoneFriend *)friends->data; + if (lf) { + MSList *vCard = ms_list_find_custom(vCards, (int (*)(const void*, const void*))find_matching_vcard, lf); + if (!vCard) { + ms_debug("Local friend %s isn't in the remote vCard list, delete it", linphone_friend_get_name(lf)); + temp_list = ms_list_append(temp_list, linphone_friend_ref(lf)); + } else { + LinphoneCardDavResponse *response = (LinphoneCardDavResponse *)vCard->data; + ms_debug("Local friend %s is in the remote vCard list, check eTag", linphone_friend_get_name(lf)); + if (response) { + LinphoneVcard *lvc = linphone_friend_get_vcard(lf); + const char *etag = linphone_vcard_get_etag(lvc); + ms_debug("Local friend eTag is %s, remote vCard eTag is %s", etag, response->etag); + if (lvc && etag && strcmp(etag, response->etag) == 0) { + ms_list_remove(vCards, vCard); + ms_free(response); + } + } + } + } + friends = ms_list_next(friends); + } + friends_to_remove = temp_list; + while(friends_to_remove) { + LinphoneFriend *lf = (LinphoneFriend *)friends_to_remove->data; + if (lf) { + if (cdc->contact_removed_cb) { + ms_debug("Contact removed: %s", linphone_friend_get_name(lf)); + cdc->contact_removed_cb(cdc, lf); + } + } + friends_to_remove = ms_list_next(friends_to_remove); + } + temp_list = ms_list_free_with_data(temp_list, (void (*)(void *))linphone_friend_unref); + + linphone_carddav_pull_vcards(cdc, vCards); + } + ms_list_free(vCards); +} + +static MSList* parse_vcards_etags_from_xml_response(const char *body) { + MSList *result = NULL; + xmlparsing_context_t *xml_ctx = linphone_xmlparsing_context_new(); + xmlSetGenericErrorFunc(xml_ctx, linphone_xmlparsing_genericxml_error); + xml_ctx->doc = xmlReadDoc((const unsigned char*)body, 0, NULL, 0); + if (xml_ctx->doc != NULL) { + if (linphone_create_xml_xpath_context(xml_ctx) < 0) goto end; + linphone_xml_xpath_context_init_carddav_ns(xml_ctx); + { + xmlXPathObjectPtr responses = linphone_get_xml_xpath_object_for_node_list(xml_ctx, "/d:multistatus/d:response"); + if (responses != NULL && responses->nodesetval != NULL) { + xmlNodeSetPtr responses_nodes = responses->nodesetval; + if (responses_nodes->nodeNr >= 1) { + int i; + for (i = 0; i < responses_nodes->nodeNr; i++) { + xmlNodePtr response_node = responses_nodes->nodeTab[i]; + xml_ctx->xpath_ctx->node = response_node; + { + char *etag = linphone_get_xml_text_content(xml_ctx, "d:propstat/d:prop/d:getetag"); + char *url = linphone_get_xml_text_content(xml_ctx, "d:href"); + LinphoneCardDavResponse *response = ms_new0(LinphoneCardDavResponse, 1); + response->etag = ms_strdup(etag); + response->url = ms_strdup(url); + result = ms_list_append(result, response); + ms_debug("Added vCard object with eTag %s and URL %s", etag, url); + } + } + } + xmlXPathFreeObject(responses); + } + } + } +end: + linphone_xmlparsing_context_destroy(xml_ctx); + return result; +} + +static void linphone_carddav_ctag_fetched(LinphoneCardDavContext *cdc, int ctag) { + ms_debug("Remote cTag for CardDAV addressbook is %i, local one is %i", ctag, cdc->ctag); + if (ctag == -1 || ctag > cdc->ctag) { + cdc->ctag = ctag; + linphone_carddav_fetch_vcards(cdc); + } else { + ms_message("No changes found on server, skipping sync"); + linphone_carddav_sync_done(cdc, TRUE, "Synchronization skipped because cTag already up to date"); + } +} + +static int parse_ctag_value_from_xml_response(const char *body) { + int result = -1; + xmlparsing_context_t *xml_ctx = linphone_xmlparsing_context_new(); + xmlSetGenericErrorFunc(xml_ctx, linphone_xmlparsing_genericxml_error); + xml_ctx->doc = xmlReadDoc((const unsigned char*)body, 0, NULL, 0); + if (xml_ctx->doc != NULL) { + char *response = NULL; + if (linphone_create_xml_xpath_context(xml_ctx) < 0) goto end; + linphone_xml_xpath_context_init_carddav_ns(xml_ctx); + response = linphone_get_xml_text_content(xml_ctx, "/d:multistatus/d:response/d:propstat/d:prop/x1:getctag"); + if (response) { + result = atoi(response); + linphone_free_xml_text_content(response); + } + } +end: + linphone_xmlparsing_context_destroy(xml_ctx); + return result; +} + +static void linphone_carddav_query_free(LinphoneCardDavQuery *query) { + if (!query) { + return; + } + + if (query->http_request_listener) { + belle_sip_object_unref(query->http_request_listener); + query->http_request_listener = NULL; + } + + ms_free(query); +} + +static void process_response_from_carddav_request(void *data, const belle_http_response_event_t *event) { + LinphoneCardDavQuery *query = (LinphoneCardDavQuery *)data; + + if (event->response) { + int code = belle_http_response_get_status_code(event->response); + if (code == 207 || code == 200 || code == 201 || code == 204) { + const char *body = belle_sip_message_get_body((belle_sip_message_t *)event->response); + switch(query->type) { + case LinphoneCardDavQueryTypePropfind: + linphone_carddav_ctag_fetched(query->context, parse_ctag_value_from_xml_response(body)); + break; + case LinphoneCardDavQueryTypeAddressbookQuery: + linphone_carddav_vcards_fetched(query->context, parse_vcards_etags_from_xml_response(body)); + break; + case LinphoneCardDavQueryTypeAddressbookMultiget: + linphone_carddav_vcards_pulled(query->context, parse_vcards_from_xml_response(body)); + break; + case LinphoneCardDavQueryTypePut: + { + belle_sip_header_t *header = belle_sip_message_get_header((belle_sip_message_t *)event->response, "ETag"); + LinphoneFriend *lf = (LinphoneFriend *)query->user_data; + LinphoneVcard *lvc = linphone_friend_get_vcard(lf); + if (lf && lvc) { + if (header) { + const char *etag = belle_sip_header_get_unparsed_value(header); + if (!linphone_vcard_get_etag(lvc)) { + ms_debug("eTag for newly created vCard is: %s", etag); + } else { + ms_debug("eTag for updated vCard is: %s", etag); + } + linphone_vcard_set_etag(lvc, etag); + + linphone_carddav_sync_done(query->context, TRUE, NULL); + linphone_friend_unref(lf); + } else { + // For some reason, server didn't return the eTag of the updated/created vCard + // We need to do a GET on the vCard to get the correct one + MSList *vcard = NULL; + LinphoneCardDavResponse *response = (LinphoneCardDavResponse *)ms_new0(LinphoneCardDavResponse, 1); + response->url = linphone_vcard_get_url(lvc); + response->context = query->context; + vcard = ms_list_append(vcard, response); + linphone_carddav_pull_vcards(query->context, vcard); + ms_list_free(vcard); + } + } + else { + linphone_carddav_sync_done(query->context, FALSE, "No LinphoneFriend found in user_date field of query"); + } + } + break; + case LinphoneCardDavQueryTypeDelete: + linphone_carddav_sync_done(query->context, TRUE, NULL); + break; + default: + ms_error("Unknown request: %i", query->type); + break; + } + } else { + char msg[100]; + snprintf(msg, sizeof(msg), "Unexpected HTTP response code: %i", code); + linphone_carddav_sync_done(query->context, FALSE, msg); + } + } else { + linphone_carddav_sync_done(query->context, FALSE, "No response found"); + } + linphone_carddav_query_free(query); +} + +static void process_io_error_from_carddav_request(void *data, const belle_sip_io_error_event_t *event) { + LinphoneCardDavQuery *query = (LinphoneCardDavQuery *)data; + ms_error("I/O error during CardDAV request sending"); + linphone_carddav_query_free(query); + linphone_carddav_sync_done(query->context, FALSE, "I/O error during CardDAV request sending"); +} + +static void process_auth_requested_from_carddav_request(void *data, belle_sip_auth_event_t *event) { + LinphoneCardDavQuery *query = (LinphoneCardDavQuery *)data; + LinphoneCardDavContext *cdc = query->context; + const char *realm = belle_sip_auth_event_get_realm(event); + belle_generic_uri_t *uri = belle_generic_uri_parse(query->url); + const char *domain = belle_generic_uri_get_host(uri); + LinphoneCore *lc = cdc->friend_list->lc; + const MSList *auth_infos = linphone_core_get_auth_info_list(lc); + + ms_debug("Looking for auth info for domain %s and realm %s", domain, realm); + while (auth_infos) { + LinphoneAuthInfo *auth_info = (LinphoneAuthInfo *)auth_infos->data; + if (auth_info->domain && strcmp(domain, auth_info->domain) == 0) { + if (!auth_info->realm || strcmp(realm, auth_info->realm) == 0) { + belle_sip_auth_event_set_username(event, auth_info->username); + belle_sip_auth_event_set_passwd(event, auth_info->passwd); + belle_sip_auth_event_set_ha1(event, auth_info->ha1); + break; + } + } + auth_infos = ms_list_next(auth_infos); + } + + if (!auth_infos) { + ms_error("Authentication requested during CardDAV request sending, and username/password weren't provided"); + linphone_carddav_sync_done(query->context, FALSE, "Authentication requested during CardDAV request sending, and username/password weren't provided"); + linphone_carddav_query_free(query); + } +} + +static void linphone_carddav_send_query(LinphoneCardDavQuery *query) { + belle_http_request_listener_callbacks_t cbs = { 0 }; + belle_generic_uri_t *uri = NULL; + belle_http_request_t *req = NULL; + belle_sip_memory_body_handler_t *bh = NULL; + + uri = belle_generic_uri_parse(query->url); + if (!uri) { + LinphoneCardDavContext *cdc = query->context; + if (cdc && cdc->sync_done_cb) { + cdc->sync_done_cb(cdc, FALSE, "Could not send request, URL is invalid"); + } + belle_sip_error("Could not send request, URL %s is invalid", query->url); + return; + } + req = belle_http_request_create(query->method, uri, belle_sip_header_content_type_create("application", "xml; charset=utf-8"), NULL); + + if (!req) { + LinphoneCardDavContext *cdc = query->context; + if (cdc && cdc->sync_done_cb) { + cdc->sync_done_cb(cdc, FALSE, "Could not create belle_http_request_t"); + } + belle_sip_object_unref(uri); + belle_sip_error("Could not create belle_http_request_t"); + return; + } + + if (query->depth) { + belle_sip_message_add_header((belle_sip_message_t *)req, belle_sip_header_create("Depth", query->depth)); + } else if (query->ifmatch) { + belle_sip_message_add_header((belle_sip_message_t *)req, belle_sip_header_create("If-Match", query->ifmatch)); + } else if (strcmp(query->method, "PUT")) { + belle_sip_message_add_header((belle_sip_message_t *)req, belle_sip_header_create("If-None-Match", "*")); + } + + if (query->body) { + bh = belle_sip_memory_body_handler_new_copy_from_buffer(query->body, strlen(query->body), NULL, NULL); + belle_sip_message_set_body_handler(BELLE_SIP_MESSAGE(req), bh ? BELLE_SIP_BODY_HANDLER(bh) : NULL); + } + + cbs.process_response = process_response_from_carddav_request; + cbs.process_io_error = process_io_error_from_carddav_request; + cbs.process_auth_requested = process_auth_requested_from_carddav_request; + query->http_request_listener = belle_http_request_listener_create_from_callbacks(&cbs, query); + belle_http_provider_send_request(query->context->friend_list->lc->http_provider, req, query->http_request_listener); +} + +static LinphoneCardDavQuery* linphone_carddav_create_put_query(LinphoneCardDavContext *cdc, LinphoneVcard *lvc) { + LinphoneCardDavQuery *query = (LinphoneCardDavQuery *)ms_new0(LinphoneCardDavQuery, 1); + query->context = cdc; + query->depth = NULL; + query->ifmatch = linphone_vcard_get_etag(lvc); + query->body = linphone_vcard_as_vcard4_string(lvc); + query->method = "PUT"; + query->url = linphone_vcard_get_url(lvc); + query->type = LinphoneCardDavQueryTypePut; + return query; +} + +static char* generate_url_from_server_address_and_uid(const char *server_url) { + char *result = NULL; + if (server_url) { + char *uuid = ms_malloc(64); + if (sal_generate_uuid(uuid, 64) == 0) { + char *url = ms_malloc(300); + snprintf(url, 300, "%s/linphone-%s.vcf", server_url, uuid); + ms_debug("Generated url is %s", url); + result = ms_strdup(url); + ms_free(url); + } + ms_free(uuid); + } + return result; +} + +void linphone_carddav_put_vcard(LinphoneCardDavContext *cdc, LinphoneFriend *lf) { + LinphoneVcard *lvc = linphone_friend_get_vcard(lf); + if (lvc) { + LinphoneCardDavQuery *query = NULL; + if (!linphone_vcard_get_uid(lvc)) { + linphone_vcard_generate_unique_id(lvc); + } + + if (!linphone_vcard_get_url(lvc)) { + char *url = generate_url_from_server_address_and_uid(cdc->friend_list->uri); + if (url) { + linphone_vcard_set_url(lvc, url); + ms_free(url); + } else { + const char *msg = "vCard doesn't have an URL, and friendlist doesn't have a CardDAV server set either, can't push it"; + ms_warning("%s", msg); + if (cdc && cdc->sync_done_cb) { + cdc->sync_done_cb(cdc, FALSE, msg); + } + return; + } + } + + query = linphone_carddav_create_put_query(cdc, lvc); + query->user_data = linphone_friend_ref(lf); + linphone_carddav_send_query(query); + } else { + const char *msg = NULL; + if (!lvc) { + msg = "LinphoneVcard is NULL"; + } else { + msg = "Unknown error"; + } + + if (msg) { + ms_error("%s", msg); + } + + if (cdc && cdc->sync_done_cb) { + cdc->sync_done_cb(cdc, FALSE, msg); + } + } +} + +static LinphoneCardDavQuery* linphone_carddav_create_delete_query(LinphoneCardDavContext *cdc, LinphoneVcard *lvc) { + LinphoneCardDavQuery *query = (LinphoneCardDavQuery *)ms_new0(LinphoneCardDavQuery, 1); + query->context = cdc; + query->depth = NULL; + query->ifmatch = linphone_vcard_get_etag(lvc); + query->body = NULL; + query->method = "DELETE"; + query->url = linphone_vcard_get_url(lvc); + query->type = LinphoneCardDavQueryTypeDelete; + return query; +} + +void linphone_carddav_delete_vcard(LinphoneCardDavContext *cdc, LinphoneFriend *lf) { + LinphoneVcard *lvc = linphone_friend_get_vcard(lf); + if (lvc && linphone_vcard_get_uid(lvc) && linphone_vcard_get_etag(lvc)) { + LinphoneCardDavQuery *query = NULL; + + if (!linphone_vcard_get_url(lvc)) { + char *url = generate_url_from_server_address_and_uid(cdc->friend_list->uri); + if (url) { + linphone_vcard_set_url(lvc, url); + ms_free(url); + } else { + const char *msg = "vCard doesn't have an URL, and friendlist doesn't have a CardDAV server set either, can't delete it"; + ms_warning("%s", msg); + if (cdc && cdc->sync_done_cb) { + cdc->sync_done_cb(cdc, FALSE, msg); + } + return; + } + } + + query = linphone_carddav_create_delete_query(cdc, lvc); + linphone_carddav_send_query(query); + } else { + const char *msg = NULL; + if (!lvc) { + msg = "LinphoneVcard is NULL"; + } else if (!linphone_vcard_get_uid(lvc)) { + msg = "LinphoneVcard doesn't have an UID"; + } else if (!linphone_vcard_get_etag(lvc)) { + msg = "LinphoneVcard doesn't have an eTag"; + } + + if (msg) { + ms_error("%s", msg); + } + + if (cdc && cdc->sync_done_cb) { + cdc->sync_done_cb(cdc, FALSE, msg); + } + } +} + +void linphone_carddav_set_synchronization_done_callback(LinphoneCardDavContext *cdc, LinphoneCardDavSynchronizationDoneCb cb) { + cdc->sync_done_cb = cb; +} + +void linphone_carddav_set_new_contact_callback(LinphoneCardDavContext *cdc, LinphoneCardDavContactCreatedCb cb) { + cdc->contact_created_cb = cb; +} + +void linphone_carddav_set_updated_contact_callback(LinphoneCardDavContext *cdc, LinphoneCardDavContactUpdatedCb cb) { + cdc->contact_updated_cb = cb; +} + +void linphone_carddav_set_removed_contact_callback(LinphoneCardDavContext *cdc, LinphoneCardDavContactRemovedCb cb) { + cdc->contact_removed_cb = cb; +} + +static LinphoneCardDavQuery* linphone_carddav_create_propfind_query(LinphoneCardDavContext *cdc) { + LinphoneCardDavQuery *query = (LinphoneCardDavQuery *)ms_new0(LinphoneCardDavQuery, 1); + query->context = cdc; + query->depth = "0"; + query->ifmatch = NULL; + query->body = ""; + query->method = "PROPFIND"; + query->url = cdc->friend_list->uri; + query->type = LinphoneCardDavQueryTypePropfind; + return query; +} + +void linphone_carddav_get_current_ctag(LinphoneCardDavContext *cdc) { + LinphoneCardDavQuery *query = linphone_carddav_create_propfind_query(cdc); + linphone_carddav_send_query(query); +} + +static LinphoneCardDavQuery* linphone_carddav_create_addressbook_query(LinphoneCardDavContext *cdc) { + LinphoneCardDavQuery *query = (LinphoneCardDavQuery *)ms_new0(LinphoneCardDavQuery, 1); + query->context = cdc; + query->depth = "1"; + query->ifmatch = NULL; + query->body = ""; + query->method = "REPORT"; + query->url = cdc->friend_list->uri; + query->type = LinphoneCardDavQueryTypeAddressbookQuery; + return query; +} + +void linphone_carddav_fetch_vcards(LinphoneCardDavContext *cdc) { + LinphoneCardDavQuery *query = linphone_carddav_create_addressbook_query(cdc); + linphone_carddav_send_query(query); +} + +static LinphoneCardDavQuery* linphone_carddav_create_addressbook_multiget_query(LinphoneCardDavContext *cdc, MSList *vcards) { + LinphoneCardDavQuery *query = (LinphoneCardDavQuery *)ms_new0(LinphoneCardDavQuery, 1); + char *body = (char *)ms_malloc((ms_list_size(vcards) + 1) * 300 * sizeof(char)); + MSList *iterator = vcards; + + query->context = cdc; + query->depth = "1"; + query->ifmatch = NULL; + query->method = "REPORT"; + query->url = cdc->friend_list->uri; + query->type = LinphoneCardDavQueryTypeAddressbookMultiget; + + sprintf(body, "%s", ""); + while (iterator) { + LinphoneCardDavResponse *response = (LinphoneCardDavResponse *)iterator->data; + if (response) { + char temp_body[300]; + snprintf(temp_body, sizeof(temp_body), "%s", response->url); + sprintf(body, "%s%s", body, temp_body); + iterator = ms_list_next(iterator); + } + } + sprintf(body, "%s%s", body, ""); + query->body = ms_strdup(body); + ms_free(body); + + return query; +} + +void linphone_carddav_pull_vcards(LinphoneCardDavContext *cdc, MSList *vcards_to_pull) { + LinphoneCardDavQuery *query = linphone_carddav_create_addressbook_multiget_query(cdc, vcards_to_pull); + linphone_carddav_send_query(query); +} \ No newline at end of file diff --git a/coreapi/carddav.h b/coreapi/carddav.h new file mode 100644 index 000000000..8ef3d70dd --- /dev/null +++ b/coreapi/carddav.h @@ -0,0 +1,170 @@ +/* +carddav.h +Copyright (C) 2015 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef LINPHONE_CARDDAV_H +#define LINPHONE_CARDDAV_H + +#include "linphonecore.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @addtogroup carddav_vcard + * @{ + */ + +typedef struct _LinphoneCardDavContext LinphoneCardDavContext; + +typedef enum _LinphoneCardDavQueryType { + LinphoneCardDavQueryTypePropfind, + LinphoneCardDavQueryTypeAddressbookQuery, + LinphoneCardDavQueryTypeAddressbookMultiget, + LinphoneCardDavQueryTypePut, + LinphoneCardDavQueryTypeDelete +} LinphoneCardDavQueryType; + +typedef struct _LinphoneCardDavQuery LinphoneCardDavQuery; + +typedef struct _LinphoneCardDavResponse LinphoneCardDavResponse; + +/** + * Callback used to notify a new contact has been created on the CardDAV server +**/ +typedef void (*LinphoneCardDavContactCreatedCb)(LinphoneCardDavContext *cdc, LinphoneFriend *lf); + +/** + * Callback used to notify a contact has been updated on the CardDAV server +**/ +typedef void (*LinphoneCardDavContactUpdatedCb)(LinphoneCardDavContext *cdc, LinphoneFriend *new_friend, LinphoneFriend *old_friend); + +/** + * Callback used to notify a contact has been removed on the CardDAV server +**/ +typedef void (*LinphoneCardDavContactRemovedCb)(LinphoneCardDavContext *cdc, LinphoneFriend *lf); + +/** + * Callback used to notify a contact has been removed on the CardDAV server +**/ +typedef void (*LinphoneCardDavSynchronizationDoneCb)(LinphoneCardDavContext *cdc, bool_t success, const char *message); + +/** + * Creates a CardDAV context for all related operations + * @param lfl LinphoneFriendList object + * @return LinphoneCardDavContext object if vCard support is enabled and server URL is available, NULL otherwise + */ +LINPHONE_PUBLIC LinphoneCardDavContext* linphone_carddav_context_new(LinphoneFriendList *lfl); + +/** + * Deletes a LinphoneCardDavContext object + * @param cdc LinphoneCardDavContext object + */ +LINPHONE_PUBLIC void linphone_carddav_context_destroy(LinphoneCardDavContext *cdc); + +/** + * Sets a user pointer to the LinphoneCardDAVContext object + * @param cdc LinphoneCardDavContext object + * @param ud The user data pointer + */ +LINPHONE_PUBLIC void linphone_carddav_set_user_data(LinphoneCardDavContext *cdc, void *ud); + +/** + * Gets the user pointer set in the LinphoneCardDAVContext object + * @param cdc LinphoneCardDavContext object + * @return The user data pointer if set, NULL otherwise + */ +LINPHONE_PUBLIC void* linphone_carddav_get_user_data(LinphoneCardDavContext *cdc); + +/** + * Starts a synchronization with the remote server to update local friends with server changes + * @param cdc LinphoneCardDavContext object + */ +LINPHONE_PUBLIC void linphone_carddav_synchronize(LinphoneCardDavContext *cdc); + +/** + * Sends a LinphoneFriend to the CardDAV server for update or creation + * @param cdc LinphoneCardDavContext object + * @param lf a LinphoneFriend object to update/create on the server + */ +LINPHONE_PUBLIC void linphone_carddav_put_vcard(LinphoneCardDavContext *cdc, LinphoneFriend *lf); + +/** + * Deletes a LinphoneFriend on the CardDAV server + * @param cdc LinphoneCardDavContext object + * @param lf a LinphoneFriend object to delete on the server + */ +LINPHONE_PUBLIC void linphone_carddav_delete_vcard(LinphoneCardDavContext *cdc, LinphoneFriend *lf); + +/** + * Set the synchronization done callback. + * @param cdc LinphoneCardDavContext object + * @param cb The synchronization done callback to be used. + */ +LINPHONE_PUBLIC void linphone_carddav_set_synchronization_done_callback(LinphoneCardDavContext *cdc, LinphoneCardDavSynchronizationDoneCb cb); + +/** + * Set the new contact callback. + * @param cdc LinphoneCardDavContext object + * @param cb The new contact callback to be used. + */ +LINPHONE_PUBLIC void linphone_carddav_set_new_contact_callback(LinphoneCardDavContext *cdc, LinphoneCardDavContactCreatedCb cb); + +/** + * Set the updated contact callback. + * @param cdc LinphoneCardDavContext object + * @param cb The updated contact callback to be used. + */ +LINPHONE_PUBLIC void linphone_carddav_set_updated_contact_callback(LinphoneCardDavContext *cdc, LinphoneCardDavContactUpdatedCb cb); + +/** + * Set the removed contact callback. + * @param cdc LinphoneCardDavContext object + * @param cb The removed contact callback to be used. + */ +LINPHONE_PUBLIC void linphone_carddav_set_removed_contact_callback(LinphoneCardDavContext *cdc, LinphoneCardDavContactRemovedCb cb); + +/** + * Retrieves the current cTag value for the remote server + * @param cdc LinphoneCardDavContext object + */ +void linphone_carddav_get_current_ctag(LinphoneCardDavContext *cdc); + +/** + * Retrieves a list of all the vCards on server side to be able to detect changes + * @param cdc LinphoneCardDavContext object + */ +void linphone_carddav_fetch_vcards(LinphoneCardDavContext *cdc); + +/** + * Download asked vCards from the server + * @param cdc LinphoneCardDavContext object + * @param vcards_to_pull a MSList of LinphoneCardDavResponse objects with at least the url field filled + */ +void linphone_carddav_pull_vcards(LinphoneCardDavContext *cdc, MSList *vcards_to_pull); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/coreapi/chat.c b/coreapi/chat.c index 77d63d32d..3f9d6692c 100644 --- a/coreapi/chat.c +++ b/coreapi/chat.c @@ -264,6 +264,10 @@ LinphoneChatRoom *linphone_core_get_chat_room_from_uri(LinphoneCore *lc, const c return _linphone_core_get_or_create_chat_room(lc, to); } +bool_t linphone_chat_room_lime_enabled(LinphoneChatRoom *cr) { + return linphone_core_lime_enabled(cr->lc) != LinphoneLimeDisabled/*&& TODO: check that cr->peer_url has a verified token */; +} + static void linphone_chat_room_delete_composing_idle_timer(LinphoneChatRoom *cr) { if (cr->composing_idle_timer) { if (cr->lc && cr->lc->sal) @@ -377,7 +381,7 @@ void _linphone_chat_room_send_message(LinphoneChatRoom *cr, LinphoneChatMessage char *peer_uri = linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr)); const char *content_type; - if (linphone_core_lime_enabled(cr->lc)) { + if (linphone_chat_room_lime_enabled(cr)) { /* ref the msg or it may be destroyed by callback if the encryption failed */ if (msg->content_type && strcmp(msg->content_type, "application/vnd.gsma.rcs-ft-http+xml") == 0) { /* it's a file transfer, content type shall be set to @@ -418,7 +422,10 @@ void _linphone_chat_room_send_message(LinphoneChatRoom *cr, LinphoneChatMessage linphone_chat_room_delete_composing_refresh_timer(cr); } - linphone_chat_message_set_state(msg, LinphoneChatMessageStateInProgress); + // if operation failed, we should not change message state + if (msg->dir == LinphoneChatMessageOutgoing) { + linphone_chat_message_set_state(msg, LinphoneChatMessageStateInProgress); + } } void linphone_chat_message_update_state(LinphoneChatMessage *msg, LinphoneChatMessageState new_state) { @@ -675,7 +682,7 @@ bool_t linphone_chat_room_is_remote_composing(const LinphoneChatRoom *cr) { } LinphoneCore *linphone_chat_room_get_lc(LinphoneChatRoom *cr) { - return cr->lc; + return linphone_chat_room_get_core(cr); } LinphoneCore *linphone_chat_room_get_core(LinphoneChatRoom *cr) { @@ -704,7 +711,7 @@ LinphoneChatMessage *linphone_chat_room_create_message_2(LinphoneChatRoom *cr, c const char *external_body_url, LinphoneChatMessageState state, time_t time, bool_t is_read, bool_t is_incoming) { LinphoneChatMessage *msg = linphone_chat_room_create_message(cr, message); - LinphoneCore *lc = linphone_chat_room_get_lc(cr); + LinphoneCore *lc = linphone_chat_room_get_core(cr); msg->external_body_url = external_body_url ? ms_strdup(external_body_url) : NULL; msg->time = time; msg->is_read = is_read; @@ -842,10 +849,10 @@ void linphone_core_real_time_text_received(LinphoneCore *lc, LinphoneChatRoom *c uint32_t new_line = 0x2028; uint32_t crlf = 0x0D0A; uint32_t lf = 0x0A; - + if (call && linphone_call_params_realtime_text_enabled(linphone_call_get_current_params(call))) { LinphoneChatMessageCharacter *cmc = ms_new0(LinphoneChatMessageCharacter, 1); - + if (cr->pending_message == NULL) { cr->pending_message = linphone_chat_room_create_message(cr, ""); } @@ -856,7 +863,7 @@ void linphone_core_real_time_text_received(LinphoneCore *lc, LinphoneChatRoom *c cr->remote_is_composing = LinphoneIsComposingActive; linphone_core_notify_is_composing_received(cr->lc, cr); - + if (character == new_line || character == crlf || character == lf) { // End of message LinphoneChatMessage *msg = cr->pending_message; diff --git a/coreapi/chat_file_transfer.c b/coreapi/chat_file_transfer.c index 03190ce7f..e64839e35 100644 --- a/coreapi/chat_file_transfer.c +++ b/coreapi/chat_file_transfer.c @@ -30,7 +30,7 @@ #define FILE_TRANSFER_KEY_SIZE 32 static bool_t file_transfer_in_progress_and_valid(LinphoneChatMessage* msg) { - return (msg->chat_room && msg->http_request && !belle_http_request_is_cancelled(msg->http_request)); + return (msg->chat_room && msg->chat_room->lc && msg->http_request && !belle_http_request_is_cancelled(msg->http_request)); } static void _release_http_request(LinphoneChatMessage* msg) { @@ -155,7 +155,7 @@ static int linphone_chat_message_file_transfer_on_send_body(belle_sip_user_body_ static void linphone_chat_message_process_response_from_post_file(void *data, const belle_http_response_event_t *event) { LinphoneChatMessage *msg = (LinphoneChatMessage *)data; - + if (msg->http_request && !file_transfer_in_progress_and_valid(msg)) { ms_warning("Cancelled request for %s msg [%p], ignoring %s", msg->chat_room?"":"ORPHAN", msg, __FUNCTION__); _release_http_request(msg); @@ -172,7 +172,8 @@ static void linphone_chat_message_process_response_from_post_file(void *data, belle_sip_body_handler_t *first_part_bh; /* shall we encrypt the file */ - if (linphone_core_lime_for_file_sharing_enabled(msg->chat_room->lc)) { + if (linphone_chat_room_lime_enabled(msg->chat_room) && + linphone_core_lime_for_file_sharing_enabled(msg->chat_room->lc)) { char keyBuffer [FILE_TRANSFER_KEY_SIZE]; /* temporary storage of generated key: 192 bits of key + 64 bits of initial vector */ @@ -315,18 +316,23 @@ const LinphoneContent *linphone_chat_message_get_file_transfer_information(const static void on_recv_body(belle_sip_user_body_handler_t *bh, belle_sip_message_t *m, void *data, size_t offset, const uint8_t *buffer, size_t size) { LinphoneChatMessage *msg = (LinphoneChatMessage *)data; - LinphoneCore *lc = msg->chat_room->lc; - - + LinphoneCore *lc; + + if (!msg->chat_room) { + linphone_chat_message_cancel_file_transfer(msg); + return; + } + lc = msg->chat_room->lc; + + if (lc == NULL){ + return; /*might happen during linphone_core_destroy()*/ + } + if (!msg->http_request || belle_http_request_is_cancelled(msg->http_request)) { ms_warning("Cancelled request for msg [%p], ignoring %s", msg, __FUNCTION__); return; } - - if (!msg->chat_room) { - linphone_chat_message_cancel_file_transfer(msg); - } - + /* first call may be with a zero size, ignore it */ if (size == 0) { return; @@ -460,7 +466,7 @@ static void linphone_chat_process_response_from_get_file(void *data, const belle int _linphone_chat_room_start_http_transfer(LinphoneChatMessage *msg, const char* url, const char* action, const belle_http_request_listener_callbacks_t *cbs) { belle_generic_uri_t *uri = NULL; - char* ua; + const char* ua = linphone_core_get_user_agent(msg->chat_room->lc); if (url == NULL) { ms_warning("Cannot process file transfer msg: no file remote URI configured."); @@ -472,9 +478,7 @@ int _linphone_chat_room_start_http_transfer(LinphoneChatMessage *msg, const char goto error; } - ua = ms_strdup_printf("%s/%s", linphone_core_get_user_agent_name(), linphone_core_get_user_agent_version()); msg->http_request = belle_http_request_create(action, uri, belle_sip_header_create("User-Agent", ua), NULL); - ms_free(ua); if (msg->http_request == NULL) { ms_warning("Could not create http request for uri %s", url); diff --git a/coreapi/conference.c b/coreapi/conference.c deleted file mode 100644 index 66898d9a2..000000000 --- a/coreapi/conference.c +++ /dev/null @@ -1,430 +0,0 @@ -/*************************************************************************** - * conference.c - * - * Mon Sep 12, 2011 - * Copyright 2011 Belledonne Communications - * Author: Simon Morlat - * Email simon dot morlat at linphone dot org - ****************************************************************************/ - -/* - * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -#include "private.h" -#include "lpconfig.h" - -#include "mediastreamer2/msvolume.h" - -/** - * @addtogroup conferencing - * @{ -**/ - - -static int convert_conference_to_call(LinphoneCore *lc); - -static void conference_check_init(LinphoneConference *ctx, int samplerate){ - if (ctx->conf==NULL){ - MSAudioConferenceParams params; - params.samplerate=samplerate; - ctx->conf=ms_audio_conference_new(¶ms); - ctx->terminated=FALSE; - } -} - -static void remove_local_endpoint(LinphoneConference *ctx){ - if (ctx->local_endpoint){ - ms_audio_conference_remove_member(ctx->conf,ctx->local_endpoint); - ms_audio_endpoint_release_from_stream(ctx->local_endpoint); - ctx->local_endpoint=NULL; - audio_stream_stop(ctx->local_participant); - ctx->local_participant=NULL; - rtp_profile_destroy(ctx->local_dummy_profile); - } -} - -static int linphone_conference_get_size(LinphoneConference *conf){ - if (conf->conf == NULL) { - return 0; - } - return ms_audio_conference_get_size(conf->conf) - (conf->record_endpoint ? 1 : 0); -} - -static int remote_participants_count(LinphoneConference *ctx) { - int count=linphone_conference_get_size(ctx); - if (count==0) return 0; - if (!ctx->local_participant) return count; - return count -1; -} - -void linphone_core_conference_check_uninit(LinphoneCore *lc){ - LinphoneConference *ctx=&lc->conf_ctx; - if (ctx->conf){ - int remote_count=remote_participants_count(ctx); - ms_message("conference_check_uninit(): size=%i",linphone_conference_get_size(ctx)); - if (remote_count==1 && !ctx->terminated){ - convert_conference_to_call(lc); - } - if (remote_count==0){ - if (ctx->local_participant!=NULL) - remove_local_endpoint(ctx); - if (ctx->record_endpoint){ - ms_audio_conference_remove_member(ctx->conf,ctx->record_endpoint); - ms_audio_endpoint_destroy(ctx->record_endpoint); - ctx->record_endpoint=NULL; - } - } - - if (ms_audio_conference_get_size(ctx->conf)==0){ - ms_audio_conference_destroy(ctx->conf); - ctx->conf=NULL; - } - } -} - -void linphone_call_add_to_conf(LinphoneCall *call, bool_t muted){ - LinphoneCore *lc=call->core; - LinphoneConference *conf=&lc->conf_ctx; - MSAudioEndpoint *ep; - call->params->has_video = FALSE; - call->camera_enabled = FALSE; - ep=ms_audio_endpoint_get_from_stream(call->audiostream,TRUE); - ms_audio_conference_add_member(conf->conf,ep); - ms_audio_conference_mute_member(conf->conf,ep,muted); - call->endpoint=ep; -} - -void linphone_call_remove_from_conf(LinphoneCall *call){ - LinphoneCore *lc=call->core; - LinphoneConference *conf=&lc->conf_ctx; - - ms_audio_conference_remove_member(conf->conf,call->endpoint); - ms_audio_endpoint_release_from_stream(call->endpoint); - call->endpoint=NULL; -} - -static RtpProfile *make_dummy_profile(int samplerate){ - RtpProfile *prof=rtp_profile_new("dummy"); - PayloadType *pt=payload_type_clone(&payload_type_l16_mono); - pt->clock_rate=samplerate; - rtp_profile_set_payload(prof,0,pt); - return prof; -} - -static void add_local_endpoint(LinphoneConference *conf,LinphoneCore *lc){ - /*create a dummy audiostream in order to extract the local part of it */ - /* network address and ports have no meaning and are not used here. */ - AudioStream *st=audio_stream_new(65000,65001,FALSE); - MSSndCard *playcard=lc->sound_conf.lsd_card ? - lc->sound_conf.lsd_card : lc->sound_conf.play_sndcard; - MSSndCard *captcard=lc->sound_conf.capt_sndcard; - const MSAudioConferenceParams *params=ms_audio_conference_get_params(conf->conf); - conf->local_dummy_profile=make_dummy_profile(params->samplerate); - - audio_stream_start_full(st, conf->local_dummy_profile, - "127.0.0.1", - 65000, - "127.0.0.1", - 65001, - 0, - 40, - NULL, - NULL, - playcard, - captcard, - linphone_core_echo_cancellation_enabled(lc) - ); - _post_configure_audio_stream(st,lc,FALSE); - conf->local_participant=st; - conf->local_endpoint=ms_audio_endpoint_get_from_stream(st,FALSE); - ms_audio_conference_add_member(conf->conf,conf->local_endpoint); - -} - -/** - * Returns the sound volume (mic input) of the local participant of the conference. - * @param lc the linphone core - * @return the measured input volume expressed in dbm0. - **/ -float linphone_core_get_conference_local_input_volume(LinphoneCore *lc){ - LinphoneConference *conf=&lc->conf_ctx; - AudioStream *st=conf->local_participant; - if (st && st->volsend && !conf->local_muted){ - float vol=0; - ms_filter_call_method(st->volsend,MS_VOLUME_GET,&vol); - return vol; - - } - return LINPHONE_VOLUME_DB_LOWEST; -} - -/** - * Merge a call into a conference. - * @param lc the linphone core - * @param call an established call, either in LinphoneCallStreamsRunning or LinphoneCallPaused state. - * - * If this is the first call that enters the conference, the virtual conference will be created automatically. - * If the local user was actively part of the call (ie not in paused state), then the local user is automatically entered into the conference. - * If the call was in paused state, then it is automatically resumed when entering into the conference. - * - * @return 0 if successful, -1 otherwise. -**/ -int linphone_core_add_to_conference(LinphoneCore *lc, LinphoneCall *call){ - LinphoneConference *conf=&lc->conf_ctx; - - if (call->current_params->in_conference){ - ms_error("Already in conference"); - return -1; - } - conference_check_init(&lc->conf_ctx, lp_config_get_int(lc->config, "sound","conference_rate",16000)); - - if (call->state==LinphoneCallPaused){ - call->params->in_conference=TRUE; - call->params->has_video=FALSE; - linphone_core_resume_call(lc,call); - }else if (call->state==LinphoneCallStreamsRunning){ - LinphoneCallParams *params=linphone_call_params_copy(linphone_call_get_current_params(call)); - params->in_conference=TRUE; - params->has_video=FALSE; - - if (call->audiostream || call->videostream){ - linphone_call_stop_media_streams(call); /*free the audio & video local resources*/ - linphone_call_init_media_streams(call); - } - if (call==lc->current_call){ - lc->current_call=NULL; - } - /*this will trigger a reINVITE that will later redraw the streams */ - /*FIXME probably a bit too much to just redraw streams !*/ - linphone_core_update_call(lc,call,params); - linphone_call_params_destroy(params); - add_local_endpoint(conf,lc); - }else{ - ms_error("Call is in state %s, it cannot be added to the conference.",linphone_call_state_to_string(call->state)); - return -1; - } - return 0; -} - -static int remove_from_conference(LinphoneCore *lc, LinphoneCall *call, bool_t active){ - int err=0; - char *str; - - if (!call->current_params->in_conference){ - if (call->params->in_conference){ - ms_warning("Not (yet) in conference, be patient"); - return -1; - }else{ - ms_error("Not in a conference."); - return -1; - } - } - call->params->in_conference=FALSE; - - str=linphone_call_get_remote_address_as_string(call); - ms_message("%s will be removed from conference", str); - ms_free(str); - if (active){ - LinphoneCallParams *params=linphone_call_params_copy(linphone_call_get_current_params(call)); - params->in_conference=FALSE; - // reconnect local audio with this call - if (linphone_core_is_in_conference(lc)){ - ms_message("Leaving conference for reconnecting with unique call."); - linphone_core_leave_conference(lc); - } - ms_message("Updating call to actually remove from conference"); - err=linphone_core_update_call(lc,call,params); - linphone_call_params_destroy(params); - } else{ - ms_message("Pausing call to actually remove from conference"); - err=_linphone_core_pause_call(lc,call); - } - return err; -} - -static int convert_conference_to_call(LinphoneCore *lc){ - int err=0; - MSList *calls=lc->calls; - - if (remote_participants_count(&lc->conf_ctx)!=1){ - ms_error("No unique call remaining in conference."); - return -1; - } - - while (calls) { - LinphoneCall *rc=(LinphoneCall*)calls->data; - calls=calls->next; - if (rc->params->in_conference) { // not using current_param - bool_t active_after_removed=linphone_core_is_in_conference(lc); - err=remove_from_conference(lc, rc, active_after_removed); - break; - } - } - return err; -} - -int linphone_core_remove_from_conference(LinphoneCore *lc, LinphoneCall *call){ - int err; - char * str=linphone_call_get_remote_address_as_string(call); - ms_message("Removing call %s from the conference", str); - ms_free(str); - err=remove_from_conference(lc,call, FALSE); - if (err){ - ms_error("Error removing participant from conference."); - return err; - } - - if (remote_participants_count(&lc->conf_ctx)==1){ - ms_message("conference size is 1: need to be converted to plain call"); - err=convert_conference_to_call(lc); - } else { - ms_message("the conference need not to be converted as size is %i", remote_participants_count(&lc->conf_ctx)); - } - return err; -} - -bool_t linphone_core_is_in_conference(const LinphoneCore *lc){ - return lc->conf_ctx.local_participant!=NULL; -} - -/** - * Moves the local participant out of the conference. - * @param lc the linphone core - * When the local participant is out of the conference, the remote participants can continue to talk normally. - * @return 0 if successful, -1 otherwise. -**/ -int linphone_core_leave_conference(LinphoneCore *lc){ - LinphoneConference *conf=&lc->conf_ctx; - if (linphone_core_is_in_conference(lc)) - remove_local_endpoint(conf); - return 0; -} - -/** - * Moves the local participant inside the conference. - * @param lc the linphone core - * - * Makes the local participant to join the conference. - * Typically, the local participant is by default always part of the conference when joining an active call into a conference. - * However, by calling linphone_core_leave_conference() and linphone_core_enter_conference() the application can decide to temporarily - * move out and in the local participant from the conference. - * - * @return 0 if successful, -1 otherwise -**/ -int linphone_core_enter_conference(LinphoneCore *lc){ - LinphoneConference *conf; - if (linphone_core_sound_resources_locked(lc)) { - return -1; - } - if (lc->current_call != NULL) { - _linphone_core_pause_call(lc, lc->current_call); - } - conf=&lc->conf_ctx; - if (conf->local_participant==NULL) add_local_endpoint(conf,lc); - return 0; -} - -/** - * Add all calls into a conference. - * @param lc the linphone core - * - * Merge all established calls (either in LinphoneCallStreamsRunning or LinphoneCallPaused) into a conference. - * - * @return 0 if successful, -1 otherwise -**/ -int linphone_core_add_all_to_conference(LinphoneCore *lc) { - MSList *calls=lc->calls; - while (calls) { - LinphoneCall *call=(LinphoneCall*)calls->data; - calls=calls->next; - if (!call->current_params->in_conference) { - linphone_core_add_to_conference(lc, call); - } - } - linphone_core_enter_conference(lc); - return 0; -} - -/** - * Terminates the conference and the calls associated with it. - * @param lc the linphone core - * - * All the calls that were merged to the conference are terminated, and the conference resources are destroyed. - * - * @return 0 if successful, -1 otherwise -**/ -int linphone_core_terminate_conference(LinphoneCore *lc) { - MSList *calls=lc->calls; - LinphoneConference *conf=&lc->conf_ctx; - conf->terminated=TRUE; - - while (calls) { - LinphoneCall *call=(LinphoneCall*)calls->data; - calls=calls->next; - if (call->current_params->in_conference) { - linphone_core_terminate_call(lc, call); - } - } - return 0; -} - -/** - * Returns the number of participants to the conference, including the local participant. - * @param lc the linphone core - * - * Typically, after merging two calls into the conference, there is total of 3 participants: - * the local participant (or local user), and two remote participants that were the destinations of the two previously establised calls. - * - * @return the number of participants to the conference -**/ -int linphone_core_get_conference_size(LinphoneCore *lc) { - LinphoneConference *conf=&lc->conf_ctx; - return linphone_conference_get_size(conf); -} - - -int linphone_core_start_conference_recording(LinphoneCore *lc, const char *path){ - LinphoneConference *conf=&lc->conf_ctx; - if (conf->conf == NULL) { - ms_warning("linphone_core_start_conference_recording(): no conference now."); - return -1; - } - if (conf->record_endpoint==NULL){ - conf->record_endpoint=ms_audio_endpoint_new_recorder(); - ms_audio_conference_add_member(conf->conf,conf->record_endpoint); - } - ms_audio_recorder_endpoint_start(conf->record_endpoint,path); - return 0; -} - -int linphone_core_stop_conference_recording(LinphoneCore *lc){ - LinphoneConference *conf=&lc->conf_ctx; - if (conf->conf == NULL) { - ms_warning("linphone_core_stop_conference_recording(): no conference now."); - return -1; - } - if (conf->record_endpoint==NULL){ - ms_warning("linphone_core_stop_conference_recording(): no record active."); - return -1; - } - ms_audio_recorder_endpoint_stop(conf->record_endpoint); - return 0; -} - -/** - * @} -**/ - diff --git a/coreapi/conference.cc b/coreapi/conference.cc new file mode 100644 index 000000000..48c46fdc5 --- /dev/null +++ b/coreapi/conference.cc @@ -0,0 +1,1041 @@ +/******************************************************************************* + * conference.cc + * + * Thu Nov 26, 2015 + * Copyright 2015 Belledonne Communications + * Author: Linphone's team + * Email info@belledonne-communications.com + ******************************************************************************/ + +/* + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "private.h" +#include "conference_private.h" +#include +#include +#include +#include + +namespace Linphone { + +class Conference { +public: + class Participant { + public: + Participant(LinphoneCall *call); + Participant(const Participant &src); + ~Participant(); + bool operator==(const Participant &src) const; + const LinphoneAddress *getUri() const {return m_uri;} + LinphoneCall *getCall() const {return m_call;} + + private: + LinphoneAddress *m_uri; + LinphoneCall *m_call; + }; + + class Params { + public: + Params(const LinphoneCore *core = NULL); + void enableVideo(bool enable) {m_enableVideo = enable;} + bool videoRequested() const {return m_enableVideo;} + void setStateChangedCallback(LinphoneConferenceStateChangedCb cb, void *user_data) { + m_state_changed_cb=cb; + m_user_data=user_data; + } + + private: + bool m_enableVideo; + LinphoneConferenceStateChangedCb m_state_changed_cb; + void *m_user_data; + + friend class Conference; + }; + + Conference(LinphoneCore *core, const Params *params = NULL); + virtual ~Conference() {}; + + const Params &getCurrentParams() const {return m_currentParams;} + + virtual int addParticipant(LinphoneCall *call) = 0; + virtual int removeParticipant(LinphoneCall *call) = 0; + virtual int removeParticipant(const LinphoneAddress *uri) = 0; + virtual int terminate() = 0; + + virtual int enter() = 0; + virtual int leave() = 0; + virtual bool isIn() const = 0; + + AudioStream *getAudioStream() const {return m_localParticipantStream;} + int muteMicrophone(bool val); + bool microphoneIsMuted() const {return m_isMuted;} + float getInputVolume() const; + + virtual int getSize() const {return m_participants.size() + (isIn()?1:0);} + const std::list &getParticipants() const {return m_participants;} + + virtual int startRecording(const char *path) = 0; + virtual int stopRecording() = 0; + + virtual void onCallStreamStarting(LinphoneCall *call, bool isPausedByRemote) {}; + virtual void onCallStreamStopping(LinphoneCall *call) {}; + virtual void onCallTerminating(LinphoneCall *call) {}; + + LinphoneConferenceState getState() const {return m_state;} + static const char *stateToString(LinphoneConferenceState state); + +protected: + const Participant *findParticipant(const LinphoneAddress* uri) const; + void setState(LinphoneConferenceState state); + + LinphoneCore *m_core; + AudioStream *m_localParticipantStream; + bool m_isMuted; + std::list m_participants; + Params m_currentParams; + LinphoneConferenceState m_state; +}; + +class LocalConference: public Conference { +public: + LocalConference(LinphoneCore *core, const Params *params = NULL); + virtual ~LocalConference(); + + virtual int addParticipant(LinphoneCall *call); + virtual int removeParticipant(LinphoneCall *call); + virtual int removeParticipant(const LinphoneAddress *uri); + virtual int terminate(); + + virtual int enter(); + virtual int leave(); + virtual bool isIn() const {return m_localParticipantStream!=NULL;} + virtual int getSize() const; + + virtual int startRecording(const char *path); + virtual int stopRecording(); + + virtual void onCallStreamStarting(LinphoneCall *call, bool isPausedByRemote); + virtual void onCallStreamStopping(LinphoneCall *call); + virtual void onCallTerminating(LinphoneCall *call); + +private: + void addLocalEndpoint(); + int remoteParticipantsCount(); + void removeLocalEndpoint(); + int removeFromConference(LinphoneCall *call, bool_t active); + int convertConferenceToCall(); + static RtpProfile *sMakeDummyProfile(int samplerate); + + MSAudioConference *m_conf; + MSAudioEndpoint *m_localEndpoint; + MSAudioEndpoint *m_recordEndpoint; + RtpProfile *m_localDummyProfile; + bool_t m_terminated; +}; + +class RemoteConference: public Conference { +public: + RemoteConference(LinphoneCore *core, const Params *params = NULL); + virtual ~RemoteConference(); + + virtual int addParticipant(LinphoneCall *call); + virtual int removeParticipant(LinphoneCall *call) {return -1;} + virtual int removeParticipant(const LinphoneAddress *uri); + virtual int terminate(); + + virtual int enter(); + virtual int leave(); + virtual bool isIn() const; + + virtual int startRecording(const char *path) {return 0;} + virtual int stopRecording() {return 0;} + +private: + bool focusIsReady() const; + bool transferToFocus(LinphoneCall *call); + void reset(); + + void onFocusCallSateChanged(LinphoneCallState state); + void onPendingCallStateChanged(LinphoneCall *call, LinphoneCallState state); + void onTransferingCallStateChanged(LinphoneCall *transfered, LinphoneCallState newCallState); + + static void callStateChangedCb(LinphoneCore *lc, LinphoneCall *call, LinphoneCallState cstate, const char *message); + static void transferStateChanged(LinphoneCore *lc, LinphoneCall *transfered, LinphoneCallState new_call_state); + + const char *m_focusAddr; + char *m_focusContact; + LinphoneCall *m_focusCall; + LinphoneCoreVTable *m_vtable; + MSList *m_pendingCalls; + MSList *m_transferingCalls; + bool m_isTerminating; +}; + +}; + + +using namespace Linphone; +using namespace std; + + +Conference::Participant::Participant(LinphoneCall *call) { + m_uri = linphone_address_clone(linphone_call_get_remote_address(call)); + m_call = linphone_call_ref(call); +} + +Conference::Participant::Participant(const Participant &src) { + m_uri = linphone_address_clone(src.m_uri); + m_call = src.m_call ? linphone_call_ref(src.m_call) : NULL; +} + +Conference::Participant::~Participant() { + linphone_address_unref(m_uri); + if(m_call) linphone_call_unref(m_call); +} + +bool Conference::Participant::operator==(const Participant &src) const { + return linphone_address_equal(m_uri, src.m_uri); +} + + + +Conference::Params::Params(const LinphoneCore *core): m_enableVideo(false) { + if(core) { + const LinphoneVideoPolicy *policy = linphone_core_get_video_policy(core); + if(policy->automatically_initiate) m_enableVideo = true; + } +} + + +Conference::Conference(LinphoneCore *core, const Conference::Params *params): + m_core(core), + m_localParticipantStream(NULL), + m_isMuted(false), + m_currentParams(), + m_state(LinphoneConferenceStopped) { + if(params) m_currentParams = *params; +} + +int Conference::addParticipant(LinphoneCall *call) { + Participant participant(call); + m_participants.push_back(participant); + call->conf_ref = (LinphoneConference *)this; + return 0; +} + +int Conference::removeParticipant(LinphoneCall *call) { + Participant participant(call); + m_participants.remove(participant); + call->conf_ref = NULL; + return 0; +} + +int Conference::terminate() { + for(list::iterator it = m_participants.begin(); it!=m_participants.end(); it++) { + it->getCall()->conf_ref = NULL; + } + m_participants.clear(); + return 0; +} + +int Conference::removeParticipant(const LinphoneAddress *uri) { + const Participant *participant = findParticipant(uri); + if(participant == NULL) return -1; + LinphoneCall *call = participant->getCall(); + if(call) call->conf_ref = NULL; + m_participants.remove(Participant(*participant)); + return 0; +} + +int Conference::muteMicrophone(bool val) { + if (val) { + audio_stream_set_mic_gain(m_localParticipantStream, 0); + } else { + audio_stream_set_mic_gain_db(m_localParticipantStream, m_core->sound_conf.soft_mic_lev); + } + if ( linphone_core_get_rtp_no_xmit_on_audio_mute(m_core) ){ + audio_stream_mute_rtp(m_localParticipantStream, val); + } + m_isMuted=val; + return 0; +} + +float Conference::getInputVolume() const { + AudioStream *st=m_localParticipantStream; + if (st && st->volsend && !m_isMuted){ + float vol=0; + ms_filter_call_method(st->volsend,MS_VOLUME_GET,&vol); + return vol; + + } + return LINPHONE_VOLUME_DB_LOWEST; +} + +const char *Conference::stateToString(LinphoneConferenceState state) { + switch(state) { + case LinphoneConferenceStopped: return "Stopped"; + case LinphoneConferenceStarting: return "Starting"; + case LinphoneConferenceReady: return "Ready"; + case LinphoneConferenceStartingFailed: return "Startig failed"; + default: return "Invalid state"; + } +} + + + +const Conference::Participant *Conference::findParticipant(const LinphoneAddress *uri) const { + list::const_iterator it = m_participants.begin(); + while(it!=m_participants.end()) { + if(linphone_address_equal(uri, it->getUri())) break; + it++; + } + if(it == m_participants.end()) return NULL; + else return &*it; +} + +void Conference::setState(LinphoneConferenceState state) { + if(m_state != state) { + ms_message("Switching conference [%p] into state '%s'", this, stateToString(state)); + m_state = state; + if(m_currentParams.m_state_changed_cb) { + m_currentParams.m_state_changed_cb((LinphoneConference *)this, state, m_currentParams.m_user_data); + } + } +} + + + + +LocalConference::LocalConference(LinphoneCore *core, const Conference::Params *params): + Conference(core, params), + m_conf(NULL), + m_localEndpoint(NULL), + m_recordEndpoint(NULL), + m_localDummyProfile(NULL), + m_terminated(FALSE) { + + MSAudioConferenceParams ms_conf_params; + ms_conf_params.samplerate = lp_config_get_int(m_core->config, "sound","conference_rate",16000); + m_conf=ms_audio_conference_new(&ms_conf_params, core->factory); + m_state=LinphoneConferenceReady; +} + +LocalConference::~LocalConference() { + terminate(); + ms_audio_conference_destroy(m_conf); +} + +RtpProfile *LocalConference::sMakeDummyProfile(int samplerate){ + RtpProfile *prof=rtp_profile_new("dummy"); + PayloadType *pt=payload_type_clone(&payload_type_l16_mono); + pt->clock_rate=samplerate; + rtp_profile_set_payload(prof,0,pt); + return prof; +} + +void LocalConference::addLocalEndpoint() { + /*create a dummy audiostream in order to extract the local part of it */ + /* network address and ports have no meaning and are not used here. */ + AudioStream *st=audio_stream_new(m_core->factory, 65000,65001,FALSE); + MSSndCard *playcard=m_core->sound_conf.lsd_card ? + m_core->sound_conf.lsd_card : m_core->sound_conf.play_sndcard; + MSSndCard *captcard=m_core->sound_conf.capt_sndcard; + const MSAudioConferenceParams *params=ms_audio_conference_get_params(m_conf); + m_localDummyProfile=sMakeDummyProfile(params->samplerate); + + audio_stream_start_full(st, m_localDummyProfile, + "127.0.0.1", + 65000, + "127.0.0.1", + 65001, + 0, + 40, + NULL, + NULL, + playcard, + captcard, + linphone_core_echo_cancellation_enabled(m_core) + ); + _post_configure_audio_stream(st,m_core,FALSE); + m_localParticipantStream=st; + m_localEndpoint=ms_audio_endpoint_get_from_stream(st,FALSE); + ms_audio_conference_add_member(m_conf,m_localEndpoint); +} + +int LocalConference::addParticipant(LinphoneCall *call) { + if (call->current_params->in_conference){ + ms_error("Already in conference"); + return -1; + } + + if (call->state==LinphoneCallPaused){ + call->params->in_conference=TRUE; + call->params->has_video=FALSE; + linphone_core_resume_call(m_core,call); + }else if (call->state==LinphoneCallStreamsRunning){ + LinphoneCallParams *params=linphone_call_params_copy(linphone_call_get_current_params(call)); + params->in_conference=TRUE; + params->has_video=FALSE; + + if (call->audiostream || call->videostream){ + linphone_call_stop_media_streams(call); /*free the audio & video local resources*/ + linphone_call_init_media_streams(call); + } + if (call==m_core->current_call){ + m_core->current_call=NULL; + } + /*this will trigger a reINVITE that will later redraw the streams */ + /*FIXME probably a bit too much to just redraw streams !*/ + linphone_core_update_call(m_core,call,params); + linphone_call_params_destroy(params); + addLocalEndpoint(); + }else{ + ms_error("Call is in state %s, it cannot be added to the conference.",linphone_call_state_to_string(call->state)); + return -1; + } + Conference::addParticipant(call); + return 0; +} + +int LocalConference::removeFromConference(LinphoneCall *call, bool_t active){ + int err=0; + char *str; + + if (!call->current_params->in_conference){ + if (call->params->in_conference){ + ms_warning("Not (yet) in conference, be patient"); + return -1; + }else{ + ms_error("Not in a conference."); + return -1; + } + } + call->params->in_conference=FALSE; + Conference::removeParticipant(call); + + str=linphone_call_get_remote_address_as_string(call); + ms_message("%s will be removed from conference", str); + ms_free(str); + if (active){ + LinphoneCallParams *params=linphone_call_params_copy(linphone_call_get_current_params(call)); + params->in_conference=FALSE; + // reconnect local audio with this call + if (isIn()){ + ms_message("Leaving conference for reconnecting with unique call."); + leave(); + } + ms_message("Updating call to actually remove from conference"); + err=linphone_core_update_call(m_core,call,params); + linphone_call_params_destroy(params); + } else{ + ms_message("Pausing call to actually remove from conference"); + err=_linphone_core_pause_call(m_core,call); + } + return err; +} + +int LocalConference::remoteParticipantsCount() { + int count=getSize(); + if (count==0) return 0; + if (!m_localParticipantStream) return count; + return count -1; +} + +int LocalConference::convertConferenceToCall(){ + int err=0; + MSList *calls=m_core->calls; + + if (remoteParticipantsCount()!=1){ + ms_error("No unique call remaining in conference."); + return -1; + } + + while (calls) { + LinphoneCall *rc=(LinphoneCall*)calls->data; + calls=calls->next; + if (rc->params->in_conference) { // not using current_param + bool_t active_after_removed=isIn(); + err=removeFromConference(rc, active_after_removed); + break; + } + } + return err; +} + +int LocalConference::removeParticipant(LinphoneCall *call) { + int err; + char * str=linphone_call_get_remote_address_as_string(call); + ms_message("Removing call %s from the conference", str); + ms_free(str); + err=removeFromConference(call, FALSE); + if (err){ + ms_error("Error removing participant from conference."); + return err; + } + + if (remoteParticipantsCount()==1){ + ms_message("conference size is 1: need to be converted to plain call"); + err=convertConferenceToCall(); + } else { + ms_message("the conference need not to be converted as size is %i", remoteParticipantsCount()); + } + return err; +} + +int LocalConference::removeParticipant(const LinphoneAddress *uri) { + const Participant *participant = findParticipant(uri); + if(participant == NULL) return -1; + LinphoneCall *call = participant->getCall(); + if(call == NULL) return -1; + return removeParticipant(call); +} + +int LocalConference::terminate() { + MSList *calls=m_core->calls; + m_terminated=TRUE; + + while (calls) { + LinphoneCall *call=(LinphoneCall*)calls->data; + calls=calls->next; + if (call->current_params->in_conference) { + linphone_core_terminate_call(m_core, call); + } + } + + Conference::terminate(); + + return 0; +} + +int LocalConference::enter() { + if (linphone_core_sound_resources_locked(m_core)) { + return -1; + } + if (m_core->current_call != NULL) { + _linphone_core_pause_call(m_core, m_core->current_call); + } + if (m_localParticipantStream==NULL) addLocalEndpoint(); + return 0; +} + +void LocalConference::removeLocalEndpoint(){ + if (m_localEndpoint){ + ms_audio_conference_remove_member(m_conf,m_localEndpoint); + ms_audio_endpoint_release_from_stream(m_localEndpoint); + m_localEndpoint=NULL; + audio_stream_stop(m_localParticipantStream); + m_localParticipantStream=NULL; + rtp_profile_destroy(m_localDummyProfile); + } +} + +int LocalConference::leave() { + if (isIn()) + removeLocalEndpoint(); + return 0; +} + +int LocalConference::getSize() const { + if (m_conf == NULL) { + return 0; + } + return ms_audio_conference_get_size(m_conf) - (m_recordEndpoint ? 1 : 0); +} + +int LocalConference::startRecording(const char *path) { + if (m_conf == NULL) { + ms_warning("linphone_core_start_conference_recording(): no conference now."); + return -1; + } + if (m_recordEndpoint==NULL){ + m_recordEndpoint=ms_audio_endpoint_new_recorder(m_core->factory); + ms_audio_conference_add_member(m_conf,m_recordEndpoint); + } + ms_audio_recorder_endpoint_start(m_recordEndpoint,path); + return 0; +} + +int LocalConference::stopRecording() { + if (m_conf == NULL) { + ms_warning("linphone_core_stop_conference_recording(): no conference now."); + return -1; + } + if (m_recordEndpoint==NULL){ + ms_warning("linphone_core_stop_conference_recording(): no record active."); + return -1; + } + ms_audio_recorder_endpoint_stop(m_recordEndpoint); + return 0; +} + +void LocalConference::onCallStreamStarting(LinphoneCall *call, bool isPausedByRemote) { + call->params->has_video = FALSE; + call->camera_enabled = FALSE; + MSAudioEndpoint *ep=ms_audio_endpoint_get_from_stream(call->audiostream,TRUE); + ms_audio_conference_add_member(m_conf,ep); + ms_audio_conference_mute_member(m_conf,ep,isPausedByRemote); + call->endpoint=ep; +} + +void LocalConference::onCallStreamStopping(LinphoneCall *call) { + ms_audio_conference_remove_member(m_conf,call->endpoint); + ms_audio_endpoint_release_from_stream(call->endpoint); + call->endpoint=NULL; +} + +void LocalConference::onCallTerminating(LinphoneCall *call) { + int remote_count=remoteParticipantsCount(); + ms_message("conference_check_uninit(): size=%i", getSize()); + if (remote_count==1 && !m_terminated){ + convertConferenceToCall(); + } + if (remote_count==0){ + if (m_localParticipantStream) + removeLocalEndpoint(); + if (m_recordEndpoint){ + ms_audio_conference_remove_member(m_conf, m_recordEndpoint); + ms_audio_endpoint_destroy(m_recordEndpoint); + } + } +} + + + +RemoteConference::RemoteConference(LinphoneCore *core, const Conference::Params *params): + Conference(core, params), + m_focusAddr(NULL), + m_focusContact(NULL), + m_focusCall(NULL), + m_vtable(NULL), + m_pendingCalls(NULL), + m_transferingCalls(NULL), + m_isTerminating(false) { + m_focusAddr = lp_config_get_string(m_core->config, "misc", "conference_focus_addr", ""); + m_vtable = linphone_core_v_table_new(); + m_vtable->call_state_changed = callStateChangedCb; + m_vtable->transfer_state_changed = transferStateChanged; + linphone_core_v_table_set_user_data(m_vtable, this); + _linphone_core_add_listener(m_core, m_vtable, FALSE, TRUE); +} + +RemoteConference::~RemoteConference() { + terminate(); + linphone_core_remove_listener(m_core, m_vtable); + linphone_core_v_table_destroy(m_vtable); +} + +int RemoteConference::addParticipant(LinphoneCall *call) { + LinphoneAddress *addr; + LinphoneCallParams *params; + + switch(m_state) { + case LinphoneConferenceStopped: + case LinphoneConferenceStartingFailed: + Conference::addParticipant(call); + ms_message("Calling the conference focus (%s)", m_focusAddr); + addr = linphone_address_new(m_focusAddr); + if(addr) { + params = linphone_core_create_call_params(m_core, NULL); + linphone_call_params_enable_video(params, m_currentParams.videoRequested()); + m_focusCall = linphone_core_invite_address_with_params(m_core, addr, params); + m_focusCall->conf_ref = (LinphoneConference *)this; + m_localParticipantStream = m_focusCall->audiostream; + m_pendingCalls = ms_list_append(m_pendingCalls, call); + LinphoneCallLog *callLog = linphone_call_get_call_log(m_focusCall); + callLog->was_conference = TRUE; + linphone_address_unref(addr); + linphone_call_params_unref(params); + setState(LinphoneConferenceStarting); + return 0; + } else return -1; + + case LinphoneConferenceStarting: + Conference::addParticipant(call); + if(focusIsReady()) { + transferToFocus(call); + } else { + m_pendingCalls = ms_list_append(m_pendingCalls, call); + } + return 0; + + case LinphoneConferenceReady: + Conference::addParticipant(call); + transferToFocus(call); + return 0; + + default: + ms_error("Could not add call %p to the conference. Bad conference state (%s)", call, stateToString(m_state)); + return -1; + } +} + +int RemoteConference::removeParticipant(const LinphoneAddress *uri) { + char *refer_to; + LinphoneAddress *refer_to_addr; + int res; + + switch(m_state) { + case LinphoneConferenceReady: + if(findParticipant(uri) == NULL) { + char *tmp = linphone_address_as_string(uri); + ms_error("Conference: could not remove participant '%s': not in the participants list", tmp); + ms_free(tmp); + return -1; + } + +// refer_to = ms_strdup_printf("%s;method=BYE", tmp); + refer_to_addr = linphone_address_clone(uri); + linphone_address_set_method_param(refer_to_addr, "BYE"); + refer_to = linphone_address_as_string(refer_to_addr); + linphone_address_unref(refer_to_addr); + res = sal_call_refer(m_focusCall->op, refer_to); + ms_free(refer_to); + + if(res == 0) { + return Conference::removeParticipant(uri); + } else { + char *tmp = linphone_address_as_string(uri); + ms_error("Conference: could not remove participant '%s': REFER with BYE has failed", tmp); + ms_free(tmp); + return -1; + } + + default: + ms_error("Cannot remove %s from conference: Bad conference state (%s)", linphone_address_as_string(uri), stateToString(m_state)); + return -1; + } +} + +int RemoteConference::terminate() { + m_isTerminating = true; + switch(m_state) { + case LinphoneConferenceReady: + case LinphoneConferenceStarting: + linphone_core_terminate_call(m_core, m_focusCall); + reset(); + Conference::terminate(); + m_isTerminating = false; + setState(LinphoneConferenceStopped); + break; + + default: + m_isTerminating = false; + break; + } + + return 0; +} + +int RemoteConference::enter() { + if(m_state != LinphoneConferenceReady) { + ms_error("Could not enter in the conference: bad conference state (%s)", stateToString(m_state)); + return -1; + } + LinphoneCallState callState = linphone_call_get_state(m_focusCall); + switch(callState) { + case LinphoneCallStreamsRunning: break; + case LinphoneCallPaused: + linphone_core_resume_call(m_core, m_focusCall); + break; + default: + ms_error("Could not join the conference: bad focus call state (%s)", linphone_call_state_to_string(callState)); + return -1; + } + return 0; +} + +int RemoteConference::leave() { + if(m_state != LinphoneConferenceReady) { + ms_error("Could not leave the conference: bad conference state (%s)", stateToString(m_state)); + return -1; + } + LinphoneCallState callState = linphone_call_get_state(m_focusCall); + switch(callState) { + case LinphoneCallPaused: break; + case LinphoneCallStreamsRunning: + linphone_core_pause_call(m_core, m_focusCall); + break; + default: + ms_error("Could not leave the conference: bad focus call state (%s)", linphone_call_state_to_string(callState)); + return -1; + } + return 0; +} + +bool RemoteConference::isIn() const { + if(m_state != LinphoneConferenceReady) return false; + LinphoneCallState callState = linphone_call_get_state(m_focusCall); + return callState == LinphoneCallStreamsRunning; +} + +bool RemoteConference::focusIsReady() const { + LinphoneCallState focusState; + if(m_focusCall == NULL) return false; + focusState = linphone_call_get_state(m_focusCall); + return focusState == LinphoneCallStreamsRunning || focusState == LinphoneCallPaused; +} + +bool RemoteConference::transferToFocus(LinphoneCall *call) { + if(linphone_core_transfer_call(m_core, call, m_focusContact) == 0) { + m_transferingCalls = ms_list_append(m_transferingCalls, call); + return true; + } else { + ms_error("Conference: could not transfer call [%p] to %s", call, m_focusContact); + return false; + } +} + +void RemoteConference::reset() { + m_localParticipantStream = NULL; + m_focusAddr = NULL; + if(m_focusContact) { + ms_free(m_focusContact); + m_focusContact = NULL; + } + m_focusCall = NULL; + m_pendingCalls = ms_list_free(m_pendingCalls); + m_transferingCalls = ms_list_free(m_transferingCalls); +} + +void RemoteConference::onFocusCallSateChanged(LinphoneCallState state) { + MSList *it; + + switch (state) { + case LinphoneCallConnected: + m_focusContact = ms_strdup(linphone_call_get_remote_contact(m_focusCall)); + it = m_pendingCalls; + while (it) { + LinphoneCall *pendingCall = (LinphoneCall *)it->data; + LinphoneCallState pendingCallState = linphone_call_get_state(pendingCall); + if(pendingCallState == LinphoneCallStreamsRunning || pendingCallState == LinphoneCallPaused) { + MSList *current_elem = it; + it = it->next; + m_pendingCalls = ms_list_remove_link(m_pendingCalls, current_elem); + transferToFocus(pendingCall); + } else { + it = it->next; + } + } + setState(LinphoneConferenceReady); + break; + + case LinphoneCallError: + reset(); + setState(LinphoneConferenceStartingFailed); + break; + + case LinphoneCallEnd: + if(!m_isTerminating) terminate(); + break; + + default: break; + } +} + +void RemoteConference::onPendingCallStateChanged(LinphoneCall *call, LinphoneCallState state) { + switch(state) { + case LinphoneCallStreamsRunning: + case LinphoneCallPaused: + if(m_state == LinphoneConferenceReady) { + m_pendingCalls = ms_list_remove(m_pendingCalls, call); + m_transferingCalls = ms_list_append(m_transferingCalls, call); + linphone_core_transfer_call(m_core, call, m_focusContact); + } + break; + + case LinphoneCallError: + case LinphoneCallEnd: + m_pendingCalls = ms_list_remove(m_pendingCalls, call); + Conference::removeParticipant(call); + break; + + default: break; + } +} + +void RemoteConference::onTransferingCallStateChanged(LinphoneCall* transfered, LinphoneCallState newCallState) { + switch (newCallState) { + case LinphoneCallConnected: + case LinphoneCallError: + m_transferingCalls = ms_list_remove(m_transferingCalls, transfered); + if(newCallState == LinphoneCallError) Conference::removeParticipant(transfered); + break; + + default: + break; + } +} + +void RemoteConference::callStateChangedCb(LinphoneCore *lc, LinphoneCall *call, LinphoneCallState cstate, const char *message) { + LinphoneCoreVTable *vtable = linphone_core_get_current_vtable(lc); + RemoteConference *conf = (RemoteConference *)linphone_core_v_table_get_user_data(vtable); + if (call == conf->m_focusCall) { + conf->onFocusCallSateChanged(cstate); + } else if(ms_list_find(conf->m_pendingCalls, call)) { + conf->onPendingCallStateChanged(call, cstate); + } +} + +void RemoteConference::transferStateChanged(LinphoneCore *lc, LinphoneCall *transfered, LinphoneCallState new_call_state) { + LinphoneCoreVTable *vtable = linphone_core_get_current_vtable(lc); + RemoteConference *conf = (RemoteConference *)linphone_core_v_table_get_user_data(vtable); + if (ms_list_find(conf->m_transferingCalls, transfered)) { + conf->onTransferingCallStateChanged(transfered, new_call_state); + } +} + + + +const char *linphone_conference_state_to_string(LinphoneConferenceState state) { + return Conference::stateToString(state); +} + +LinphoneConferenceParams *linphone_conference_params_new(const LinphoneCore *core) { + return (LinphoneConferenceParams *)new Conference::Params(core); +} + +void linphone_conference_params_free(LinphoneConferenceParams *params) { + delete (Conference::Params *)params; +} + +LinphoneConferenceParams *linphone_conference_params_clone(const LinphoneConferenceParams *params) { + return (LinphoneConferenceParams *)new Conference::Params(*(Conference::Params *)params); +} + +void linphone_conference_params_enable_video(LinphoneConferenceParams *params, bool_t enable) { + ((Conference::Params *)params)->enableVideo(enable); +} + +bool_t linphone_conference_params_video_requested(const LinphoneConferenceParams *params) { + return ((Conference::Params *)params)->videoRequested(); +} + +void linphone_conference_params_set_state_changed_callback(LinphoneConferenceParams *params, LinphoneConferenceStateChangedCb cb, void *user_data) { + ((Conference::Params *)params)->setStateChangedCallback(cb, user_data); +} + + + +LinphoneConference *linphone_local_conference_new(LinphoneCore *core) { + return (LinphoneConference *) new LocalConference(core); +} + +LinphoneConference *linphone_local_conference_new_with_params(LinphoneCore *core, const LinphoneConferenceParams *params) { + return (LinphoneConference *) new LocalConference(core, (Conference::Params *)params); +} + +LinphoneConference *linphone_remote_conference_new(LinphoneCore *core) { + return (LinphoneConference *) new RemoteConference(core); +} + +LinphoneConference *linphone_remote_conference_new_with_params(LinphoneCore *core, const LinphoneConferenceParams *params) { + return (LinphoneConference *) new RemoteConference(core, (Conference::Params *)params); +} + +void linphone_conference_free(LinphoneConference *obj) { + delete (Conference *)obj; +} + +LinphoneConferenceState linphone_conference_get_state(const LinphoneConference *obj) { + return ((Conference *)obj)->getState(); +} + +int linphone_conference_add_participant(LinphoneConference *obj, LinphoneCall *call) { + return ((Conference *)obj)->addParticipant(call); +} + +int linphone_conference_remove_participant(LinphoneConference *obj, const LinphoneAddress *uri) { + return ((Conference *)obj)->removeParticipant(uri); +} + +int linphone_conference_remove_participant_with_call(LinphoneConference *obj, LinphoneCall *call) { + return ((Conference *)obj)->removeParticipant(call); +} + +int linphone_conference_terminate(LinphoneConference *obj) { + return ((Conference *)obj)->terminate(); +} + +int linphone_conference_enter(LinphoneConference *obj) { + return ((Conference *)obj)->enter(); +} + +int linphone_conference_leave(LinphoneConference *obj) { + return ((Conference *)obj)->leave(); +} + +bool_t linphone_conference_is_in(const LinphoneConference *obj) { + return ((Conference *)obj)->isIn(); +} + +AudioStream *linphone_conference_get_audio_stream(const LinphoneConference *obj) { + return ((Conference *)obj)->getAudioStream(); +} + +int linphone_conference_mute_microphone(LinphoneConference *obj, bool_t val) { + return ((Conference *)obj)->muteMicrophone(val); +} + +bool_t linphone_conference_microphone_is_muted(const LinphoneConference *obj) { + return ((Conference *)obj)->microphoneIsMuted(); +} + +float linphone_conference_get_input_volume(const LinphoneConference *obj) { + return ((Conference *)obj)->getInputVolume(); +} + +int linphone_conference_get_size(const LinphoneConference *obj) { + return ((Conference *)obj)->getSize(); +} + +MSList *linphone_conference_get_participants(const LinphoneConference *obj) { + const list &participants = ((Conference *)obj)->getParticipants(); + MSList *participants_list = NULL; + for(list::const_iterator it=participants.begin();it!=participants.end();it++) { + LinphoneAddress *uri = linphone_address_clone(it->getUri()); + participants_list = ms_list_append(participants_list, uri); + } + return participants_list; +} + +int linphone_conference_start_recording(LinphoneConference *obj, const char *path) { + return ((Conference *)obj)->startRecording(path); +} + +int linphone_conference_stop_recording(LinphoneConference *obj) { + return ((Conference *)obj)->stopRecording(); +} + +void linphone_conference_on_call_stream_starting(LinphoneConference *obj, LinphoneCall *call, bool_t is_paused_by_remote) { + ((Conference *)obj)->onCallStreamStarting(call, is_paused_by_remote); +} + +void linphone_conference_on_call_stream_stopping(LinphoneConference *obj, LinphoneCall *call) { + ((Conference *)obj)->onCallStreamStopping(call); +} + +void linphone_conference_on_call_terminating(LinphoneConference *obj, LinphoneCall *call) { + ((Conference *)obj)->onCallTerminating(call); +} + +bool_t linphone_conference_check_class(LinphoneConference *obj, LinphoneConferenceClass _class) { + switch(_class) { + case LinphoneConferenceClassLocal: return typeid(obj) == typeid(LocalConference); + case LinphoneConferenceClassRemote: return typeid(obj) == typeid(RemoteConference); + default: return FALSE; + } +} diff --git a/coreapi/conference.h b/coreapi/conference.h new file mode 100644 index 000000000..d14ff2f9d --- /dev/null +++ b/coreapi/conference.h @@ -0,0 +1,110 @@ +/******************************************************************************* + * conference.h + * + * Thu Nov 26, 2015 + * Copyright 2015 Belledonne Communications + * Author: Linphone's team + * Email info@belledonne-communications.com + ******************************************************************************/ + +/* + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef CONFERENCE_H +#define CONFERENCE_H + +#include "linphonecore.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @addtogroup call_control + * @{ + */ + +/** + * LinphoneConference class + */ +typedef struct _LinphoneConference LinphoneConference; +/** + * Parameters for initialization of conferences + */ +typedef struct _LinphoneCorferenceParams LinphoneConferenceParams; + + + + +/** + * Create a #LinphoneConferenceParams with default parameters set. + * @param core #LinphoneCore to use to find out the default parameters. Can be NULL. + * @return A freshly allocated #LinphoneConferenceParams + */ +LINPHONE_PUBLIC LinphoneConferenceParams *linphone_conference_params_new(const LinphoneCore *core); +/** + * Free a #LinphoneConferenceParams + * @param params #LinphoneConferenceParams to free + */ +LINPHONE_PUBLIC void linphone_conference_params_free(LinphoneConferenceParams *params); +/** + * Clone a #LinphoneConferenceParams + * @param params The #LinphoneConfrenceParams to clone + * @return An allocated #LinphoneConferenceParams with the same parameters than params + */ +LINPHONE_PUBLIC LinphoneConferenceParams *linphone_conference_params_clone(const LinphoneConferenceParams *params); +/** + * Enable video when starting a conference + * @param params A #LinphoneConnferenceParams + * @param enable If true, video will be enabled during conference + */ +LINPHONE_PUBLIC void linphone_conference_params_enable_video(LinphoneConferenceParams *params, bool_t enable); +/** + * Check whether video will be enable at conference starting + * @return if true, the video will be enable at conference starting + */ +LINPHONE_PUBLIC bool_t linphone_conference_params_video_requested(const LinphoneConferenceParams *params); + + + + + +/** + * Remove a participant from a conference + * @param obj A #LinphoneConference + * @param uri SIP URI of the participant to remove + * @warning The passed SIP URI must be one of the URIs returned by linphone_conference_get_participants() + * @return 0 if succeeded, -1 if failed + */ +LINPHONE_PUBLIC int linphone_conference_remove_participant(LinphoneConference *obj, const LinphoneAddress *uri); +/** + * Get URIs of all participants of one conference + * The returned MSList contains URIs of all participant. That list must be + * freed after use and each URI must be unref with linphone_address_unref() + * @param obj A #LinphoneConference + * @return \mslist{LinphoneAddress} + */ +LINPHONE_PUBLIC MSList *linphone_conference_get_participants(const LinphoneConference *obj); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif // CONFERENCE_H diff --git a/coreapi/conference_private.h b/coreapi/conference_private.h new file mode 100644 index 000000000..bc203f794 --- /dev/null +++ b/coreapi/conference_private.h @@ -0,0 +1,111 @@ +/******************************************************************************* + * conference_private.h + * + * Tue Jan 12, 2015 + * Copyright 2015 Belledonne Communications + * Author: Linphone's team + * Email info@belledonne-communications.com + ******************************************************************************/ + +/* + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef CONFERENCE_PRIVATE_H +#define CONFERENCE_PRIVATE_H + +#include "linphonecore.h" +#include "conference.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + LinphoneConferenceClassLocal, + LinphoneConferenceClassRemote +} LinphoneConferenceClass; + +/** + * List of states used by #LinphoneConference + */ +typedef enum { + LinphoneConferenceStopped, /*< Initial state */ + LinphoneConferenceStarting, /*< A participant has been added but the conference is not running yet */ + LinphoneConferenceReady, /*< The conference is running */ + LinphoneConferenceStartingFailed /*< A participant has been added but the initialization of the conference has failed */ +} LinphoneConferenceState; +/** + * Type of the funtion to pass as callback to linphone_conference_params_set_state_changed_callback() + * @param conference The conference instance which the state has changed + * @param new_state The new state of the conferenece + * @param user_data Pointer pass to user_data while linphone_conference_params_set_state_changed_callback() was being called + */ +typedef void (*LinphoneConferenceStateChangedCb)(LinphoneConference *conference, LinphoneConferenceState new_state, void *user_data); + + +/** + * A function to converte a #LinphoneConferenceState into a string + */ +const char *linphone_conference_state_to_string(LinphoneConferenceState state); + + +/** + * Set a callback which will be called when the state of the conferenec is switching + * @param params A #LinphoneConferenceParams object + * @param cb The callback to call + * @param user_data Pointer to pass to the user_data parameter of #LinphoneConferenceStateChangedCb + */ +void linphone_conference_params_set_state_changed_callback(LinphoneConferenceParams *params, LinphoneConferenceStateChangedCb cb, void *user_data); + + +LinphoneConference *linphone_local_conference_new(LinphoneCore *core); +LinphoneConference *linphone_local_conference_new_with_params(LinphoneCore *core, const LinphoneConferenceParams *params); +LinphoneConference *linphone_remote_conference_new(LinphoneCore *core); +LinphoneConference *linphone_remote_conference_new_with_params(LinphoneCore *core, const LinphoneConferenceParams *params); +void linphone_conference_free(LinphoneConference *obj); +/** + * Get the state of a conference + */ +LinphoneConferenceState linphone_conference_get_state(const LinphoneConference *obj); + +int linphone_conference_add_participant(LinphoneConference *obj, LinphoneCall *call); +int linphone_conference_remove_participant_with_call(LinphoneConference *obj, LinphoneCall *call); +int linphone_conference_terminate(LinphoneConference *obj); +int linphone_conference_get_size(const LinphoneConference *obj); + +int linphone_conference_enter(LinphoneConference *obj); +int linphone_conference_leave(LinphoneConference *obj); +bool_t linphone_conference_is_in(const LinphoneConference *obj); + +AudioStream *linphone_conference_get_audio_stream(const LinphoneConference *obj); + +int linphone_conference_mute_microphone(LinphoneConference *obj, bool_t val); +bool_t linphone_conference_microphone_is_muted(const LinphoneConference *obj); +float linphone_conference_get_input_volume(const LinphoneConference *obj); + +int linphone_conference_start_recording(LinphoneConference *obj, const char *path); +int linphone_conference_stop_recording(LinphoneConference *obj); + +void linphone_conference_on_call_stream_starting(LinphoneConference *obj, LinphoneCall *call, bool_t is_paused_by_remote); +void linphone_conference_on_call_stream_stopping(LinphoneConference *obj, LinphoneCall *call); +void linphone_conference_on_call_terminating(LinphoneConference *obj, LinphoneCall *call); + +LINPHONE_PUBLIC bool_t linphone_conference_check_class(LinphoneConference *obj, LinphoneConferenceClass _class); + +#ifdef __cplusplus +} +#endif + +#endif //CONFERENCE_PRIVATE_H diff --git a/coreapi/content.c b/coreapi/content.c index 9f5cd4fee..cc0760b0a 100644 --- a/coreapi/content.c +++ b/coreapi/content.c @@ -22,23 +22,46 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +static void linphone_content_set_sal_body_handler(LinphoneContent *content, SalBodyHandler *body_handler) { + if (content->body_handler != NULL) { + sal_body_handler_unref(content->body_handler); + content->body_handler = NULL; + } + content->body_handler = sal_body_handler_ref(body_handler); +} + +static LinphoneContent * linphone_content_new_with_body_handler(SalBodyHandler *body_handler) { + LinphoneContent *content = belle_sip_object_new(LinphoneContent); + belle_sip_object_ref(content); + content->owned_fields = TRUE; + content->cryptoContext = NULL; /* this field is managed externally by encryption/decryption functions so be careful to initialise it to NULL */ + if (body_handler == NULL) { + linphone_content_set_sal_body_handler(content, sal_body_handler_new()); + } else { + linphone_content_set_sal_body_handler(content, body_handler); + } + return content; +} + static void linphone_content_destroy(LinphoneContent *content) { if (content->owned_fields == TRUE) { - if (content->lcp.type) belle_sip_free(content->lcp.type); - if (content->lcp.subtype) belle_sip_free(content->lcp.subtype); - if (content->lcp.data) belle_sip_free(content->lcp.data); - if (content->lcp.encoding) belle_sip_free(content->lcp.encoding); - if (content->lcp.name) belle_sip_free(content->lcp.name); - if (content->lcp.key) belle_sip_free(content->lcp.key); + if (content->body_handler) sal_body_handler_unref(content->body_handler); + if (content->name) belle_sip_free(content->name); + if (content->key) belle_sip_free(content->key); /* note : crypto context is allocated/destroyed by the encryption function */ } } static void linphone_content_clone(LinphoneContent *obj, const LinphoneContent *ref) { obj->owned_fields = TRUE; - linphone_content_set_type(obj, linphone_content_get_type(ref)); - linphone_content_set_subtype(obj, linphone_content_get_subtype(ref)); - linphone_content_set_encoding(obj, linphone_content_get_encoding(ref)); + linphone_content_set_sal_body_handler(obj, sal_body_handler_new()); + if ((linphone_content_get_type(ref) != NULL) || (linphone_content_get_subtype(ref) != NULL)) { + linphone_content_set_type(obj, linphone_content_get_type(ref)); + linphone_content_set_subtype(obj, linphone_content_get_subtype(ref)); + } + if (linphone_content_get_encoding(ref) != NULL) { + linphone_content_set_encoding(obj, linphone_content_get_encoding(ref)); + } linphone_content_set_name(obj, linphone_content_get_name(ref)); linphone_content_set_key(obj, linphone_content_get_key(ref), linphone_content_get_key_size(ref)); if (linphone_content_get_buffer(ref) != NULL) { @@ -81,163 +104,136 @@ void linphone_content_set_user_data(LinphoneContent *content, void *ud) { } const char * linphone_content_get_type(const LinphoneContent *content) { - return content->lcp.type; + return sal_body_handler_get_type(content->body_handler); } void linphone_content_set_type(LinphoneContent *content, const char *type) { - if (content->lcp.type != NULL) { - belle_sip_free(content->lcp.type); - content->lcp.type = NULL; - } - if (type != NULL) { - content->lcp.type = belle_sip_strdup(type); - } + sal_body_handler_set_type(content->body_handler, type); } const char * linphone_content_get_subtype(const LinphoneContent *content) { - return content->lcp.subtype; + return sal_body_handler_get_subtype(content->body_handler); } void linphone_content_set_subtype(LinphoneContent *content, const char *subtype) { - if (content->lcp.subtype != NULL) { - belle_sip_free(content->lcp.subtype); - content->lcp.subtype = NULL; - } - if (subtype != NULL) { - content->lcp.subtype = belle_sip_strdup(subtype); - } + sal_body_handler_set_subtype(content->body_handler, subtype); } void * linphone_content_get_buffer(const LinphoneContent *content) { - return content->lcp.data; + return sal_body_handler_get_data(content->body_handler); } void linphone_content_set_buffer(LinphoneContent *content, const void *buffer, size_t size) { - content->lcp.size = size; - content->lcp.data = belle_sip_malloc(size + 1); - memcpy(content->lcp.data, buffer, size); - ((char *)content->lcp.data)[size] = '\0'; + void *data; + sal_body_handler_set_size(content->body_handler, size); + data = belle_sip_malloc(size + 1); + memcpy(data, buffer, size); + ((char *)data)[size] = '\0'; + sal_body_handler_set_data(content->body_handler, data); } const char * linphone_content_get_string_buffer(const LinphoneContent *content) { - return (char *)content->lcp.data; + return (const char *)linphone_content_get_buffer(content); } void linphone_content_set_string_buffer(LinphoneContent *content, const char *buffer) { - content->lcp.size = strlen(buffer); - content->lcp.data = belle_sip_strdup(buffer); + sal_body_handler_set_size(content->body_handler, strlen(buffer)); + sal_body_handler_set_data(content->body_handler, belle_sip_strdup(buffer)); } size_t linphone_content_get_size(const LinphoneContent *content) { - return content->lcp.size; + return sal_body_handler_get_size(content->body_handler); } void linphone_content_set_size(LinphoneContent *content, size_t size) { - content->lcp.size = size; + sal_body_handler_set_size(content->body_handler, size); } const char * linphone_content_get_encoding(const LinphoneContent *content) { - return content->lcp.encoding; + return sal_body_handler_get_encoding(content->body_handler); } void linphone_content_set_encoding(LinphoneContent *content, const char *encoding) { - if (content->lcp.encoding != NULL) { - belle_sip_free(content->lcp.encoding); - content->lcp.encoding = NULL; - } - if (encoding != NULL) { - content->lcp.encoding = belle_sip_strdup(encoding); - } + sal_body_handler_set_encoding(content->body_handler, encoding); } const char * linphone_content_get_name(const LinphoneContent *content) { - return content->lcp.name; + return content->name; } void linphone_content_set_name(LinphoneContent *content, const char *name) { - if (content->lcp.name != NULL) { - belle_sip_free(content->lcp.name); - content->lcp.name = NULL; + if (content->name != NULL) { + belle_sip_free(content->name); + content->name = NULL; } if (name != NULL) { - content->lcp.name = belle_sip_strdup(name); + content->name = belle_sip_strdup(name); } } size_t linphone_content_get_key_size(const LinphoneContent *content) { - return content->lcp.keyLength; + return content->keyLength; } const char * linphone_content_get_key(const LinphoneContent *content) { - return content->lcp.key; + return content->key; } void linphone_content_set_key(LinphoneContent *content, const char *key, const size_t keyLength) { - if (content->lcp.key != NULL) { - belle_sip_free(content->lcp.key); - content->lcp.key = NULL; + if (content->key != NULL) { + belle_sip_free(content->key); + content->key = NULL; } if (key != NULL) { - content->lcp.key = belle_sip_malloc(keyLength); - memcpy(content->lcp.key, key, keyLength); + content->key = belle_sip_malloc(keyLength); + memcpy(content->key, key, keyLength); } } /* crypto context is managed(allocated/freed) by the encryption function, so provide the address of field in the private structure */ void ** linphone_content_get_cryptoContext_address(LinphoneContent *content) { - return &(content->lcp.cryptoContext); + return &(content->cryptoContext); +} + +bool_t linphone_content_is_multipart(const LinphoneContent *content) { + return sal_body_handler_is_multipart(content->body_handler); +} + +LinphoneContent * linphone_content_get_part(const LinphoneContent *content, int idx) { + SalBodyHandler *part_body_handler; + if (!linphone_content_is_multipart(content)) return NULL; + part_body_handler = sal_body_handler_get_part(content->body_handler, idx); + return linphone_content_from_sal_body_handler(part_body_handler); +} + +LinphoneContent * linphone_content_find_part_by_header(const LinphoneContent *content, const char *header_name, const char *header_value) { + SalBodyHandler *part_body_handler; + if (!linphone_content_is_multipart(content)) return NULL; + part_body_handler = sal_body_handler_find_part_by_header(content->body_handler, header_name, header_value); + return linphone_content_from_sal_body_handler(part_body_handler); +} + +const char * linphone_content_get_custom_header(const LinphoneContent *content, const char *header_name) { + return sal_body_handler_get_header(content->body_handler, header_name); } LinphoneContent * linphone_content_new(void) { - LinphoneContent *content = belle_sip_object_new(LinphoneContent); - belle_sip_object_ref(content); - content->owned_fields = TRUE; - content->lcp.cryptoContext = NULL; /* this field is managed externally by encryption/decryption functions so be careful to initialise it to NULL */ - return content; + return linphone_content_new_with_body_handler(NULL); } LinphoneContent * linphone_content_copy(const LinphoneContent *ref) { return (LinphoneContent *)belle_sip_object_ref(belle_sip_object_clone(BELLE_SIP_OBJECT(ref))); } -LinphoneContent * linphone_content_from_sal_body(const SalBody *ref) { - if (ref && ref->type) { - LinphoneContent *content = linphone_content_new(); - linphone_content_set_type(content, ref->type); - linphone_content_set_subtype(content, ref->subtype); - linphone_content_set_encoding(content, ref->encoding); - if (ref->data != NULL) { - linphone_content_set_buffer(content, ref->data, ref->size); - } else { - linphone_content_set_size(content, ref->size); - } - return content; +LinphoneContent * linphone_content_from_sal_body_handler(SalBodyHandler *body_handler) { + if (body_handler) { + return linphone_content_new_with_body_handler(body_handler); } return NULL; } -SalBody *sal_body_from_content(SalBody *body, const LinphoneContent *content) { - if (content && linphone_content_get_type(content)) { - body->type = linphone_content_get_type(content); - body->subtype = linphone_content_get_subtype(content); - body->data = linphone_content_get_buffer(content); - body->size = linphone_content_get_size(content); - body->encoding = linphone_content_get_encoding(content); - return body; - } - return NULL; -} - - - -LinphoneContent * linphone_content_private_to_linphone_content(const LinphoneContentPrivate *lcp) { - LinphoneContent *content = belle_sip_object_new(LinphoneContent); - memcpy(&content->lcp, lcp, sizeof(LinphoneContentPrivate)); - content->owned_fields = FALSE; - return content; -} - -LinphoneContentPrivate * linphone_content_to_linphone_content_private(const LinphoneContent *content) { - return (LinphoneContentPrivate *)&content->lcp; +SalBodyHandler * sal_body_handler_from_content(const LinphoneContent *content) { + if (content == NULL) return NULL; + return content->body_handler; } diff --git a/coreapi/content.h b/coreapi/content.h index 1e21138eb..104488d42 100644 --- a/coreapi/content.h +++ b/coreapi/content.h @@ -40,51 +40,6 @@ struct _LinphoneContent; **/ typedef struct _LinphoneContent LinphoneContent; -/** - * @deprecated Use LinphoneContent objects instead of this structure. - */ -struct _LinphoneContentPrivate{ - char *type; /**sndread,MS_FILTER_GET_SAMPLE_RATE,&rate); ms_filter_call_method(ecc->sndread,MS_FILTER_SET_NCHANNELS,&ecc_channels); ms_filter_call_method(ecc->sndread,MS_FILTER_GET_NCHANNELS,&channels); - ecc->read_resampler=ms_filter_new(MS_RESAMPLE_ID); + ecc->read_resampler=ms_factory_create_filter(ecc->factory, MS_RESAMPLE_ID); ms_filter_call_method(ecc->read_resampler,MS_FILTER_SET_SAMPLE_RATE,&rate); ms_filter_call_method(ecc->read_resampler,MS_FILTER_SET_OUTPUT_SAMPLE_RATE,&ecc->rate); ms_filter_call_method(ecc->read_resampler,MS_FILTER_SET_NCHANNELS,&ecc_channels); ms_filter_call_method(ecc->read_resampler,MS_FILTER_SET_OUTPUT_NCHANNELS,&channels); - ecc->det=ms_filter_new(MS_TONE_DETECTOR_ID); + ecc->det=ms_factory_create_filter(ecc->factory, MS_TONE_DETECTOR_ID); ms_filter_call_method(ecc->det,MS_FILTER_SET_SAMPLE_RATE,&ecc->rate); - ecc->rec=ms_filter_new(MS_VOID_SINK_ID); + ecc->rec=ms_factory_create_filter(ecc->factory, MS_VOID_SINK_ID); ms_filter_link(ecc->sndread,0,ecc->read_resampler,0); ms_filter_link(ecc->read_resampler,0,ecc->det,0); ms_filter_link(ecc->det,0,ecc->rec,0); - ecc->play=ms_filter_new(MS_VOID_SOURCE_ID); - ecc->gen=ms_filter_new(MS_DTMF_GEN_ID); + ecc->play=ms_factory_create_filter(ecc->factory, MS_VOID_SOURCE_ID); + ecc->gen=ms_factory_create_filter(ecc->factory, MS_DTMF_GEN_ID); ms_filter_call_method(ecc->gen,MS_FILTER_SET_SAMPLE_RATE,&ecc->rate); - ecc->write_resampler=ms_filter_new(MS_RESAMPLE_ID); + ecc->write_resampler=ms_factory_create_filter(ecc->factory, MS_RESAMPLE_ID); ecc->sndwrite=ms_snd_card_create_writer(ecc->play_card); ms_filter_call_method(ecc->sndwrite,MS_FILTER_SET_SAMPLE_RATE,&ecc->rate); @@ -193,36 +193,60 @@ static void ecc_play_tones(EcCalibrator *ecc){ ms_filter_add_notify_callback(ecc->gen,on_tone_sent,ecc,TRUE); /* play the three tones*/ - strncpy(tone.tone_name, "D", sizeof(tone.tone_name)); - tone.frequencies[0]=(int)2349.32; - tone.duration=100; - ms_filter_call_method(ecc->gen,MS_DTMF_GEN_PLAY_CUSTOM,&tone); - ms_usleep(300000); - strncpy(tone.tone_name, "E", sizeof(tone.tone_name)); - tone.frequencies[0]=(int)2637.02; - tone.duration=100; - ms_filter_call_method(ecc->gen,MS_DTMF_GEN_PLAY_CUSTOM,&tone); - ms_usleep(300000); - strncpy(tone.tone_name, "C", sizeof(tone.tone_name)); - tone.frequencies[0]=(int)2093; - tone.duration=100; - ms_filter_call_method(ecc->gen,MS_DTMF_GEN_PLAY_CUSTOM,&tone); - ms_usleep(300000); + if (ecc->play_cool_tones){ + strncpy(tone.tone_name, "D", sizeof(tone.tone_name)); + tone.frequencies[0]=(int)2349.32; + tone.duration=100; + ms_filter_call_method(ecc->gen,MS_DTMF_GEN_PLAY_CUSTOM,&tone); + ms_usleep(300000); + + strncpy(tone.tone_name, "E", sizeof(tone.tone_name)); + tone.frequencies[0]=(int)2637.02; + tone.duration=100; + ms_filter_call_method(ecc->gen,MS_DTMF_GEN_PLAY_CUSTOM,&tone); + ms_usleep(300000); + + strncpy(tone.tone_name, "C", sizeof(tone.tone_name)); + tone.frequencies[0]=(int)2093; + tone.duration=100; + ms_filter_call_method(ecc->gen,MS_DTMF_GEN_PLAY_CUSTOM,&tone); + ms_usleep(300000); + }else{ + strncpy(tone.tone_name, "C", sizeof(tone.tone_name)); + tone.frequencies[0]=(int)2093; + tone.duration=100; + ms_filter_call_method(ecc->gen,MS_DTMF_GEN_PLAY_CUSTOM,&tone); + ms_usleep(300000); + + strncpy(tone.tone_name, "D", sizeof(tone.tone_name)); + tone.frequencies[0]=(int)2349.32; + tone.duration=100; + ms_filter_call_method(ecc->gen,MS_DTMF_GEN_PLAY_CUSTOM,&tone); + ms_usleep(300000); + + strncpy(tone.tone_name, "E", sizeof(tone.tone_name)); + tone.frequencies[0]=(int)2637.02; + tone.duration=100; + ms_filter_call_method(ecc->gen,MS_DTMF_GEN_PLAY_CUSTOM,&tone); + ms_usleep(300000); + } /*these two next ones are for lyrism*/ + if (ecc->play_cool_tones){ + tone.tone_name[0]='\0'; + tone.frequencies[0]=(int)1046.5; + tone.duration=400; + ms_filter_call_method(ecc->gen,MS_DTMF_GEN_PLAY_CUSTOM,&tone); + ms_usleep(300000); + + tone.tone_name[0]='\0'; + tone.frequencies[0]=(int)1567.98; + tone.duration=400; + ms_filter_call_method(ecc->gen,MS_DTMF_GEN_PLAY_CUSTOM,&tone); + } - tone.tone_name[0]='\0'; - tone.frequencies[0]=(int)1046.5; - tone.duration=400; - ms_filter_call_method(ecc->gen,MS_DTMF_GEN_PLAY_CUSTOM,&tone); - ms_usleep(300000); - - tone.tone_name[0]='\0'; - tone.frequencies[0]=(int)1567.98; - tone.duration=400; - ms_filter_call_method(ecc->gen,MS_DTMF_GEN_PLAY_CUSTOM,&tone); ms_sleep(1); if (ecc->freq1 && ecc->freq2 && ecc->freq3) { @@ -257,7 +281,7 @@ static void * ecc_thread(void *p){ return NULL; } -EcCalibrator * ec_calibrator_new(MSSndCard *play_card, MSSndCard *capt_card, unsigned int rate, LinphoneEcCalibrationCallback cb, +EcCalibrator * ec_calibrator_new(MSFactory *factory, MSSndCard *play_card, MSSndCard *capt_card, unsigned int rate, LinphoneEcCalibrationCallback cb, LinphoneEcCalibrationAudioInit audio_init_cb, LinphoneEcCalibrationAudioUninit audio_uninit_cb, void *cb_data){ EcCalibrator *ecc=ms_new0(EcCalibrator,1); @@ -268,16 +292,20 @@ EcCalibrator * ec_calibrator_new(MSSndCard *play_card, MSSndCard *capt_card, uns ecc->audio_uninit_cb=audio_uninit_cb; ecc->capt_card=capt_card; ecc->play_card=play_card; - ms_thread_create(&ecc->thread,NULL,ecc_thread,ecc); + ecc->factory=factory; return ecc; } +void ec_calibrator_start(EcCalibrator *ecc){ + ms_thread_create(&ecc->thread,NULL,ecc_thread,ecc); +} + LinphoneEcCalibratorStatus ec_calibrator_get_status(EcCalibrator *ecc){ return ecc->status; } void ec_calibrator_destroy(EcCalibrator *ecc){ - ms_thread_join(ecc->thread,NULL); + if (ecc->thread != 0) ms_thread_join(ecc->thread,NULL); ms_free(ecc); } @@ -290,7 +318,9 @@ int linphone_core_start_echo_calibration(LinphoneCore *lc, LinphoneEcCalibration return -1; } rate = lp_config_get_int(lc->config,"sound","echo_cancellation_rate",8000); - lc->ecc=ec_calibrator_new(lc->sound_conf.play_sndcard,lc->sound_conf.capt_sndcard,rate,cb,audio_init_cb,audio_uninit_cb,cb_data); + lc->ecc=ec_calibrator_new(lc->factory, lc->sound_conf.play_sndcard,lc->sound_conf.capt_sndcard,rate,cb,audio_init_cb,audio_uninit_cb,cb_data); + lc->ecc->play_cool_tones = lp_config_get_int(lc->config, "sound", "ec_calibrator_cool_tones", 0); + ec_calibrator_start(lc->ecc); return 0; } diff --git a/coreapi/event.c b/coreapi/event.c index 836b929a7..e8d2ac49a 100644 --- a/coreapi/event.c +++ b/coreapi/event.c @@ -20,6 +20,17 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "private.h" #include "lpconfig.h" +const char * linphone_subscription_dir_to_string(LinphoneSubscriptionDir dir){ + switch(dir){ + case LinphoneSubscriptionIncoming: + return "LinphoneSubscriptionIncoming"; + case LinphoneSubscriptionOutgoing: + return "LinphoneSubscriptionOutgoing"; + case LinphoneSubscriptionInvalidDir: + return "LinphoneSubscriptionInvalidDir"; + } + return "INVALID"; +} LinphoneSubscriptionState linphone_subscription_state_from_sal(SalSubscribeStatus ss){ switch(ss){ @@ -35,7 +46,7 @@ const char *linphone_subscription_state_to_string(LinphoneSubscriptionState stat switch(state){ case LinphoneSubscriptionNone: return "LinphoneSubscriptionNone"; case LinphoneSubscriptionIncomingReceived: return "LinphoneSubscriptionIncomingReceived"; - case LinphoneSubscriptionOutgoingInit: return "LinphoneSubscriptionOutoingInit"; + case LinphoneSubscriptionOutgoingProgress: return "LinphoneSubscriptionOutgoingProgress"; case LinphoneSubscriptionPending: return "LinphoneSubscriptionPending"; case LinphoneSubscriptionActive: return "LinphoneSubscriptionActive"; case LinphoneSubscriptionTerminated: return "LinphoneSubscriptionTerminated"; @@ -58,11 +69,10 @@ LINPHONE_PUBLIC const char *linphone_publish_state_to_string(LinphonePublishStat } static LinphoneEvent * linphone_event_new_base(LinphoneCore *lc, LinphoneSubscriptionDir dir, const char *name, SalOp *op){ - LinphoneEvent *lev=ms_new0(LinphoneEvent,1); + LinphoneEvent *lev=belle_sip_object_new(LinphoneEvent); lev->lc=lc; lev->dir=dir; lev->op=op; - lev->refcnt=1; lev->name=ms_strdup(name); sal_op_set_user_pointer(lev->op,lev); return lev; @@ -88,12 +98,20 @@ LinphoneEvent *linphone_event_new_with_out_of_dialog_op(LinphoneCore *lc, SalOp return linphone_event_new_with_op_base(lc,op,dir,name,TRUE); } +void linphone_event_set_internal(LinphoneEvent *lev, bool_t internal) { + lev->internal = internal; +} + +bool_t linphone_event_is_internal(LinphoneEvent *lev) { + return lev->internal; +} + void linphone_event_set_state(LinphoneEvent *lev, LinphoneSubscriptionState state){ if (lev->subscription_state!=state){ ms_message("LinphoneEvent [%p] moving to subscription state %s",lev,linphone_subscription_state_to_string(state)); lev->subscription_state=state; linphone_core_notify_subscription_state_changed(lev->lc,lev,state); - if (state==LinphoneSubscriptionTerminated){ + if (state==LinphoneSubscriptionTerminated || state == LinphoneSubscriptionError){ linphone_event_unref(lev); } } @@ -151,7 +169,7 @@ LinphoneEvent *linphone_core_subscribe(LinphoneCore *lc, const LinphoneAddress * int linphone_event_send_subscribe(LinphoneEvent *lev, const LinphoneContent *body){ - SalBody salbody; + SalBodyHandler *body_handler; int err; if (lev->dir!=LinphoneSubscriptionOutgoing){ @@ -176,10 +194,12 @@ int linphone_event_send_subscribe(LinphoneEvent *lev, const LinphoneContent *bod if (lev->send_custom_headers){ sal_op_set_sent_custom_header(lev->op,lev->send_custom_headers); + sal_custom_header_free(lev->send_custom_headers); lev->send_custom_headers=NULL; }else sal_op_set_sent_custom_header(lev->op,NULL); - err=sal_subscribe(lev->op,NULL,NULL,lev->name,lev->expires,sal_body_from_content(&salbody,body)); + body_handler = sal_body_handler_from_content(body); + err=sal_subscribe(lev->op,NULL,NULL,lev->name,lev->expires,body_handler); if (err==0){ if (lev->subscription_state==LinphoneSubscriptionNone) linphone_event_set_state(lev,LinphoneSubscriptionOutgoingInit); @@ -191,6 +211,10 @@ int linphone_event_update_subscribe(LinphoneEvent *lev, const LinphoneContent *b return linphone_event_send_subscribe(lev,body); } +int linphone_event_refresh_subscribe(LinphoneEvent *lev) { + return sal_op_refresh(lev->op); +} + int linphone_event_accept_subscription(LinphoneEvent *lev){ int err; if (lev->subscription_state!=LinphoneSubscriptionIncomingReceived){ @@ -216,7 +240,7 @@ int linphone_event_deny_subscription(LinphoneEvent *lev, LinphoneReason reason){ } int linphone_event_notify(LinphoneEvent *lev, const LinphoneContent *body){ - SalBody salbody; + SalBodyHandler *body_handler; if (lev->subscription_state!=LinphoneSubscriptionActive){ ms_error("linphone_event_notify(): cannot notify if subscription is not active."); return -1; @@ -225,7 +249,8 @@ int linphone_event_notify(LinphoneEvent *lev, const LinphoneContent *body){ ms_error("linphone_event_notify(): cannot notify if not an incoming subscription."); return -1; } - return sal_notify(lev->op,sal_body_from_content(&salbody,body)); + body_handler = sal_body_handler_from_content(body); + return sal_notify(lev->op, body_handler); } LinphoneEvent *linphone_core_create_publish(LinphoneCore *lc, const LinphoneAddress *resource, const char *event, int expires){ @@ -236,7 +261,7 @@ LinphoneEvent *linphone_core_create_publish(LinphoneCore *lc, const LinphoneAddr } static int _linphone_event_send_publish(LinphoneEvent *lev, const LinphoneContent *body, bool_t notify_err){ - SalBody salbody; + SalBodyHandler *body_handler; int err; if (lev->dir!=LinphoneSubscriptionInvalidDir){ @@ -245,9 +270,11 @@ static int _linphone_event_send_publish(LinphoneEvent *lev, const LinphoneConten } if (lev->send_custom_headers){ sal_op_set_sent_custom_header(lev->op,lev->send_custom_headers); + sal_custom_header_free(lev->send_custom_headers); lev->send_custom_headers=NULL; }else sal_op_set_sent_custom_header(lev->op,NULL); - err=sal_publish(lev->op,NULL,NULL,lev->name,lev->expires,sal_body_from_content(&salbody,body)); + body_handler = sal_body_handler_from_content(body); + err=sal_publish(lev->op,NULL,NULL,lev->name,lev->expires,body_handler); if (err==0){ linphone_event_set_publish_state(lev,LinphonePublishProgress); }else if (notify_err){ @@ -276,6 +303,16 @@ int linphone_event_update_publish(LinphoneEvent* lev, const LinphoneContent* bod return linphone_event_send_publish(lev,body); } +int linphone_event_refresh_publish(LinphoneEvent *lev) { + return sal_op_refresh(lev->op); +} +void linphone_event_pause_publish(LinphoneEvent *lev) { + if (lev->op) sal_op_stop_refreshing(lev->op); +} +void linphone_event_unpublish(LinphoneEvent *lev) { + lev->terminating = TRUE; /* needed to get clear event*/ + if (lev->op) sal_op_unpublish(lev->op); +} void linphone_event_set_user_data(LinphoneEvent *ev, void *up){ ev->userdata=up; } @@ -305,7 +342,7 @@ void linphone_event_terminate(LinphoneEvent *lev){ if (lev->publish_state!=LinphonePublishNone){ if (lev->publish_state==LinphonePublishOk && lev->expires!=-1){ sal_publish(lev->op,NULL,NULL,NULL,0,NULL); - }else sal_op_stop_refreshing(lev->op); + }else sal_op_unpublish(lev->op); linphone_event_set_publish_state(lev,LinphonePublishCleared); return; } @@ -318,20 +355,18 @@ void linphone_event_terminate(LinphoneEvent *lev){ LinphoneEvent *linphone_event_ref(LinphoneEvent *lev){ - lev->refcnt++; + belle_sip_object_ref(lev); return lev; } static void linphone_event_destroy(LinphoneEvent *lev){ - if (lev->op) - sal_op_release(lev->op); + if (lev->op) sal_op_release(lev->op); + if (lev->send_custom_headers) sal_custom_header_free(lev->send_custom_headers); ms_free(lev->name); - ms_free(lev); } void linphone_event_unref(LinphoneEvent *lev){ - lev->refcnt--; - if (lev->refcnt==0) linphone_event_destroy(lev); + belle_sip_object_unref(lev); } LinphoneSubscriptionDir linphone_event_get_subscription_dir(LinphoneEvent *lev){ @@ -366,3 +401,22 @@ LinphoneCore *linphone_event_get_core(const LinphoneEvent *lev){ return lev->lc; } +static belle_sip_error_code _linphone_event_marshall(belle_sip_object_t *obj, char* buff, size_t buff_size, size_t *offset) { + LinphoneEvent *ev = (LinphoneEvent*)obj; + belle_sip_error_code err = BELLE_SIP_OK; + + err = belle_sip_snprintf(buff, buff_size, offset, "%s of %s", ev->dir == LinphoneSubscriptionIncoming ? + "Incoming Subscribe" : (ev->dir == LinphoneSubscriptionOutgoing ? "Outgoing subscribe" : "Publish"), ev->name); + + return err; +} + +BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(LinphoneEvent); + +BELLE_SIP_INSTANCIATE_VPTR(LinphoneEvent, belle_sip_object_t, + (belle_sip_object_destroy_t) linphone_event_destroy, + NULL, // clone + _linphone_event_marshall, + FALSE +); + diff --git a/coreapi/event.h b/coreapi/event.h index fe3f984de..d84d6d180 100644 --- a/coreapi/event.h +++ b/coreapi/event.h @@ -138,12 +138,19 @@ LINPHONE_PUBLIC LinphoneEvent *linphone_core_create_subscribe(LinphoneCore *lc, LINPHONE_PUBLIC int linphone_event_send_subscribe(LinphoneEvent *ev, const LinphoneContent *body); /** - * Update (refresh) an outgoing subscription. + * Update (refresh) an outgoing subscription, changing the body. * @param lev a LinphoneEvent * @param body an optional body to include in the subscription update, may be NULL. **/ LINPHONE_PUBLIC int linphone_event_update_subscribe(LinphoneEvent *lev, const LinphoneContent *body); +/** + * Refresh an outgoing subscription keeping the same body. + * @param lev LinphoneEvent object. + * @return 0 if successful, -1 otherwise. + */ +LINPHONE_PUBLIC int linphone_event_refresh_subscribe(LinphoneEvent *lev); + /** * Accept an incoming subcription. @@ -201,6 +208,21 @@ LINPHONE_PUBLIC int linphone_event_send_publish(LinphoneEvent *lev, const Linpho **/ LINPHONE_PUBLIC int linphone_event_update_publish(LinphoneEvent *lev, const LinphoneContent *body); +/** + * Refresh an outgoing publish keeping the same body. + * @param lev LinphoneEvent object. + * @return 0 if successful, -1 otherwise. + */ +LINPHONE_PUBLIC int linphone_event_refresh_publish(LinphoneEvent *lev); + +/** + * Prevent an event from refreshing its publish. + * This is useful to let registrations to expire naturally (or) when the application wants to keep control on when + * refreshes are sent. + * The refreshing operations can be resumed with linphone_proxy_config_refresh_register(). + * @param[in] cfg #LinphoneEvent object. + **/ +LINPHONE_PUBLIC void linphone_event_pause_publish(LinphoneEvent *lev); /** * Return reason code (in case of error state reached). @@ -256,10 +278,8 @@ LINPHONE_PUBLIC const char *linphone_event_get_custom_header(LinphoneEvent *ev, /** * Terminate an incoming or outgoing subscription that was previously acccepted, or a previous publication. - * This function does not unref the object. The core will unref() if it does not need this object anymore. - * - * For subscribed event, when the subscription is terminated normally or because of an error, the core will unref. - * For published events, no unref is performed. This is because it is allowed to re-publish an expired publish, as well as retry it in case of error. + * The LinphoneEvent shall not be used anymore after this operation, unless the application explicitely took a reference on the object with + * linphone_event_ref(). **/ LINPHONE_PUBLIC void linphone_event_terminate(LinphoneEvent *lev); diff --git a/coreapi/friend.c b/coreapi/friend.c index 487cfd2a4..41d191535 100644 --- a/coreapi/friend.c +++ b/coreapi/friend.c @@ -26,6 +26,21 @@ #include "private.h" #include "lpconfig.h" +#ifdef FRIENDS_SQL_STORAGE_ENABLED +#ifndef _WIN32 +#if !defined(ANDROID) && !defined(__QNXNTO__) +# include +# include +# include +#endif +#else +#include +#endif + +#define MAX_PATH_SIZE 1024 +#include "sqlite3.h" +#endif + const char *linphone_online_status_to_string(LinphoneOnlineStatus ss){ const char *str=NULL; switch(ss){ @@ -88,25 +103,6 @@ MSList *linphone_find_friend_by_address(MSList *fl, const LinphoneAddress *addr, return res; } -LinphoneFriend *linphone_find_friend_by_inc_subscribe(MSList *l, SalOp *op){ - MSList *elem; - for (elem=l;elem!=NULL;elem=elem->next){ - LinphoneFriend *lf=(LinphoneFriend*)elem->data; - if (ms_list_find(lf->insubs, op)) return lf; - } - return NULL; -} - -LinphoneFriend *linphone_find_friend_by_out_subscribe(MSList *l, SalOp *op){ - MSList *elem; - LinphoneFriend *lf; - for (elem=l;elem!=NULL;elem=elem->next){ - lf=(LinphoneFriend*)elem->data; - if (lf->outsub && (lf->outsub == op || sal_op_is_forked_of(lf->outsub, op))) return lf; - } - return NULL; -} - void __linphone_friend_do_subscribe(LinphoneFriend *fr){ LinphoneCore *lc=fr->lc; @@ -127,11 +123,13 @@ void __linphone_friend_do_subscribe(LinphoneFriend *fr){ fr->subscribe_active=TRUE; } -LinphoneFriend * linphone_friend_new(){ - LinphoneFriend *obj=belle_sip_object_new(LinphoneFriend); - obj->pol=LinphoneSPAccept; - obj->presence=NULL; - obj->subscribe=TRUE; +LinphoneFriend * linphone_friend_new(void){ + LinphoneFriend *obj = belle_sip_object_new(LinphoneFriend); + obj->pol = LinphoneSPAccept; + obj->presence = NULL; + obj->subscribe = TRUE; + obj->vcard = NULL; + obj->storage_id = 0; return obj; } @@ -145,7 +143,7 @@ LinphoneFriend *linphone_friend_new_with_address(const char *addr){ } fr=linphone_friend_new(); linphone_friend_set_address(fr,linphone_address); - linphone_address_destroy(linphone_address); + linphone_address_unref(linphone_address); return fr; } @@ -157,8 +155,8 @@ void* linphone_friend_get_user_data(const LinphoneFriend *lf){ return lf->user_data; } -bool_t linphone_friend_in_list(const LinphoneFriend *lf){ - return lf->lc!=NULL; +bool_t linphone_friend_in_list(const LinphoneFriend *lf) { + return lf->friend_list != NULL; } void linphone_core_interpret_friend_uri(LinphoneCore *lc, const char *uri, char **result){ @@ -182,7 +180,7 @@ void linphone_core_interpret_friend_uri(LinphoneCore *lc, const char *uri, char linphone_address_set_display_name(id,NULL); linphone_address_set_username(id,uri); *result=linphone_address_as_string(id); - linphone_address_destroy(id); + linphone_address_unref(id); } } if (*result){ @@ -193,25 +191,51 @@ void linphone_core_interpret_friend_uri(LinphoneCore *lc, const char *uri, char } }else { *result=linphone_address_as_string(fr); - linphone_address_destroy(fr); + linphone_address_unref(fr); } } int linphone_friend_set_address(LinphoneFriend *lf, const LinphoneAddress *addr){ - LinphoneAddress *fr=linphone_address_clone(addr); + LinphoneAddress *fr = linphone_address_clone(addr); + LinphoneVcard *vcard = NULL; + linphone_address_clean(fr); - if (lf->uri!=NULL) linphone_address_destroy(lf->uri); - lf->uri=fr; + if (lf->uri != NULL) linphone_address_unref(lf->uri); + lf->uri = fr; + + vcard = linphone_friend_get_vcard(lf); + if (vcard) { + linphone_vcard_edit_main_sip_address(vcard, linphone_address_as_string_uri_only(fr)); + } + return 0; } int linphone_friend_set_name(LinphoneFriend *lf, const char *name){ - LinphoneAddress *fr=lf->uri; - if (fr==NULL){ - ms_error("linphone_friend_set_sip_addr() must be called before linphone_friend_set_name()."); - return -1; + LinphoneAddress *fr = lf->uri; + LinphoneVcard *vcard = NULL; + bool_t vcard_created = FALSE; + + vcard = linphone_friend_get_vcard(lf); + if (!vcard) { + linphone_friend_create_vcard(lf, name); + vcard = linphone_friend_get_vcard(lf); + vcard_created = TRUE; } - linphone_address_set_display_name(fr,name); + if (vcard) { + linphone_vcard_set_full_name(vcard, name); + if (fr && vcard_created) { // SIP address wasn't set yet, let's do it + linphone_vcard_edit_main_sip_address(vcard, linphone_address_as_string_uri_only(fr)); + } + } + + if (!fr && !vcard) { + ms_warning("linphone_friend_set_address() must be called before linphone_friend_set_name() to be able to set display name."); + return -1; + } else if (fr) { + linphone_address_set_display_name(fr, name); + } + return 0; } @@ -220,8 +244,7 @@ int linphone_friend_enable_subscribes(LinphoneFriend *fr, bool_t val){ return 0; } -int linphone_friend_set_inc_subscribe_policy(LinphoneFriend *fr, LinphoneSubscribePolicy pol) -{ +int linphone_friend_set_inc_subscribe_policy(LinphoneFriend *fr, LinphoneSubscribePolicy pol) { fr->pol=pol; return 0; } @@ -249,7 +272,7 @@ void linphone_friend_remove_incoming_subscription(LinphoneFriend *lf, SalOp *op) sal_op_release(op); lf->insubs = ms_list_remove(lf->insubs, op); } - + } static void linphone_friend_unsubscribe(LinphoneFriend *lf){ @@ -259,19 +282,22 @@ static void linphone_friend_unsubscribe(LinphoneFriend *lf){ } } -static void linphone_friend_invalidate_subscription(LinphoneFriend *lf){ +void linphone_friend_invalidate_subscription(LinphoneFriend *lf){ + LinphoneCore *lc=lf->lc; + if (lf->outsub!=NULL) { - LinphoneCore *lc=lf->lc; sal_op_release(lf->outsub); lf->outsub=NULL; lf->subscribe_active=FALSE; - /*notify application that we no longer know the presence activity */ - if (lf->presence != NULL) { - linphone_presence_model_unref(lf->presence); - } + } + + /* Notify application that we no longer know the presence activity */ + if (lf->presence != NULL) { + linphone_presence_model_unref(lf->presence); lf->presence = linphone_presence_model_new_with_activity(LinphonePresenceActivityOffline,"unknown activity"); linphone_core_notify_notify_presence_received(lc,lf); } + lf->initial_subscribes_sent=FALSE; } @@ -281,15 +307,20 @@ void linphone_friend_close_subscriptions(LinphoneFriend *lf){ lf->insubs = ms_list_free_with_data(lf->insubs, (MSIterateFunc)sal_op_release); } -static void _linphone_friend_destroy(LinphoneFriend *lf){ +static void _linphone_friend_release_ops(LinphoneFriend *lf){ lf->insubs = ms_list_free_with_data(lf->insubs, (MSIterateFunc) sal_op_release); if (lf->outsub){ sal_op_release(lf->outsub); lf->outsub=NULL; } +} + +static void _linphone_friend_destroy(LinphoneFriend *lf){ + _linphone_friend_release_ops(lf); if (lf->presence != NULL) linphone_presence_model_unref(lf->presence); - if (lf->uri!=NULL) linphone_address_destroy(lf->uri); + if (lf->uri!=NULL) linphone_address_unref(lf->uri); if (lf->info!=NULL) buddy_info_free(lf->info); + if (lf->vcard != NULL) linphone_vcard_free(lf->vcard); } static belle_sip_error_code _linphone_friend_marshall(belle_sip_object_t *obj, char* buff, size_t buff_size, size_t *offset) { @@ -308,9 +339,13 @@ const LinphoneAddress *linphone_friend_get_address(const LinphoneFriend *lf){ } const char * linphone_friend_get_name(const LinphoneFriend *lf) { - LinphoneAddress *fr = lf->uri; - if (fr == NULL) return NULL; - return linphone_address_get_display_name(fr); + if (lf && lf->vcard) { + return linphone_vcard_get_full_name(lf->vcard); + } else if (lf && lf->uri) { + LinphoneAddress *fr = lf->uri; + return linphone_address_get_display_name(fr); + } + return NULL; } bool_t linphone_friend_get_send_subscribe(const LinphoneFriend *lf){ @@ -405,6 +440,17 @@ const LinphonePresenceModel * linphone_friend_get_presence_model(LinphoneFriend return lf->presence; } +void linphone_friend_set_presence_model(LinphoneFriend *lf, LinphonePresenceModel *presence) { + if (lf->presence != NULL) { + linphone_presence_model_unref(lf->presence); + } + lf->presence = presence; +} + +bool_t linphone_friend_is_presence_received(const LinphoneFriend *lf) { + return lf->presence_received; +} + BuddyInfo * linphone_friend_get_info(const LinphoneFriend *lf){ return lf->info; } @@ -440,97 +486,113 @@ void linphone_friend_update_subscribes(LinphoneFriend *fr, LinphoneProxyConfig * } } -void linphone_friend_apply(LinphoneFriend *fr, LinphoneCore *lc){ +void linphone_friend_save(LinphoneFriend *fr, LinphoneCore *lc) { +#ifdef FRIENDS_SQL_STORAGE_ENABLED + linphone_core_store_friend_in_db(lc, fr); +#else + linphone_core_write_friends_config(lc); +#endif +} + +void linphone_friend_apply(LinphoneFriend *fr, LinphoneCore *lc) { LinphonePresenceModel *model; - if (fr->uri==NULL) { + if (!fr->uri) { ms_warning("No sip url defined."); return; } - linphone_core_write_friends_config(lc); - - if (fr->inc_subscribe_pending){ - switch(fr->pol){ + if (fr->inc_subscribe_pending) { + switch(fr->pol) { case LinphoneSPWait: model = linphone_presence_model_new_with_activity(LinphonePresenceActivityOther, "Waiting for user acceptance"); - linphone_friend_notify(fr,model); + linphone_friend_notify(fr, model); linphone_presence_model_unref(model); break; case LinphoneSPAccept: - if (fr->lc!=NULL) - linphone_friend_notify(fr,fr->lc->presence_model); + if (fr->lc) + linphone_friend_notify(fr, fr->lc->presence_model); break; case LinphoneSPDeny: - linphone_friend_notify(fr,NULL); + linphone_friend_notify(fr, NULL); break; } - fr->inc_subscribe_pending=FALSE; + fr->inc_subscribe_pending = FALSE; } - if (fr->lc) - linphone_friend_update_subscribes(fr,NULL,linphone_core_should_subscribe_friends_only_when_registered(fr->lc)); - ms_message("linphone_friend_apply() done."); + if (fr->lc) { + linphone_friend_list_update_subscriptions(fr->friend_list, NULL, linphone_core_should_subscribe_friends_only_when_registered(fr->lc)); + } + ms_debug("linphone_friend_apply() done."); lc->bl_refresh=TRUE; fr->commit=FALSE; } -void linphone_friend_edit(LinphoneFriend *fr){ +void linphone_friend_edit(LinphoneFriend *fr) { + if (fr && fr->vcard) { + linphone_vcard_compute_md5_hash(fr->vcard); + } } -void linphone_friend_done(LinphoneFriend *fr){ - ms_return_if_fail(fr!=NULL); - if (fr->lc==NULL) return; - linphone_friend_apply(fr,fr->lc); +void linphone_friend_done(LinphoneFriend *fr) { + ms_return_if_fail(fr); + if (!fr->lc || !fr->friend_list) return; + linphone_friend_apply(fr, fr->lc); + linphone_friend_save(fr, fr->lc); + + if (fr && fr->vcard) { + if (linphone_vcard_compare_md5_hash(fr->vcard) != 0) { + ms_debug("vCard's md5 has changed, mark friend as dirty"); + fr->friend_list->dirty_friends_to_update = ms_list_append(fr->friend_list->dirty_friends_to_update, linphone_friend_ref(fr)); + } + } } +#if __clang__ || ((__GNUC__ == 4 && __GNUC_MINOR__ >= 6) || __GNUC__ > 4) +#pragma GCC diagnostic push +#endif +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" LinphoneFriend * linphone_core_create_friend(LinphoneCore *lc) { - return linphone_friend_new(); + LinphoneFriend * lf = linphone_friend_new(); + lf->lc = lc; + return lf; } LinphoneFriend * linphone_core_create_friend_with_address(LinphoneCore *lc, const char *address) { - return linphone_friend_new_with_address(address); + LinphoneFriend * lf = linphone_friend_new_with_address(address); + if (lf) + lf->lc = lc; + return lf; } +#if __clang__ || ((__GNUC__ == 4 && __GNUC_MINOR__ >= 6) || __GNUC__ > 4) +#pragma GCC diagnostic pop +#endif -void linphone_core_add_friend(LinphoneCore *lc, LinphoneFriend *lf) -{ - ms_return_if_fail(lf->lc==NULL); - ms_return_if_fail(lf->uri!=NULL); - if (ms_list_find(lc->friends,lf)!=NULL){ - char *tmp=NULL; - const LinphoneAddress *addr=linphone_friend_get_address(lf); - if (addr) tmp=linphone_address_as_string(addr); - ms_warning("Friend %s already in list, ignored.", tmp ? tmp : "unknown"); - if (tmp) ms_free(tmp); - return ; - } - lc->friends=ms_list_append(lc->friends,linphone_friend_ref(lf)); - if (ms_list_find(lc->subscribers, lf)){ +void linphone_core_add_friend(LinphoneCore *lc, LinphoneFriend *lf) { + if (linphone_friend_list_add_friend(linphone_core_get_default_friend_list(lc), lf) != LinphoneFriendListOK) return; + if (ms_list_find(lc->subscribers, lf)) { /*if this friend was in the pending subscriber list, now remove it from this list*/ lc->subscribers = ms_list_remove(lc->subscribers, lf); linphone_friend_unref(lf); } - lf->lc=lc; - if ( linphone_core_ready(lc)) linphone_friend_apply(lf,lc); - else lf->commit=TRUE; - return ; + if (linphone_core_ready(lc)) linphone_friend_apply(lf, lc); + else lf->commit = TRUE; + linphone_friend_save(lf, lc); } -void linphone_core_remove_friend(LinphoneCore *lc, LinphoneFriend* fl){ - MSList *el=ms_list_find(lc->friends,fl); - if (el!=NULL){ - linphone_friend_unref((LinphoneFriend*)el->data); - lc->friends=ms_list_remove_link(lc->friends,el); - linphone_core_write_friends_config(lc); - }else{ - ms_error("linphone_core_remove_friend(): friend [%p] is not part of core's list.",fl); +void linphone_core_remove_friend(LinphoneCore *lc, LinphoneFriend *lf) { + if (lf && lf->friend_list) { + if (linphone_friend_list_remove_friend(lf->friend_list, lf) == LinphoneFriendListNonExistentFriend) { + ms_error("linphone_core_remove_friend(): friend [%p] is not part of core's list.", lf); + } } } -void linphone_core_update_friends_subscriptions(LinphoneCore *lc, LinphoneProxyConfig *cfg, bool_t only_when_registered){ - const MSList *elem; - for(elem=lc->friends;elem!=NULL;elem=elem->next){ - LinphoneFriend *f=(LinphoneFriend*)elem->data; - linphone_friend_update_subscribes(f,cfg,only_when_registered); +void linphone_core_update_friends_subscriptions(LinphoneCore *lc, LinphoneProxyConfig *cfg, bool_t only_when_registered) { + MSList *lists = lc->friends_lists; + while (lists) { + LinphoneFriendList *list = (LinphoneFriendList *)lists->data; + linphone_friend_list_update_subscriptions(list, cfg, only_when_registered); + lists = ms_list_next(lists); } } @@ -538,65 +600,100 @@ bool_t linphone_core_should_subscribe_friends_only_when_registered(const Linphon return lp_config_get_int(lc->config,"sip","subscribe_presence_only_when_registered",1); } -void linphone_core_send_initial_subscribes(LinphoneCore *lc){ +void linphone_core_send_initial_subscribes(LinphoneCore *lc) { + MSList *lists = lc->friends_lists; + bool_t proxy_config_for_rls_presence_uri_domain = FALSE; + LinphoneAddress *rls_address = NULL; + const MSList *elem; + if (lc->initial_subscribes_sent) return; lc->initial_subscribes_sent=TRUE; - linphone_core_update_friends_subscriptions(lc,NULL,linphone_core_should_subscribe_friends_only_when_registered(lc)); + while (lists) { + LinphoneFriendList *list = (LinphoneFriendList *)lists->data; + if (list->rls_uri != NULL) { + rls_address = linphone_core_create_address(lc, list->rls_uri); + if (rls_address != NULL) { + const char *rls_domain = linphone_address_get_domain(rls_address); + if (rls_domain != NULL) { + for (elem = linphone_core_get_proxy_config_list(lc); elem != NULL; elem = elem->next) { + LinphoneProxyConfig *cfg = (LinphoneProxyConfig *)elem->data; + const char *proxy_domain = linphone_proxy_config_get_domain(cfg); + if (strcmp(rls_domain, proxy_domain) == 0) { + proxy_config_for_rls_presence_uri_domain = TRUE; + break; + } + } + } + linphone_address_unref(rls_address); + } + } + if (proxy_config_for_rls_presence_uri_domain == TRUE) { + ms_message("Presence list activated so do not send initial subscribes it will be done when registered"); + } else { + linphone_core_update_friends_subscriptions(lc,NULL,linphone_core_should_subscribe_friends_only_when_registered(lc)); + } + lists = ms_list_next(lists); + } } -void linphone_core_invalidate_friend_subscriptions(LinphoneCore *lc){ - const MSList *elem; - for(elem=lc->friends;elem!=NULL;elem=elem->next){ - LinphoneFriend *f=(LinphoneFriend*)elem->data; - linphone_friend_invalidate_subscription(f); - } +void linphone_core_invalidate_friend_subscriptions(LinphoneCore *lc) { + MSList *lists = lc->friends_lists; + while (lists) { + LinphoneFriendList *list = (LinphoneFriendList *)lists->data; + linphone_friend_list_invalidate_subscriptions(list); + lists = ms_list_next(lists); + } lc->initial_subscribes_sent=FALSE; } void linphone_friend_set_ref_key(LinphoneFriend *lf, const char *key){ - if (lf->refkey!=NULL){ + if (lf->refkey != NULL) { ms_free(lf->refkey); - lf->refkey=NULL; + lf->refkey = NULL; + } + if (key) { + lf->refkey = ms_strdup(key); + } + if (lf->lc) { + linphone_friend_save(lf, lf->lc); } - if (key) - lf->refkey=ms_strdup(key); - if (lf->lc) - linphone_core_write_friends_config(lf->lc); } const char *linphone_friend_get_ref_key(const LinphoneFriend *lf){ return lf->refkey; } -LinphoneFriend *linphone_core_find_friend(const LinphoneCore *lc, const LinphoneAddress *addr){ - LinphoneFriend *lf=NULL; - MSList *elem; - for(elem=lc->friends;elem!=NULL;elem=ms_list_next(elem)){ - lf=(LinphoneFriend*)elem->data; - if (linphone_address_weak_equal(lf->uri,addr)) - break; - lf=NULL; +LinphoneFriend *linphone_core_find_friend(const LinphoneCore *lc, const LinphoneAddress *addr) { + MSList *lists = lc->friends_lists; + LinphoneFriend *lf = NULL; + while (lists && !lf) { + LinphoneFriendList *list = (LinphoneFriendList *)lists->data; + lf = linphone_friend_list_find_friend_by_address(list, addr); + lists = ms_list_next(lists); } return lf; } -LinphoneFriend *linphone_core_get_friend_by_address(const LinphoneCore *lc, const char *uri){ - LinphoneAddress *puri=linphone_address_new(uri); - LinphoneFriend *lf=puri ? linphone_core_find_friend(lc,puri) : NULL; - if (puri) linphone_address_unref(puri); +LinphoneFriend *linphone_core_get_friend_by_address(const LinphoneCore *lc, const char *uri) { + MSList *lists = lc->friends_lists; + LinphoneFriend *lf = NULL; + while (lists && !lf) { + LinphoneFriendList *list = (LinphoneFriendList *)lists->data; + lf = linphone_friend_list_find_friend_by_uri(list, uri); + lists = ms_list_next(lists); + } return lf; } -LinphoneFriend *linphone_core_get_friend_by_ref_key(const LinphoneCore *lc, const char *key){ - const MSList *elem; - if (key==NULL) return NULL; - for(elem=linphone_core_get_friend_list(lc);elem!=NULL;elem=elem->next){ - LinphoneFriend *lf=(LinphoneFriend*)elem->data; - if (lf->refkey!=NULL && strcmp(lf->refkey,key)==0){ - return lf; - } +LinphoneFriend *linphone_core_get_friend_by_ref_key(const LinphoneCore *lc, const char *key) { + MSList *lists = lc->friends_lists; + LinphoneFriend *lf = NULL; + while (lists && !lf) { + LinphoneFriendList *list = (LinphoneFriendList *)lists->data; + lf = linphone_friend_list_find_friend_by_ref_key(list, key); + lists = ms_list_next(lists); } - return NULL; + return lf; } #define key_compare(s1,s2) strcmp(s1,s2) @@ -637,7 +734,7 @@ LinphoneFriend * linphone_friend_new_from_config_file(LinphoneCore *lc, int inde if (tmp==NULL) { return NULL; } - lf=linphone_friend_new_with_address(tmp); + lf=linphone_core_create_friend_with_address(lc, tmp); if (lf==NULL) { return NULL; } @@ -648,6 +745,8 @@ LinphoneFriend * linphone_friend_new_from_config_file(LinphoneCore *lc, int inde } a=lp_config_get_int(config,item,"subscribe",0); linphone_friend_send_subscribe(lf,a); + a = lp_config_get_int(config, item, "presence_received", 0); + lf->presence_received = (bool_t)a; linphone_friend_set_ref_key(lf,lp_config_get_string(config,item,"refkey",NULL)); return lf; @@ -690,6 +789,7 @@ void linphone_friend_write_to_config_file(LpConfig *config, LinphoneFriend *lf, } lp_config_set_string(config,key,"pol",__policy_enum_to_str(lf->pol)); lp_config_set_int(config,key,"subscribe",lf->subscribe); + lp_config_set_int(config, key, "presence_received", lf->presence_received); refkey=linphone_friend_get_ref_key(lf); if (refkey){ @@ -697,15 +797,22 @@ void linphone_friend_write_to_config_file(LpConfig *config, LinphoneFriend *lf, } } -void linphone_core_write_friends_config(LinphoneCore* lc) -{ +void linphone_core_write_friends_config(LinphoneCore* lc) { MSList *elem; int i; + int store_friends; +#ifdef FRIENDS_SQL_STORAGE_ENABLED + return; +#endif if (! linphone_core_ready(lc)) return; /*dont write config when reading it !*/ - for (elem=lc->friends,i=0; elem!=NULL; elem=ms_list_next(elem),i++){ - linphone_friend_write_to_config_file(lc->config,(LinphoneFriend*)elem->data,i); + store_friends = lp_config_get_int(lc->config, "misc", "store_friends", 1); + if (store_friends) { + + for (elem=linphone_core_get_default_friend_list(lc)->friends,i=0; elem!=NULL; elem=ms_list_next(elem),i++){ + linphone_friend_write_to_config_file(lc->config,(LinphoneFriend*)elem->data,i); + } + linphone_friend_write_to_config_file(lc->config,NULL,i); /* set the end */ } - linphone_friend_write_to_config_file(lc->config,NULL,i); /* set the end */ } LinphoneCore *linphone_friend_get_core(const LinphoneFriend *fr){ @@ -726,6 +833,104 @@ void linphone_friend_destroy(LinphoneFriend *lf) { linphone_friend_unref(lf); } +LinphoneVcard* linphone_friend_get_vcard(LinphoneFriend *fr) { + if (fr) { + return fr->vcard; + } + return NULL; +} + +void linphone_friend_set_vcard(LinphoneFriend *fr, LinphoneVcard *vcard) { + if (!fr) { + return; + } + + if (fr->vcard) { + linphone_vcard_free(fr->vcard); + } + fr->vcard = vcard; + linphone_friend_save(fr, fr->lc); +} + +bool_t linphone_friend_create_vcard(LinphoneFriend *fr, const char *name) { + LinphoneVcard *vcard = NULL; + const char *fullName = NULL; + LinphoneAddress *addr = NULL; + + if (!fr || fr->vcard) { + ms_error("Friend is either null or already has a vcard"); + return FALSE; + } + + addr = fr->uri; + if (!addr && !name) { + ms_error("friend doesn't have an URI and name parameter is null"); + return FALSE; + } + + if (name) { + fullName = name; + } else { + const char *displayName = linphone_address_get_display_name(addr); + if (!displayName) { + fullName = linphone_address_get_username(addr); + } else { + fullName = displayName; + } + } + + if (!fullName) { + ms_error("Couldn't determine the name to use for the vCard"); + return FALSE; + } + + vcard = linphone_vcard_new(); + linphone_vcard_set_full_name(vcard, fullName); + linphone_friend_set_vcard(fr, vcard); + return TRUE; +} + +LinphoneFriend *linphone_friend_new_from_vcard(LinphoneVcard *vcard) { + LinphoneAddress* linphone_address = NULL; + LinphoneFriend *fr; + const char *name = NULL; + MSList *sipAddresses = NULL; + + if (vcard == NULL) { + ms_error("Cannot create friend from null vcard"); + return NULL; + } + name = linphone_vcard_get_full_name(vcard); + sipAddresses = linphone_vcard_get_sip_addresses(vcard); + + fr = linphone_friend_new(); + // Currently presence takes too much time when dealing with hundreds of friends, so I disabled it for now + fr->pol = LinphoneSPDeny; + fr->subscribe = FALSE; + + if (sipAddresses) { + const char *sipAddress = (const char *)sipAddresses->data; + linphone_address = linphone_address_new(sipAddress); + if (linphone_address) { + linphone_friend_set_address(fr, linphone_address); + linphone_address_unref(linphone_address); + } + } + if (name) { + linphone_friend_set_name(fr, name); + } + fr->vcard = vcard; + + return fr; +} + +/*drops all references to the core and unref*/ +void _linphone_friend_release(LinphoneFriend *lf){ + lf->lc = NULL; + _linphone_friend_release_ops(lf); + linphone_friend_unref(lf); +} + BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(LinphoneFriend); BELLE_SIP_INSTANCIATE_VPTR(LinphoneFriend, belle_sip_object_t, @@ -733,4 +938,465 @@ BELLE_SIP_INSTANCIATE_VPTR(LinphoneFriend, belle_sip_object_t, NULL, // clone _linphone_friend_marshall, FALSE -); \ No newline at end of file +); + +/******************************************************************************* + * SQL storage related functions * + ******************************************************************************/ + +#ifdef FRIENDS_SQL_STORAGE_ENABLED + +static void linphone_create_table(sqlite3* db) { + char* errmsg = NULL; + int ret; + ret = sqlite3_exec(db,"CREATE TABLE IF NOT EXISTS friends (" + "id INTEGER PRIMARY KEY AUTOINCREMENT," + "friend_list_id INTEGER," + "sip_uri TEXT NOT NULL," + "subscribe_policy INTEGER," + "send_subscribe INTEGER," + "ref_key TEXT," + "vCard TEXT," + "vCard_etag TEXT," + "vCard_url TEXT," + "presence_received INTEGER" + ");", + 0, 0, &errmsg); + if (ret != SQLITE_OK) { + ms_error("Error in creation: %s.\n", errmsg); + sqlite3_free(errmsg); + } + + ret = sqlite3_exec(db,"CREATE TABLE IF NOT EXISTS friends_lists (" + "id INTEGER PRIMARY KEY AUTOINCREMENT," + "display_name TEXT," + "rls_uri TEXT," + "uri TEXT," + "revision INTEGER" + ");", + 0, 0, &errmsg); + if (ret != SQLITE_OK) { + ms_error("Error in creation: %s.\n", errmsg); + sqlite3_free(errmsg); + } +} + +static void linphone_update_table(sqlite3* db) { + +} + +void linphone_core_friends_storage_init(LinphoneCore *lc) { + int ret; + const char *errmsg; + sqlite3 *db; + const MSList *friends_lists = NULL; + + linphone_core_friends_storage_close(lc); + + ret = _linphone_sqlite3_open(lc->friends_db_file, &db); + if (ret != SQLITE_OK) { + errmsg = sqlite3_errmsg(db); + ms_error("Error in the opening: %s.\n", errmsg); + sqlite3_close(db); + return; + } + + linphone_create_table(db); + linphone_update_table(db); + lc->friends_db = db; + + friends_lists = linphone_core_fetch_friends_lists_from_db(lc); + if (friends_lists) { + ms_warning("Replacing current default friend list by the one(s) from the database"); + lc->friends_lists = ms_list_free_with_data(lc->friends_lists, (void (*)(void*))linphone_friend_list_unref); + lc->friends_lists = NULL; + + while (friends_lists) { + LinphoneFriendList *list = (LinphoneFriendList *)friends_lists->data; + linphone_core_add_friend_list(lc, list); + friends_lists = ms_list_next(friends_lists); + } + } +} + +void linphone_core_friends_storage_close(LinphoneCore *lc) { + if (lc->friends_db) { + sqlite3_close(lc->friends_db); + lc->friends_db = NULL; + } +} + +/* DB layout: + * | 0 | storage_id + * | 1 | display_name + * | 2 | rls_uri + * | 3 | uri + * | 4 | revision + */ +static int create_friend_list(void *data, int argc, char **argv, char **colName) { + MSList **list = (MSList **)data; + unsigned int storage_id = atoi(argv[0]); + LinphoneFriendList *lfl = linphone_core_create_friend_list(NULL); + + lfl->storage_id = storage_id; + linphone_friend_list_set_display_name(lfl, argv[1]); + linphone_friend_list_set_rls_uri(lfl, argv[2]); + linphone_friend_list_set_uri(lfl, argv[3]); + lfl->revision = atoi(argv[4]); + + *list = ms_list_append(*list, linphone_friend_list_ref(lfl)); + linphone_friend_list_unref(lfl); + return 0; +} + +/* DB layout: + * | 0 | storage_id + * | 1 | friend_list_id + * | 2 | sip_uri + * | 3 | subscribe_policy + * | 4 | send_subscribe + * | 5 | ref_key + * | 6 | vCard + * | 7 | vCard eTag + * | 8 | vCard URL + * | 9 | presence_received + */ +static int create_friend(void *data, int argc, char **argv, char **colName) { + MSList **list = (MSList **)data; + LinphoneFriend *lf = NULL; + LinphoneVcard *vcard = NULL; + unsigned int storage_id = atoi(argv[0]); + + vcard = linphone_vcard_new_from_vcard4_buffer(argv[6]); + if (vcard) { + linphone_vcard_set_etag(vcard, argv[7]); + linphone_vcard_set_url(vcard, argv[8]); + lf = linphone_friend_new_from_vcard(vcard); + } + if (!lf) { + LinphoneAddress *addr = linphone_address_new(argv[2]); + lf = linphone_friend_new(); + linphone_friend_set_address(lf, addr); + linphone_address_unref(addr); + } + linphone_friend_set_inc_subscribe_policy(lf, atoi(argv[3])); + linphone_friend_send_subscribe(lf, atoi(argv[4])); + linphone_friend_set_ref_key(lf, ms_strdup(argv[5])); + lf->presence_received = atoi(argv[9]); + lf->storage_id = storage_id; + + *list = ms_list_append(*list, linphone_friend_ref(lf)); + linphone_friend_unref(lf); + return 0; +} + +static int linphone_sql_request_friend(sqlite3* db, const char *stmt, MSList **list) { + char* errmsg = NULL; + int ret; + ret = sqlite3_exec(db, stmt, create_friend, list, &errmsg); + if (ret != SQLITE_OK) { + ms_error("linphone_sql_request: statement %s -> error sqlite3_exec(): %s.", stmt, errmsg); + sqlite3_free(errmsg); + } + return ret; +} + +static int linphone_sql_request_friends_list(sqlite3* db, const char *stmt, MSList **list) { + char* errmsg = NULL; + int ret; + ret = sqlite3_exec(db, stmt, create_friend_list, list, &errmsg); + if (ret != SQLITE_OK) { + ms_error("linphone_sql_request: statement %s -> error sqlite3_exec(): %s.", stmt, errmsg); + sqlite3_free(errmsg); + } + return ret; +} + +static int linphone_sql_request_generic(sqlite3* db, const char *stmt) { + char* errmsg = NULL; + int ret; + ret = sqlite3_exec(db, stmt, NULL, NULL, &errmsg); + if (ret != SQLITE_OK) { + ms_error("linphone_sql_request: statement %s -> error sqlite3_exec(): %s.", stmt, errmsg); + sqlite3_free(errmsg); + } + return ret; +} + +void linphone_core_store_friend_in_db(LinphoneCore *lc, LinphoneFriend *lf) { + if (lc && lc->friends_db) { + char *buf; + int store_friends = lp_config_get_int(lc->config, "misc", "store_friends", 1); + LinphoneVcard *vcard = linphone_friend_get_vcard(lf); + + if (!store_friends) { + return; + } + + if (!lf || !lf->friend_list) { + ms_warning("Either the friend or the friend list is null, skipping..."); + return; + } + + if (lf->friend_list->storage_id == 0) { + ms_warning("Trying to add a friend in db, but friend list isn't, let's do that first"); + linphone_core_store_friends_list_in_db(lc, lf->friend_list); + } + + if (lf->storage_id > 0) { + buf = sqlite3_mprintf("UPDATE friends SET friend_list_id=%i,sip_uri=%Q,subscribe_policy=%i,send_subscribe=%i,ref_key=%Q,vCard=%Q,vCard_etag=%Q,vCard_url=%Q,presence_received=%i WHERE (id = %i);", + lf->friend_list->storage_id, + linphone_address_as_string(linphone_friend_get_address(lf)), + lf->pol, + lf->subscribe, + lf->refkey, + linphone_vcard_as_vcard4_string(vcard), + linphone_vcard_get_etag(vcard), + linphone_vcard_get_url(vcard), + lf->presence_received, + lf->storage_id + ); + } else { + buf = sqlite3_mprintf("INSERT INTO friends VALUES(NULL,%i,%Q,%i,%i,%Q,%Q,%Q,%Q,%i);", + lf->friend_list->storage_id, + linphone_address_as_string(linphone_friend_get_address(lf)), + lf->pol, + lf->subscribe, + lf->refkey, + linphone_vcard_as_vcard4_string(vcard), + linphone_vcard_get_etag(vcard), + linphone_vcard_get_url(vcard), + lf->presence_received + ); + } + linphone_sql_request_generic(lc->friends_db, buf); + sqlite3_free(buf); + + if (lf->storage_id == 0) { + lf->storage_id = sqlite3_last_insert_rowid(lc->friends_db); + } + } +} + +void linphone_core_store_friends_list_in_db(LinphoneCore *lc, LinphoneFriendList *list) { + if (lc && lc->friends_db) { + char *buf; + int store_friends = lp_config_get_int(lc->config, "misc", "store_friends", 1); + + if (!store_friends) { + return; + } + + if (list->storage_id > 0) { + buf = sqlite3_mprintf("UPDATE friends_lists SET display_name=%Q,rls_uri=%Q,uri=%Q,revision=%i WHERE (id = %i);", + list->display_name, + list->rls_uri, + list->uri, + list->revision, + list->storage_id + ); + } else { + buf = sqlite3_mprintf("INSERT INTO friends_lists VALUES(NULL,%Q,%Q,%Q,%i);", + list->display_name, + list->rls_uri, + list->uri, + list->revision + ); + } + linphone_sql_request_generic(lc->friends_db, buf); + sqlite3_free(buf); + + if (list->storage_id == 0) { + list->storage_id = sqlite3_last_insert_rowid(lc->friends_db); + } + } +} + +void linphone_core_remove_friend_from_db(LinphoneCore *lc, LinphoneFriend *lf) { + if (lc && lc->friends_db) { + char *buf; + if (lf->storage_id == 0) { + ms_error("Friend doesn't have a storage_id !"); + return; + } + + buf = sqlite3_mprintf("DELETE FROM friends WHERE id = %i", lf->storage_id); + linphone_sql_request_generic(lc->friends_db, buf); + sqlite3_free(buf); + + lf->storage_id = 0; + } +} + +void linphone_core_remove_friends_list_from_db(LinphoneCore *lc, LinphoneFriendList *list) { + if (lc && lc->friends_db) { + char *buf; + if (list->storage_id == 0) { + ms_error("Friends list doesn't have a storage_id !"); + return; + } + + buf = sqlite3_mprintf("DELETE FROM friends_lists WHERE id = %i", list->storage_id); + linphone_sql_request_generic(lc->friends_db, buf); + sqlite3_free(buf); + + list->storage_id = 0; + } +} + +MSList* linphone_core_fetch_friends_from_db(LinphoneCore *lc, LinphoneFriendList *list) { + char *buf; + uint64_t begin,end; + MSList *result = NULL; + MSList *elem = NULL; + + if (!lc || lc->friends_db == NULL || list == NULL) { + ms_warning("Either lc (or list) is NULL or friends database wasn't initialized with linphone_core_friends_storage_init() yet"); + return NULL; + } + + buf = sqlite3_mprintf("SELECT * FROM friends WHERE friend_list_id = %i ORDER BY id", list->storage_id); + + begin = ortp_get_cur_time_ms(); + linphone_sql_request_friend(lc->friends_db, buf, &result); + end = ortp_get_cur_time_ms(); + ms_message("%s(): %i results fetched, completed in %i ms",__FUNCTION__, ms_list_size(result), (int)(end-begin)); + sqlite3_free(buf); + + for(elem = result; elem != NULL; elem = elem->next) { + LinphoneFriend *lf = (LinphoneFriend *)elem->data; + lf->lc = lc; + lf->friend_list = list; + } + + return result; +} + +MSList* linphone_core_fetch_friends_lists_from_db(LinphoneCore *lc) { + char *buf; + uint64_t begin,end; + MSList *result = NULL; + MSList *elem = NULL; + + if (!lc || lc->friends_db == NULL) { + ms_warning("Either lc is NULL or friends database wasn't initialized with linphone_core_friends_storage_init() yet"); + return NULL; + } + + buf = sqlite3_mprintf("SELECT * FROM friends_lists ORDER BY id"); + + begin = ortp_get_cur_time_ms(); + linphone_sql_request_friends_list(lc->friends_db, buf, &result); + end = ortp_get_cur_time_ms(); + ms_message("%s(): %i results fetched, completed in %i ms",__FUNCTION__, ms_list_size(result), (int)(end-begin)); + sqlite3_free(buf); + + for(elem = result; elem != NULL; elem = elem->next) { + LinphoneFriendList *lfl = (LinphoneFriendList *)elem->data; + lfl->lc = lc; + lfl->friends = linphone_core_fetch_friends_from_db(lc, lfl); + } + + return result; +} + +#else + +void linphone_core_friends_storage_init(LinphoneCore *lc) { +} + +void linphone_core_friends_storage_close(LinphoneCore *lc) { +} + +void linphone_core_store_friend_in_db(LinphoneCore *lc, LinphoneFriend *lf) { +} + +void linphone_core_store_friends_list_in_db(LinphoneCore *lc, LinphoneFriendList *list) { +} + +void linphone_core_remove_friend_from_db(LinphoneCore *lc, LinphoneFriend *lf) { +} + +void linphone_core_remove_friends_list_from_db(LinphoneCore *lc, LinphoneFriendList *list) { +} + +MSList* linphone_core_fetch_friends_from_db(LinphoneCore *lc, LinphoneFriendList *list) { + return NULL; +} + +MSList* linphone_core_fetch_friends_lists_from_db(LinphoneCore *lc) { + return NULL; +} + +#endif + +void linphone_core_set_friends_database_path(LinphoneCore *lc, const char *path) { + if (lc->friends_db_file){ + ms_free(lc->friends_db_file); + lc->friends_db_file = NULL; + } + if (path) { + lc->friends_db_file = ms_strdup(path); + linphone_core_friends_storage_init(lc); + + linphone_core_migrate_friends_from_rc_to_db(lc); + } +} + +void linphone_core_migrate_friends_from_rc_to_db(LinphoneCore *lc) { + LpConfig *lpc = NULL; + LinphoneFriend *lf = NULL; + LinphoneFriendList *lfl = linphone_core_get_default_friend_list(lc); + int i; +#ifndef FRIENDS_SQL_STORAGE_ENABLED + ms_warning("linphone has been compiled without sqlite, can't migrate friends"); + return; +#endif + if (!lc) { + return; + } + + lpc = linphone_core_get_config(lc); + if (!lpc) { + ms_warning("this core has been started without a rc file, nothing to migrate"); + return; + } + if (lp_config_get_int(lpc, "misc", "friends_migration_done", 0) == 1) { + ms_warning("the friends migration has already been done, skipping..."); + return; + } + + if (ms_list_size(linphone_friend_list_get_friends(lfl)) > 0) { + linphone_core_remove_friend_list(lc, lfl); + lfl = linphone_core_create_friend_list(lc); + linphone_core_add_friend_list(lc, lfl); + linphone_friend_list_unref(lfl); + } + + for (i = 0; (lf = linphone_friend_new_from_config_file(lc, i)) != NULL; i++) { + char friend_section[32]; + + const LinphoneAddress *addr = linphone_friend_get_address(lf); + if (addr) { + const char *displayName = linphone_address_get_display_name(addr); + if (!displayName) { + displayName = linphone_address_get_username(addr); + } + + if (!linphone_friend_create_vcard(lf, displayName)) { + ms_warning("Couldn't create vCard for friend %s", linphone_address_as_string(addr)); + } else { + linphone_vcard_add_sip_address(linphone_friend_get_vcard(lf), linphone_address_as_string_uri_only(addr)); + } + + linphone_friend_list_add_friend(lfl, lf); + linphone_friend_unref(lf); + + snprintf(friend_section, sizeof(friend_section), "friend_%i", i); + lp_config_clean_section(lpc, friend_section); + } + } + + ms_debug("friends migration successful: %i friends migrated", i); + lp_config_set_int(lpc, "misc", "friends_migration_done", 1); +} diff --git a/coreapi/friendlist.c b/coreapi/friendlist.c new file mode 100644 index 000000000..1ab02b5a5 --- /dev/null +++ b/coreapi/friendlist.c @@ -0,0 +1,847 @@ +/* +linphone +Copyright (C) 2010-2015 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "linphonecore.h" +#include "private.h" + +#include + +BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(LinphoneFriendListCbs); + +BELLE_SIP_INSTANCIATE_VPTR(LinphoneFriendListCbs, belle_sip_object_t, + NULL, // destroy + NULL, // clone + NULL, // Marshall + FALSE +); + +static LinphoneFriendListCbs * linphone_friend_list_cbs_new(void) { + return belle_sip_object_new(LinphoneFriendListCbs); +} + +LinphoneFriendListCbs * linphone_friend_list_get_callbacks(const LinphoneFriendList *list) { + return list->cbs; +} + +LinphoneFriendListCbs * linphone_friend_list_cbs_ref(LinphoneFriendListCbs *cbs) { + belle_sip_object_ref(cbs); + return cbs; +} + +void linphone_friend_list_cbs_unref(LinphoneFriendListCbs *cbs) { + belle_sip_object_unref(cbs); +} + +void *linphone_friend_list_cbs_get_user_data(const LinphoneFriendListCbs *cbs) { + return cbs->user_data; +} + +void linphone_friend_list_cbs_set_user_data(LinphoneFriendListCbs *cbs, void *ud) { + cbs->user_data = ud; +} + +LinphoneFriendListCbsContactCreatedCb linphone_friend_list_cbs_get_contact_created(const LinphoneFriendListCbs *cbs) { + return cbs->contact_created_cb; +} + +void linphone_friend_list_cbs_set_contact_created(LinphoneFriendListCbs *cbs, LinphoneFriendListCbsContactCreatedCb cb) { + cbs->contact_created_cb = cb; +} + +LinphoneFriendListCbsContactDeletedCb linphone_friend_list_cbs_get_contact_deleted(const LinphoneFriendListCbs *cbs) { + return cbs->contact_deleted_cb; +} + +void linphone_friend_list_cbs_set_contact_deleted(LinphoneFriendListCbs *cbs, LinphoneFriendListCbsContactDeletedCb cb) { + cbs->contact_deleted_cb = cb; +} + +LinphoneFriendListCbsContactUpdatedCb linphone_friend_list_cbs_get_contact_updated(const LinphoneFriendListCbs *cbs) { + return cbs->contact_updated_cb; +} + +void linphone_friend_list_cbs_set_contact_updated(LinphoneFriendListCbs *cbs, LinphoneFriendListCbsContactUpdatedCb cb) { + cbs->contact_updated_cb = cb; +} + +LinphoneFriendListCbsSyncStateChangedCb linphone_friend_list_cbs_get_sync_status_changed(const LinphoneFriendListCbs *cbs) { + return cbs->sync_state_changed_cb; +} + +void linphone_friend_list_cbs_set_sync_status_changed(LinphoneFriendListCbs *cbs, LinphoneFriendListCbsSyncStateChangedCb cb) { + cbs->sync_state_changed_cb = cb; +} + +static char * create_resource_list_xml(const LinphoneFriendList *list) { + char *xml_content = NULL; + MSList *elem; + xmlBufferPtr buf; + xmlTextWriterPtr writer; + int err; + + if (ms_list_size(list->friends) <= 0) return NULL; + + buf = xmlBufferCreate(); + if (buf == NULL) { + ms_error("%s: Error creating the XML buffer", __FUNCTION__); + return NULL; + } + writer = xmlNewTextWriterMemory(buf, 0); + if (writer == NULL) { + ms_error("%s: Error creating the XML writer", __FUNCTION__); + return NULL; + } + + xmlTextWriterSetIndent(writer,1); + err = xmlTextWriterStartDocument(writer, "1.0", "UTF-8", NULL); + if (err >= 0) { + err = xmlTextWriterStartElementNS(writer, NULL, (const xmlChar *)"resource-lists", (const xmlChar *)"urn:ietf:params:xml:ns:resource-lists"); + } + if (err >= 0) { + err = xmlTextWriterWriteAttributeNS(writer, (const xmlChar *)"xmlns", (const xmlChar *)"xsi", + NULL, (const xmlChar *)"http://www.w3.org/2001/XMLSchema-instance"); + } + + if (err>= 0) { + err = xmlTextWriterStartElement(writer, (const xmlChar *)"list"); + } + for (elem = list->friends; elem != NULL; elem = elem->next) { + LinphoneFriend *lf = (LinphoneFriend *)elem->data; + char *uri = linphone_address_as_string_uri_only(lf->uri); + if (err >= 0) { + err = xmlTextWriterStartElement(writer, (const xmlChar *)"entry"); + } + if (err >= 0) { + err = xmlTextWriterWriteAttribute(writer, (const xmlChar *)"uri", (const xmlChar *)uri); + } + if (err >= 0) { + /* Close the "entry" element. */ + err = xmlTextWriterEndElement(writer); + } + if (uri) ms_free(uri); + } + if (err >= 0) { + /* Close the "list" element. */ + err = xmlTextWriterEndElement(writer); + } + + if (err >= 0) { + /* Close the "resource-lists" element. */ + err = xmlTextWriterEndElement(writer); + } + if (err >= 0) { + err = xmlTextWriterEndDocument(writer); + } + if (err > 0) { + /* xmlTextWriterEndDocument returns the size of the content. */ + xml_content = ms_strdup((char *)buf->content); + } + xmlFreeTextWriter(writer); + xmlBufferFree(buf); + + return xml_content; +} + +static void linphone_friend_list_parse_multipart_related_body(LinphoneFriendList *list, const LinphoneContent *body, const char *first_part_body) { + xmlparsing_context_t *xml_ctx = linphone_xmlparsing_context_new(); + xmlSetGenericErrorFunc(xml_ctx, linphone_xmlparsing_genericxml_error); + xml_ctx->doc = xmlReadDoc((const unsigned char*)first_part_body, 0, NULL, 0); + if (xml_ctx->doc != NULL) { + char xpath_str[MAX_XPATH_LENGTH]; + LinphoneFriend *lf; + LinphoneContent *presence_part; + xmlXPathObjectPtr resource_object; + const char *version_str = NULL; + const char *full_state_str = NULL; + const char *uri = NULL; + bool_t full_state = FALSE; + int version; + int i; + + if (linphone_create_xml_xpath_context(xml_ctx) < 0) goto end; + xmlXPathRegisterNs(xml_ctx->xpath_ctx, (const xmlChar *)"rlmi", (const xmlChar *)"urn:ietf:params:xml:ns:rlmi"); + + version_str = linphone_get_xml_attribute_text_content(xml_ctx, "/rlmi:list", "version"); + if (version_str == NULL) { + ms_warning("rlmi+xml: No version attribute in list"); + goto end; + } + version = atoi(version_str); + linphone_free_xml_text_content(version_str); + if (version < list->expected_notification_version) { + ms_warning("rlmi+xml: Discarding received notification with version %d because %d was expected", version, list->expected_notification_version); + linphone_friend_list_update_subscriptions(list, NULL, FALSE); /* Refresh subscription to get new full state notify. */ + goto end; + } + + full_state_str = linphone_get_xml_attribute_text_content(xml_ctx, "/rlmi:list", "fullState"); + if (full_state_str == NULL) { + ms_warning("rlmi+xml: No fullState attribute in list"); + goto end; + } + if ((strcmp(full_state_str, "true") == 0) || (strcmp(full_state_str, "1") == 0)) { + MSList *l = list->friends; + for (; l != NULL; l = l->next) { + lf = (LinphoneFriend *)l->data; + linphone_friend_set_presence_model(lf, NULL); + } + full_state = TRUE; + } + linphone_free_xml_text_content(full_state_str); + if ((list->expected_notification_version == 0) && (full_state == FALSE)) { + ms_warning("rlmi+xml: Notification with version 0 is not full state, this is not valid"); + goto end; + } + list->expected_notification_version = version + 1; + + resource_object = linphone_get_xml_xpath_object_for_node_list(xml_ctx, "/rlmi:list/rlmi:resource"); + if ((resource_object != NULL) && (resource_object->nodesetval != NULL)) { + for (i = 1; i <= resource_object->nodesetval->nodeNr; i++) { + snprintf(xpath_str, sizeof(xpath_str), "/rlmi:list/rlmi:resource[%i]/@uri", i); + uri = linphone_get_xml_text_content(xml_ctx, xpath_str); + if (uri == NULL) continue; + lf = linphone_friend_list_find_friend_by_uri(list, uri); + if (lf != NULL) { + const char *state = NULL; + snprintf(xpath_str, sizeof(xpath_str),"/rlmi:list/rlmi:resource[%i]/rlmi:instance/@state", i); + state = linphone_get_xml_text_content(xml_ctx, xpath_str); + if ((state != NULL) && (strcmp(state, "active") == 0)) { + const char *cid = NULL; + snprintf(xpath_str, sizeof(xpath_str),"/rlmi:list/rlmi:resource[%i]/rlmi:instance/@cid", i); + cid = linphone_get_xml_text_content(xml_ctx, xpath_str); + if (cid != NULL) { + presence_part = linphone_content_find_part_by_header(body, "Content-Id", cid); + if (presence_part == NULL) { + ms_warning("rlmi+xml: Cannot find part with Content-Id: %s", cid); + } else { + SalPresenceModel *presence = NULL; + linphone_notify_parse_presence(linphone_content_get_type(presence_part), linphone_content_get_subtype(presence_part), linphone_content_get_string_buffer(presence_part), &presence); + if (presence != NULL) { + lf->presence_received = TRUE; + linphone_friend_set_presence_model(lf, (LinphonePresenceModel *)presence); + if (full_state == FALSE) { + linphone_core_notify_notify_presence_received(list->lc, lf); + } + } + linphone_content_unref(presence_part); + } + } + if (cid != NULL) linphone_free_xml_text_content(cid); + } + if (state != NULL) linphone_free_xml_text_content(state); + lf->subscribe_active = TRUE; + } + linphone_free_xml_text_content(uri); + } + } + if (resource_object != NULL) xmlXPathFreeObject(resource_object); + + if (full_state == TRUE) { + MSList *l = list->friends; + for (; l != NULL; l = l->next) { + lf = (LinphoneFriend *)l->data; + if (linphone_friend_is_presence_received(lf) == TRUE) { + linphone_core_notify_notify_presence_received(list->lc, lf); + } + } + } + } else { + ms_warning("Wrongly formatted rlmi+xml body: %s", xml_ctx->errorBuffer); + } + +end: + linphone_xmlparsing_context_destroy(xml_ctx); +} + +static bool_t linphone_friend_list_has_subscribe_inactive(const LinphoneFriendList *list) { + MSList *l = list->friends; + bool_t has_subscribe_inactive = FALSE; + for (; l != NULL; l = l->next) { + LinphoneFriend *lf = (LinphoneFriend *)l->data; + if (lf->subscribe_active != TRUE) { + has_subscribe_inactive = TRUE; + break; + } + } + return has_subscribe_inactive; +} + +static LinphoneFriendList * linphone_friend_list_new(void) { + LinphoneFriendList *list = belle_sip_object_new(LinphoneFriendList); + list->cbs = linphone_friend_list_cbs_new(); + belle_sip_object_ref(list); + return list; +} + +static void linphone_friend_list_destroy(LinphoneFriendList *list) { + if (list->display_name != NULL) ms_free(list->display_name); + if (list->rls_uri != NULL) ms_free(list->rls_uri); + if (list->content_digest != NULL) ms_free(list->content_digest); + if (list->event != NULL) { + linphone_event_terminate(list->event); + linphone_event_unref(list->event); + } + if (list->uri != NULL) ms_free(list->uri); + if (list->cbs) linphone_friend_list_cbs_unref(list->cbs); + if (list->dirty_friends_to_update) list->dirty_friends_to_update = ms_list_free_with_data(list->dirty_friends_to_update, (void (*)(void *))linphone_friend_unref); + if (list->friends) list->friends = ms_list_free_with_data(list->friends, (void (*)(void *))_linphone_friend_release); +} + +BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(LinphoneFriendList); + +BELLE_SIP_INSTANCIATE_VPTR(LinphoneFriendList, belle_sip_object_t, + (belle_sip_object_destroy_t)linphone_friend_list_destroy, + NULL, // clone + NULL, // marshal + TRUE +); + + +LinphoneFriendList * linphone_core_create_friend_list(LinphoneCore *lc) { + LinphoneFriendList *list = linphone_friend_list_new(); + list->lc = lc; + return list; +} + +LinphoneFriendList * linphone_friend_list_ref(LinphoneFriendList *list) { + belle_sip_object_ref(list); + return list; +} + +void _linphone_friend_list_release(LinphoneFriendList *list){ + /*drops all references to core and unref*/ + list->lc = NULL; + if (list->event != NULL) { + linphone_event_unref(list->event); + list->event = NULL; + } + if (list->cbs) { + linphone_friend_list_cbs_unref(list->cbs); + list->cbs = NULL; + } + if (list->dirty_friends_to_update) { + list->dirty_friends_to_update = ms_list_free_with_data(list->dirty_friends_to_update, (void (*)(void *))linphone_friend_unref); + } + if (list->friends) { + list->friends = ms_list_free_with_data(list->friends, (void (*)(void *))_linphone_friend_release); + } + linphone_friend_list_unref(list); +} + +void linphone_friend_list_unref(LinphoneFriendList *list) { + belle_sip_object_unref(list); +} + +void * linphone_friend_list_get_user_data(const LinphoneFriendList *list) { + return list->user_data; +} + +void linphone_friend_list_set_user_data(LinphoneFriendList *list, void *ud) { + list->user_data = ud; +} + +const char * linphone_friend_list_get_display_name(const LinphoneFriendList *list) { + return list->display_name; +} + +void linphone_friend_list_set_display_name(LinphoneFriendList *list, const char *display_name) { + if (list->display_name != NULL) { + ms_free(list->display_name); + list->display_name = NULL; + } + if (display_name != NULL) { + list->display_name = ms_strdup(display_name); + linphone_core_store_friends_list_in_db(list->lc, list); + } +} + +const char * linphone_friend_list_get_rls_uri(const LinphoneFriendList *list) { + return list->rls_uri; +} + +void linphone_friend_list_set_rls_uri(LinphoneFriendList *list, const char *rls_uri) { + if (list->rls_uri != NULL) { + ms_free(list->rls_uri); + list->rls_uri = NULL; + } + if (rls_uri != NULL) { + list->rls_uri = ms_strdup(rls_uri); + linphone_core_store_friends_list_in_db(list->lc, list); + } +} + +static LinphoneFriendListStatus _linphone_friend_list_add_friend(LinphoneFriendList *list, LinphoneFriend *lf, bool_t synchronize) { + if (!list || !lf->uri || lf->friend_list) { + if (!list) + ms_error("linphone_friend_list_add_friend(): invalid list, null"); + if (!lf->uri) + ms_error("linphone_friend_list_add_friend(): invalid friend, no sip uri"); + if (lf->friend_list) + ms_error("linphone_friend_list_add_friend(): invalid friend, already in list"); + return LinphoneFriendListInvalidFriend; + } + if (ms_list_find(list->friends, lf) != NULL) { + char *tmp = NULL; + const LinphoneAddress *addr = linphone_friend_get_address(lf); + if (addr) tmp = linphone_address_as_string(addr); + ms_warning("Friend %s already in list [%s], ignored.", tmp ? tmp : "unknown", list->display_name); + if (tmp) ms_free(tmp); + } else { + return linphone_friend_list_import_friend(list, lf, synchronize); + } + return LinphoneFriendListOK; +} + +LinphoneFriendListStatus linphone_friend_list_add_friend(LinphoneFriendList *list, LinphoneFriend *lf) { + return _linphone_friend_list_add_friend(list, lf, TRUE); +} + +LinphoneFriendListStatus linphone_friend_list_add_local_friend(LinphoneFriendList *list, LinphoneFriend *lf) { + return _linphone_friend_list_add_friend(list, lf, FALSE); +} + +LinphoneFriendListStatus linphone_friend_list_import_friend(LinphoneFriendList *list, LinphoneFriend *lf, bool_t synchronize) { + if (!lf->uri || lf->friend_list) { + if (!lf->uri) + ms_error("linphone_friend_list_add_friend(): invalid friend, no sip uri"); + if (lf->friend_list) + ms_error("linphone_friend_list_add_friend(): invalid friend, already in list"); + return LinphoneFriendListInvalidFriend; + } + lf->friend_list = list; + lf->lc = list->lc; + list->friends = ms_list_append(list->friends, linphone_friend_ref(lf)); + if (synchronize) { + list->dirty_friends_to_update = ms_list_append(list->dirty_friends_to_update, linphone_friend_ref(lf)); + } +#ifdef FRIENDS_SQL_STORAGE_ENABLED + linphone_core_store_friend_in_db(lf->lc, lf); +#endif + return LinphoneFriendListOK; +} + +static void carddav_done(LinphoneCardDavContext *cdc, bool_t success, const char *msg) { + if (cdc && cdc->friend_list->cbs->sync_state_changed_cb) { + cdc->friend_list->cbs->sync_state_changed_cb(cdc->friend_list, success ? LinphoneFriendListSyncSuccessful : LinphoneFriendListSyncFailure, msg); + } + linphone_carddav_context_destroy(cdc); +} + +static LinphoneFriendListStatus _linphone_friend_list_remove_friend(LinphoneFriendList *list, LinphoneFriend *lf, bool_t remove_from_server) { + MSList *elem = ms_list_find(list->friends, lf); + if (elem == NULL) return LinphoneFriendListNonExistentFriend; + +#ifdef FRIENDS_SQL_STORAGE_ENABLED + if (lf && lf->lc && lf->lc->friends_db) { + linphone_core_remove_friend_from_db(lf->lc, lf); + } +#endif + if (remove_from_server) { + LinphoneVcard *lvc = linphone_friend_get_vcard(lf); + if (lvc && linphone_vcard_get_uid(lvc)) { + LinphoneCardDavContext *cdc = linphone_carddav_context_new(list); + if (cdc) { + cdc->sync_done_cb = carddav_done; + if (cdc->friend_list->cbs->sync_state_changed_cb) { + cdc->friend_list->cbs->sync_state_changed_cb(cdc->friend_list, LinphoneFriendListSyncStarted, NULL); + } + linphone_carddav_delete_vcard(cdc, lf); + } + } + } + + lf->friend_list = NULL; + linphone_friend_unref(lf); + list->friends = ms_list_remove_link(list->friends, elem); + return LinphoneFriendListOK; +} + +LinphoneFriendListStatus linphone_friend_list_remove_friend(LinphoneFriendList *list, LinphoneFriend *lf) { + return _linphone_friend_list_remove_friend(list, lf, TRUE); +} + +const MSList * linphone_friend_list_get_friends(const LinphoneFriendList *list) { + return list->friends; +} + +void linphone_friend_list_update_dirty_friends(LinphoneFriendList *list) { + LinphoneCardDavContext *cdc = linphone_carddav_context_new(list); + MSList *dirty_friends = list->dirty_friends_to_update; + + if (cdc) { + cdc->sync_done_cb = carddav_done; + while (dirty_friends) { + LinphoneFriend *lf = (LinphoneFriend *)dirty_friends->data; + if (lf) { + if (cdc->friend_list->cbs->sync_state_changed_cb) { + cdc->friend_list->cbs->sync_state_changed_cb(cdc->friend_list, LinphoneFriendListSyncStarted, NULL); + } + linphone_carddav_put_vcard(cdc, lf); + } + dirty_friends = ms_list_next(dirty_friends); + } + list->dirty_friends_to_update = ms_list_free_with_data(list->dirty_friends_to_update, (void (*)(void *))linphone_friend_unref); + } +} + +static void carddav_created(LinphoneCardDavContext *cdc, LinphoneFriend *lf) { + if (cdc) { + LinphoneFriendList *lfl = cdc->friend_list; + linphone_friend_list_import_friend(lfl, lf, FALSE); + if (cdc->friend_list->cbs->contact_created_cb) { + cdc->friend_list->cbs->contact_created_cb(lfl, lf); + } + } +} + +static void carddav_removed(LinphoneCardDavContext *cdc, LinphoneFriend *lf) { + if (cdc) { + LinphoneFriendList *lfl = cdc->friend_list; + _linphone_friend_list_remove_friend(lfl, lf, FALSE); + if (cdc->friend_list->cbs->contact_deleted_cb) { + cdc->friend_list->cbs->contact_deleted_cb(lfl, lf); + } + } +} + +static void carddav_updated(LinphoneCardDavContext *cdc, LinphoneFriend *lf_new, LinphoneFriend *lf_old) { + if (cdc) { + LinphoneFriendList *lfl = cdc->friend_list; + MSList *elem = ms_list_find(lfl->friends, lf_old); + if (elem) { + elem->data = linphone_friend_ref(lf_new); + } + linphone_core_store_friend_in_db(lf_new->lc, lf_new); + + if (cdc->friend_list->cbs->contact_updated_cb) { + cdc->friend_list->cbs->contact_updated_cb(lfl, lf_new, lf_old); + } + linphone_friend_unref(lf_old); + } +} + +void linphone_friend_list_synchronize_friends_from_server(LinphoneFriendList *list) { + LinphoneCardDavContext *cdc = linphone_carddav_context_new(list); + + if (cdc) { + cdc->contact_created_cb = carddav_created; + cdc->contact_removed_cb = carddav_removed; + cdc->contact_updated_cb = carddav_updated; + cdc->sync_done_cb = carddav_done; + if (cdc && cdc->friend_list->cbs->sync_state_changed_cb) { + cdc->friend_list->cbs->sync_state_changed_cb(cdc->friend_list, LinphoneFriendListSyncStarted, NULL); + } + linphone_carddav_synchronize(cdc); + } +} + +LinphoneFriend * linphone_friend_list_find_friend_by_address(const LinphoneFriendList *list, const LinphoneAddress *address) { + LinphoneFriend *lf = NULL; + const MSList *elem; + for (elem = list->friends; elem != NULL; elem = elem->next) { + lf = (LinphoneFriend *)elem->data; + if (linphone_address_weak_equal(lf->uri, address)) + return lf; + } + return NULL; +} + +LinphoneFriend * linphone_friend_list_find_friend_by_uri(const LinphoneFriendList *list, const char *uri) { + LinphoneAddress *address = linphone_address_new(uri); + LinphoneFriend *lf = address ? linphone_friend_list_find_friend_by_address(list, address) : NULL; + if (address) linphone_address_unref(address); + return lf; +} + +LinphoneFriend * linphone_friend_list_find_friend_by_ref_key(const LinphoneFriendList *list, const char *ref_key) { + const MSList *elem; + if (ref_key == NULL) return NULL; + for (elem = list->friends; elem != NULL; elem = elem->next) { + LinphoneFriend *lf = (LinphoneFriend *)elem->data; + if ((lf->refkey != NULL) && (strcmp(lf->refkey, ref_key) == 0)) return lf; + } + return NULL; +} + +LinphoneFriend * linphone_friend_list_find_friend_by_inc_subscribe(const LinphoneFriendList *list, SalOp *op) { + const MSList *elem; + for (elem = list->friends; elem != NULL; elem = elem->next) { + LinphoneFriend *lf = (LinphoneFriend *)elem->data; + if (ms_list_find(lf->insubs, op)) return lf; + } + return NULL; +} + +LinphoneFriend * linphone_friend_list_find_friend_by_out_subscribe(const LinphoneFriendList *list, SalOp *op) { + const MSList *elem; + for (elem = list->friends; elem != NULL; elem = elem->next) { + LinphoneFriend *lf = (LinphoneFriend *)elem->data; + if (lf->outsub && ((lf->outsub == op) || sal_op_is_forked_of(lf->outsub, op))) return lf; + } + return NULL; +} + +void linphone_friend_list_close_subscriptions(LinphoneFriendList *list) { + /* FIXME we should wait until subscription to complete. */ + if (list->event) { + linphone_event_terminate(list->event); + } else if (list->friends) + ms_list_for_each(list->friends, (void (*)(void *))linphone_friend_close_subscriptions); +} + +void linphone_friend_list_update_subscriptions(LinphoneFriendList *list, LinphoneProxyConfig *cfg, bool_t only_when_registered) { + const MSList *elem; + if (list->rls_uri != NULL) { + LinphoneAddress *address = linphone_address_new(list->rls_uri); + char *xml_content = create_resource_list_xml(list); + if ((address != NULL) && (xml_content != NULL) && (linphone_friend_list_has_subscribe_inactive(list) == TRUE)) { + unsigned char digest[16]; + bctoolbox_md5((unsigned char *)xml_content, strlen(xml_content), digest); + if ((list->event != NULL) && (list->content_digest != NULL) && (memcmp(list->content_digest, digest, sizeof(digest)) == 0)) { + /* The content has not changed, only refresh the event. */ + linphone_event_refresh_subscribe(list->event); + } else { + LinphoneContent *content; + int expires = lp_config_get_int(list->lc->config, "sip", "rls_presence_expires", 3600); + list->expected_notification_version = 0; + if (list->content_digest != NULL) ms_free(list->content_digest); + list->content_digest = ms_malloc(sizeof(digest)); + memcpy(list->content_digest, digest, sizeof(digest)); + if (list->event != NULL) { + linphone_event_terminate(list->event); + linphone_event_unref(list->event); + } + list->event = linphone_core_create_subscribe(list->lc, address, "presence", expires); + linphone_event_ref(list->event); + linphone_event_set_internal(list->event, TRUE); + linphone_event_add_custom_header(list->event, "Require", "recipient-list-subscribe"); + linphone_event_add_custom_header(list->event, "Supported", "eventlist"); + linphone_event_add_custom_header(list->event, "Accept", "multipart/related, application/pidf+xml, application/rlmi+xml"); + linphone_event_add_custom_header(list->event, "Content-Disposition", "recipient-list"); + content = linphone_core_create_content(list->lc); + linphone_content_set_type(content, "application"); + linphone_content_set_subtype(content, "resource-lists+xml"); + linphone_content_set_string_buffer(content, xml_content); + if (linphone_core_content_encoding_supported(list->lc, "deflate")) { + linphone_content_set_encoding(content, "deflate"); + linphone_event_add_custom_header(list->event, "Accept-Encoding", "deflate"); + } + linphone_event_send_subscribe(list->event, content); + linphone_content_unref(content); + linphone_event_set_user_data(list->event, list); + } + } + if (address != NULL) linphone_address_unref(address); + if (xml_content != NULL) ms_free(xml_content); + } else { + for (elem = list->friends; elem != NULL; elem = elem->next) { + LinphoneFriend *lf = (LinphoneFriend *)elem->data; + linphone_friend_update_subscribes(lf, cfg, only_when_registered); + } + } +} + +void linphone_friend_list_invalidate_subscriptions(LinphoneFriendList *list) { + const MSList *elem; + for (elem = list->friends; elem != NULL; elem = elem->next) { + LinphoneFriend *lf = (LinphoneFriend *)elem->data; + linphone_friend_invalidate_subscription(lf); + } +} + +void linphone_friend_list_notify_presence(LinphoneFriendList *list, LinphonePresenceModel *presence) { + const MSList *elem; + for(elem = list->friends; elem != NULL; elem = elem->next) { + LinphoneFriend *lf = (LinphoneFriend *)elem->data; + linphone_friend_notify(lf, presence); + } +} + +void linphone_friend_list_notify_presence_received(LinphoneFriendList *list, LinphoneEvent *lev, const LinphoneContent *body) { + if (linphone_content_is_multipart(body)) { + LinphoneContent *first_part; + const char *type = linphone_content_get_type(body); + const char *subtype = linphone_content_get_subtype(body); + + if ((strcmp(type, "multipart") != 0) || (strcmp(subtype, "related") != 0)) { + ms_warning("multipart presence notified but it is not 'multipart/related'"); + return; + } + + first_part = linphone_content_get_part(body, 0); + if (first_part == NULL) { + ms_warning("'multipart/related' presence notified but it doesn't contain any part"); + return; + } + + type = linphone_content_get_type(first_part); + subtype = linphone_content_get_subtype(first_part); + if ((strcmp(type, "application") != 0) || (strcmp(subtype, "rlmi+xml") != 0)) { + ms_warning("multipart presence notified but first part is not 'application/rlmi+xml'"); + linphone_content_unref(first_part); + return; + } + + linphone_friend_list_parse_multipart_related_body(list, body, linphone_content_get_string_buffer(first_part)); + linphone_content_unref(first_part); + } +} + +const char * linphone_friend_list_get_uri(const LinphoneFriendList *list) { + return list->uri; +} + +void linphone_friend_list_set_uri(LinphoneFriendList *list, const char *uri) { + if (list->uri != NULL) { + ms_free(list->uri); + list->uri = NULL; + } + if (uri != NULL) { + list->uri = ms_strdup(uri); + linphone_core_store_friends_list_in_db(list->lc, list); + } +} + +void linphone_friend_list_update_revision(LinphoneFriendList *list, int rev) { + list->revision = rev; + linphone_core_store_friends_list_in_db(list->lc, list); +} + +void linphone_friend_list_subscription_state_changed(LinphoneCore *lc, LinphoneEvent *lev, LinphoneSubscriptionState state) { + LinphoneFriendList *list = (LinphoneFriendList *)linphone_event_get_user_data(lev); + if (!list) { + ms_warning("core [%p] Receiving unexpected state [%s] for event [%p], no associated friend list",lc + , linphone_subscription_state_to_string(state) + , lev); + } else { + ms_message("Receiving new state [%s] for event [%p] for friend list [%p]" + , linphone_subscription_state_to_string(state) + , lev + , list); + + if (state == LinphoneSubscriptionOutgoingProgress && linphone_event_get_reason(lev) == LinphoneReasonNoMatch) { + ms_message("Resseting version count for friend list [%p]",list); + list->expected_notification_version = 0; + } + } +} + +LinphoneCore* linphone_friend_list_get_core(LinphoneFriendList *list) { + return list->lc; +} + +int linphone_friend_list_import_friends_from_vcard4_file(LinphoneFriendList *list, const char *vcard_file) { + MSList *vcards = linphone_vcard_list_from_vcard4_file(vcard_file); + int count = 0; + +#ifndef VCARD_ENABLED + ms_error("vCard support wasn't enabled at compilation time"); + return -1; +#endif + if (!vcards) { + ms_error("Failed to parse the file %s", vcard_file); + return -1; + } + if (!list) { + ms_error("Can't import into a NULL list"); + return -1; + } + + while (vcards != NULL && vcards->data != NULL) { + LinphoneVcard *vcard = (LinphoneVcard *)vcards->data; + LinphoneFriend *lf = linphone_friend_new_from_vcard(vcard); + if (lf) { + if (LinphoneFriendListOK == linphone_friend_list_import_friend(list, lf, TRUE)) { + count++; + } + linphone_friend_unref(lf); + } else { + linphone_vcard_free(vcard); + } + vcards = ms_list_next(vcards); + } +#ifndef FRIENDS_SQL_STORAGE_ENABLED + linphone_core_write_friends_config(list->lc); +#endif + return count; +} + +int linphone_friend_list_import_friends_from_vcard4_buffer(LinphoneFriendList *list, const char *vcard_buffer) { + MSList *vcards = linphone_vcard_list_from_vcard4_buffer(vcard_buffer); + int count = 0; + +#ifndef VCARD_ENABLED + ms_error("vCard support wasn't enabled at compilation time"); + return -1; +#endif + if (!vcards) { + ms_error("Failed to parse the buffer"); + return -1; + } + if (!list) { + ms_error("Can't import into a NULL list"); + return -1; + } + + while (vcards != NULL && vcards->data != NULL) { + LinphoneVcard *vcard = (LinphoneVcard *)vcards->data; + LinphoneFriend *lf = linphone_friend_new_from_vcard(vcard); + if (lf) { + if (LinphoneFriendListOK == linphone_friend_list_import_friend(list, lf, TRUE)) { + count++; + } + linphone_friend_unref(lf); + } else { + linphone_vcard_free(vcard); + } + vcards = ms_list_next(vcards); + } +#ifndef FRIENDS_SQL_STORAGE_ENABLED + linphone_core_write_friends_config(list->lc); +#endif + return count; +} + +void linphone_friend_list_export_friends_as_vcard4_file(LinphoneFriendList *list, const char *vcard_file) { + FILE *file = NULL; + const MSList *friends = linphone_friend_list_get_friends(list); + + file = fopen(vcard_file, "wb"); + if (file == NULL) { + ms_warning("Could not write %s ! Maybe it is read-only. Contacts will not be saved.", vcard_file); + return; + } + +#ifndef VCARD_ENABLED + ms_error("vCard support wasn't enabled at compilation time"); +#endif + while (friends != NULL && friends->data != NULL) { + LinphoneFriend *lf = (LinphoneFriend *)friends->data; + LinphoneVcard *vcard = linphone_friend_get_vcard(lf); + if (vcard) { + const char *vcard_text = linphone_vcard_as_vcard4_string(vcard); + fprintf(file, "%s", vcard_text); + } else { + ms_warning("Couldn't export friend %s because it doesn't have a vCard attached", linphone_address_as_string(linphone_friend_get_address(lf))); + } + friends = ms_list_next(friends); + } + + fclose(file); +} diff --git a/coreapi/friendlist.h b/coreapi/friendlist.h new file mode 100644 index 000000000..acda26934 --- /dev/null +++ b/coreapi/friendlist.h @@ -0,0 +1,419 @@ +/* +friendlist.h +Copyright (C) 2010-2015 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + + +#ifndef LINPHONE_FRIENDLIST_H_ +#define LINPHONE_FRIENDLIST_H_ + + +#ifdef IN_LINPHONE +#include "linphonefriend.h" +#include "linphonepresence.h" +#else +#include "linphone/linphonefriend.h" +#include "linphone/linphonepresence.h" +#endif + + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @addtogroup buddy_list + * @{ + */ + +/** +* Enum describing the status of a LinphoneFriendList operation. +**/ +typedef enum _LinphoneFriendListStatus { + LinphoneFriendListOK, + LinphoneFriendListNonExistentFriend, + LinphoneFriendListInvalidFriend +} LinphoneFriendListStatus; + +/** + * Enum describing the status of a CardDAV synchronization + */ +typedef enum _LinphoneFriendListSyncStatus { + LinphoneFriendListSyncStarted, + LinphoneFriendListSyncSuccessful, + LinphoneFriendListSyncFailure +} LinphoneFriendListSyncStatus; + +/** + * The LinphoneFriendList object representing a list of friends. +**/ +typedef struct _LinphoneFriendList LinphoneFriendList; + +/** + * Create a new empty LinphoneFriendList object. + * @param[in] lc LinphoneCore object. + * @return A new LinphoneFriendList object. +**/ +LINPHONE_PUBLIC LinphoneFriendList * linphone_core_create_friend_list(LinphoneCore *lc); + +/** + * Add a friend list. + * @param[in] lc LinphoneCore object + * @param[in] list LinphoneFriendList object + */ +LINPHONE_PUBLIC void linphone_core_add_friend_list(LinphoneCore *lc, LinphoneFriendList *list); + +/** + * Removes a friend list. + * @param[in] lc LinphoneCore object + * @param[in] list LinphoneFriendList object + */ +LINPHONE_PUBLIC void linphone_core_remove_friend_list(LinphoneCore *lc, LinphoneFriendList *list); + +/** + * Retrieves the list of LinphoneFriendList from the core. + * @param[in] lc LinphoneCore object + * @return \mslist{LinphoneFriendList} a list of LinphoneFriendList + */ +LINPHONE_PUBLIC const MSList * linphone_core_get_friends_lists(const LinphoneCore *lc); + +/** + * Retrieves the first list of LinphoneFriend from the core. + * @param[in] lc LinphoneCore object + * @return the first LinphoneFriendList object or NULL + */ +LINPHONE_PUBLIC LinphoneFriendList * linphone_core_get_default_friend_list(const LinphoneCore *lc); + +/** + * Acquire a reference to the friend list. + * @param[in] list LinphoneFriendList object. + * @return The same LinphoneFriendList object. +**/ +LINPHONE_PUBLIC LinphoneFriendList * linphone_friend_list_ref(LinphoneFriendList *list); + +/** + * Release reference to the friend list. + * @param[in] list LinphoneFriendList object. +**/ +LINPHONE_PUBLIC void linphone_friend_list_unref(LinphoneFriendList *list); + +/** + * Retrieve the user pointer associated with the friend list. + * @param[in] list LinphoneFriendList object. + * @return The user pointer associated with the friend list. +**/ +LINPHONE_PUBLIC void * linphone_friend_list_get_user_data(const LinphoneFriendList *list); + +/** + * Assign a user pointer to the friend list. + * @param[in] list LinphoneFriendList object. + * @param[in] ud The user pointer to associate with the friend list. +**/ +LINPHONE_PUBLIC void linphone_friend_list_set_user_data(LinphoneFriendList *list, void *ud); + +/** + * Get the display name of the friend list. + * @param[in] list LinphoneFriendList object. + * @return The display name of the friend list. +**/ +LINPHONE_PUBLIC const char * linphone_friend_list_get_display_name(const LinphoneFriendList *list); + +/** + * Set the display name of the friend list. + * @param[in] list LinphoneFriendList object. + * @param[in] display_name The new display name of the friend list. +**/ +LINPHONE_PUBLIC void linphone_friend_list_set_display_name(LinphoneFriendList *list, const char *display_name); + +/** + * Get the RLS (Resource List Server) URI associated with the friend list to subscribe to these friends presence. + * @param[in] list LinphoneFriendList object. + * @return The RLS URI associated with the friend list. +**/ +LINPHONE_PUBLIC const char * linphone_friend_list_get_rls_uri(const LinphoneFriendList *list); + +/** + * Set the RLS (Resource List Server) URI associated with the friend list to subscribe to these friends presence. + * @param[in] list LinphoneFriendList object. + * @param[in] rls_uri The RLS URI to associate with the friend list. +**/ +LINPHONE_PUBLIC void linphone_friend_list_set_rls_uri(LinphoneFriendList *list, const char *rls_uri); + +/** + * Add a friend to a friend list. If or when a remote CardDAV server will be attached to the list, the friend will be sent to the server. + * @param[in] list LinphoneFriendList object. + * @param[in] friend LinphoneFriend object to add to the friend list. + * @return LinphoneFriendListOK if successfully added, LinphoneFriendListInvalidFriend if the friend is not valid. +**/ +LINPHONE_PUBLIC LinphoneFriendListStatus linphone_friend_list_add_friend(LinphoneFriendList *list, LinphoneFriend *lf); + +/** + * Add a friend to a friend list. The friend will never be sent to a remote CardDAV server. + * Warning! LinphoneFriends added this way will be removed on the next synchronization, and the callback contact_deleted will be called. + * @param[in] list LinphoneFriendList object. + * @param[in] friend LinphoneFriend object to add to the friend list. + * @return LinphoneFriendListOK if successfully added, LinphoneFriendListInvalidFriend if the friend is not valid. +**/ +LINPHONE_PUBLIC LinphoneFriendListStatus linphone_friend_list_add_local_friend(LinphoneFriendList *list, LinphoneFriend *lf); + +/** + * Remove a friend from a friend list. + * @param[in] list LinphoneFriendList object. + * @param[in] friend LinphoneFriend object to remove from the friend list. + * @return LinphoneFriendListOK if removed successfully, LinphoneFriendListNonExistentFriend if the friend is not in the list. +**/ +LINPHONE_PUBLIC LinphoneFriendListStatus linphone_friend_list_remove_friend(LinphoneFriendList *list, LinphoneFriend *afriend); + +/** + * Retrieves the list of LinphoneFriend from this LinphoneFriendList. + * @param[in] list LinphoneFriendList object + * @return \mslist{LinphoneFriend} a list of LinphoneFriend + */ +LINPHONE_PUBLIC const MSList * linphone_friend_list_get_friends(const LinphoneFriendList *list); + +/** + * Find a friend in the friend list using a LinphoneAddress. + * @param[in] list LinphoneFriendList object. + * @param[in] address LinphoneAddress object of the friend we want to search for. + * @return A LinphoneFriend if found, NULL otherwise. +**/ +LINPHONE_PUBLIC LinphoneFriend * linphone_friend_list_find_friend_by_address(const LinphoneFriendList *list, const LinphoneAddress *address); + +/** + * Find a friend in the friend list using an URI string. + * @param[in] list LinphoneFriendList object. + * @param[in] uri A string containing the URI of the friend we want to search for. + * @return A LinphoneFriend if found, NULL otherwise. +**/ +LINPHONE_PUBLIC LinphoneFriend * linphone_friend_list_find_friend_by_uri(const LinphoneFriendList *list, const char *uri); + +/** + * Find a frient in the friend list using a ref key. + * @param[in] list LinphoneFriendList object. + * @param[in] ref_key The ref key string of the friend we want to search for. + * @return A LinphoneFriend if found, NULL otherwise. +**/ +LINPHONE_PUBLIC LinphoneFriend * linphone_friend_list_find_friend_by_ref_key(const LinphoneFriendList *list, const char *ref_key); + +LINPHONE_PUBLIC void linphone_friend_list_close_subscriptions(LinphoneFriendList *list); + +LINPHONE_PUBLIC void linphone_friend_list_update_subscriptions(LinphoneFriendList *list, LinphoneProxyConfig *cfg, bool_t only_when_registered); + +/** + * Notify our presence to all the friends in the friend list that have subscribed to our presence directly (not using a RLS). + * @param[in] list LinphoneFriendList object. + * @param[in] presence LinphonePresenceModel object. +**/ +LINPHONE_PUBLIC void linphone_friend_list_notify_presence(LinphoneFriendList *list, LinphonePresenceModel *presence); + +/** + * Get the URI associated with the friend list. + * @param[in] list LinphoneFriendList object. + * @return The URI associated with the friend list. +**/ +LINPHONE_PUBLIC const char * linphone_friend_list_get_uri(const LinphoneFriendList *list); + +/** + * Set the URI associated with the friend list. + * @param[in] list LinphoneFriendList object. + * @param[in] rls_uri The URI to associate with the friend list. +**/ +LINPHONE_PUBLIC void linphone_friend_list_set_uri(LinphoneFriendList *list, const char *uri); + +/** + * Sets the revision from the last synchronization. + * @param[in] list LinphoneFriendList object. + * @param[in] rev The revision + */ +void linphone_friend_list_update_revision(LinphoneFriendList *list, int rev); + +/** + * An object to handle the callbacks for LinphoneFriend synchronization. +**/ +typedef struct _LinphoneFriendListCbs LinphoneFriendListCbs; + +/** + * Callback used to notify a new contact has been created on the CardDAV server and downloaded locally + * @param list The LinphoneFriendList object the new contact is added to + * @param lf The LinphoneFriend object that has been created +**/ +typedef void (*LinphoneFriendListCbsContactCreatedCb)(LinphoneFriendList *list, LinphoneFriend *lf); + +/** + * Callback used to notify a contact has been deleted on the CardDAV server + * @param list The LinphoneFriendList object a contact has been removed from + * @param lf The LinphoneFriend object that has been deleted +**/ +typedef void (*LinphoneFriendListCbsContactDeletedCb)(LinphoneFriendList *list, LinphoneFriend *lf); + +/** + * Callback used to notify a contact has been updated on the CardDAV server + * @param list The LinphoneFriendList object in which a contact has been updated + * @param new_friend The new LinphoneFriend object corresponding to the updated contact + * @param old_friend The old LinphoneFriend object before update +**/ +typedef void (*LinphoneFriendListCbsContactUpdatedCb)(LinphoneFriendList *list, LinphoneFriend *new_friend, LinphoneFriend *old_friend); + +/** + * Callback used to notify the status of the synchronization has changed + * @param list The LinphoneFriendList object for which the status has changed + * @param status The new synchronisation status + * @param msg An additional information on the status update +**/ +typedef void (*LinphoneFriendListCbsSyncStateChangedCb)(LinphoneFriendList *list, LinphoneFriendListSyncStatus status, const char *msg); + +/** + * Get the LinphoneFriendListCbs object associated with a LinphoneFriendList. + * @param[in] request LinphoneXmlRpcRequest object + * @return The LinphoneFriendListCbs object associated with the LinphoneFriendList. +**/ +LINPHONE_PUBLIC LinphoneFriendListCbs * linphone_friend_list_get_callbacks(const LinphoneFriendList *list); + +/** + * Acquire a reference to a LinphoneFriendListCbs object. + * @param[in] cbs LinphoneFriendListCbs object. + * @return The same LinphoneFriendListCbs object. +**/ +LINPHONE_PUBLIC LinphoneFriendListCbs * linphone_friend_list_cbs_ref(LinphoneFriendListCbs *cbs); + +/** + * Release a reference to a LinphoneFriendListCbs object. + * @param[in] cbs LinphoneFriendListCbs object. +**/ +LINPHONE_PUBLIC void linphone_friend_list_cbs_unref(LinphoneFriendListCbs *cbs); + +/** + * Retrieve the user pointer associated with a LinphoneFriendListCbs object. + * @param[in] cbs LinphoneFriendListCbs object. + * @return The user pointer associated with the LinphoneFriendListCbs object. +**/ +LINPHONE_PUBLIC void *linphone_friend_list_cbs_get_user_data(const LinphoneFriendListCbs *cbs); + +/** + * Assign a user pointer to a LinphoneFriendListCbs object. + * @param[in] cbs LinphoneFriendListCbs object. + * @param[in] ud The user pointer to associate with the LinphoneFriendListCbs object. +**/ +LINPHONE_PUBLIC void linphone_friend_list_cbs_set_user_data(LinphoneFriendListCbs *cbs, void *ud); + +/** + * Get the contact created callback. + * @param[in] cbs LinphoneFriendListCbs object. + * @return The current contact created callback. +**/ +LINPHONE_PUBLIC LinphoneFriendListCbsContactCreatedCb linphone_friend_list_cbs_get_contact_created(const LinphoneFriendListCbs *cbs); + +/** + * Set the contact created callback. + * @param[in] cbs LinphoneFriendListCbs object. + * @param[in] cb The contact created to be used. +**/ +LINPHONE_PUBLIC void linphone_friend_list_cbs_set_contact_created(LinphoneFriendListCbs *cbs, LinphoneFriendListCbsContactCreatedCb cb); + +/** + * Get the contact deleted callback. + * @param[in] cbs LinphoneFriendListCbs object. + * @return The current contact deleted callback. +**/ +LINPHONE_PUBLIC LinphoneFriendListCbsContactDeletedCb linphone_friend_list_cbs_get_contact_deleted(const LinphoneFriendListCbs *cbs); + +/** + * Set the contact deleted callback. + * @param[in] cbs LinphoneFriendListCbs object. + * @param[in] cb The contact deleted to be used. +**/ +LINPHONE_PUBLIC void linphone_friend_list_cbs_set_contact_deleted(LinphoneFriendListCbs *cbs, LinphoneFriendListCbsContactDeletedCb cb); + +/** + * Get the contact updated callback. + * @param[in] cbs LinphoneFriendListCbs object. + * @return The current contact updated callback. +**/ +LINPHONE_PUBLIC LinphoneFriendListCbsContactUpdatedCb linphone_friend_list_cbs_get_contact_updated(const LinphoneFriendListCbs *cbs); + +/** + * Set the contact updated callback. + * @param[in] cbs LinphoneFriendListCbs object. + * @param[in] cb The contact updated to be used. +**/ +LINPHONE_PUBLIC void linphone_friend_list_cbs_set_contact_updated(LinphoneFriendListCbs *cbs, LinphoneFriendListCbsContactUpdatedCb cb); + +/** + * Get the sync status changed callback. + * @param[in] cbs LinphoneFriendListCbs object. + * @return The current sync status changedcallback. +**/ +LINPHONE_PUBLIC LinphoneFriendListCbsSyncStateChangedCb linphone_friend_list_cbs_get_sync_status_changed(const LinphoneFriendListCbs *cbs); + +/** + * Set the contact updated callback. + * @param[in] cbs LinphoneFriendListCbs object. + * @param[in] cb The sync status changed to be used. +**/ +LINPHONE_PUBLIC void linphone_friend_list_cbs_set_sync_status_changed(LinphoneFriendListCbs *cbs, LinphoneFriendListCbsSyncStateChangedCb cb); + +/** + * Starts a CardDAV synchronization using value set using linphone_friend_list_set_uri. + * @param[in] list LinphoneFriendList object. + */ +LINPHONE_PUBLIC void linphone_friend_list_synchronize_friends_from_server(LinphoneFriendList *list); + +/** + * Goes through all the LinphoneFriend that are dirty and does a CardDAV PUT to update the server. + * @param[in] list LinphoneFriendList object. + */ +void linphone_friend_list_update_dirty_friends(LinphoneFriendList *list); + +/** + * Returns the LinphoneCore object attached to this LinphoneFriendList. + * @param[in] list LinphoneFriendList object. + * @return a LinphoneCore object + */ +LINPHONE_PUBLIC LinphoneCore* linphone_friend_list_get_core(LinphoneFriendList *list); + +/** + * Creates and adds LinphoneFriend objects to LinphoneFriendList from a file that contains the vCard(s) to parse + * @param[in] list the LinphoneFriendList object + * @param[in] vcard_file the path to a file that contains the vCard(s) to parse + * @return the amount of linphone friends created + */ +LINPHONE_PUBLIC int linphone_friend_list_import_friends_from_vcard4_file(LinphoneFriendList *list, const char *vcard_file); + +/** + * Creates and adds LinphoneFriend objects to LinphoneFriendList from a buffer that contains the vCard(s) to parse + * @param[in] list the LinphoneFriendList object + * @param[in] vcard_buffer the buffer that contains the vCard(s) to parse + * @return the amount of linphone friends created + */ +LINPHONE_PUBLIC int linphone_friend_list_import_friends_from_vcard4_buffer(LinphoneFriendList *list, const char *vcard_buffer); + +/** + * Creates and export LinphoneFriend objects from LinphoneFriendList to a file using vCard 4 format + * @param[in] list the LinphoneFriendList object + * @param[in] vcard_file the path to a file that will contain the vCards + */ +LINPHONE_PUBLIC void linphone_friend_list_export_friends_as_vcard4_file(LinphoneFriendList *list, const char *vcard_file); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* LINPHONE_FRIENDLIST_H_ */ diff --git a/coreapi/gitversion.cmake b/coreapi/gitversion.cmake index eb0f99e3c..bb20ebae5 100644 --- a/coreapi/gitversion.cmake +++ b/coreapi/gitversion.cmake @@ -21,14 +21,32 @@ ############################################################################ if(GIT_EXECUTABLE) - execute_process( - COMMAND ${GIT_EXECUTABLE} describe --always - WORKING_DIRECTORY ${WORK_DIR} - OUTPUT_VARIABLE GIT_REVISION - OUTPUT_STRIP_TRAILING_WHITESPACE - ) -else() - set(GIT_REVISION "unknown") + macro(GIT_COMMAND OUTPUT_VAR) + set(GIT_ARGS ${ARGN}) + execute_process( + COMMAND ${GIT_EXECUTABLE} ${ARGN} + WORKING_DIRECTORY ${WORK_DIR} + OUTPUT_VARIABLE ${OUTPUT_VAR} + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + endmacro() + + GIT_COMMAND(GIT_DESCRIBE describe --always) + GIT_COMMAND(GIT_TAG describe --abbrev=0) + GIT_COMMAND(GIT_REVISION rev-parse HEAD) endif() -configure_file("${WORK_DIR}/gitversion.h.in" "${OUTPUT_DIR}/liblinphone_gitversion.h" @ONLY) +if(GIT_DESCRIBE) + if(NOT GIT_TAG STREQUAL LINPHONE_VERSION) + message(FATAL_ERROR "LINPHONE_VERSION and git tag differ. Please put them identical") + endif() + set(LIBLINPHONE_GIT_VERSION "${GIT_DESCRIBE}") + configure_file("${WORK_DIR}/gitversion.h.in" "${OUTPUT_DIR}/liblinphone_gitversion.h" @ONLY) +elseif(GIT_REVISION) + set(LIBLINPHONE_GIT_VERSION "${LINPHONE_VERSION}_${GIT_REVISION}") + configure_file("${WORK_DIR}/gitversion.h.in" "${OUTPUT_DIR}/liblinphone_gitversion.h" @ONLY) +else() + if(NOT EXISTS "${OUTPUT_DIR}/liblinphone_gitversion.h") + execute_process(COMMAND ${CMAKE_COMMAND} -E touch "${OUTPUT_DIR}/liblinphone_gitversion.h") + endif() +endif() diff --git a/coreapi/gitversion.h.in b/coreapi/gitversion.h.in index 493b4bcb1..c4664950f 100644 --- a/coreapi/gitversion.h.in +++ b/coreapi/gitversion.h.in @@ -18,4 +18,4 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#define LIBLINPHONE_GIT_VERSION "@GIT_REVISION@" \ No newline at end of file +#define LIBLINPHONE_GIT_VERSION "@LIBLINPHONE_GIT_VERSION@" \ No newline at end of file diff --git a/coreapi/help/CMakeLists.txt b/coreapi/help/CMakeLists.txt index 3a550e294..f3062f307 100644 --- a/coreapi/help/CMakeLists.txt +++ b/coreapi/help/CMakeLists.txt @@ -38,7 +38,7 @@ if(DOXYGEN_FOUND) ) add_custom_target(linphone-doc ALL DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/doc/html/index.html") install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/doc/html" "${CMAKE_CURRENT_BINARY_DIR}/doc/xml" - DESTINATION "share/doc/linphone-${LINPHONE_VERSION}") + DESTINATION "${CMAKE_INSTALL_DATADIR}/doc/linphone-${LINPHONE_VERSION}") else() message(WARNING "The dot program is needed to generate the linphone documentation. You can get it from http://www.graphviz.org/.") endif() diff --git a/coreapi/help/Doxyfile.in b/coreapi/help/Doxyfile.in index 12facea9b..6692ba25e 100644 --- a/coreapi/help/Doxyfile.in +++ b/coreapi/help/Doxyfile.in @@ -1,233 +1,2412 @@ -# Doxyfile 1.4.1 +# Doxyfile 1.8.11 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- -PROJECT_NAME = liblinphone + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all text +# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv +# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv +# for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = Liblinphone + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + PROJECT_NUMBER = @LINPHONE_VERSION@ + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + OUTPUT_DIRECTORY = doc + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + OUTPUT_LANGUAGE = English -USE_WINDOWS_ENCODING = NO + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + REPEAT_BRIEF = YES -ABBREVIATE_BRIEF = "The $name class" \ - "The $name widget" \ - "The $name file" \ - is \ - provides \ - specifies \ - contains \ - represents \ - a \ - an \ - the + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + FULL_PATH_NAMES = NO -STRIP_FROM_PATH = -STRIP_FROM_INC_PATH = + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + MULTILINE_CPP_IS_BRIEF = NO -DETAILS_AT_TOP = NO + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + INHERIT_DOCS = YES -DISTRIBUTE_GROUP_DOC = NO + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + TAB_SIZE = 8 -ALIASES = mslist{1}="A list of \ref \1 objects. \xmlonly \1 \endxmlonly" + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines. + +ALIASES = "mslist{1}=A list of \ref \1 objects. \xmlonly \1 \endxmlonly" + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: +# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: +# Fortran. In the later case the parser tries to guess whether the code is fixed +# or free formatted code, this is the default for Fortran type files), VHDL. For +# instance to make doxygen treat .inc files as Fortran files (default is PHP), +# and .f files as C (default is Fortran), use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- -EXTRACT_ALL = NO -EXTRACT_PRIVATE = NO -EXTRACT_STATIC = NO -EXTRACT_LOCAL_CLASSES = YES -EXTRACT_LOCAL_METHODS = NO -HIDE_UNDOC_MEMBERS = YES -HIDE_UNDOC_CLASSES = YES -HIDE_FRIEND_COMPOUNDS = NO -HIDE_IN_BODY_DOCS = NO -INTERNAL_DOCS = NO -CASE_SENSE_NAMES = YES -HIDE_SCOPE_NAMES = NO -SHOW_INCLUDE_FILES = YES -INLINE_INFO = YES -SORT_MEMBER_DOCS = NO -SORT_BRIEF_DOCS = NO -SORT_BY_SCOPE_NAME = NO -GENERATE_TODOLIST = YES -GENERATE_TESTLIST = YES -GENERATE_BUGLIST = YES -GENERATE_DEPRECATEDLIST= YES -ENABLED_SECTIONS = -MAX_INITIALIZER_LINES = 30 -SHOW_USED_FILES = YES -SHOW_DIRECTORIES = YES -FILE_VERSION_FILTER = -#--------------------------------------------------------------------------- -# configuration options related to warning and progress messages -#--------------------------------------------------------------------------- -QUIET = NO -WARNINGS = YES -WARN_IF_UNDOCUMENTED = YES -WARN_IF_DOC_ERROR = YES -WARN_NO_PARAMDOC = NO -WARN_FORMAT = "$file:$line: $text" -WARN_LOGFILE = -#--------------------------------------------------------------------------- -# configuration options related to the input files -#--------------------------------------------------------------------------- -INPUT = @top_srcdir@/coreapi @top_srcdir@/coreapi/help -FILE_PATTERNS = *.h \ - *.c \ +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO, these declarations will be +# included in the documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES, upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. +# The default value is: system dependent. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = YES + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = NO + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong or incomplete +# parameter documentation, but not about the absence of documentation. +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = @top_srcdir@/coreapi \ + @top_srcdir@/coreapi/help + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: http://www.gnu.org/software/libiconv) for the list of +# possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, +# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, +# *.vhdl, *.ucf, *.qsf, *.as and *.js. + +FILE_PATTERNS = *.c \ + *.h \ *.dox + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + RECURSIVE = NO -EXCLUDE = + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + EXCLUDE_SYMLINKS = NO -EXCLUDE_PATTERNS = -EXAMPLE_PATH = @top_srcdir@ @top_srcdir@/coreapi/help -EXAMPLE_PATTERNS = + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = @top_srcdir@ \ + @top_srcdir@/coreapi/help + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + EXAMPLE_RECURSIVE = NO -IMAGE_PATH = -INPUT_FILTER = -FILTER_PATTERNS = + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + #--------------------------------------------------------------------------- -# configuration options related to source browsing +# Configuration options related to source browsing #--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# function all documented functions referencing it will be listed. +# The default value is: NO. + REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see http://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + VERBATIM_HEADERS = NO + #--------------------------------------------------------------------------- -# configuration options related to the alphabetical class index +# Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + ALPHABETICAL_INDEX = NO + +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + COLS_IN_ALPHA_INDEX = 5 -IGNORE_PREFIX = + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + #--------------------------------------------------------------------------- -# configuration options related to the HTML output +# Configuration options related to the HTML output #--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + HTML_FILE_EXTENSION = .html -HTML_HEADER = -HTML_FOOTER = -HTML_STYLESHEET = -HTML_ALIGN_MEMBERS = YES + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: http://developer.apple.com/tools/xcode/), introduced with +# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + GENERATE_HTMLHELP = NO -CHM_FILE = -HHC_LOCATION = + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the master .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + DISABLE_INDEX = NO -ENUM_VALUES_PER_LINE = 1 + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 1 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + TREEVIEW_WIDTH = 250 -#--------------------------------------------------------------------------- -# configuration options related to the LaTeX output -#--------------------------------------------------------------------------- -GENERATE_LATEX = YES -LATEX_OUTPUT = latex -LATEX_CMD_NAME = latex -MAKEINDEX_CMD_NAME = makeindex -COMPACT_LATEX = NO -PAPER_TYPE = a4wide -EXTRA_PACKAGES = -LATEX_HEADER = -PDF_HYPERLINKS = NO -USE_PDFLATEX = NO -LATEX_BATCHMODE = NO -LATEX_HIDE_INDICES = NO -#--------------------------------------------------------------------------- -# configuration options related to the RTF output -#--------------------------------------------------------------------------- -GENERATE_RTF = NO -RTF_OUTPUT = rtf -COMPACT_RTF = NO -RTF_HYPERLINKS = NO -RTF_STYLESHEET_FILE = -RTF_EXTENSIONS_FILE = -#--------------------------------------------------------------------------- -# configuration options related to the man page output -#--------------------------------------------------------------------------- -GENERATE_MAN = YES -MAN_OUTPUT = man -MAN_EXTENSION = .3 -MAN_LINKS = NO -#--------------------------------------------------------------------------- -# configuration options related to the XML output -#--------------------------------------------------------------------------- -GENERATE_XML = YES -XML_OUTPUT = xml -XML_SCHEMA = -XML_DTD = -XML_PROGRAMLISTING = YES -#--------------------------------------------------------------------------- -# configuration options for the AutoGen Definitions output -#--------------------------------------------------------------------------- -GENERATE_AUTOGEN_DEF = NO -#--------------------------------------------------------------------------- -# configuration options related to the Perl module output -#--------------------------------------------------------------------------- -GENERATE_PERLMOD = NO -PERLMOD_LATEX = NO -PERLMOD_PRETTY = YES -PERLMOD_MAKEVAR_PREFIX = -#--------------------------------------------------------------------------- -# Configuration options related to the preprocessor -#--------------------------------------------------------------------------- -ENABLE_PREPROCESSING = YES -MACRO_EXPANSION = YES -EXPAND_ONLY_PREDEF = NO -SEARCH_INCLUDES = YES -INCLUDE_PATH = . -INCLUDE_FILE_PATTERNS = *.h -PREDEFINED = DOXYGEN MS2_PUBLIC= LINPHONE_PUBLIC= -EXPAND_AS_DEFINED = -SKIP_FUNCTION_MACROS = YES -#--------------------------------------------------------------------------- -# Configuration::additions related to external references -#--------------------------------------------------------------------------- -TAGFILES = -GENERATE_TAGFILE = -ALLEXTERNALS = NO -EXTERNAL_GROUPS = YES -PERL_PATH = /usr/bin/perl -#--------------------------------------------------------------------------- -# Configuration options related to the dot tool -#--------------------------------------------------------------------------- -CLASS_DIAGRAMS = YES -HIDE_UNDOC_RELATIONS = YES -HAVE_DOT = NO -CLASS_GRAPH = YES -COLLABORATION_GRAPH = YES -GROUP_GRAPHS = YES -UML_LOOK = NO -TEMPLATE_RELATIONS = YES -INCLUDE_GRAPH = YES -INCLUDED_BY_GRAPH = YES -CALL_GRAPH = NO -GRAPHICAL_HIERARCHY = YES -DIRECTORY_GRAPH = YES -DOT_IMAGE_FORMAT = png -DOT_PATH = -DOTFILE_DIRS = -MAX_DOT_GRAPH_WIDTH = 1024 -MAX_DOT_GRAPH_HEIGHT = 1024 -MAX_DOT_GRAPH_DEPTH = 1000 -DOT_TRANSPARENT = NO -DOT_MULTI_TARGETS = NO -GENERATE_LEGEND = YES -DOT_CLEANUP = YES -#--------------------------------------------------------------------------- -# Configuration::additions related to the search engine -#--------------------------------------------------------------------------- + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# http://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from http://www.mathjax.org before deployment. +# The default value is: http://cdn.mathjax.org/mathjax/latest. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /