diff --git a/coreapi/call_log.c b/coreapi/call_log.c index d0f67d781..47e689b1a 100644 --- a/coreapi/call_log.c +++ b/coreapi/call_log.c @@ -238,6 +238,12 @@ char * linphone_call_log_to_str(LinphoneCallLog *cl){ case LinphoneCallMissed: status=_("missed"); break; + case LinphoneCallAcceptedElsewhere: + status=_("answered elsewhere"); + break; + case LinphoneCallDeclinedElsewhere: + status=_("declined elsewhere"); + break; default: status=_("unknown"); } diff --git a/coreapi/chat.c b/coreapi/chat.c index 2d0a9dc18..b0b2ef27d 100644 --- a/coreapi/chat.c +++ b/coreapi/chat.c @@ -22,6 +22,10 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +#include +#include +#include + #include "linphone/core.h" #include "private.h" #include "linphone/lpconfig.h" @@ -29,22 +33,22 @@ #include "ortp/b64.h" #include "linphone/wrapper_utils.h" -#include -#include -#include +#include "c-wrapper/wrapper.h" +#include "chat/chat-room-p.h" +#include "chat/chat-room.h" +#include "utils/content-type.h" +#include "utils/utils.h" -#define COMPOSING_DEFAULT_IDLE_TIMEOUT 15 -#define COMPOSING_DEFAULT_REFRESH_TIMEOUT 60 -#define COMPOSING_DEFAULT_REMOTE_REFRESH_TIMEOUT 120 -static void linphone_chat_message_release(LinphoneChatMessage *msg); -static void linphone_chat_room_delete_composing_idle_timer(LinphoneChatRoom *cr); -static void linphone_chat_room_delete_composing_refresh_timer(LinphoneChatRoom *cr); -static void linphone_chat_room_delete_remote_composing_refresh_timer(LinphoneChatRoom *cr); +struct _LinphoneChatRoom{ + belle_sip_object_t base; + void *user_data; + LinphonePrivate::ChatRoom *cr; +}; + +BELLE_SIP_DECLARE_VPTR_NO_EXPORT(LinphoneChatRoom); + static void _linphone_chat_message_destroy(LinphoneChatMessage *msg); -static void linphone_chat_room_notify_is_composing(LinphoneChatRoom *cr, const char *text); -static void linphone_chat_room_notify_imdn(LinphoneChatRoom *cr, const char *text); -static void linphone_chat_message_deactivate(LinphoneChatMessage *msg); BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(LinphoneChatMessageCbs); @@ -116,31 +120,6 @@ void linphone_chat_message_cbs_set_file_transfer_progress_indication( BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(LinphoneChatMessage); -static void _linphone_chat_room_destroy(LinphoneChatRoom *cr) { - linphone_chat_room_delete_composing_idle_timer(cr); - linphone_chat_room_delete_composing_refresh_timer(cr); - linphone_chat_room_delete_remote_composing_refresh_timer(cr); - bctbx_list_free_with_data(cr->transient_messages, (bctbx_list_free_func)linphone_chat_message_release); - if (cr->weak_messages != NULL) bctbx_list_free(cr->weak_messages); - if (cr->received_rtt_characters) { - cr->received_rtt_characters = bctbx_list_free_with_data(cr->received_rtt_characters, (bctbx_list_free_func)ms_free); - } - if (cr->lc != NULL) { - if (bctbx_list_find(cr->lc->chatrooms, cr)) { - ms_error("LinphoneChatRoom[%p] is destroyed while still being used by the LinphoneCore. This is abnormal." - " linphone_core_get_chat_room() doesn't give a reference, there is no need to call " - "linphone_chat_room_unref(). " - "In order to remove a chat room from the core, use linphone_core_delete_chat_room().", - cr); - cr->lc->chatrooms = bctbx_list_remove(cr->lc->chatrooms, cr); - } - } - linphone_address_unref(cr->peer_url); - if (cr->pending_message) - linphone_chat_message_destroy(cr->pending_message); - ms_free(cr->peer); -} - void linphone_chat_message_set_state(LinphoneChatMessage *msg, LinphoneChatMessageState state) { /* do not invoke callbacks on orphan messages */ if (state != msg->state && msg->chat_room != NULL) { @@ -184,24 +163,25 @@ const bctbx_list_t *linphone_core_get_chat_rooms(LinphoneCore *lc) { } static bool_t linphone_chat_room_matches(LinphoneChatRoom *cr, const LinphoneAddress *from) { - return linphone_address_weak_equal(cr->peer_url, from); + return linphone_address_weak_equal(cr->cr->getPeerAddress(), from); } BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(LinphoneChatRoom); +static void _linphone_chat_room_destroy(LinphoneChatRoom *cr) { + delete cr->cr; +} + BELLE_SIP_INSTANCIATE_VPTR(LinphoneChatRoom, belle_sip_object_t, (belle_sip_object_destroy_t)_linphone_chat_room_destroy, NULL, // clone NULL, // marshal FALSE); -static LinphoneChatRoom *_linphone_core_create_chat_room_base(LinphoneCore *lc, LinphoneAddress *addr){ +LinphoneChatRoom *_linphone_core_create_chat_room_base(LinphoneCore *lc, LinphoneAddress *addr){ LinphoneChatRoom *cr = belle_sip_object_new(LinphoneChatRoom); - cr->lc = lc; - cr->peer = linphone_address_as_string(addr); - cr->peer_url = addr; - cr->unread_count = -1; - cr->received_rtt_characters = NULL; + cr->cr = new LinphonePrivate::ChatRoom(lc, addr); + L_GET_PRIVATE(cr->cr)->setCBackPointer(cr); return cr; } @@ -214,7 +194,7 @@ static LinphoneChatRoom *_linphone_core_create_chat_room(LinphoneCore *lc, Linph LinphoneChatRoom *_linphone_core_create_chat_room_from_call(LinphoneCall *call){ LinphoneChatRoom *cr = _linphone_core_create_chat_room_base(call->core, linphone_address_clone(linphone_call_get_remote_address(call))); - cr->call = call; + linphone_chat_room_set_call(cr, call); return cr; } @@ -265,7 +245,7 @@ LinphoneChatRoom *linphone_core_get_chat_room(LinphoneCore *lc, const LinphoneAd void linphone_core_delete_chat_room(LinphoneCore *lc, LinphoneChatRoom *cr) { if (bctbx_list_find(lc->chatrooms, cr)) { - lc->chatrooms = bctbx_list_remove(cr->lc->chatrooms, cr); + lc->chatrooms = bctbx_list_remove(lc->chatrooms, cr); linphone_chat_room_delete_history(cr); linphone_chat_room_unref(cr); } else { @@ -277,57 +257,12 @@ LinphoneChatRoom *linphone_core_get_chat_room_from_uri(LinphoneCore *lc, const c return _linphone_core_get_or_create_chat_room(lc, to); } -static void linphone_chat_room_delete_composing_idle_timer(LinphoneChatRoom *cr) { - if (cr->composing_idle_timer) { - if (cr->lc && cr->lc->sal) - sal_cancel_timer(cr->lc->sal, cr->composing_idle_timer); - belle_sip_object_unref(cr->composing_idle_timer); - cr->composing_idle_timer = NULL; - } -} - -static void linphone_chat_room_delete_composing_refresh_timer(LinphoneChatRoom *cr) { - if (cr->composing_refresh_timer) { - if (cr->lc && cr->lc->sal) - sal_cancel_timer(cr->lc->sal, cr->composing_refresh_timer); - belle_sip_object_unref(cr->composing_refresh_timer); - cr->composing_refresh_timer = NULL; - } -} - -static void linphone_chat_room_delete_remote_composing_refresh_timer(LinphoneChatRoom *cr) { - if (cr->remote_composing_refresh_timer) { - if (cr->lc && cr->lc->sal) - sal_cancel_timer(cr->lc->sal, cr->remote_composing_refresh_timer); - belle_sip_object_unref(cr->remote_composing_refresh_timer); - cr->remote_composing_refresh_timer = NULL; - } -} - void linphone_chat_room_destroy(LinphoneChatRoom *cr) { linphone_chat_room_unref(cr); } void linphone_chat_room_release(LinphoneChatRoom *cr) { - linphone_chat_room_delete_composing_idle_timer(cr); - linphone_chat_room_delete_composing_refresh_timer(cr); - linphone_chat_room_delete_remote_composing_refresh_timer(cr); - bctbx_list_for_each(cr->weak_messages, (bctbx_list_iterate_func)linphone_chat_message_deactivate); - bctbx_list_for_each(cr->transient_messages, (bctbx_list_iterate_func)linphone_chat_message_deactivate); - cr->lc = NULL; - linphone_chat_room_unref(cr); -} - -static void on_weak_message_destroy(void *obj, belle_sip_object_t *message_being_destroyed) { - LinphoneChatRoom *cr = (LinphoneChatRoom *)obj; - cr->weak_messages = bctbx_list_remove(cr->weak_messages, message_being_destroyed); -} - -void linphone_chat_room_add_weak_message(LinphoneChatRoom *cr, LinphoneChatMessage *cm) { - bctbx_list_t *item = bctbx_list_find(cr->weak_messages, cm); - if (item == NULL) { - cr->weak_messages = bctbx_list_append(cr->weak_messages, belle_sip_object_weak_ref(cm, on_weak_message_destroy, cr)); - } + L_GET_PRIVATE(cr->cr)->release(); } LinphoneChatRoom *linphone_chat_room_ref(LinphoneChatRoom *cr) { @@ -347,183 +282,8 @@ void linphone_chat_room_set_user_data(LinphoneChatRoom *cr, void *ud) { cr->user_data = ud; } -void linphone_chat_room_add_transient_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg) { - if (bctbx_list_find(msg->chat_room->transient_messages, msg) == NULL) { - cr->transient_messages = bctbx_list_append(cr->transient_messages, linphone_chat_message_ref(msg)); - } -} - void linphone_chat_room_remove_transient_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg) { - if (bctbx_list_find(msg->chat_room->transient_messages, msg) != NULL) { - cr->transient_messages = bctbx_list_remove(cr->transient_messages, msg); - linphone_chat_message_unref(msg); - } -} - -static void store_or_update_chat_message(LinphoneChatMessage *msg) { - if (msg->storage_id != 0) { - /* The message has already been stored (probably because of file transfer), update it */ - linphone_chat_message_store_update(msg); - } else { - /* Store the new message */ - msg->storage_id = linphone_chat_message_store(msg); - } -} - -void _linphone_chat_room_send_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg) { - int retval = -1; - LinphoneCore *lc = cr->lc; - LinphoneImEncryptionEngine *imee = lc->im_encryption_engine; - - /*stubed rtt text*/ - if (cr->call && linphone_call_params_realtime_text_enabled(linphone_call_get_current_params(cr->call))) { - uint32_t new_line = 0x2028; - linphone_chat_message_put_char(msg, new_line); // New Line - linphone_chat_message_unref(msg); - return; - } - - msg->dir = LinphoneChatMessageOutgoing; - - /* Check if we shall upload a file to a server */ - if (msg->file_transfer_information != NULL && msg->content_type == NULL) { - /* open a transaction with the server and send an empty request(RCS5.1 section 3.5.4.8.3.1) */ - if (linphone_chat_room_upload_file(msg) == 0) { - /* Add to transient list only if message is going out */ - linphone_chat_room_add_transient_message(cr, msg); - /* Store the message so that even if the upload is stopped, it can be done again */ - msg->storage_id = linphone_chat_message_store(msg); - } else { - linphone_chat_message_unref(msg); - return; - } - } else { - SalOp *op = msg->op; - LinphoneCall *call=NULL; - char *content_type; - const char *identity = NULL; - char *clear_text_message = NULL; - char *clear_text_content_type = NULL; - - if (msg->message) { - clear_text_message = ms_strdup(msg->message); - } - if (msg->content_type) { - clear_text_content_type = ms_strdup(msg->content_type); - } - - /* Add to transient list */ - linphone_chat_room_add_transient_message(cr, msg); - msg->time = ms_time(0); - if (lp_config_get_int(cr->lc->config, "sip", "chat_use_call_dialogs", 0) != 0) { - if ((call = linphone_core_get_call_by_remote_address(cr->lc, cr->peer)) != NULL) { - if (call->state == LinphoneCallConnected || call->state == LinphoneCallStreamsRunning || - call->state == LinphoneCallPaused || call->state == LinphoneCallPausing || - call->state == LinphoneCallPausedByRemote) { - ms_message("send SIP msg through the existing call."); - op = call->op; - identity = linphone_core_find_best_identity(cr->lc, linphone_call_get_remote_address(call)); - } - } - } - - if (!identity) { - LinphoneProxyConfig *proxy = linphone_core_lookup_known_proxy(cr->lc, cr->peer_url); - if (proxy) { - identity = linphone_proxy_config_get_identity(proxy); - } else { - identity = linphone_core_get_primary_contact(cr->lc); - } - } - if (msg->from){ - /* - * BUG - * the file transfer message constructor sets the from, but doesn't do it as well as here. - */ - linphone_address_unref(msg->from); - } - msg->from = linphone_address_new(identity); - - if (imee) { - LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee); - LinphoneImEncryptionEngineCbsOutgoingMessageCb cb_process_outgoing_message = linphone_im_encryption_engine_cbs_get_process_outgoing_message(imee_cbs); - if (cb_process_outgoing_message) { - retval = cb_process_outgoing_message(imee, cr, msg); - if(retval == 0) { - msg->is_secured = TRUE; - } - } - } - - if (op == NULL) { - /*sending out of calls*/ - msg->op = op = sal_op_new(cr->lc->sal); - linphone_configure_op(cr->lc, op, cr->peer_url, msg->custom_headers, - lp_config_get_int(cr->lc->config, "sip", "chat_msg_with_contact", 0)); - sal_op_set_user_pointer(op, msg); /*if out of call, directly store msg*/ - } - - if (retval > 0) { - sal_error_info_set((SalErrorInfo *)sal_op_get_error_info(op), SalReasonNotAcceptable, "SIP", retval, "Unable to encrypt IM", NULL); - store_or_update_chat_message(msg); - linphone_chat_message_update_state(msg, LinphoneChatMessageStateNotDelivered); - linphone_chat_message_unref(msg); - return; - } - - if (msg->external_body_url) { - content_type = ms_strdup_printf("message/external-body; access-type=URL; URL=\"%s\"", msg->external_body_url); - sal_message_send(op, identity, cr->peer, content_type, NULL, NULL); - ms_free(content_type); - } else { - char *peer_uri = linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr)); - const char *content_type = msg->content_type; - if (content_type == NULL) { - sal_text_send(op, identity, cr->peer, msg->message); - } else { - sal_message_send(op, identity, cr->peer, content_type, msg->message, peer_uri); - } - ms_free(peer_uri); - } - - if (msg->message && clear_text_message && strcmp(msg->message, clear_text_message) != 0) { - // We replace the encrypted message by the original one so it can be correctly stored and displayed by the application - ms_free(msg->message); - msg->message = ms_strdup(clear_text_message); - } - if (msg->content_type && clear_text_content_type && (strcmp(msg->content_type, clear_text_content_type) != 0)) { - /* We replace the encrypted content type by the original one */ - ms_free(msg->content_type); - msg->content_type = ms_strdup(clear_text_content_type); - } - msg->message_id = ms_strdup(sal_op_get_call_id(op)); /* must be known at that time */ - store_or_update_chat_message(msg); - - if (cr->is_composing == LinphoneIsComposingActive) { - cr->is_composing = LinphoneIsComposingIdle; - } - linphone_chat_room_delete_composing_idle_timer(cr); - linphone_chat_room_delete_composing_refresh_timer(cr); - - if (clear_text_message) { - ms_free(clear_text_message); - } - if (clear_text_content_type) { - ms_free(clear_text_content_type); - } - - if (call && call->op == op) { - /*In this case, chat delivery status is not notified, so unrefing chat message right now*/ - /*Might be better fixed by delivering status, but too costly for now*/ - linphone_chat_room_remove_transient_message(msg->chat_room, msg); - linphone_chat_message_unref(msg); - return; - } - } - // if operation failed, we should not change message state - if (msg->dir == LinphoneChatMessageOutgoing) { - linphone_chat_message_set_state(msg, LinphoneChatMessageStateInProgress); - } + L_GET_PRIVATE(cr->cr)->removeTransientMessage(msg); } void linphone_chat_message_update_state(LinphoneChatMessage *msg, LinphoneChatMessageState new_state) { @@ -531,50 +291,15 @@ void linphone_chat_message_update_state(LinphoneChatMessage *msg, LinphoneChatMe linphone_chat_message_store_state(msg); if (msg->state == LinphoneChatMessageStateDelivered || msg->state == LinphoneChatMessageStateNotDelivered) { - if (bctbx_list_find(msg->chat_room->transient_messages, msg) != NULL) { - // msg is not transient anymore, we can remove it from our transient list and unref it - linphone_chat_room_add_weak_message(msg->chat_room, msg); - linphone_chat_room_remove_transient_message(msg->chat_room, msg); - } else { - // msg has already been removed from the transient messages, do nothing. */ - } + L_GET_PRIVATE(msg->chat_room->cr)->moveTransientMessageToWeakMessages(msg); } } void linphone_chat_room_send_message(LinphoneChatRoom *cr, const char *msg) { - _linphone_chat_room_send_message(cr, linphone_chat_room_create_message(cr, msg)); + cr->cr->sendMessage(cr->cr->createMessage(msg)); } -static bool_t is_file_transfer(const char *content_type) { - return (strcmp("application/vnd.gsma.rcs-ft-http+xml", content_type) == 0); -} - -static bool_t is_im_iscomposing(const char* content_type) { - return (strcmp("application/im-iscomposing+xml", content_type) == 0); -} - -static bool_t is_imdn(const char *content_type) { - return (strcmp("message/imdn+xml", content_type) == 0); -} - -static bool_t is_text(const char *content_type) { - return (strcmp("text/plain", content_type) == 0); -} - -void linphone_chat_room_message_received(LinphoneChatRoom *cr, LinphoneCore *lc, LinphoneChatMessage *msg) { - if (msg->message) { - /*legacy API*/ - linphone_core_notify_text_message_received(lc, cr, msg->from, msg->message); - } - linphone_core_notify_message_received(lc, cr, msg); - if(!is_imdn(msg->content_type) && !is_im_iscomposing(msg->content_type)) { - cr->remote_is_composing = LinphoneIsComposingIdle; - linphone_core_notify_is_composing_received(cr->lc, cr); - linphone_chat_message_send_delivery_notification(msg, LinphoneReasonNone); - } -} - -static void create_file_transfer_information_from_vnd_gsma_rcs_ft_http_xml(LinphoneChatMessage *msg) { +void create_file_transfer_information_from_vnd_gsma_rcs_ft_http_xml(LinphoneChatMessage *msg) { xmlChar *file_url = NULL; xmlDocPtr xmlMessageBody; xmlNodePtr cur; @@ -623,7 +348,7 @@ static void create_file_transfer_information_from_vnd_gsma_rcs_ft_http_xml(Linph file_url = xmlGetProp(cur, (const xmlChar *)"url"); } - if (!xmlStrcmp(cur->name, (const xmlChar *)"file-key")) { + if (!xmlStrcmp(cur->name, (const xmlChar *)"file-key")) { /* there is a key in the msg: file has been encrypted */ /* convert the key from base 64 */ xmlChar *keyb64 = xmlNodeListGetString(xmlMessageBody, cur->xmlChildrenNode, 1); @@ -631,7 +356,7 @@ static void create_file_transfer_information_from_vnd_gsma_rcs_ft_http_xml(Linph uint8_t *keyBuffer = (uint8_t *)malloc(keyLength); /* decode the key into local key buffer */ b64::b64_decode((char *)keyb64, strlen((char *)keyb64), keyBuffer, keyLength); - linphone_content_set_key(msg->file_transfer_information, (char *)keyBuffer, keyLength); + linphone_content_set_key(msg->file_transfer_information, (char *)keyBuffer, keyLength); /* duplicate key value into the linphone content private structure */ xmlFree(keyb64); free(keyBuffer); @@ -653,285 +378,17 @@ static void create_file_transfer_information_from_vnd_gsma_rcs_ft_http_xml(Linph xmlFree(file_url); } -LinphoneReason linphone_core_message_received(LinphoneCore *lc, SalOp *op, const SalMessage *sal_msg) { - LinphoneChatRoom *cr = NULL; - LinphoneAddress *addr; - LinphoneAddress *to; - LinphoneChatMessage *msg = NULL; - LinphoneImEncryptionEngine *imee = lc->im_encryption_engine; - const SalCustomHeader *ch; - LinphoneReason reason = LinphoneReasonNone; - int retval = -1; - bool_t increase_msg_count = TRUE; - - addr = linphone_address_new(sal_msg->from); +int linphone_core_message_received(LinphoneCore *lc, SalOp *op, const SalMessage *sal_msg) { + LinphoneAddress *addr = linphone_address_new(sal_msg->from); linphone_address_clean(addr); - cr = linphone_core_get_chat_room(lc, addr); - - /* Check if this is a duplicate message */ - if ((msg = linphone_chat_room_find_message_with_dir(cr, sal_op_get_call_id(op), LinphoneChatMessageIncoming))) { - reason = lc->chat_deny_code; - goto end; - } - - msg = linphone_chat_room_create_message(cr, sal_msg->text); - linphone_chat_message_set_content_type(msg, sal_msg->content_type); - linphone_chat_message_set_from(msg, cr->peer_url); - - to = sal_op_get_to(op) ? linphone_address_new(sal_op_get_to(op)) : linphone_address_new(linphone_core_get_identity(lc)); - msg->to = to; - - msg->time = sal_msg->time; - msg->state = LinphoneChatMessageStateDelivered; - msg->dir = LinphoneChatMessageIncoming; - msg->message_id = ms_strdup(sal_op_get_call_id(op)); - - ch = sal_op_get_recv_custom_header(op); - if (ch) { - msg->custom_headers = sal_custom_header_clone(ch); - } - - if (sal_msg->url) { - linphone_chat_message_set_external_body_url(msg, sal_msg->url); - } - - if (imee) { - LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee); - LinphoneImEncryptionEngineCbsIncomingMessageCb cb_process_incoming_message = linphone_im_encryption_engine_cbs_get_process_incoming_message(imee_cbs); - if (cb_process_incoming_message) { - retval = cb_process_incoming_message(imee, cr, msg); - if(retval == 0) { - msg->is_secured = TRUE; - } else if(retval > 0) { - // Unable to decrypt message - linphone_core_notify_message_received_unable_decrypt(cr->lc, cr, msg); - reason = linphone_error_code_to_reason(retval); - linphone_chat_message_send_delivery_notification(msg, reason); - // return LinphoneReasonNone to avoid flexisip resending us a message we can't decrypt - reason = LinphoneReasonNone; - goto end; - } - } - } - - if ((retval <= 0) && (linphone_core_is_content_type_supported(lc, msg->content_type) == FALSE)) { - retval = 415; - ms_error("Unsupported MESSAGE (content-type %s not recognized)", msg->content_type); - } - - if (retval > 0) { - reason = linphone_error_code_to_reason(retval); - linphone_chat_message_send_delivery_notification(msg, reason); - goto end; - } - - if (is_file_transfer(msg->content_type)) { - create_file_transfer_information_from_vnd_gsma_rcs_ft_http_xml(msg); - linphone_chat_message_set_to_be_stored(msg, TRUE); - } else if (is_im_iscomposing(msg->content_type)) { - linphone_chat_room_notify_is_composing(cr, msg->message); - linphone_chat_message_set_to_be_stored(msg, FALSE); - increase_msg_count = FALSE; - if(lp_config_get_int(cr->lc->config, "sip", "deliver_imdn", 0) != 1) { - goto end; - } - } else if (is_imdn(msg->content_type)) { - linphone_chat_room_notify_imdn(cr, msg->message); - linphone_chat_message_set_to_be_stored(msg, FALSE); - increase_msg_count = FALSE; - if(lp_config_get_int(cr->lc->config, "sip", "deliver_imdn", 0) != 1) { - goto end; - } - } else if (is_text(msg->content_type)) { - linphone_chat_message_set_to_be_stored(msg, TRUE); - } - - if (increase_msg_count == TRUE) { - if (cr->unread_count < 0) - cr->unread_count = 1; - else - cr->unread_count++; - /* Mark the message as pending so that if linphone_core_chat_room_mark_as_read() is called - in the linphone_chat_room_message_received() callback, it will effectively be marked as - being read before being stored. */ - cr->pending_message = msg; - } - - linphone_chat_room_message_received(cr, lc, msg); - - if(linphone_chat_message_get_to_be_stored(msg)) { - msg->storage_id = linphone_chat_message_store(msg); - } - - cr->pending_message = NULL; - -end: + LinphoneChatRoom *cr = linphone_core_get_chat_room(lc, addr); + LinphoneReason reason = L_GET_PRIVATE(cr->cr)->messageReceived(op, sal_msg); linphone_address_unref(addr); - if (msg != NULL) linphone_chat_message_unref(msg); return reason; } -static int linphone_chat_room_remote_refresh_composing_expired(void *data, unsigned int revents) { - LinphoneChatRoom *cr = (LinphoneChatRoom *)data; - belle_sip_object_unref(cr->remote_composing_refresh_timer); - cr->remote_composing_refresh_timer = NULL; - cr->remote_is_composing = LinphoneIsComposingIdle; - linphone_core_notify_is_composing_received(cr->lc, cr); - return BELLE_SIP_STOP; -} - -static const char *iscomposing_prefix = "/xsi:isComposing"; - -static void process_im_is_composing_notification(LinphoneChatRoom *cr, xmlparsing_context_t *xml_ctx) { - char xpath_str[MAX_XPATH_LENGTH]; - xmlXPathObjectPtr iscomposing_object; - char *state_str = NULL; - char *refresh_str = NULL; - int refresh_duration = lp_config_get_int(cr->lc->config, "sip", "composing_remote_refresh_timeout", - COMPOSING_DEFAULT_REMOTE_REFRESH_TIMEOUT); - int i; - LinphoneIsComposingState state = LinphoneIsComposingIdle; - - if (linphone_create_xml_xpath_context(xml_ctx) < 0) - return; - - xmlXPathRegisterNs(xml_ctx->xpath_ctx, (const xmlChar *)"xsi", - (const xmlChar *)"urn:ietf:params:xml:ns:im-iscomposing"); - iscomposing_object = linphone_get_xml_xpath_object_for_node_list(xml_ctx, iscomposing_prefix); - if (iscomposing_object != NULL) { - if (iscomposing_object->nodesetval != NULL) { - for (i = 1; i <= iscomposing_object->nodesetval->nodeNr; i++) { - snprintf(xpath_str, sizeof(xpath_str), "%s[%i]/xsi:state", iscomposing_prefix, i); - state_str = linphone_get_xml_text_content(xml_ctx, xpath_str); - if (state_str == NULL) - continue; - snprintf(xpath_str, sizeof(xpath_str), "%s[%i]/xsi:refresh", iscomposing_prefix, i); - refresh_str = linphone_get_xml_text_content(xml_ctx, xpath_str); - } - } - xmlXPathFreeObject(iscomposing_object); - } - - if (state_str != NULL) { - if (strcmp(state_str, "active") == 0) { - state = LinphoneIsComposingActive; - if (refresh_str != NULL) { - refresh_duration = atoi(refresh_str); - } - if (!cr->remote_composing_refresh_timer) { - cr->remote_composing_refresh_timer = - sal_create_timer(cr->lc->sal, linphone_chat_room_remote_refresh_composing_expired, cr, - refresh_duration * 1000, "composing remote refresh timeout"); - } else { - belle_sip_source_set_timeout(cr->remote_composing_refresh_timer, refresh_duration * 1000); - } - } else { - linphone_chat_room_delete_remote_composing_refresh_timer(cr); - } - - cr->remote_is_composing = state; - linphone_core_notify_is_composing_received(cr->lc, cr); - linphone_free_xml_text_content(state_str); - } - if (refresh_str != NULL) { - linphone_free_xml_text_content(refresh_str); - } -} - -static void linphone_chat_room_notify_is_composing(LinphoneChatRoom *cr, const char *text) { - xmlparsing_context_t *xml_ctx = linphone_xmlparsing_context_new(); - xmlSetGenericErrorFunc(xml_ctx, linphone_xmlparsing_genericxml_error); - xml_ctx->doc = xmlReadDoc((const unsigned char *)text, 0, NULL, 0); - if (xml_ctx->doc != NULL) { - process_im_is_composing_notification(cr, xml_ctx); - } else { - ms_warning("Wrongly formatted presence XML: %s", xml_ctx->errorBuffer); - } - linphone_xmlparsing_context_destroy(xml_ctx); -} - bool_t linphone_chat_room_is_remote_composing(const LinphoneChatRoom *cr) { - return (cr->remote_is_composing == LinphoneIsComposingActive) ? TRUE : FALSE; -} - -static const char *imdn_prefix = "/imdn:imdn"; - -static void process_imdn(LinphoneChatRoom *cr, xmlparsing_context_t *xml_ctx) { - char xpath_str[MAX_XPATH_LENGTH]; - xmlXPathObjectPtr imdn_object; - xmlXPathObjectPtr delivery_status_object; - xmlXPathObjectPtr display_status_object; - char *message_id_str = NULL; - char *datetime_str = NULL; - LinphoneCore *lc = linphone_chat_room_get_core(cr); - LinphoneImNotifPolicy *policy = linphone_core_get_im_notif_policy(lc); - - if (linphone_create_xml_xpath_context(xml_ctx) < 0) - return; - - xmlXPathRegisterNs(xml_ctx->xpath_ctx, (const xmlChar *)"imdn", - (const xmlChar *)"urn:ietf:params:xml:ns:imdn"); - imdn_object = linphone_get_xml_xpath_object_for_node_list(xml_ctx, imdn_prefix); - if (imdn_object != NULL) { - if ((imdn_object->nodesetval != NULL) && (imdn_object->nodesetval->nodeNr >= 1)) { - snprintf(xpath_str, sizeof(xpath_str), "%s[1]/imdn:message-id", imdn_prefix); - message_id_str = linphone_get_xml_text_content(xml_ctx, xpath_str); - snprintf(xpath_str, sizeof(xpath_str), "%s[1]/imdn:datetime", imdn_prefix); - datetime_str = linphone_get_xml_text_content(xml_ctx, xpath_str); - } - xmlXPathFreeObject(imdn_object); - } - - if ((message_id_str != NULL) && (datetime_str != NULL)) { - LinphoneChatMessage *cm = linphone_chat_room_find_message_with_dir(cr, message_id_str, LinphoneChatMessageOutgoing); - if (cm == NULL) { - ms_warning("Received IMDN for unknown message %s", message_id_str); - } else { - snprintf(xpath_str, sizeof(xpath_str), "%s[1]/imdn:delivery-notification/imdn:status", imdn_prefix); - delivery_status_object = linphone_get_xml_xpath_object_for_node_list(xml_ctx, xpath_str); - snprintf(xpath_str, sizeof(xpath_str), "%s[1]/imdn:display-notification/imdn:status", imdn_prefix); - display_status_object = linphone_get_xml_xpath_object_for_node_list(xml_ctx, xpath_str); - if ((delivery_status_object != NULL) && (linphone_im_notif_policy_get_recv_imdn_delivered(policy) == TRUE)) { - if ((delivery_status_object->nodesetval != NULL) && (delivery_status_object->nodesetval->nodeNr >= 1)) { - xmlNodePtr node = delivery_status_object->nodesetval->nodeTab[0]; - if ((node->children != NULL) && (node->children->name != NULL)) { - if (strcmp((const char *)node->children->name, "delivered") == 0) { - linphone_chat_message_update_state(cm, LinphoneChatMessageStateDeliveredToUser); - } else if (strcmp((const char *)node->children->name, "error") == 0) { - linphone_chat_message_update_state(cm, LinphoneChatMessageStateNotDelivered); - } - } - } - xmlXPathFreeObject(delivery_status_object); - } - if ((display_status_object != NULL) && (linphone_im_notif_policy_get_recv_imdn_displayed(policy) == TRUE)) { - if ((display_status_object->nodesetval != NULL) && (display_status_object->nodesetval->nodeNr >= 1)) { - xmlNodePtr node = display_status_object->nodesetval->nodeTab[0]; - if ((node->children != NULL) && (node->children->name != NULL)) { - if (strcmp((const char *)node->children->name, "displayed") == 0) { - linphone_chat_message_update_state(cm, LinphoneChatMessageStateDisplayed); - } - } - } - xmlXPathFreeObject(display_status_object); - } - linphone_chat_message_unref(cm); - } - } - if (message_id_str != NULL) linphone_free_xml_text_content(message_id_str); - if (datetime_str != NULL) linphone_free_xml_text_content(datetime_str); -} - -static void linphone_chat_room_notify_imdn(LinphoneChatRoom *cr, const char *text) { - xmlparsing_context_t *xml_ctx = linphone_xmlparsing_context_new(); - xmlSetGenericErrorFunc(xml_ctx, linphone_xmlparsing_genericxml_error); - xml_ctx->doc = xmlReadDoc((const unsigned char *)text, 0, NULL, 0); - if (xml_ctx->doc != NULL) { - process_imdn(cr, xml_ctx); - } else { - ms_warning("Wrongly formatted IMDN XML: %s", xml_ctx->errorBuffer); - } - linphone_xmlparsing_context_destroy(xml_ctx); + return cr->cr->isRemoteComposing(); } LinphoneCore *linphone_chat_room_get_lc(LinphoneChatRoom *cr) { @@ -939,25 +396,15 @@ LinphoneCore *linphone_chat_room_get_lc(LinphoneChatRoom *cr) { } LinphoneCore *linphone_chat_room_get_core(LinphoneChatRoom *cr) { - return cr->lc; + return cr->cr->getCore(); } const LinphoneAddress *linphone_chat_room_get_peer_address(LinphoneChatRoom *cr) { - return cr->peer_url; + return cr->cr->getPeerAddress(); } LinphoneChatMessage *linphone_chat_room_create_message(LinphoneChatRoom *cr, const char *message) { - LinphoneChatMessage *msg = belle_sip_object_new(LinphoneChatMessage); - msg->state = LinphoneChatMessageStateIdle; - msg->callbacks = linphone_chat_message_cbs_new(); - msg->chat_room = (LinphoneChatRoom *)cr; - msg->message = message ? ms_strdup(message) : NULL; - msg->content_type = ms_strdup("text/plain"); - msg->file_transfer_information = NULL; /* this property is used only when transfering file */ - msg->http_request = NULL; - msg->time = ms_time(0); - msg->is_secured = FALSE; - return msg; + return cr->cr->createMessage(message ? message : ""); } LinphoneChatMessage *linphone_chat_room_create_message_2(LinphoneChatRoom *cr, const char *message, @@ -985,16 +432,20 @@ void linphone_chat_room_send_message2(LinphoneChatRoom *cr, LinphoneChatMessage LinphoneChatMessageStateChangedCb status_cb, void *ud) { msg->message_state_changed_cb = status_cb; msg->message_state_changed_user_data = ud; - _linphone_chat_room_send_message(cr, msg); + cr->cr->sendMessage(msg); } void linphone_chat_room_send_chat_message_2(LinphoneChatRoom *cr, LinphoneChatMessage *msg) { linphone_chat_message_ref(msg); - _linphone_chat_room_send_message(cr, msg); + cr->cr->sendMessage(msg); } void linphone_chat_room_send_chat_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg) { - _linphone_chat_room_send_message(cr, msg); + cr->cr->sendMessage(msg); +} + +LinphonePrivate::ChatRoom& linphone_chat_room_get_cpp_obj(LinphoneChatRoom *cr) { + return *cr->cr; } void _linphone_chat_message_resend(LinphoneChatMessage *msg, bool_t ref_msg) { @@ -1008,7 +459,7 @@ void _linphone_chat_message_resend(LinphoneChatMessage *msg, bool_t ref_msg) { cr = linphone_chat_message_get_chat_room(msg); if (ref_msg) linphone_chat_message_ref(msg); - _linphone_chat_room_send_message(cr, msg); + cr->cr->sendMessage(msg); } void linphone_chat_message_resend(LinphoneChatMessage *msg) { @@ -1019,121 +470,7 @@ void linphone_chat_message_resend_2(LinphoneChatMessage *msg) { _linphone_chat_message_resend(msg, TRUE); } -static char *linphone_chat_room_create_is_composing_xml(LinphoneChatRoom *cr) { - xmlBufferPtr buf; - xmlTextWriterPtr writer; - int err; - char *content = NULL; - - buf = xmlBufferCreate(); - if (buf == NULL) { - ms_error("Error creating the XML buffer"); - return content; - } - writer = xmlNewTextWriterMemory(buf, 0); - if (writer == NULL) { - ms_error("Error creating the XML writer"); - return content; - } - - err = xmlTextWriterStartDocument(writer, "1.0", "UTF-8", NULL); - if (err >= 0) { - err = xmlTextWriterStartElementNS(writer, NULL, (const xmlChar *)"isComposing", - (const xmlChar *)"urn:ietf:params:xml:ns:im-iscomposing"); - } - 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 = xmlTextWriterWriteAttributeNS(writer, (const xmlChar *)"xsi", (const xmlChar *)"schemaLocation", NULL, - (const xmlChar *)"urn:ietf:params:xml:ns:im-composing iscomposing.xsd"); - } - if (err >= 0) { - err = xmlTextWriterWriteElement(writer, (const xmlChar *)"state", - (cr->is_composing == LinphoneIsComposingActive) ? (const xmlChar *)"active" - : (const xmlChar *)"idle"); - } - if ((err >= 0) && (cr->is_composing == LinphoneIsComposingActive)) { - char refresh_str[4] = {0}; - int refresh_timeout = - lp_config_get_int(cr->lc->config, "sip", "composing_refresh_timeout", COMPOSING_DEFAULT_REFRESH_TIMEOUT); - snprintf(refresh_str, sizeof(refresh_str), "%u", refresh_timeout); - err = xmlTextWriterWriteElement(writer, (const xmlChar *)"refresh", (const xmlChar *)refresh_str); - } - if (err >= 0) { - /* Close the "isComposing" element. */ - err = xmlTextWriterEndElement(writer); - } - if (err >= 0) { - err = xmlTextWriterEndDocument(writer); - } - if (err > 0) { - /* xmlTextWriterEndDocument returns the size of the content. */ - content = ms_strdup((char *)buf->content); - } - xmlFreeTextWriter(writer); - xmlBufferFree(buf); - return content; -} - -static void linphone_chat_room_send_is_composing_notification(LinphoneChatRoom *cr) { - SalOp *op = NULL; - const char *identity = NULL; - char *content = NULL; - LinphoneCore *lc = linphone_chat_room_get_core(cr); - LinphoneImNotifPolicy *policy = linphone_core_get_im_notif_policy(lc); - if (linphone_im_notif_policy_get_send_is_composing(policy) == TRUE) { - LinphoneProxyConfig *proxy = linphone_core_lookup_known_proxy(lc, cr->peer_url); - LinphoneImEncryptionEngine *imee = linphone_core_get_im_encryption_engine(lc); - LinphoneChatMessage *msg = NULL; - - if (proxy) - identity = linphone_proxy_config_get_identity(proxy); - else - identity = linphone_core_get_primary_contact(lc); - /*sending out of calls*/ - op = sal_op_new(lc->sal); - linphone_configure_op(lc, op, cr->peer_url, NULL, - lp_config_get_int(lc->config, "sip", "chat_msg_with_contact", 0)); - - content = linphone_chat_room_create_is_composing_xml(cr); - if (content != NULL) { - int retval = -1; - LinphoneAddress *from_addr = linphone_address_new(identity); - LinphoneAddress *to_addr = linphone_address_new(cr->peer); - msg = linphone_chat_room_create_message(cr, content); - linphone_chat_message_set_from_address(msg, from_addr); - linphone_chat_message_set_to_address(msg, to_addr); - linphone_chat_message_set_content_type(msg, "application/im-iscomposing+xml"); - - if (imee) { - LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee); - LinphoneImEncryptionEngineCbsOutgoingMessageCb cb_process_outgoing_message = linphone_im_encryption_engine_cbs_get_process_outgoing_message(imee_cbs); - if (cb_process_outgoing_message) { - retval = cb_process_outgoing_message(imee, cr, msg); - } - } - - if (retval <= 0) { - sal_message_send(op, identity, cr->peer, msg->content_type, msg->message, NULL); - } - - linphone_chat_message_unref(msg); - linphone_address_unref(from_addr); - linphone_address_unref(to_addr); - ms_free(content); - sal_op_unref(op); - } - } -} - -enum ImdnType { - ImdnTypeDelivery, - ImdnTypeDisplay -}; - -static char *linphone_chat_message_create_imdn_xml(LinphoneChatMessage *cm, enum ImdnType imdn_type, LinphoneReason reason) { +static char *linphone_chat_message_create_imdn_xml(LinphoneChatMessage *cm, ImdnType imdn_type, LinphoneReason reason) { xmlBufferPtr buf; xmlTextWriterPtr writer; int err; @@ -1235,53 +572,12 @@ static char *linphone_chat_message_create_imdn_xml(LinphoneChatMessage *cm, enum return content; } -static void linphone_chat_message_send_imdn(LinphoneChatMessage *cm, enum ImdnType imdn_type, LinphoneReason reason) { - SalOp *op = NULL; - const char *identity = NULL; - char *content = NULL; - LinphoneChatRoom *cr = linphone_chat_message_get_chat_room(cm); - LinphoneProxyConfig *proxy = linphone_core_lookup_known_proxy(cr->lc, cr->peer_url); - LinphoneImEncryptionEngine *imee = linphone_core_get_im_encryption_engine(cr->lc); - LinphoneChatMessage *msg; - - if (proxy) - identity = linphone_proxy_config_get_identity(proxy); - else - identity = linphone_core_get_primary_contact(cr->lc); - /* Sending out of calls */ - op = sal_op_new(cr->lc->sal); - linphone_configure_op(cr->lc, op, cr->peer_url, NULL, - lp_config_get_int(cr->lc->config, "sip", "chat_msg_with_contact", 0)); - - content = linphone_chat_message_create_imdn_xml(cm, imdn_type, reason); - if (content != NULL) { - int retval = -1; - LinphoneAddress *from_addr = linphone_address_new(identity); - LinphoneAddress *to_addr = linphone_address_new(cr->peer); - msg = linphone_chat_room_create_message(cr, content); - linphone_chat_message_set_from_address(msg, from_addr); - linphone_chat_message_set_to_address(msg, to_addr); - linphone_chat_message_set_content_type(msg, "message/imdn+xml"); - - /* Do not try to encrypt the notification when it is reporting an error (maybe it should be bypassed only for some reasons). */ - if (imee && (reason == LinphoneReasonNone)) { - LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee); - LinphoneImEncryptionEngineCbsOutgoingMessageCb cb_process_outgoing_message = linphone_im_encryption_engine_cbs_get_process_outgoing_message(imee_cbs); - if (cb_process_outgoing_message) { - retval = cb_process_outgoing_message(imee, cr, msg); - } - } - - if (retval <= 0) { - sal_message_send(op, identity, cr->peer, msg->content_type, msg->message, NULL); - } - - linphone_chat_message_unref(msg); - linphone_address_unref(from_addr); - linphone_address_unref(to_addr); +void linphone_chat_message_send_imdn(LinphoneChatMessage *cm, ImdnType imdn_type, LinphoneReason reason) { + char *content = linphone_chat_message_create_imdn_xml(cm, imdn_type, reason); + if (content) { + L_GET_PRIVATE(linphone_chat_message_get_chat_room(cm)->cr)->sendImdn(content, reason); ms_free(content); } - sal_op_unref(op); } void linphone_chat_message_send_delivery_notification(LinphoneChatMessage *cm, LinphoneReason reason) { @@ -1302,107 +598,21 @@ void linphone_chat_message_send_display_notification(LinphoneChatMessage *cm) { } } -static char* utf8_to_char(uint32_t ic) { - char *result = reinterpret_cast(ms_malloc(sizeof(char) * 5)); - int size = 0; - if (ic < 0x80) { - result[0] = ic; - size = 1; - } else if (ic < 0x800) { - result[1] = 0x80 + ((ic & 0x3F)); - result[0] = 0xC0 + ((ic >> 6) & 0x1F); - size = 2; - } else if (ic < 0x100000) { - result[2] = 0x80 + (ic & 0x3F); - result[1] = 0x80 + ((ic >> 6) & 0x3F); - result[0] = 0xE0 + ((ic >> 12) & 0xF); - size = 3; - } else if (ic < 0x110000) { - result[3] = 0x80 + (ic & 0x3F); - result[2] = 0x80 + ((ic >> 6) & 0x3F); - result[1] = 0x80 + ((ic >> 12) & 0x3F); - result[0] = 0xF0 + ((ic >> 18) & 0x7); - size = 4; - } - result[size] = '\0'; - return result; -} - void linphone_core_real_time_text_received(LinphoneCore *lc, LinphoneChatRoom *cr, uint32_t character, LinphoneCall *call) { - 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, ""); - } - - cmc->value = character; - cmc->has_been_read = FALSE; - cr->received_rtt_characters = bctbx_list_append(cr->received_rtt_characters, (void *)cmc); - - 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; - ms_debug("New line received, forge a message with content %s", cr->pending_message->message); - - linphone_chat_message_set_from(msg, cr->peer_url); - if (msg->to) - linphone_address_unref(msg->to); - msg->to = call->dest_proxy ? linphone_address_clone(call->dest_proxy->identity_address) : - linphone_address_new(linphone_core_get_identity(lc)); - msg->time = ms_time(0); - msg->state = LinphoneChatMessageStateDelivered; - msg->dir = LinphoneChatMessageIncoming; - - if (lp_config_get_int(lc->config, "misc", "store_rtt_messages", 1) == 1) { - msg->storage_id = linphone_chat_message_store(msg); - } - - if (cr->unread_count < 0) cr->unread_count = 1; - else cr->unread_count++; - - linphone_chat_room_message_received(cr, lc, msg); - linphone_chat_message_unref(msg); - cr->pending_message = NULL; - cr->received_rtt_characters = bctbx_list_free_with_data(cr->received_rtt_characters, (void (*)(void *))ms_free); - } else { - char *value = utf8_to_char(character); - cr->pending_message->message = ms_strcat_printf(cr->pending_message->message, value); - ms_debug("Received RTT character: %s (%lu), pending text is %s", value, (unsigned long)character, cr->pending_message->message); - ms_free(value); - } - } + L_GET_PRIVATE(cr->cr)->realtimeTextReceived(character, call); } uint32_t linphone_chat_room_get_char(const LinphoneChatRoom *cr) { - if (cr && cr->received_rtt_characters) { - bctbx_list_t *characters = cr->received_rtt_characters; - while (characters != NULL) { - LinphoneChatMessageCharacter *cmc = (LinphoneChatMessageCharacter *)characters->data; - if (!cmc->has_been_read) { - cmc->has_been_read = TRUE; - return cmc->value; - } - characters = bctbx_list_next(characters); - } - } - return 0; + return cr->cr->getChar(); } LinphoneStatus linphone_chat_message_put_char(LinphoneChatMessage *msg, uint32_t character) { LinphoneChatRoom *cr = linphone_chat_message_get_chat_room(msg); - LinphoneCall *call = cr->call; - LinphoneCore *lc = cr->lc; - uint32_t new_line = 0x2028; - uint32_t crlf = 0x0D0A; - uint32_t lf = 0x0A; + LinphoneCall *call = cr->cr->getCall(); + LinphoneCore *lc = cr->cr->getCore(); + const uint32_t new_line = 0x2028; + const uint32_t crlf = 0x0D0A; + const uint32_t lf = 0x0A; if (!call || !call->textstream) { return -1; @@ -1421,10 +631,10 @@ LinphoneStatus linphone_chat_message_put_char(LinphoneChatMessage *msg, uint32_t msg->message = NULL; } } else { - char *value = utf8_to_char(character); + char *value = LinphonePrivate::Utils::utf8ToChar(character); msg->message = ms_strcat_printf(msg->message, value); ms_debug("Sent RTT character: %s (%lu), pending text is %s", value, (unsigned long)character, msg->message); - ms_free(value); + delete value; } text_stream_putchar32(call->textstream, character); @@ -1435,42 +645,24 @@ const char* linphone_chat_message_get_message_id(const LinphoneChatMessage *cm) return cm->message_id; } -static int linphone_chat_room_stop_composing(void *data, unsigned int revents) { - LinphoneChatRoom *cr = (LinphoneChatRoom *)data; - cr->is_composing = LinphoneIsComposingIdle; - linphone_chat_room_send_is_composing_notification(cr); - linphone_chat_room_delete_composing_refresh_timer(cr); - belle_sip_object_unref(cr->composing_idle_timer); - cr->composing_idle_timer = NULL; - return BELLE_SIP_STOP; -} - -static int linphone_chat_room_refresh_composing(void *data, unsigned int revents) { - LinphoneChatRoom *cr = (LinphoneChatRoom *)data; - linphone_chat_room_send_is_composing_notification(cr); - return BELLE_SIP_CONTINUE; -} - void linphone_chat_room_compose(LinphoneChatRoom *cr) { - int idle_timeout = - lp_config_get_int(cr->lc->config, "sip", "composing_idle_timeout", COMPOSING_DEFAULT_IDLE_TIMEOUT); - int refresh_timeout = - lp_config_get_int(cr->lc->config, "sip", "composing_refresh_timeout", COMPOSING_DEFAULT_REFRESH_TIMEOUT); - if (cr->is_composing == LinphoneIsComposingIdle) { - cr->is_composing = LinphoneIsComposingActive; - linphone_chat_room_send_is_composing_notification(cr); - if (!cr->composing_refresh_timer) { - cr->composing_refresh_timer = sal_create_timer(cr->lc->sal, linphone_chat_room_refresh_composing, cr, - refresh_timeout * 1000, "composing refresh timeout"); - } else { - belle_sip_source_set_timeout(cr->composing_refresh_timer, refresh_timeout * 1000); - } - if (!cr->composing_idle_timer) { - cr->composing_idle_timer = sal_create_timer(cr->lc->sal, linphone_chat_room_stop_composing, cr, - idle_timeout * 1000, "composing idle timeout"); - } - } - belle_sip_source_set_timeout(cr->composing_idle_timer, idle_timeout * 1000); + cr->cr->compose(); +} + +LinphoneCall *linphone_chat_room_get_call(const LinphoneChatRoom *room) { + return room->cr->getCall(); +} + +void linphone_chat_room_set_call(LinphoneChatRoom *cr, LinphoneCall *call) { + L_GET_PRIVATE(cr->cr)->setCall(call); +} + +bctbx_list_t * linphone_chat_room_get_transient_messages(const LinphoneChatRoom *cr) { + std::list l = L_GET_PRIVATE(cr->cr)->getTransientMessages(); + bctbx_list_t *result = nullptr; + for (auto it = l.begin(); it != l.end(); it++) + result = bctbx_list_append(result, *it); + return result; } const char *linphone_chat_message_state_to_string(const LinphoneChatMessageState state) { @@ -1534,11 +726,11 @@ void linphone_chat_message_set_content_type(LinphoneChatMessage *msg, const char } bool_t linphone_chat_message_is_file_transfer(const LinphoneChatMessage *msg) { - return is_file_transfer(msg->content_type); + return LinphonePrivate::ContentType::isFileTransfer(msg->content_type); } bool_t linphone_chat_message_is_text(const LinphoneChatMessage *msg) { - return is_text(msg->content_type); + return LinphonePrivate::ContentType::isText(msg->content_type); } bool_t linphone_chat_message_get_to_be_stored(const LinphoneChatMessage *msg) { @@ -1581,7 +773,7 @@ const LinphoneAddress *linphone_chat_message_get_to_address(const LinphoneChatMe if (msg->to) return msg->to; if (msg->dir == LinphoneChatMessageOutgoing) { - return msg->chat_room->peer_url; + return msg->chat_room->cr->getPeerAddress(); } return NULL; } @@ -1617,7 +809,7 @@ int linphone_chat_message_set_text(LinphoneChatMessage *msg, const char* text) { msg->message = ms_strdup(text); else msg->message = NULL; - + return 0; } @@ -1717,7 +909,7 @@ void linphone_chat_message_unref(LinphoneChatMessage *msg) { belle_sip_object_unref(msg); } -static void linphone_chat_message_deactivate(LinphoneChatMessage *msg){ +void linphone_chat_message_deactivate(LinphoneChatMessage *msg){ if (msg->file_transfer_information != NULL) { _linphone_chat_message_cancel_file_transfer(msg, FALSE); } @@ -1725,7 +917,7 @@ static void linphone_chat_message_deactivate(LinphoneChatMessage *msg){ msg->chat_room = NULL; } -static void linphone_chat_message_release(LinphoneChatMessage *msg) { +void linphone_chat_message_release(LinphoneChatMessage *msg) { linphone_chat_message_deactivate(msg); linphone_chat_message_unref(msg); } @@ -1743,7 +935,3 @@ LinphoneReason linphone_chat_message_get_reason(LinphoneChatMessage *msg) { LinphoneChatMessageCbs *linphone_chat_message_get_callbacks(const LinphoneChatMessage *msg) { return msg->callbacks; } - -LinphoneCall *linphone_chat_room_get_call(const LinphoneChatRoom *room) { - return room->call; -} diff --git a/coreapi/chat_file_transfer.c b/coreapi/chat_file_transfer.c index c1fd37984..02fb3daa3 100644 --- a/coreapi/chat_file_transfer.c +++ b/coreapi/chat_file_transfer.c @@ -26,8 +26,12 @@ #include "private.h" #include "ortp/b64.h" +#include "chat/chat-room.h" + +extern LinphonePrivate::ChatRoom& linphone_chat_room_get_cpp_obj(LinphoneChatRoom *cr); + static bool_t file_transfer_in_progress_and_valid(LinphoneChatMessage* msg) { - return (msg->chat_room && msg->chat_room->lc && msg->http_request && !belle_http_request_is_cancelled(msg->http_request)); + return (msg->chat_room && linphone_chat_room_get_core(msg->chat_room) && msg->http_request && !belle_http_request_is_cancelled(msg->http_request)); } static void _release_http_request(LinphoneChatMessage* msg) { @@ -88,7 +92,7 @@ static void linphone_chat_message_file_transfer_on_progress(belle_sip_body_handl msg, msg->file_transfer_information, offset, total); } else { /* Legacy: call back given by application level */ - linphone_core_notify_file_transfer_progress_indication(msg->chat_room->lc, msg, msg->file_transfer_information, + linphone_core_notify_file_transfer_progress_indication(linphone_chat_room_get_core(msg->chat_room), msg, msg->file_transfer_information, offset, total); } } @@ -108,7 +112,7 @@ static int on_send_body(belle_sip_user_body_handler_t *bh, belle_sip_message_t * return BELLE_SIP_STOP; } - lc = msg->chat_room->lc; + lc = linphone_chat_room_get_core(msg->chat_room); /* if we've not reach the end of file yet, ask for more data */ /* in case of file body handler, won't be called */ if (msg->file_transfer_filepath == NULL && offset < linphone_content_get_size(msg->file_transfer_information)) { @@ -153,7 +157,7 @@ static int on_send_body(belle_sip_user_body_handler_t *bh, belle_sip_message_t * static void on_send_end(belle_sip_user_body_handler_t *bh, void *data) { LinphoneChatMessage *msg = (LinphoneChatMessage *)data; - LinphoneCore *lc = msg->chat_room->lc; + LinphoneCore *lc = linphone_chat_room_get_core(msg->chat_room); LinphoneImEncryptionEngine *imee = linphone_core_get_im_encryption_engine(lc); if (imee) { @@ -185,8 +189,7 @@ static void file_upload_begin_background_task(LinphoneChatMessage *obj){ } } -static void linphone_chat_message_process_response_from_post_file(void *data, - const belle_http_response_event_t *event) { +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)) { @@ -205,7 +208,7 @@ static void linphone_chat_message_process_response_from_post_file(void *data, belle_sip_body_handler_t *first_part_bh; bool_t is_file_encryption_enabled = FALSE; - LinphoneImEncryptionEngine *imee = linphone_core_get_im_encryption_engine(msg->chat_room->lc); + LinphoneImEncryptionEngine *imee = linphone_core_get_im_encryption_engine(linphone_chat_room_get_core(msg->chat_room)); if (imee) { LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee); LinphoneImEncryptionEngineCbsIsEncryptionEnabledForFileTransferCb is_encryption_enabled_for_file_transfer_cb = @@ -339,7 +342,7 @@ static void linphone_chat_message_process_response_from_post_file(void *data, linphone_chat_message_ref(msg); linphone_chat_message_set_state(msg, LinphoneChatMessageStateFileTransferDone); _release_http_request(msg); - _linphone_chat_room_send_message(msg->chat_room, msg); + linphone_chat_room_get_cpp_obj(msg->chat_room).sendMessage(msg); file_upload_end_background_task(msg); linphone_chat_message_unref(msg); } else { @@ -374,7 +377,7 @@ static void on_recv_body(belle_sip_user_body_handler_t *bh, belle_sip_message_t linphone_chat_message_cancel_file_transfer(msg); return; } - lc = msg->chat_room->lc; + lc = linphone_chat_room_get_core(msg->chat_room); if (lc == NULL){ return; /*might happen during linphone_core_destroy()*/ @@ -425,7 +428,7 @@ static void on_recv_body(belle_sip_user_body_handler_t *bh, belle_sip_message_t static void on_recv_end(belle_sip_user_body_handler_t *bh, void *data) { LinphoneChatMessage *msg = (LinphoneChatMessage *)data; - LinphoneCore *lc = msg->chat_room->lc; + LinphoneCore *lc = linphone_chat_room_get_core(msg->chat_room); LinphoneImEncryptionEngine *imee = linphone_core_get_im_encryption_engine(lc); int retval = -1; @@ -536,7 +539,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; - const char* ua = linphone_core_get_user_agent(msg->chat_room->lc); + const char* ua = linphone_core_get_user_agent(linphone_chat_room_get_core(msg->chat_room)); if (url == NULL) { ms_warning("Cannot process file transfer msg: no file remote URI configured."); @@ -559,7 +562,7 @@ int _linphone_chat_room_start_http_transfer(LinphoneChatMessage *msg, const char /* give msg to listener to be able to start the actual file upload when server answer a 204 No content */ msg->http_listener = belle_http_request_listener_create_from_callbacks(cbs, linphone_chat_message_ref(msg)); - belle_http_provider_send_request(msg->chat_room->lc->http_provider, msg->http_request, msg->http_listener); + belle_http_provider_send_request(linphone_chat_room_get_core(msg->chat_room)->http_provider, msg->http_request, msg->http_listener); return 0; error: if (uri) { @@ -580,7 +583,7 @@ int linphone_chat_room_upload_file(LinphoneChatMessage *msg) { cbs.process_response = linphone_chat_message_process_response_from_post_file; cbs.process_io_error = linphone_chat_message_process_io_error_upload; cbs.process_auth_requested = linphone_chat_message_process_auth_requested_upload; - err = _linphone_chat_room_start_http_transfer(msg, linphone_core_get_file_transfer_server(msg->chat_room->lc), "POST", &cbs); + err = _linphone_chat_room_start_http_transfer(msg, linphone_core_get_file_transfer_server(linphone_chat_room_get_core(msg->chat_room)), "POST", &cbs); if (err == -1){ linphone_chat_message_set_state(msg, LinphoneChatMessageStateNotDelivered); } @@ -621,10 +624,10 @@ void _linphone_chat_message_cancel_file_transfer(LinphoneChatMessage *msg, bool_ if (!belle_http_request_is_cancelled(msg->http_request)) { if (msg->chat_room) { ms_message("Canceling file transfer %s - msg [%p] chat room[%p]" - , (msg->external_body_url == NULL) ? linphone_core_get_file_transfer_server(msg->chat_room->lc) : msg->external_body_url + , (msg->external_body_url == NULL) ? linphone_core_get_file_transfer_server(linphone_chat_room_get_core(msg->chat_room)) : msg->external_body_url , msg , msg->chat_room); - belle_http_provider_cancel_request(msg->chat_room->lc->http_provider, msg->http_request); + belle_http_provider_cancel_request(linphone_chat_room_get_core(msg->chat_room)->http_provider, msg->http_request); if ((msg->dir == LinphoneChatMessageOutgoing) && unref) { // must release it linphone_chat_message_unref(msg); @@ -654,20 +657,6 @@ const char *linphone_chat_message_get_file_transfer_filepath(LinphoneChatMessage return msg->file_transfer_filepath; } -LinphoneChatMessage *linphone_chat_room_create_file_transfer_message(LinphoneChatRoom *cr, - const LinphoneContent *initial_content) { - LinphoneChatMessage *msg = belle_sip_object_new(LinphoneChatMessage); - msg->callbacks = linphone_chat_message_cbs_new(); - msg->chat_room = (LinphoneChatRoom *)cr; - msg->message = NULL; - msg->file_transfer_information = linphone_content_copy(initial_content); - msg->dir = LinphoneChatMessageOutgoing; - linphone_chat_message_set_to(msg, linphone_chat_room_get_peer_address(cr)); - msg->from = linphone_address_new(linphone_core_get_identity(cr->lc)); /*direct assignment*/ - /* this will be set to application/vnd.gsma.rcs-ft-http+xml when we will transfer the xml reply from server to the peers */ - msg->content_type = NULL; - /* this will store the http request during file upload to the server */ - msg->http_request = NULL; - msg->time = ms_time(0); - return msg; +LinphoneChatMessage *linphone_chat_room_create_file_transfer_message(LinphoneChatRoom *cr, const LinphoneContent *initial_content) { + return linphone_chat_room_get_cpp_obj(cr).createFileTransferMessage(initial_content); } diff --git a/coreapi/lime.c b/coreapi/lime.c index ba4e00027..80ebfefc1 100644 --- a/coreapi/lime.c +++ b/coreapi/lime.c @@ -749,11 +749,11 @@ error: bool_t linphone_chat_room_lime_available(LinphoneChatRoom *cr) { if (cr) { - switch (linphone_core_lime_enabled(cr->lc)) { + switch (linphone_core_lime_enabled(linphone_chat_room_get_core(cr))) { case LinphoneLimeDisabled: return FALSE; case LinphoneLimeMandatory: case LinphoneLimePreferred: { - void *zrtp_cache_db = linphone_core_get_zrtp_cache_db(cr->lc); + void *zrtp_cache_db = linphone_core_get_zrtp_cache_db(linphone_chat_room_get_core(cr)); if (zrtp_cache_db != NULL) { bool_t res; limeURIKeys_t associatedKeys; @@ -832,7 +832,7 @@ int lime_im_encryption_engine_process_outgoing_message_cb(LinphoneImEncryptionEn LinphoneCore *lc = linphone_im_encryption_engine_get_core(engine); int errcode = -1; const char *new_content_type = "xml/cipher"; - if(linphone_core_lime_enabled(room->lc)) { + if(linphone_core_lime_enabled(linphone_chat_room_get_core(room))) { if (linphone_chat_room_lime_available(room)) { void *zrtp_cache_db = NULL; /* use a void * instead of sqlite3 * to avoid problems and ifdef when SQLITE is not available(the get function shall return NULL in that case) */ if (msg->content_type) { @@ -862,7 +862,7 @@ int lime_im_encryption_engine_process_outgoing_message_cb(LinphoneImEncryptionEn retval = lime_createMultipartMessage(zrtp_cache_db, msg->content_type, (uint8_t *)msg->message, selfUri, peerUri, &crypted_body); if (retval != 0) { /* fail to encrypt */ - ms_warning("Unable to encrypt message for %s : %s", room->peer, lime_error_code_to_string(retval)); + ms_warning("Unable to encrypt message for %s : %s", peerUri, lime_error_code_to_string(retval)); if (crypted_body) ms_free(crypted_body); errcode = 488; } else { /* encryption ok, swap plain text message body by encrypted one */ diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index c455c68e2..64fe34ecd 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -1719,7 +1719,7 @@ static void linphone_call_set_terminated(LinphoneCall *call){ call->ringing_beep=FALSE; } if (call->chat_room){ - call->chat_room->call = NULL; + linphone_chat_room_set_call(call->chat_room, NULL); } if (lc->calls == NULL){ ms_bandwidth_controller_reset_state(lc->bw_controller); @@ -1864,6 +1864,32 @@ void linphone_call_set_state(LinphoneCall *call, LinphoneCallState cstate, const call->log->status=LinphoneCallMissed; } break; + case LinphoneReasonNone: + if (call->log->dir == LinphoneCallIncoming){ + const LinphoneErrorInfo *ei = linphone_call_get_error_info(call); + if (ei) { + int code = linphone_error_info_get_protocol_code(ei); + if((code >= 200 && code < 300)) { + // error between 200-299 means accepted elsewhere + call->log->status=LinphoneCallAcceptedElsewhere; + break; + } + } + } + break; + case LinphoneReasonDoNotDisturb: + if (call->log->dir == LinphoneCallIncoming){ + const LinphoneErrorInfo *ei = linphone_call_get_error_info(call); + if (ei) { + int code = linphone_error_info_get_protocol_code(ei); + if(code >= 600 && code < 700) { + // error between 600-699 means declined elsewhere + call->log->status=LinphoneCallDeclinedElsewhere; + break; + } + } + } + break; default: break; } diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index bcca4c19f..0c2b485e8 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -463,8 +463,10 @@ void linphone_core_set_log_handler(OrtpLogFunc logfunc) { if (ortp_get_log_handler() == linphone_core_log_collection_handler) { ms_message("There is already a log collection handler, keep it"); liblinphone_log_func = logfunc; - } else + } else { ortp_set_log_handler(logfunc); + sal_set_log_handler(logfunc); + } } void linphone_core_set_log_file(FILE *file) { @@ -696,8 +698,10 @@ void linphone_core_enable_log_collection(LinphoneLogCollectionState state) { liblinphone_log_func = ortp_get_log_handler(); } ortp_set_log_handler(linphone_core_log_collection_handler); + sal_set_log_handler(linphone_core_log_collection_handler); } else { ortp_set_log_handler(liblinphone_log_func); + sal_set_log_handler(liblinphone_log_func); } } diff --git a/coreapi/message_storage.c b/coreapi/message_storage.c index 9067afdd7..0b9b39bd0 100644 --- a/coreapi/message_storage.c +++ b/coreapi/message_storage.c @@ -23,6 +23,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #ifdef SQLITE_STORAGE_ENABLED +#include "chat/chat-room.h" #ifndef _WIN32 #if !defined(__QNXNTO__) && !defined(__ANDROID__) @@ -42,6 +43,9 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include +extern LinphonePrivate::ChatRoom& linphone_chat_room_get_cpp_obj(LinphoneChatRoom *cr); + + static char *utf8_convert(const char *filename){ char db_file_utf8[MAX_PATH_SIZE] = ""; #if defined(_WIN32) @@ -117,31 +121,6 @@ int _linphone_sqlite3_open(const char *db_file, sqlite3 **db) { #ifdef SQLITE_STORAGE_ENABLED - -static LinphoneChatMessage * get_weak_message(LinphoneChatRoom *cr, unsigned int storage_id) { - LinphoneChatMessage *cm; - bctbx_list_t *item; - for (item = cr->weak_messages; item != NULL; item = bctbx_list_next(item)) { - cm = (LinphoneChatMessage *)bctbx_list_get_data(item); - if (linphone_chat_message_get_storage_id(cm) == storage_id) - return linphone_chat_message_ref(cm); - } - return NULL; -} - -static ORTP_INLINE LinphoneChatMessage* get_transient_message(LinphoneChatRoom* cr, unsigned int storage_id){ - bctbx_list_t* transients = cr->transient_messages; - LinphoneChatMessage* chat; - while( transients ){ - chat = (LinphoneChatMessage*)transients->data; - if(chat->storage_id == storage_id){ - return linphone_chat_message_ref(chat); - } - transients = transients->next; - } - return NULL; -} - /* DB layout: * | 0 | storage_id * | 1 | type @@ -172,7 +151,7 @@ static int callback_content(void *data, int argc, char **argv, char **colName) { return 0; } -static void fetch_content_from_database(sqlite3 *db, LinphoneChatMessage *message, int content_id) { +void linphone_chat_message_fetch_content_from_database(sqlite3 *db, LinphoneChatMessage *message, int content_id) { char* errmsg = NULL; int ret; char * buf; @@ -186,8 +165,6 @@ static void fetch_content_from_database(sqlite3 *db, LinphoneChatMessage *messag sqlite3_free(buf); } - - // Called when fetching all conversations from database static int callback_all(void *data, int argc, char **argv, char **colName){ LinphoneCore* lc = (LinphoneCore*) data; @@ -200,93 +177,6 @@ static int callback_all(void *data, int argc, char **argv, char **colName){ return 0; } -/* DB layout: - * | 0 | storage_id - * | 1 | localContact - * | 2 | remoteContact - * | 3 | direction flag (LinphoneChatMessageDir) - * | 4 | message (text content of the message) - * | 5 | time (unused now, used to be string-based timestamp, replaced by the utc timestamp) - * | 6 | read flag (no longer used, replaced by the LinphoneChatMessageStateDisplayed state) - * | 7 | status (LinphoneChatMessageState) - * | 8 | external body url (deprecated file transfer system) - * | 9 | utc timestamp - * | 10 | app data text - * | 11 | linphone content id (LinphoneContent describing a file transfer) - * | 12 | message id (used for IMDN) - * | 13 | content type (of the message field [must be text representable]) - * | 14 | secured flag - */ -static int create_chat_message(void *data, int argc, char **argv, char **colName){ - LinphoneChatRoom *cr = (LinphoneChatRoom *)data; - unsigned int storage_id = (unsigned int)atoi(argv[0]); - LinphoneChatMessage* new_message; - - /* Check if the message exists in the weak messages list, in which case we should return that one. */ - new_message = get_weak_message(cr, storage_id); - if (new_message == NULL) { - /* Check if the message exists in the transient list, in which case we should return that one. */ - new_message = get_transient_message(cr, storage_id); - } - if (new_message == NULL) { - new_message = linphone_chat_room_create_message(cr, argv[4]); - - if(atoi(argv[3])==LinphoneChatMessageIncoming){ - new_message->dir=LinphoneChatMessageIncoming; - linphone_chat_message_set_from(new_message,linphone_chat_room_get_peer_address(cr)); - new_message->to = NULL; /*will be filled at the end */ - } else { - new_message->dir=LinphoneChatMessageOutgoing; - new_message->from = NULL; /*will be filled at the end */ - linphone_chat_message_set_to(new_message,linphone_chat_room_get_peer_address(cr)); - } - - new_message->time = (time_t)atol(argv[9]); - new_message->is_read=atoi(argv[6]); - new_message->state=static_cast(atoi(argv[7])); - new_message->storage_id=storage_id; - new_message->external_body_url= ms_strdup(argv[8]); - new_message->appdata = ms_strdup(argv[10]); - new_message->message_id = ms_strdup(argv[12]); - linphone_chat_message_set_content_type(new_message, argv[13]); - new_message->is_secured = (bool_t)atoi(argv[14]); - - if (argv[11] != NULL) { - int id = atoi(argv[11]); - if (id >= 0) { - fetch_content_from_database(cr->lc->db, new_message, id); - } - } - - /* Fix content type for old messages that were stored without it */ - if (new_message->content_type == NULL) { - if (new_message->file_transfer_information != NULL) { - new_message->content_type = ms_strdup("application/vnd.gsma.rcs-ft-http+xml"); - } else if (new_message->external_body_url != NULL) { - new_message->content_type = ms_strdup("message/external-body"); - } else { - new_message->content_type = ms_strdup("text/plain"); - } - } - - /* Add the new message to the weak messages list. */ - linphone_chat_room_add_weak_message(cr, new_message); - } - cr->messages_hist=bctbx_list_prepend(cr->messages_hist,new_message); - - return 0; -} - -void linphone_sql_request_message(sqlite3 *db,const char *stmt,LinphoneChatRoom *cr){ - char* errmsg=NULL; - int ret; - ret=sqlite3_exec(db,stmt,create_chat_message,cr,&errmsg); - if(ret != SQLITE_OK) { - ms_error("Error in creation: %s.", errmsg); - sqlite3_free(errmsg); - } -} - int linphone_sql_request(sqlite3* db,const char *stmt){ char* errmsg=NULL; int ret; @@ -407,7 +297,7 @@ void linphone_chat_message_store_update(LinphoneChatMessage *msg) { } void linphone_chat_message_store_state(LinphoneChatMessage *msg){ - LinphoneCore *lc=msg->chat_room->lc; + LinphoneCore *lc=linphone_chat_room_get_core(msg->chat_room); if (lc->db){ char *buf=sqlite3_mprintf("UPDATE history SET status=%i WHERE (id = %u);", msg->state,msg->storage_id); @@ -417,7 +307,7 @@ void linphone_chat_message_store_state(LinphoneChatMessage *msg){ } void linphone_chat_message_store_appdata(LinphoneChatMessage* msg){ - LinphoneCore *lc=msg->chat_room->lc; + LinphoneCore *lc=linphone_chat_room_get_core(msg->chat_room); if (lc->db){ char *buf=sqlite3_mprintf("UPDATE history SET appdata=%Q WHERE id=%u;", msg->appdata,msg->storage_id); @@ -426,251 +316,44 @@ void linphone_chat_message_store_appdata(LinphoneChatMessage* msg){ } } -void linphone_chat_room_mark_as_read(LinphoneChatRoom *cr){ - LinphoneCore *lc=linphone_chat_room_get_core(cr); - bctbx_list_t *item; - char *peer; - char *buf; - - if (lc->db==NULL) return ; - - // optimization: do not modify the database if no message is marked as unread - if(linphone_chat_room_get_unread_messages_count(cr) == 0) return; - - peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr)); - buf = sqlite3_mprintf("SELECT * FROM history WHERE remoteContact = %Q AND direction = %i AND status != %i", peer, LinphoneChatMessageIncoming, LinphoneChatMessageStateDisplayed); - linphone_sql_request_message(lc->db, buf, cr); - sqlite3_free(buf); - for (item = cr->messages_hist; item != NULL; item = bctbx_list_next(item)) { - LinphoneChatMessage *cm = (LinphoneChatMessage *)bctbx_list_get_data(item); - linphone_chat_message_send_display_notification(cm); - } - bctbx_list_free_with_data(cr->messages_hist, (bctbx_list_free_func)linphone_chat_message_unref); - cr->messages_hist = NULL; - buf=sqlite3_mprintf("UPDATE history SET status=%i WHERE remoteContact=%Q AND direction=%i;", - LinphoneChatMessageStateDisplayed, peer, LinphoneChatMessageIncoming); - linphone_sql_request(lc->db,buf); - sqlite3_free(buf); - ms_free(peer); - - if (cr->pending_message) { - linphone_chat_message_set_state(cr->pending_message, LinphoneChatMessageStateDisplayed); - linphone_chat_message_send_display_notification(cr->pending_message); - } - - cr->unread_count = 0; +void linphone_chat_room_mark_as_read(LinphoneChatRoom *cr) { + linphone_chat_room_get_cpp_obj(cr).markAsRead(); } -void linphone_chat_room_update_url(LinphoneChatRoom *cr, LinphoneChatMessage *msg) { - LinphoneCore *lc=linphone_chat_room_get_core(cr); - char *buf; - - if (lc->db==NULL) return ; - - buf=sqlite3_mprintf("UPDATE history SET url=%Q WHERE id=%u;",msg->external_body_url,msg->storage_id); - linphone_sql_request(lc->db,buf); - sqlite3_free(buf); +int linphone_chat_room_get_unread_messages_count(LinphoneChatRoom *cr) { + return linphone_chat_room_get_cpp_obj(cr).getUnreadMessagesCount(); } -static int linphone_chat_room_get_messages_count(LinphoneChatRoom *cr, bool_t unread_only){ - LinphoneCore *lc=linphone_chat_room_get_core(cr); - int numrows=0; - char *peer; - char *buf; - char *option = NULL; - sqlite3_stmt *selectStatement; - int returnValue; - - if (lc->db==NULL) return 0; - - // optimization: do not read database if the count is already available in memory - if(unread_only && cr->unread_count >= 0) return cr->unread_count; - - peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr)); - if (unread_only) { - option = bctbx_strdup_printf("AND status!=%i AND direction=%i", LinphoneChatMessageStateDisplayed, LinphoneChatMessageIncoming); - } - buf=sqlite3_mprintf("SELECT count(*) FROM history WHERE remoteContact = %Q %s;",peer,unread_only?option:""); - returnValue = sqlite3_prepare_v2(lc->db,buf,-1,&selectStatement,NULL); - if (returnValue == SQLITE_OK){ - if(sqlite3_step(selectStatement) == SQLITE_ROW){ - numrows= sqlite3_column_int(selectStatement, 0); - } - } - sqlite3_finalize(selectStatement); - sqlite3_free(buf); - ms_free(peer); - - /* no need to test the sign of cr->unread_count here - * because it has been tested above */ - if(unread_only) { - cr->unread_count = numrows; - if (option) bctbx_free(option); - } - - return numrows; -} - -int linphone_chat_room_get_unread_messages_count(LinphoneChatRoom *cr){ - return linphone_chat_room_get_messages_count(cr, TRUE); -} - -int linphone_chat_room_get_history_size(LinphoneChatRoom *cr){ - return linphone_chat_room_get_messages_count(cr, FALSE); +int linphone_chat_room_get_history_size(LinphoneChatRoom *cr) { + return linphone_chat_room_get_cpp_obj(cr).getHistorySize(); } void linphone_chat_room_delete_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg) { - LinphoneCore *lc=cr->lc; - char *buf; - - if (lc->db==NULL) return ; - - buf=sqlite3_mprintf("DELETE FROM history WHERE id = %u;", msg->storage_id); - linphone_sql_request(lc->db,buf); - sqlite3_free(buf); - - /* Invalidate unread_count when we modify the database, so that next - time we need it it will be recomputed from latest database state */ - cr->unread_count = -1; + linphone_chat_room_get_cpp_obj(cr).deleteMessage(msg); } -void linphone_chat_room_delete_history(LinphoneChatRoom *cr){ - LinphoneCore *lc=cr->lc; - char *peer; - char *buf; - - if (lc->db==NULL) return ; - - peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr)); - buf=sqlite3_mprintf("DELETE FROM history WHERE remoteContact = %Q;",peer); - linphone_sql_request(lc->db,buf); - sqlite3_free(buf); - ms_free(peer); - - if(cr->unread_count > 0) cr->unread_count = 0; +void linphone_chat_room_delete_history(LinphoneChatRoom *cr) { + linphone_chat_room_get_cpp_obj(cr).deleteHistory(); } -bctbx_list_t *linphone_chat_room_get_history_range(LinphoneChatRoom *cr, int startm, int endm){ - LinphoneCore *lc=linphone_chat_room_get_core(cr); - bctbx_list_t *ret; - char *buf,*buf2; - char *peer; - uint64_t begin,end; - int buf_max_size = 512; - - if (lc->db==NULL) return NULL; - peer = linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr)); - - cr->messages_hist = NULL; - - /*since we want to append query parameters depending on arguments given, we use malloc instead of sqlite3_mprintf*/ - buf=reinterpret_cast(ms_malloc(buf_max_size)); - buf=sqlite3_snprintf(buf_max_size-1,buf,"SELECT * FROM history WHERE remoteContact = %Q ORDER BY id DESC",peer); - - - if (startm<0) startm=0; - - if ((endm>0&&endm>=startm) || (startm == 0 && endm == 0) ){ - buf2=ms_strdup_printf("%s LIMIT %i ",buf,endm+1-startm); - ms_free(buf); - buf = buf2; - }else if(startm>0){ - ms_message("%s(): end is lower than start (%d < %d). Assuming no end limit.",__FUNCTION__,endm,startm); - buf2=ms_strdup_printf("%s LIMIT -1",buf); - ms_free(buf); - buf = buf2; - } - - if (startm>0){ - buf2=ms_strdup_printf("%s OFFSET %i ",buf,startm); - ms_free(buf); - buf = buf2; - } - - begin=ortp_get_cur_time_ms(); - linphone_sql_request_message(lc->db,buf,cr); - end=ortp_get_cur_time_ms(); - - if (endm+1-startm > 1) { - //display message only if at least 2 messages are loaded - ms_message("%s(): completed in %i ms",__FUNCTION__, (int)(end-begin)); - } - ms_free(buf); - - if (cr->messages_hist) { - //fill local addr with core identity instead of per message - LinphoneAddress* local_addr = linphone_address_new(linphone_core_get_identity(cr->lc)); - bctbx_list_t* it = cr->messages_hist; - while (it) { - LinphoneChatMessage* msg = reinterpret_cast(it->data); - if (msg->dir == LinphoneChatMessageOutgoing) { - if (msg->from != NULL) linphone_address_unref(msg->from); - msg->from = linphone_address_ref(local_addr); - } else { - msg->to = linphone_address_ref(local_addr); - } - it = it->next; - } - linphone_address_unref(local_addr); - } - - ret=cr->messages_hist; - cr->messages_hist=NULL; - ms_free(peer); - return ret; +bctbx_list_t *linphone_chat_room_get_history_range(LinphoneChatRoom *cr, int startm, int endm) { + std::list l = linphone_chat_room_get_cpp_obj(cr).getHistoryRange(startm, endm); + bctbx_list_t *result = nullptr; + for (auto it = l.begin(); it != l.end(); it++) + result = bctbx_list_append(result, *it); + return result; } -bctbx_list_t *linphone_chat_room_get_history(LinphoneChatRoom *cr,int nb_message){ - return linphone_chat_room_get_history_range(cr, 0, nb_message-1); -} - - -bctbx_list_t* linphone_chat_room_find_messages(LinphoneChatRoom *cr, const char *message_id) { - LinphoneCore *lc = linphone_chat_room_get_core(cr); - char *buf; - char *peer; - bctbx_list_t* messages; - - if (lc->db == NULL) return NULL; - peer = linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr)); - cr->messages_hist = NULL; - buf = sqlite3_mprintf("SELECT * FROM history WHERE remoteContact = %Q AND messageId = %Q", peer, message_id); - linphone_sql_request_message(lc->db, buf, cr); - sqlite3_free(buf); - ms_free(peer); - messages = cr->messages_hist; - cr->messages_hist = NULL; - return messages; -} - -LinphoneChatMessage * linphone_chat_room_find_message_with_dir(LinphoneChatRoom *cr, const char *message_id, LinphoneChatMessageDir dir) { - bctbx_list_t* messages = linphone_chat_room_find_messages(cr, message_id); - bctbx_list_t* it; - LinphoneChatMessage *ret = NULL; - for (it = messages; it != NULL; it = it->next) { - LinphoneChatMessage * cm = (LinphoneChatMessage*)it->data; - if (cm->dir == dir) { - linphone_chat_message_ref(cm); - ret = cm; - break; - } - } - if (messages) - bctbx_list_free_with_data(messages, (bctbx_list_free_func)linphone_chat_message_unref); - - return ret; - +bctbx_list_t *linphone_chat_room_get_history(LinphoneChatRoom *cr, int nb_message) { + std::list l = linphone_chat_room_get_cpp_obj(cr).getHistory(nb_message); + bctbx_list_t *result = nullptr; + for (auto it = l.begin(); it != l.end(); it++) + result = bctbx_list_append(result, *it); + return result; } LinphoneChatMessage * linphone_chat_room_find_message(LinphoneChatRoom *cr, const char *message_id) { - bctbx_list_t* messages = linphone_chat_room_find_messages(cr, message_id); - LinphoneChatMessage *cm = NULL; - if (messages) { - cm = (LinphoneChatMessage *)bctbx_list_nth_data(messages, 0); - linphone_chat_message_ref(cm); - bctbx_list_free_with_data(messages, (bctbx_list_free_func)linphone_chat_message_unref); - } - return cm; + return linphone_chat_room_get_cpp_obj(cr).findMessage(message_id); } static void linphone_create_history_table(sqlite3* db){ @@ -975,9 +658,6 @@ void linphone_core_message_storage_init(LinphoneCore *lc){ void linphone_core_message_storage_close(LinphoneCore *lc){ } -void linphone_chat_room_update_url(LinphoneChatRoom *cr, LinphoneChatMessage *msg) { -} - int linphone_chat_room_get_unread_messages_count(LinphoneChatRoom *cr){ return 0; } diff --git a/coreapi/private.h b/coreapi/private.h index 8d4c02c1a..097649d84 100644 --- a/coreapi/private.h +++ b/coreapi/private.h @@ -180,6 +180,12 @@ struct _LinphoneCallParams{ BELLE_SIP_DECLARE_VPTR_NO_EXPORT(LinphoneCallParams); +typedef enum _ImdnType { + ImdnTypeDelivery, + ImdnTypeDisplay +} ImdnType; + + struct _LinphoneQualityReporting{ reporting_session_report_t * reports[3]; /**Store information on audio and video media streams (RFC 6035) */ bool_t was_video_running; /*Keep video state since last check in order to detect its (de)activation*/ @@ -279,11 +285,6 @@ struct _LinphoneChatMessage { #endif }; -/* - *Gets a Message with a given message id and direction. - */ -LINPHONE_PUBLIC LinphoneChatMessage * linphone_chat_room_find_message_with_dir(LinphoneChatRoom *cr, const char *message_id,LinphoneChatMessageDir dir); - BELLE_SIP_DECLARE_VPTR_NO_EXPORT(LinphoneChatMessage); typedef struct StunCandidate{ @@ -594,7 +595,7 @@ LINPHONE_PUBLIC void linphone_core_get_local_ip(LinphoneCore *lc, int af, const LinphoneProxyConfig *linphone_proxy_config_new_from_config_file(LinphoneCore *lc, int index); void linphone_proxy_config_write_to_config_file(LinphoneConfig* config,LinphoneProxyConfig *obj, int index); -LinphoneReason linphone_core_message_received(LinphoneCore *lc, SalOp *op, const SalMessage *msg); +int linphone_core_message_received(LinphoneCore *lc, SalOp *op, const SalMessage *msg); void linphone_core_real_time_text_received(LinphoneCore *lc, LinphoneChatRoom *cr, uint32_t character, LinphoneCall *call); void linphone_call_init_stats(LinphoneCallStats *stats, LinphoneStreamType type); @@ -650,7 +651,8 @@ void _linphone_proxy_config_release_ops(LinphoneProxyConfig *obj); /*chat*/ void linphone_chat_room_release(LinphoneChatRoom *cr); -void linphone_chat_room_add_weak_message(LinphoneChatRoom *cr, LinphoneChatMessage *cm); +void linphone_chat_room_set_call(LinphoneChatRoom *cr, LinphoneCall *call); +bctbx_list_t * linphone_chat_room_get_transient_messages(const LinphoneChatRoom *cr); void linphone_chat_message_destroy(LinphoneChatMessage* msg); void linphone_chat_message_update_state(LinphoneChatMessage *msg, LinphoneChatMessageState new_state); void linphone_chat_message_set_state(LinphoneChatMessage *msg, LinphoneChatMessageState state); @@ -659,11 +661,14 @@ void linphone_chat_message_send_delivery_notification(LinphoneChatMessage *cm, L void linphone_chat_message_send_display_notification(LinphoneChatMessage *cm); void _linphone_chat_message_cancel_file_transfer(LinphoneChatMessage *msg, bool_t unref); int linphone_chat_room_upload_file(LinphoneChatMessage *msg); -void _linphone_chat_room_send_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg); LinphoneChatMessageCbs *linphone_chat_message_cbs_new(void); LinphoneChatRoom *_linphone_core_create_chat_room_from_call(LinphoneCall *call); -void linphone_chat_room_add_transient_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg); void linphone_chat_room_remove_transient_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg); +void linphone_chat_message_deactivate(LinphoneChatMessage *msg); +void linphone_chat_message_release(LinphoneChatMessage *msg); +void create_file_transfer_information_from_vnd_gsma_rcs_ft_http_xml(LinphoneChatMessage *msg); +void linphone_chat_message_fetch_content_from_database(sqlite3 *db, LinphoneChatMessage *message, int content_id); +void linphone_chat_message_send_imdn(LinphoneChatMessage *cm, ImdnType imdn_type, LinphoneReason reason); /**/ struct _LinphoneProxyConfig @@ -740,33 +745,11 @@ typedef enum _LinphoneIsComposingState { LinphoneIsComposingActive } LinphoneIsComposingState; -struct _LinphoneChatRoom{ - belle_sip_object_t base; - void *user_data; - struct _LinphoneCore *lc; - char *peer; - LinphoneAddress *peer_url; - MSList *messages_hist; - MSList *transient_messages; - bctbx_list_t *weak_messages; - int unread_count; - LinphoneIsComposingState remote_is_composing; - LinphoneIsComposingState is_composing; - belle_sip_source_t *remote_composing_refresh_timer; - belle_sip_source_t *composing_idle_timer; - belle_sip_source_t *composing_refresh_timer; - LinphoneCall *call; - LinphoneChatMessage *pending_message; - MSList *received_rtt_characters; -}; - typedef struct _LinphoneChatMessageCharacter { uint32_t value; bool_t has_been_read; } LinphoneChatMessageCharacter; -BELLE_SIP_DECLARE_VPTR_NO_EXPORT(LinphoneChatRoom); - typedef struct _LinphoneFriendPresence { char *uri_or_tel; diff --git a/gtk/calllogs.c b/gtk/calllogs.c index 5f5384d45..9ee24eba8 100644 --- a/gtk/calllogs.c +++ b/gtk/calllogs.c @@ -342,6 +342,12 @@ void linphone_gtk_call_log_update(GtkWidget *w){ case LinphoneCallDeclined: status=_("Declined"); break; + case LinphoneCallAnsweredElsewhere: + status=_("Answered elsewhere"); + break; + case LinphoneCallDeclinedElsewhere: + status=_("Declined elsewhere"); + break; default: break; } diff --git a/include/linphone/types.h b/include/linphone/types.h index 177e4c003..67a72eafa 100644 --- a/include/linphone/types.h +++ b/include/linphone/types.h @@ -343,7 +343,9 @@ typedef enum _LinphoneCallStatus { LinphoneCallAborted, /**< The call was aborted */ LinphoneCallMissed, /**< The call was missed (unanswered) */ LinphoneCallDeclined, /**< The call was declined, either locally or by remote end */ - LinphoneCallEarlyAborted /**. + */ + +// From coreapi. +#include "private.h" + +#include "logger/logger.h" +#include "object/clonable-object-p.h" + +#include "address.h" + +// ============================================================================= + +using namespace std; + +LINPHONE_BEGIN_NAMESPACE + +class AddressPrivate : public ClonableObjectPrivate { +public: + SalAddress *internalAddress = nullptr; +}; + +// ----------------------------------------------------------------------------- + +Address::Address (const string &address) : ClonableObject(*new AddressPrivate) { + L_D(Address); + if (!address.empty() && !(d->internalAddress = sal_address_new(address.c_str()))) + lWarning() << "Cannot create address, bad uri [" << address << "]."; +} + +Address::Address (const Address &src) : ClonableObject(*new AddressPrivate) { + L_D(Address); + SalAddress *salAddress = src.getPrivate()->internalAddress; + if (salAddress) + d->internalAddress = sal_address_clone(salAddress); +} + +Address::~Address () { + L_D(Address); + if (d->internalAddress) + sal_address_destroy(d->internalAddress); +} + +Address &Address::operator= (const Address &src) { + L_D(Address); + if (this != &src) { + if (d->internalAddress) + sal_address_destroy(d->internalAddress); + SalAddress *salAddress = src.getPrivate()->internalAddress; + d->internalAddress = salAddress ? sal_address_clone(salAddress) : nullptr; + } + + return *this; +} + +Address::operator bool () const { + L_D(const Address); + return static_cast(d->internalAddress); +} + +bool Address::operator== (const Address &address) const { + return equal(address); +} + +string Address::getScheme () const { + L_D(const Address); + return d->internalAddress ? sal_address_get_scheme(d->internalAddress) : ""; +} + +string Address::getDisplayName () const { + L_D(const Address); + return d->internalAddress ? sal_address_get_display_name(d->internalAddress) : ""; +} + +bool Address::setDisplayName (const string &displayName) { + L_D(const Address); + + if (!d->internalAddress) + return false; + + sal_address_set_display_name(d->internalAddress, displayName.c_str()); + return true; +} + +string Address::getUsername () const { + L_D(const Address); + return d->internalAddress ? sal_address_get_username(d->internalAddress) : ""; +} + +bool Address::setUsername (const string &username) { + L_D(const Address); + + if (!d->internalAddress) + return false; + + sal_address_set_username(d->internalAddress, username.c_str()); + return true; +} + +string Address::getDomain () const { + L_D(const Address); + return d->internalAddress ? sal_address_get_domain(d->internalAddress) : ""; +} + +bool Address::setDomain (const string &domain) { + L_D(const Address); + + if (!d->internalAddress) + return false; + + sal_address_set_domain(d->internalAddress, domain.c_str()); + return true; +} + +int Address::getPort () const { + L_D(const Address); + return d->internalAddress ? sal_address_get_port(d->internalAddress) : 0; +} + +bool Address::setPort (int port) { + L_D(const Address); + + if (!d->internalAddress) + return false; + + sal_address_set_port(d->internalAddress, port); + return true; +} + +Transport Address::getTransport () const { + L_D(const Address); + return d->internalAddress ? static_cast(sal_address_get_transport(d->internalAddress)) : Transport::Udp; +} + +bool Address::setTransport (Transport transport) { + L_D(const Address); + + if (!d->internalAddress) + return false; + + sal_address_set_transport(d->internalAddress, static_cast(transport)); + return true; +} + +bool Address::getSecure () const { + L_D(const Address); + return d->internalAddress ? sal_address_is_secure(d->internalAddress) : false; +} + +bool Address::setSecure (bool enabled) { + L_D(const Address); + + if (!d->internalAddress) + return false; + + sal_address_set_secure(d->internalAddress, enabled); + return true; +} + +bool Address::isSip () const { + L_D(const Address); + return d->internalAddress ? sal_address_is_sip(d->internalAddress) : false; +} + +string Address::getMethodParam () const { + L_D(const Address); + return d->internalAddress ? sal_address_get_method_param(d->internalAddress) : ""; +} + +bool Address::setMethodParam (const string &methodParam) { + L_D(const Address); + + if (!d->internalAddress) + return false; + + sal_address_set_method_param(d->internalAddress, methodParam.c_str()); + return true; +} + +string Address::getPassword () const { + L_D(const Address); + return sal_address_get_password(d->internalAddress); +} + +bool Address::setPassword (const string &password) { + L_D(const Address); + + if (!d->internalAddress) + return false; + + sal_address_set_password(d->internalAddress, password.c_str()); + return true; +} + +bool Address::clean () { + L_D(const Address); + + if (!d->internalAddress) + return false; + + sal_address_clean(d->internalAddress); + return true; +} + +string Address::asString () const { + L_D(const Address); + + if (!d->internalAddress) + return ""; + + char *buf = sal_address_as_string(d->internalAddress); + string out = buf; + ms_free(buf); + return out; +} + +string Address::asStringUriOnly () const { + L_D(const Address); + + if (!d->internalAddress) + return ""; + + char *buf = sal_address_as_string_uri_only(d->internalAddress); + string out = buf; + ms_free(buf); + return out; +} + +bool Address::equal (const Address &address) const { + return asString() == address.asString(); +} + +bool Address::weakEqual (const Address &address) const { + return getUsername() == address.getUsername() && + getDomain() == address.getDomain() && + getPort() == address.getPort(); +} + +string Address::getHeaderValue (const string &headerName) const { + L_D(const Address); + return d->internalAddress ? sal_address_get_header(d->internalAddress, headerName.c_str()) : ""; +} + +bool Address::setHeader (const string &headerName, const string &headerValue) { + L_D(const Address); + + if (!d->internalAddress) + return false; + + sal_address_set_header(d->internalAddress, headerName.c_str(), headerValue.c_str()); + return true; +} + +LINPHONE_END_NAMESPACE diff --git a/src/address/address.h b/src/address/address.h new file mode 100644 index 000000000..56154c526 --- /dev/null +++ b/src/address/address.h @@ -0,0 +1,90 @@ +/* + * address.h + * Copyright (C) 2017 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 3 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, see . + */ + +#ifndef _ADDRESS_H_ +#define _ADDRESS_H_ + +#include + +#include "enums.h" +#include "object/clonable-object.h" + +// ============================================================================= + +LINPHONE_BEGIN_NAMESPACE + +class AddressPrivate; + +class LINPHONE_PUBLIC Address : public ClonableObject { +public: + Address (const std::string &address = ""); + Address (const Address &src); + ~Address (); + + Address &operator= (const Address &src); + + operator bool () const; + + bool operator== (const Address &address) const; + + std::string getScheme () const; + + std::string getDisplayName () const; + bool setDisplayName (const std::string &displayName); + + std::string getUsername () const; + bool setUsername (const std::string &username); + + std::string getDomain () const; + bool setDomain (const std::string &domain); + + int getPort () const; + bool setPort (int port); + + Transport getTransport () const; + bool setTransport (Transport transport); + + bool getSecure () const; + bool setSecure (bool enabled); + + bool isSip () const; + + std::string getMethodParam () const; + bool setMethodParam (const std::string &methodParam); + + std::string getPassword () const; + bool setPassword (const std::string &password); + + bool clean (); + + std::string asString () const; + std::string asStringUriOnly () const; + + bool equal (const Address &address) const; + bool weakEqual (const Address &address) const; + + std::string getHeaderValue (const std::string &headerName) const; + bool setHeader (const std::string &headerName, const std::string &headerValue); + +private: + L_DECLARE_PRIVATE(Address); +}; + +LINPHONE_END_NAMESPACE + +#endif // ifndef _ADDRESS_H_ diff --git a/src/c-wrapper/api/c-event-log.cpp b/src/c-wrapper/api/c-event-log.cpp index c246aef3d..2404b5418 100644 --- a/src/c-wrapper/api/c-event-log.cpp +++ b/src/c-wrapper/api/c-event-log.cpp @@ -20,12 +20,18 @@ #include "c-event-log.h" +#include "event-log/call-event.h" +#include "event-log/conference-participant-event.h" #include "event-log/message-event.h" // ============================================================================= using namespace std; +extern "C" { +// ----------------------------------------------------------------------------- +// Event log. +// ----------------------------------------------------------------------------- L_DECLARE_C_STRUCT_IMPL(EventLog, event_log); L_DECLARE_C_STRUCT_NEW_DEFAULT(EventLog, event_log); @@ -34,6 +40,10 @@ LinphoneEventLogType linphone_event_log_get_type (const LinphoneEventLog *eventL return static_cast(eventLog->cppPtr->getType()); } +// ----------------------------------------------------------------------------- +// Message event. +// ----------------------------------------------------------------------------- + L_DECLARE_C_STRUCT_IMPL(MessageEvent, message_event); LinphoneMessageEvent *linphone_message_event_new (LinphoneMessage *message) { @@ -47,3 +57,59 @@ LinphoneMessage *linphone_message_event_get_message (const LinphoneMessageEvent // TODO. return nullptr; } + +// ----------------------------------------------------------------------------- +// Call event. +// ----------------------------------------------------------------------------- + +// L_DECLARE_C_STRUCT_IMPL(CallEvent, call_event); + +LinphoneCallEvent *linphone_call_event_new (LinphoneEventLogType type, LinphoneCall *call) { + // TODO. + return nullptr; +} + +LinphoneCall *linphone_call_event_get_call (const LinphoneCallEvent *call_event) { + // TODO. + return nullptr; +} + +// ----------------------------------------------------------------------------- +// Conference event. +// ----------------------------------------------------------------------------- + +// L_DECLARE_C_STRUCT_IMPL(ConferenceEvent, conference_event); + +LinphoneConferenceEvent *linphone_conference_event_new ( + LinphoneEventLogType type, + const LinphoneAddress *address +) { + // TODO. + return nullptr; +} + +const LinphoneAddress *linphone_conference_event_get_address (const LinphoneConferenceEvent *conference_event) { + // TODO. + return nullptr; +} + +// ----------------------------------------------------------------------------- +// Conference participant event. +// ----------------------------------------------------------------------------- + +// L_DECLARE_C_STRUCT_IMPL(ConferenceParticipantEvent, conference_participant_event); + +LinphoneConferenceParticipantEvent *linphone_conference_participant_event_new ( + LinphoneEventLogType type, + const LinphoneAddress *conference_address, + const LinphoneAddress *participant_address +) { + // TODO. + return nullptr; +} + +const LinphoneAddress *linphone_conference_participant_event_get_participant_address (const LinphoneConferenceParticipantEvent *conference_participant_event) { + // TODO. + return nullptr; +} +} diff --git a/src/c-wrapper/api/c-event-log.h b/src/c-wrapper/api/c-event-log.h index 38eea75aa..a33edbabb 100644 --- a/src/c-wrapper/api/c-event-log.h +++ b/src/c-wrapper/api/c-event-log.h @@ -23,6 +23,10 @@ // ============================================================================= +#ifdef __cplusplus + extern "C" { +#endif + LINPHONE_PUBLIC LinphoneEventLog *linphone_event_log_new (); LINPHONE_PUBLIC LinphoneEventLogType linphone_event_log_get_type (const LinphoneEventLog *event_log); @@ -34,7 +38,7 @@ LINPHONE_PUBLIC LinphoneConferenceEvent *linphone_conference_event_new ( const LinphoneAddress *address ); -LINPHONE_PUBLIC const LinphoneAddress *linphone_conference_event_get_address (); +LINPHONE_PUBLIC const LinphoneAddress *linphone_conference_event_get_address (const LinphoneConferenceEvent *conference_event); LINPHONE_PUBLIC LinphoneConferenceParticipantEvent *linphone_conference_participant_event_new ( LinphoneEventLogType type, @@ -42,9 +46,13 @@ LINPHONE_PUBLIC LinphoneConferenceParticipantEvent *linphone_conference_particip const LinphoneAddress *participantAddress ); -LINPHONE_PUBLIC const LinphoneAddress *linphone_conference_participant_event_get_participant_address (); +LINPHONE_PUBLIC const LinphoneAddress *linphone_conference_participant_event_get_participant_address (const LinphoneConferenceParticipantEvent *conference_participant_event); LINPHONE_PUBLIC LinphoneMessageEvent *linphone_message_event_new (LinphoneMessage *message); LINPHONE_PUBLIC LinphoneMessage *linphone_message_event_get_message (const LinphoneMessageEvent *message_event); +#ifdef __cplusplus + } +#endif + #endif // ifndef _C_EVENT_LOG_H_ diff --git a/src/c-wrapper/c-tools.h b/src/c-wrapper/c-tools.h index 1a4c23db3..a63eef594 100644 --- a/src/c-wrapper/c-tools.h +++ b/src/c-wrapper/c-tools.h @@ -52,7 +52,7 @@ ); #define L_DECLARE_C_STRUCT_NEW_DEFAULT(STRUCT, C_NAME) \ - Linphone ## STRUCT * CNAME ## _new() { \ + Linphone ## STRUCT * linphone_ ## C_NAME ## _new() { \ Linphone ## STRUCT * object = _linphone_ ## C_NAME ## _init(); \ object->cppPtr = make_shared(); \ return object; \ diff --git a/src/c-wrapper/c-types.h b/src/c-wrapper/c-types.h index 51ff9cfe2..933b067b6 100644 --- a/src/c-wrapper/c-types.h +++ b/src/c-wrapper/c-types.h @@ -19,18 +19,25 @@ #ifndef _C_TYPES_H_ #define _C_TYPES_H_ -// Do not move these defines. -#define L_DECLARE_ENUM(CLASS, ENUM) enum Linphone ## CLASS ## ENUM -#define L_DECLARE_C_STRUCT(STRUCT) typedef struct _Linphone ## STRUCT Linphone ## STRUCT; +// Do not move this define. +// Enable C enums. +#define L_USE_C_ENUM #include "event-log/event-log-enums.h" +#define L_DECLARE_C_ENUM(CLASS, ENUM, VALUES) enum Linphone ## CLASS ## ENUM { VALUES } +#define L_DECLARE_C_STRUCT(STRUCT) typedef struct _Linphone ## STRUCT Linphone ## STRUCT; + // ============================================================================= #ifdef __cplusplus extern "C" { #endif +// ============================================================================= +// C Structures. +// ============================================================================= + L_DECLARE_C_STRUCT(Call); L_DECLARE_C_STRUCT(CallEvent); L_DECLARE_C_STRUCT(ConferenceEvent); @@ -42,6 +49,12 @@ L_DECLARE_C_STRUCT(MessageEvent); // TODO: Remove me in the future. typedef struct SalAddress LinphoneAddress; +// ============================================================================= +// C Enums. +// ============================================================================= + +L_DECLARE_C_ENUM(EventLog, Type, L_ENUM_VALUES_EVENT_LOG_TYPE); + #ifdef __cplusplus } #endif diff --git a/src/c-wrapper/wrapper.h b/src/c-wrapper/wrapper.h new file mode 100644 index 000000000..18f2e30e6 --- /dev/null +++ b/src/c-wrapper/wrapper.h @@ -0,0 +1,48 @@ +/* + * wrapper.h + * Copyright (C) 2017 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 3 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, see . + */ + +#ifndef _WRAPPER_H_ +#define _WRAPPER_H_ + +#include + +#include "object/object.h" + +// ============================================================================= + +LINPHONE_BEGIN_NAMESPACE + +class Wrapper { +public: + template + static decltype(std::declval().getPrivate()) getPrivate (T *object) { + return object->getPrivate(); + } + +private: + Wrapper (); + + L_DISABLE_COPY(Wrapper); +}; + +LINPHONE_END_NAMESPACE + +#define L_GET_PRIVATE(OBJECT) \ + LINPHONE_NAMESPACE::Wrapper::getPrivate(OBJECT) + +#endif // ifndef _WRAPPER_H_ diff --git a/src/chat/chat-room-p.h b/src/chat/chat-room-p.h new file mode 100644 index 000000000..9e6de5c25 --- /dev/null +++ b/src/chat/chat-room-p.h @@ -0,0 +1,127 @@ +/* + * chat-room-p.h + * Copyright (C) 2017 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 3 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, see . + */ + +#ifndef _CHAT_ROOM_P_H_ +#define _CHAT_ROOM_P_H_ + +// From coreapi. +#include "private.h" + +#include "chat-room.h" +#include "object/object-p.h" + +// ============================================================================= + +LINPHONE_BEGIN_NAMESPACE + +class ChatRoomPrivate : public ObjectPrivate { +public: + virtual ~ChatRoomPrivate (); + +private: + static int refreshComposing (void *data, unsigned int revents); + static int remoteRefreshComposing (void *data, unsigned int revents); + static int stopComposing (void *data, unsigned int revents); + + static int createChatMessageFromDb (void *data, int argc, char **argv, char **colName); + static void onWeakMessageDestroyed (void *obj, belle_sip_object_t *messageBeingDestroyed); + +public: + void addTransientMessage (LinphoneChatMessage *msg); + void addWeakMessage (LinphoneChatMessage *msg); + std::list getTransientMessages () const { + return transientMessages; + } + + void moveTransientMessageToWeakMessages (LinphoneChatMessage *msg); + void removeTransientMessage (LinphoneChatMessage *msg); + + void release (); + void sendImdn (const std::string &content, LinphoneReason reason); + + int getMessagesCount (bool unreadOnly); + void setCBackPointer (LinphoneChatRoom *cr) { + this->cBackPointer = cr; + } + + void setCall (LinphoneCall *call) { + this->call = call; + } + +private: + std::string createIsComposingXml () const; + void deleteComposingIdleTimer (); + void deleteComposingRefreshTimer (); + void deleteRemoteComposingRefreshTimer (); + int refreshComposing (unsigned int revents); + int remoteRefreshComposing (unsigned int revents); + void sendIsComposingNotification (); + int stopComposing (unsigned int revents); + void processImdn (xmlparsing_context_t *xmlCtx); + void processIsComposingNotification (xmlparsing_context_t *xmlCtx); + + int createChatMessageFromDb (int argc, char **argv, char **colName); + void onWeakMessageDestroyed (LinphoneChatMessage *messageBeingDestroyed); + LinphoneChatMessage *getTransientMessage (unsigned int storageId) const; + LinphoneChatMessage *getWeakMessage (unsigned int storageId) const; + int sqlRequest (sqlite3 *db, const std::string &stmt); + void sqlRequestMessage (sqlite3 *db, const std::string &stmt); + std::list findMessages (const std::string &messageId); + LinphoneChatMessage *findMessageWithDirection (const std::string &messageId, LinphoneChatMessageDir direction); + void storeOrUpdateMessage (LinphoneChatMessage *msg); + +public: + LinphoneReason messageReceived (SalOp *op, const SalMessage *msg); + void realtimeTextReceived (uint32_t character, LinphoneCall *call); + +private: + void chatMessageReceived (LinphoneChatMessage *msg); + void imdnReceived (const std::string &text); + void isComposingReceived (const std::string &text); + +public: + static const int composingDefaultIdleTimeout = 15; + static const int composingDefaultRefreshTimeout = 60; + static const int composingDefaultRemoteRefreshTimeout = 120; + static const std::string imdnPrefix; + static const std::string isComposingPrefix; + + LinphoneChatRoom *cBackPointer = nullptr; + LinphoneCore *core = nullptr; + LinphoneCall *call = nullptr; + LinphoneAddress *peerAddress = nullptr; + std::string peer; + int unreadCount = -1; + bool isComposing = false; + bool remoteIsComposing = false; + belle_sip_source_t *remoteComposingRefreshTimer = nullptr; + belle_sip_source_t *composingIdleTimer = nullptr; + belle_sip_source_t *composingRefreshTimer = nullptr; + std::list messages; + std::list transientMessages; + std::list weakMessages; + std::list receivedRttCharacters; + LinphoneChatMessage *pendingMessage = nullptr; + +private: + L_DECLARE_PUBLIC(ChatRoom); +}; + +LINPHONE_END_NAMESPACE + +#endif // ifndef _CHAT_ROOM_P_H_ diff --git a/src/chat/chat-room.cpp b/src/chat/chat-room.cpp new file mode 100644 index 000000000..b6dcb41d2 --- /dev/null +++ b/src/chat/chat-room.cpp @@ -0,0 +1,1235 @@ +/* + * chat-room.cpp + * Copyright (C) 2017 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 3 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, see . + */ + +#include + +#include "chat-room-p.h" +#include "logger/logger.h" +#include "utils/content-type.h" +#include "utils/utils.h" + +#include "chat-room.h" + +// ============================================================================= + +using namespace std; + +LINPHONE_BEGIN_NAMESPACE + +const string ChatRoomPrivate::imdnPrefix = "/imdn:imdn"; +const string ChatRoomPrivate::isComposingPrefix = "/xsi:isComposing"; + +// ----------------------------------------------------------------------------- + +ChatRoomPrivate::~ChatRoomPrivate () { + deleteComposingIdleTimer(); + deleteComposingRefreshTimer(); + deleteRemoteComposingRefreshTimer(); + for (auto it = transientMessages.begin(); it != transientMessages.end(); it++) { + linphone_chat_message_release(*it); + } + if (!receivedRttCharacters.empty()) { + for (auto it = receivedRttCharacters.begin(); it != receivedRttCharacters.end(); it++) + bctbx_free(*it); + } + if (core) { + if (bctbx_list_find(core->chatrooms, cBackPointer)) { + lError() << "LinphoneChatRoom[" << cBackPointer << "] is destroyed while still being used by the LinphoneCore. " << + "This is abnormal. linphone_core_get_chat_room() doesn't give a reference, there is no need to call linphone_chat_room_unref(). " << + "In order to remove a chat room from the core, use linphone_core_delete_chat_room()."; + core->chatrooms = bctbx_list_remove(core->chatrooms, cBackPointer); + } + } + linphone_address_unref(peerAddress); + if (pendingMessage) + linphone_chat_message_destroy(pendingMessage); +} + +// ----------------------------------------------------------------------------- + +int ChatRoomPrivate::refreshComposing (void *data, unsigned int revents) { + ChatRoomPrivate *d = reinterpret_cast(data); + return d->refreshComposing(revents); +} + +int ChatRoomPrivate::remoteRefreshComposing (void *data, unsigned int revents) { + ChatRoomPrivate *d = reinterpret_cast(data); + return d->remoteRefreshComposing(revents); +} + +int ChatRoomPrivate::stopComposing (void *data, unsigned int revents) { + ChatRoomPrivate *d = reinterpret_cast(data); + return d->stopComposing(revents); +} + +// ----------------------------------------------------------------------------- + +int ChatRoomPrivate::createChatMessageFromDb (void *data, int argc, char **argv, char **colName) { + ChatRoomPrivate *d = reinterpret_cast(data); + return d->createChatMessageFromDb(argc, argv, colName); +} + +void ChatRoomPrivate::onWeakMessageDestroyed (void *obj, belle_sip_object_t *messageBeingDestroyed) { + ChatRoomPrivate *d = reinterpret_cast(obj); + d->onWeakMessageDestroyed(reinterpret_cast(messageBeingDestroyed)); +} + +// ----------------------------------------------------------------------------- + +void ChatRoomPrivate::addTransientMessage (LinphoneChatMessage *msg) { + auto iter = find(transientMessages.begin(), transientMessages.end(), msg); + if (iter == transientMessages.end()) + transientMessages.push_back(linphone_chat_message_ref(msg)); +} + +void ChatRoomPrivate::addWeakMessage (LinphoneChatMessage *msg) { + auto iter = find(weakMessages.begin(), weakMessages.end(), msg); + if (iter == weakMessages.end()) + weakMessages.push_back(reinterpret_cast(belle_sip_object_weak_ref(msg, onWeakMessageDestroyed, this))); +} + +void ChatRoomPrivate::moveTransientMessageToWeakMessages (LinphoneChatMessage *msg) { + auto iter = find(transientMessages.begin(), transientMessages.end(), msg); + if (iter != transientMessages.end()) { + /* msg is not transient anymore, we can remove it from our transient list and unref it */ + addWeakMessage(msg); + removeTransientMessage(msg); + } else { + /* msg has already been removed from the transient messages, do nothing */ + } +} + +void ChatRoomPrivate::removeTransientMessage (LinphoneChatMessage *msg) { + auto iter = find(transientMessages.begin(), transientMessages.end(), msg); + if (iter != transientMessages.end()) { + linphone_chat_message_unref(*iter); + transientMessages.erase(iter); + } +} + +// ----------------------------------------------------------------------------- + +void ChatRoomPrivate::release () { + deleteComposingIdleTimer(); + deleteComposingRefreshTimer(); + deleteRemoteComposingRefreshTimer(); + for (auto it = weakMessages.begin(); it != weakMessages.end(); it++) { + linphone_chat_message_deactivate(*it); + } + for (auto it = transientMessages.begin(); it != transientMessages.end(); it++) { + linphone_chat_message_deactivate(*it); + } + core = nullptr; + linphone_chat_room_unref(cBackPointer); +} + +void ChatRoomPrivate::sendImdn (const string &content, LinphoneReason reason) { + L_Q(ChatRoom); + + const char *identity = nullptr; + LinphoneProxyConfig *proxy = linphone_core_lookup_known_proxy(core, peerAddress); + if (proxy) + identity = linphone_address_as_string(linphone_proxy_config_get_identity_address(proxy)); + else + identity = linphone_core_get_primary_contact(core); + + /* Sending out of call */ + SalOp *op = sal_op_new(core->sal); + linphone_configure_op(core, op, peerAddress, nullptr, lp_config_get_int(core->config, "sip", "chat_msg_with_contact", 0)); + LinphoneChatMessage *msg = q->createMessage(content); + LinphoneAddress *fromAddr = linphone_address_new(identity); + linphone_chat_message_set_from_address(msg, fromAddr); + LinphoneAddress *toAddr = linphone_address_new(peer.c_str()); + linphone_chat_message_set_to_address(msg, toAddr); + linphone_chat_message_set_content_type(msg, "message/imdn+xml"); + + /* Do not try to encrypt the notification when it is reporting an error (maybe it should be bypassed only for some reasons). */ + int retval = -1; + LinphoneImEncryptionEngine *imee = linphone_core_get_im_encryption_engine(core); + if (imee && (reason == LinphoneReasonNone)) { + LinphoneImEncryptionEngineCbs *imeeCbs = linphone_im_encryption_engine_get_callbacks(imee); + LinphoneImEncryptionEngineCbsOutgoingMessageCb cbProcessOutgoingMessage = linphone_im_encryption_engine_cbs_get_process_outgoing_message(imeeCbs); + if (cbProcessOutgoingMessage) { + retval = cbProcessOutgoingMessage(imee, cBackPointer, msg); + } + } + + if (retval <= 0) { + sal_message_send(op, identity, peer.c_str(), msg->content_type, msg->message, nullptr); + } + + linphone_chat_message_unref(msg); + linphone_address_unref(fromAddr); + linphone_address_unref(toAddr); + sal_op_unref(op); +} + +// ----------------------------------------------------------------------------- + +int ChatRoomPrivate::getMessagesCount (bool unreadOnly) { + if (!core->db) return 0; + + /* Optimization: do not read database if the count is already available in memory */ + if (unreadOnly && unreadCount >= 0) return unreadCount; + + char *peer = linphone_address_as_string_uri_only(peerAddress); + char *option = nullptr; + if (unreadOnly) + option = bctbx_strdup_printf("AND status!=%i AND direction=%i", LinphoneChatMessageStateDisplayed, LinphoneChatMessageIncoming); + char *buf = sqlite3_mprintf("SELECT count(*) FROM history WHERE remoteContact = %Q %s;", peer, unreadOnly ? option : ""); + sqlite3_stmt *selectStatement; + int numrows = 0; + int returnValue = sqlite3_prepare_v2(core->db, buf, -1, &selectStatement, nullptr); + if (returnValue == SQLITE_OK) { + if (sqlite3_step(selectStatement) == SQLITE_ROW) { + numrows = sqlite3_column_int(selectStatement, 0); + } + } + sqlite3_finalize(selectStatement); + sqlite3_free(buf); + ms_free(peer); + + /* No need to test the sign of unreadCount here because it has been tested above */ + if (unreadOnly) { + unreadCount = numrows; + } + if (option) bctbx_free(option); + return numrows; +} + +// ----------------------------------------------------------------------------- + +string ChatRoomPrivate::createIsComposingXml () const { + string content; + + xmlBufferPtr buf = xmlBufferCreate(); + if (!buf) { + lError() << "Error creating the XML buffer"; + return content; + } + xmlTextWriterPtr writer = xmlNewTextWriterMemory(buf, 0); + if (!writer) { + lError() << "Error creating the XML writer"; + return content; + } + + int err = xmlTextWriterStartDocument(writer, "1.0", "UTF-8", nullptr); + if (err >= 0) { + err = xmlTextWriterStartElementNS(writer, nullptr, (const xmlChar *)"isComposing", + (const xmlChar *)"urn:ietf:params:xml:ns:im-iscomposing"); + } + if (err >= 0) { + err = xmlTextWriterWriteAttributeNS(writer, (const xmlChar *)"xmlns", (const xmlChar *)"xsi", nullptr, + (const xmlChar *)"http://www.w3.org/2001/XMLSchema-instance"); + } + if (err >= 0) { + err = xmlTextWriterWriteAttributeNS(writer, (const xmlChar *)"xsi", (const xmlChar *)"schemaLocation", nullptr, + (const xmlChar *)"urn:ietf:params:xml:ns:im-composing iscomposing.xsd"); + } + if (err >= 0) { + err = xmlTextWriterWriteElement(writer, (const xmlChar *)"state", + isComposing ? (const xmlChar *)"active" : (const xmlChar *)"idle"); + } + if ((err >= 0) && isComposing) { + int refreshTimeout = lp_config_get_int(core->config, "sip", "composing_refresh_timeout", composingDefaultRefreshTimeout); + err = xmlTextWriterWriteElement(writer, (const xmlChar *)"refresh", (const xmlChar *)Utils::toString(refreshTimeout).c_str()); + } + if (err >= 0) { + /* Close the "isComposing" element. */ + err = xmlTextWriterEndElement(writer); + } + if (err >= 0) { + err = xmlTextWriterEndDocument(writer); + } + if (err > 0) { + /* xmlTextWriterEndDocument returns the size of the content. */ + content = (char *)buf->content; + } + xmlFreeTextWriter(writer); + xmlBufferFree(buf); + return content; +} + +void ChatRoomPrivate::deleteComposingIdleTimer () { + if (composingIdleTimer) { + if (core && core->sal) + sal_cancel_timer(core->sal, composingIdleTimer); + belle_sip_object_unref(composingIdleTimer); + composingIdleTimer = nullptr; + } +} + +void ChatRoomPrivate::deleteComposingRefreshTimer () { + if (composingRefreshTimer) { + if (core && core->sal) + sal_cancel_timer(core->sal, composingRefreshTimer); + belle_sip_object_unref(composingRefreshTimer); + composingRefreshTimer = nullptr; + } +} + +void ChatRoomPrivate::deleteRemoteComposingRefreshTimer () { + if (remoteComposingRefreshTimer) { + if (core && core->sal) + sal_cancel_timer(core->sal, remoteComposingRefreshTimer); + belle_sip_object_unref(remoteComposingRefreshTimer); + remoteComposingRefreshTimer = nullptr; + } +} + +int ChatRoomPrivate::refreshComposing (unsigned int revents) { + sendIsComposingNotification(); + return BELLE_SIP_CONTINUE; +} + +int ChatRoomPrivate::remoteRefreshComposing (unsigned int revents) { + belle_sip_object_unref(remoteComposingRefreshTimer); + remoteComposingRefreshTimer = nullptr; + remoteIsComposing = false; + linphone_core_notify_is_composing_received(core, cBackPointer); + return BELLE_SIP_STOP; +} + +void ChatRoomPrivate::sendIsComposingNotification () { + L_Q(ChatRoom); + LinphoneImNotifPolicy *policy = linphone_core_get_im_notif_policy(core); + if (linphone_im_notif_policy_get_send_is_composing(policy)) { + LinphoneProxyConfig *proxy = linphone_core_lookup_known_proxy(core, peerAddress); + const char *identity = nullptr; + + if (proxy) + identity = linphone_address_as_string(linphone_proxy_config_get_identity_address(proxy)); + else + identity = linphone_core_get_primary_contact(core); + + /* Sending out of call */ + SalOp *op = sal_op_new(core->sal); + linphone_configure_op(core, op, peerAddress, nullptr, lp_config_get_int(core->config, "sip", "chat_msg_with_contact", 0)); + string content = createIsComposingXml(); + if (!content.empty()) { + int retval = -1; + LinphoneAddress *fromAddr = linphone_address_new(identity); + LinphoneChatMessage *msg = q->createMessage(content); + linphone_chat_message_set_from_address(msg, fromAddr); + linphone_chat_message_set_to_address(msg, peerAddress); + linphone_chat_message_set_content_type(msg, "application/im-iscomposing+xml"); + + LinphoneImEncryptionEngine *imee = linphone_core_get_im_encryption_engine(core); + if (imee) { + LinphoneImEncryptionEngineCbs *imeeCbs = linphone_im_encryption_engine_get_callbacks(imee); + LinphoneImEncryptionEngineCbsOutgoingMessageCb cbProcessOutgoingMessage = linphone_im_encryption_engine_cbs_get_process_outgoing_message(imeeCbs); + if (cbProcessOutgoingMessage) { + retval = cbProcessOutgoingMessage(imee, cBackPointer, msg); + } + } + + if (retval <= 0) { + sal_message_send(op, identity, peer.c_str(), msg->content_type, msg->message, nullptr); + } + + linphone_chat_message_unref(msg); + linphone_address_unref(fromAddr); + sal_op_unref(op); + } + } +} + +int ChatRoomPrivate::stopComposing (unsigned int revents) { + isComposing = false; + sendIsComposingNotification(); + deleteComposingRefreshTimer(); + belle_sip_object_unref(composingIdleTimer); + composingIdleTimer = nullptr; + return BELLE_SIP_STOP; +} + +void ChatRoomPrivate::processImdn (xmlparsing_context_t *xmlCtx) { + char xpathStr[MAX_XPATH_LENGTH]; + char *messageIdStr = nullptr; + char *datetimeStr = nullptr; + if (linphone_create_xml_xpath_context(xmlCtx) < 0) + return; + + xmlXPathRegisterNs(xmlCtx->xpath_ctx, (const xmlChar *)"imdn", (const xmlChar *)"urn:ietf:params:xml:ns:imdn"); + xmlXPathObjectPtr imdnObject = linphone_get_xml_xpath_object_for_node_list(xmlCtx, imdnPrefix.c_str()); + if (imdnObject) { + if (imdnObject->nodesetval && (imdnObject->nodesetval->nodeNr >= 1)) { + snprintf(xpathStr, sizeof(xpathStr), "%s[1]/imdn:message-id", imdnPrefix.c_str()); + messageIdStr = linphone_get_xml_text_content(xmlCtx, xpathStr); + snprintf(xpathStr, sizeof(xpathStr), "%s[1]/imdn:datetime", imdnPrefix.c_str()); + datetimeStr = linphone_get_xml_text_content(xmlCtx, xpathStr); + } + xmlXPathFreeObject(imdnObject); + } + + if (messageIdStr && datetimeStr) { + LinphoneChatMessage *cm = findMessageWithDirection(messageIdStr, LinphoneChatMessageOutgoing); + if (!cm) { + lWarning() << "Received IMDN for unknown message " << messageIdStr; + } else { + LinphoneImNotifPolicy *policy = linphone_core_get_im_notif_policy(core); + snprintf(xpathStr, sizeof(xpathStr), "%s[1]/imdn:delivery-notification/imdn:status", imdnPrefix.c_str()); + xmlXPathObjectPtr deliveryStatusObject = linphone_get_xml_xpath_object_for_node_list(xmlCtx, xpathStr); + snprintf(xpathStr, sizeof(xpathStr), "%s[1]/imdn:display-notification/imdn:status", imdnPrefix.c_str()); + xmlXPathObjectPtr displayStatusObject = linphone_get_xml_xpath_object_for_node_list(xmlCtx, xpathStr); + if (deliveryStatusObject && linphone_im_notif_policy_get_recv_imdn_delivered(policy)) { + if (deliveryStatusObject->nodesetval && (deliveryStatusObject->nodesetval->nodeNr >= 1)) { + xmlNodePtr node = deliveryStatusObject->nodesetval->nodeTab[0]; + if (node->children && node->children->name) { + if (strcmp((const char *)node->children->name, "delivered") == 0) { + linphone_chat_message_update_state(cm, LinphoneChatMessageStateDeliveredToUser); + } else if (strcmp((const char *)node->children->name, "error") == 0) { + linphone_chat_message_update_state(cm, LinphoneChatMessageStateNotDelivered); + } + } + } + xmlXPathFreeObject(deliveryStatusObject); + } + if (displayStatusObject && linphone_im_notif_policy_get_recv_imdn_displayed(policy)) { + if (displayStatusObject->nodesetval && (displayStatusObject->nodesetval->nodeNr >= 1)) { + xmlNodePtr node = displayStatusObject->nodesetval->nodeTab[0]; + if (node->children && node->children->name) { + if (strcmp((const char *)node->children->name, "displayed") == 0) { + linphone_chat_message_update_state(cm, LinphoneChatMessageStateDisplayed); + } + } + } + xmlXPathFreeObject(displayStatusObject); + } + linphone_chat_message_unref(cm); + } + } + if (messageIdStr) + linphone_free_xml_text_content(messageIdStr); + if (datetimeStr) + linphone_free_xml_text_content(datetimeStr); +} + +void ChatRoomPrivate::processIsComposingNotification (xmlparsing_context_t *xmlCtx) { + char xpathStr[MAX_XPATH_LENGTH]; + char *stateStr = nullptr; + char *refreshStr = nullptr; + int i; + bool state = false; + + if (linphone_create_xml_xpath_context(xmlCtx) < 0) + return; + + xmlXPathRegisterNs(xmlCtx->xpath_ctx, (const xmlChar *)"xsi", (const xmlChar *)"urn:ietf:params:xml:ns:im-iscomposing"); + xmlXPathObjectPtr isComposingObject = linphone_get_xml_xpath_object_for_node_list(xmlCtx, isComposingPrefix.c_str()); + if (isComposingObject) { + if (isComposingObject->nodesetval) { + for (i = 1; i <= isComposingObject->nodesetval->nodeNr; i++) { + snprintf(xpathStr, sizeof(xpathStr), "%s[%i]/xsi:state", isComposingPrefix.c_str(), i); + stateStr = linphone_get_xml_text_content(xmlCtx, xpathStr); + if (!stateStr) + continue; + snprintf(xpathStr, sizeof(xpathStr), "%s[%i]/xsi:refresh", isComposingPrefix.c_str(), i); + refreshStr = linphone_get_xml_text_content(xmlCtx, xpathStr); + } + } + xmlXPathFreeObject(isComposingObject); + } + + if (stateStr) { + if (strcmp(stateStr, "active") == 0) { + int refreshDuration = lp_config_get_int(core->config, "sip", "composing_remote_refresh_timeout", composingDefaultRemoteRefreshTimeout); + state = true; + if (refreshStr) + refreshDuration = atoi(refreshStr); + if (!remoteComposingRefreshTimer) { + remoteComposingRefreshTimer = sal_create_timer(core->sal, remoteRefreshComposing, this, + refreshDuration * 1000, "composing remote refresh timeout"); + } else { + belle_sip_source_set_timeout(remoteComposingRefreshTimer, refreshDuration * 1000); + } + } else { + deleteRemoteComposingRefreshTimer(); + } + + remoteIsComposing = state; + linphone_core_notify_is_composing_received(core, cBackPointer); + linphone_free_xml_text_content(stateStr); + } + if (refreshStr) + linphone_free_xml_text_content(refreshStr); +} + +// ----------------------------------------------------------------------------- + +/** + * DB layout: + * + * | 0 | storage_id + * | 1 | localContact + * | 2 | remoteContact + * | 3 | direction flag (LinphoneChatMessageDir) + * | 4 | message (text content of the message) + * | 5 | time (unused now, used to be string-based timestamp, replaced by the utc timestamp) + * | 6 | read flag (no longer used, replaced by the LinphoneChatMessageStateDisplayed state) + * | 7 | status (LinphoneChatMessageState) + * | 8 | external body url (deprecated file transfer system) + * | 9 | utc timestamp + * | 10 | app data text + * | 11 | linphone content id (LinphoneContent describing a file transfer) + * | 12 | message id (used for IMDN) + * | 13 | content type (of the message field [must be text representable]) + * | 14 | secured flag + */ +int ChatRoomPrivate::createChatMessageFromDb (int argc, char **argv, char **colName) { + L_Q(ChatRoom); + unsigned int storageId = (unsigned int)atoi(argv[0]); + + /* Check if the message exists in the weak messages list, in which case we should return that one. */ + LinphoneChatMessage *newMessage = getWeakMessage(storageId); + if (!newMessage) { + /* Check if the message exists in the transient list, in which case we should return that one. */ + newMessage = getTransientMessage(storageId); + } + if (!newMessage) { + newMessage = q->createMessage(argv[4] ? argv[4] : ""); + + if (atoi(argv[3]) == LinphoneChatMessageIncoming) { + newMessage->dir = LinphoneChatMessageIncoming; + linphone_chat_message_set_from(newMessage, peerAddress); + newMessage->to = nullptr; /* Will be filled at the end */ + } else { + newMessage->dir = LinphoneChatMessageOutgoing; + newMessage->from = nullptr; /* Will be filled at the end */ + linphone_chat_message_set_to(newMessage, peerAddress); + } + + newMessage->time = (time_t)atol(argv[9]); + newMessage->is_read = atoi(argv[6]); + newMessage->state = static_cast(atoi(argv[7])); + newMessage->storage_id = storageId; + newMessage->external_body_url = ms_strdup(argv[8]); + newMessage->appdata = ms_strdup(argv[10]); + newMessage->message_id = ms_strdup(argv[12]); + linphone_chat_message_set_content_type(newMessage, argv[13]); + newMessage->is_secured = (bool_t)atoi(argv[14]); + + if (argv[11]) { + int id = atoi(argv[11]); + if (id >= 0) + linphone_chat_message_fetch_content_from_database(core->db, newMessage, id); + } + + /* Fix content type for old messages that were stored without it */ + if (!newMessage->content_type) { + if (newMessage->file_transfer_information) { + newMessage->content_type = ms_strdup("application/vnd.gsma.rcs-ft-http+xml"); + } else if (newMessage->external_body_url) { + newMessage->content_type = ms_strdup("message/external-body"); + } else { + newMessage->content_type = ms_strdup("text/plain"); + } + } + + /* Add the new message to the weak messages list. */ + addWeakMessage(newMessage); + } + messages.push_front(newMessage); + return 0; +} + +void ChatRoomPrivate::onWeakMessageDestroyed (LinphoneChatMessage *messageBeingDestroyed) { + auto iter = find(weakMessages.begin(), weakMessages.end(), messageBeingDestroyed); + if (iter != transientMessages.end()) + weakMessages.erase(iter); +} + +LinphoneChatMessage *ChatRoomPrivate::getTransientMessage (unsigned int storageId) const { + for (auto it = transientMessages.begin(); it != transientMessages.end(); it++) { + if (linphone_chat_message_get_storage_id(*it) == storageId) + return linphone_chat_message_ref(*it); + } + return nullptr; +} + +LinphoneChatMessage *ChatRoomPrivate::getWeakMessage (unsigned int storageId) const { + for (auto it = weakMessages.begin(); it != weakMessages.end(); it++) { + if (linphone_chat_message_get_storage_id(*it) == storageId) + return linphone_chat_message_ref(*it); + } + return nullptr; +} + +int ChatRoomPrivate::sqlRequest (sqlite3 *db, const string &stmt) { + char *errmsg = nullptr; + int ret = sqlite3_exec(db, stmt.c_str(), nullptr, nullptr, &errmsg); + if (ret != SQLITE_OK) { + lError() << "ChatRoomPrivate::sqlRequest: statement " << stmt << " -> error sqlite3_exec(): " << errmsg; + sqlite3_free(errmsg); + } + return ret; +} + +void ChatRoomPrivate::sqlRequestMessage (sqlite3 *db, const string &stmt) { + char *errmsg = nullptr; + int ret = sqlite3_exec(db, stmt.c_str(), createChatMessageFromDb, this, &errmsg); + if (ret != SQLITE_OK) { + lError() << "Error in creation: " << errmsg; + sqlite3_free(errmsg); + } +} + +list ChatRoomPrivate::findMessages (const string &messageId) { + if (!core->db) + return list(); + char *peer = linphone_address_as_string_uri_only(peerAddress); + char *buf = sqlite3_mprintf("SELECT * FROM history WHERE remoteContact = %Q AND messageId = %Q", peer, messageId.c_str()); + messages.clear(); + sqlRequestMessage(core->db, buf); + sqlite3_free(buf); + ms_free(peer); + list result = messages; + messages.clear(); + return result; +} + +LinphoneChatMessage *ChatRoomPrivate::findMessageWithDirection (const string &messageId, LinphoneChatMessageDir direction) { + LinphoneChatMessage *ret = nullptr; + list l = findMessages(messageId); + for (auto it = l.begin(); it != l.end(); it++) { + LinphoneChatMessage *cm = *it; + if (cm->dir == direction) { + linphone_chat_message_ref(cm); + ret = cm; + break; + } + } + if (!l.empty()) { + for (auto it = l.begin(); it != l.end(); it++) + linphone_chat_message_unref(*it); + } + return ret; +} + +/** + * TODO: Should be handled directly by the LinphoneChatMessage object! + */ +void ChatRoomPrivate::storeOrUpdateMessage (LinphoneChatMessage *msg) { + if (msg->storage_id != 0) { + /* The message has already been stored (probably because of file transfer), update it */ + linphone_chat_message_store_update(msg); + } else { + /* Store the new message */ + msg->storage_id = linphone_chat_message_store(msg); + } +} + +// ----------------------------------------------------------------------------- + +LinphoneReason ChatRoomPrivate::messageReceived (SalOp *op, const SalMessage *salMsg) { + L_Q(ChatRoom); + + bool increaseMsgCount = true; + LinphoneReason reason = LinphoneReasonNone; + LinphoneChatMessage *msg; + + /* Check if this is a duplicate message */ + if ((msg = findMessageWithDirection(sal_op_get_call_id(op), LinphoneChatMessageIncoming))) { + reason = core->chat_deny_code; + if (msg) + linphone_chat_message_unref(msg); + return reason; + } + + msg = q->createMessage(salMsg->text ? salMsg->text : ""); + linphone_chat_message_set_content_type(msg, salMsg->content_type); + linphone_chat_message_set_from(msg, peerAddress); + + LinphoneAddress *to = sal_op_get_to(op) ? linphone_address_new(sal_op_get_to(op)) : linphone_address_new(linphone_core_get_identity(core)); + msg->to = to; + msg->time = salMsg->time; + msg->state = LinphoneChatMessageStateDelivered; + msg->dir = LinphoneChatMessageIncoming; + msg->message_id = ms_strdup(sal_op_get_call_id(op)); + + const SalCustomHeader *ch = sal_op_get_recv_custom_header(op); + if (ch) + msg->custom_headers = sal_custom_header_clone(ch); + if (salMsg->url) + linphone_chat_message_set_external_body_url(msg, salMsg->url); + + int retval = -1; + LinphoneImEncryptionEngine *imee = core->im_encryption_engine; + if (imee) { + LinphoneImEncryptionEngineCbs *imeeCbs = linphone_im_encryption_engine_get_callbacks(imee); + LinphoneImEncryptionEngineCbsIncomingMessageCb cbProcessIncomingMessage = linphone_im_encryption_engine_cbs_get_process_incoming_message(imeeCbs); + if (cbProcessIncomingMessage) { + retval = cbProcessIncomingMessage(imee, cBackPointer, msg); + if (retval == 0) { + msg->is_secured = TRUE; + } else if (retval > 0) { + /* Unable to decrypt message */ + linphone_core_notify_message_received_unable_decrypt(core, cBackPointer, msg); + reason = linphone_error_code_to_reason(retval); + linphone_chat_message_send_delivery_notification(msg, reason); + /* Return LinphoneReasonNone to avoid flexisip resending us a message we can't decrypt */ + reason = LinphoneReasonNone; + goto end; + } + } + } + + if ((retval <= 0) && (linphone_core_is_content_type_supported(core, msg->content_type) == FALSE)) { + retval = 415; + lError() << "Unsupported MESSAGE (content-type " << msg->content_type << " not recognized)"; + } + + if (retval > 0) { + reason = linphone_error_code_to_reason(retval); + linphone_chat_message_send_delivery_notification(msg, reason); + goto end; + } + + if (ContentType::isFileTransfer(msg->content_type)) { + create_file_transfer_information_from_vnd_gsma_rcs_ft_http_xml(msg); + linphone_chat_message_set_to_be_stored(msg, TRUE); + } else if (ContentType::isImIsComposing(msg->content_type)) { + isComposingReceived(msg->message); + linphone_chat_message_set_to_be_stored(msg, FALSE); + increaseMsgCount = FALSE; + if (lp_config_get_int(core->config, "sip", "deliver_imdn", 0) != 1) { + goto end; + } + } else if (ContentType::isImdn(msg->content_type)) { + imdnReceived(msg->message); + linphone_chat_message_set_to_be_stored(msg, FALSE); + increaseMsgCount = FALSE; + if (lp_config_get_int(core->config, "sip", "deliver_imdn", 0) != 1) { + goto end; + } + } else if (ContentType::isText(msg->content_type)) { + linphone_chat_message_set_to_be_stored(msg, TRUE); + } + + if (increaseMsgCount) { + if (unreadCount < 0) + unreadCount = 1; + else + unreadCount++; + /* Mark the message as pending so that if linphone_core_chat_room_mark_as_read() is called + in the linphone_chat_room_message_received() callback, it will effectively be marked as + being read before being stored. */ + pendingMessage = msg; + } + + chatMessageReceived(msg); + + if (linphone_chat_message_get_to_be_stored(msg)) { + msg->storage_id = linphone_chat_message_store(msg); + } + + pendingMessage = nullptr; + +end: + if (msg) + linphone_chat_message_unref(msg); + return reason; +} + +void ChatRoomPrivate::realtimeTextReceived (uint32_t character, LinphoneCall *call) { + L_Q(ChatRoom); + const uint32_t new_line = 0x2028; + const uint32_t crlf = 0x0D0A; + const uint32_t lf = 0x0A; + + if (call && linphone_call_params_realtime_text_enabled(linphone_call_get_current_params(call))) { + LinphoneChatMessageCharacter *cmc = bctbx_new0(LinphoneChatMessageCharacter, 1); + + if (!pendingMessage) + pendingMessage = q->createMessage(""); + + cmc->value = character; + cmc->has_been_read = FALSE; + receivedRttCharacters.push_back(cmc); + + remoteIsComposing = true; + linphone_core_notify_is_composing_received(core, cBackPointer); + + if ((character == new_line) || (character == crlf) || (character == lf)) { + /* End of message */ + lDebug() << "New line received, forge a message with content " << pendingMessage->message; + linphone_chat_message_set_from(pendingMessage, peerAddress); + if (pendingMessage->to) + linphone_address_unref(pendingMessage->to); + pendingMessage->to = call->dest_proxy + ? linphone_address_clone(call->dest_proxy->identity_address) + : linphone_address_new(linphone_core_get_identity(core)); + pendingMessage->time = ms_time(0); + pendingMessage->state = LinphoneChatMessageStateDelivered; + pendingMessage->dir = LinphoneChatMessageIncoming; + + if (lp_config_get_int(core->config, "misc", "store_rtt_messages", 1) == 1) + storeOrUpdateMessage(pendingMessage); + + if (unreadCount < 0) unreadCount = 1; + else unreadCount++; + + chatMessageReceived(pendingMessage); + linphone_chat_message_unref(pendingMessage); + pendingMessage = nullptr; + for (auto it = receivedRttCharacters.begin(); it != receivedRttCharacters.end(); it++) + ms_free(*it); + receivedRttCharacters.clear(); + } else { + char *value = Utils::utf8ToChar(character); + pendingMessage->message = ms_strcat_printf(pendingMessage->message, value); + lDebug() << "Received RTT character: " << value << " (" << character << "), pending text is " << pendingMessage->message; + delete value; + } + } +} + +// ----------------------------------------------------------------------------- + +void ChatRoomPrivate::chatMessageReceived (LinphoneChatMessage *msg) { + if (msg->message) { + /* Legacy API */ + linphone_core_notify_text_message_received(core, cBackPointer, msg->from, msg->message); + } + linphone_core_notify_message_received(core, cBackPointer, msg); + if (!ContentType::isImdn(msg->content_type) && !ContentType::isImIsComposing(msg->content_type)) { + remoteIsComposing = false; + linphone_core_notify_is_composing_received(core, cBackPointer); + linphone_chat_message_send_delivery_notification(msg, LinphoneReasonNone); + } +} + +void ChatRoomPrivate::imdnReceived (const string &text) { + xmlparsing_context_t *xmlCtx = linphone_xmlparsing_context_new(); + xmlSetGenericErrorFunc(xmlCtx, linphone_xmlparsing_genericxml_error); + xmlCtx->doc = xmlReadDoc((const unsigned char *)text.c_str(), 0, nullptr, 0); + if (xmlCtx->doc) + processImdn(xmlCtx); + else + lWarning() << "Wrongly formatted IMDN XML: " << xmlCtx->errorBuffer; + linphone_xmlparsing_context_destroy(xmlCtx); +} + +void ChatRoomPrivate::isComposingReceived (const string &text) { + xmlparsing_context_t *xmlCtx = linphone_xmlparsing_context_new(); + xmlSetGenericErrorFunc(xmlCtx, linphone_xmlparsing_genericxml_error); + xmlCtx->doc = xmlReadDoc((const unsigned char *)text.c_str(), 0, nullptr, 0); + if (xmlCtx->doc) + processIsComposingNotification(xmlCtx); + else + lWarning() << "Wrongly formatted presence XML: " << xmlCtx->errorBuffer; + linphone_xmlparsing_context_destroy(xmlCtx); +} + +// ============================================================================= + +ChatRoom::ChatRoom (LinphoneCore *core, LinphoneAddress *peerAddress) : Object(*new ChatRoomPrivate) { + L_D(ChatRoom); + d->core = core; + d->peerAddress = peerAddress; + char *peerStr = linphone_address_as_string(d->peerAddress); + d->peer = peerStr; + ms_free(peerStr); +} + +// ----------------------------------------------------------------------------- + +void ChatRoom::compose () { + L_D(ChatRoom); + int idleTimeout = lp_config_get_int(getCore()->config, "sip", "composing_idle_timeout", ChatRoomPrivate::composingDefaultIdleTimeout); + int refreshTimeout = lp_config_get_int(getCore()->config, "sip", "composing_refresh_timeout", ChatRoomPrivate::composingDefaultRefreshTimeout); + if (!d->isComposing) { + d->isComposing = true; + d->sendIsComposingNotification(); + if (!d->composingRefreshTimer) { + d->composingRefreshTimer = sal_create_timer(getCore()->sal, ChatRoomPrivate::refreshComposing, d, + refreshTimeout * 1000, "composing refresh timeout"); + } else { + belle_sip_source_set_timeout(d->composingRefreshTimer, refreshTimeout * 1000); + } + if (!d->composingIdleTimer) { + d->composingIdleTimer = sal_create_timer(getCore()->sal, ChatRoomPrivate::stopComposing, d, + idleTimeout * 1000, "composing idle timeout"); + } + } + belle_sip_source_set_timeout(d->composingIdleTimer, idleTimeout * 1000); +} + +LinphoneChatMessage *ChatRoom::createFileTransferMessage (const LinphoneContent *initialContent) { + L_D(ChatRoom); + LinphoneChatMessage *cm = belle_sip_object_new(LinphoneChatMessage); + cm->callbacks = linphone_chat_message_cbs_new(); + cm->chat_room = d->cBackPointer; + cm->message = nullptr; + cm->file_transfer_information = linphone_content_copy(initialContent); + cm->dir = LinphoneChatMessageOutgoing; + linphone_chat_message_set_to(cm, d->peerAddress); + cm->from = linphone_address_new(linphone_core_get_identity(d->core)); + /* This will be set to application/vnd.gsma.rcs-ft-http+xml when we will transfer the xml reply from server to the peers */ + cm->content_type = nullptr; + /* This will store the http request during file upload to the server */ + cm->http_request = nullptr; + cm->time = ms_time(0); + return cm; +} + +LinphoneChatMessage *ChatRoom::createMessage (const string &msg) { + L_D(ChatRoom); + LinphoneChatMessage *cm = belle_sip_object_new(LinphoneChatMessage); + cm->state = LinphoneChatMessageStateIdle; + cm->callbacks = linphone_chat_message_cbs_new(); + cm->chat_room = d->cBackPointer; + cm->message = msg.empty() ? nullptr : ms_strdup(msg.c_str()); + cm->content_type = ms_strdup("text/plain"); + cm->file_transfer_information = nullptr; /* this property is used only when transfering file */ + cm->http_request = nullptr; + cm->time = ms_time(0); + cm->is_secured = FALSE; + return cm; +} + +void ChatRoom::deleteHistory () { + L_D(ChatRoom); + if (!d->core->db) return; + char *peer = linphone_address_as_string_uri_only(d->peerAddress); + char *buf = sqlite3_mprintf("DELETE FROM history WHERE remoteContact = %Q;", peer); + d->sqlRequest(d->core->db, buf); + sqlite3_free(buf); + ms_free(peer); + if (d->unreadCount > 0) d->unreadCount = 0; +} + +void ChatRoom::deleteMessage (LinphoneChatMessage *msg) { + L_D(ChatRoom); + if (!d->core->db) return; + char *buf = sqlite3_mprintf("DELETE FROM history WHERE id = %u;", msg->storage_id); + d->sqlRequest(d->core->db, buf); + sqlite3_free(buf); + + /* Invalidate unread_count when we modify the database, so that next + time we need it it will be recomputed from latest database state */ + d->unreadCount = -1; +} + +LinphoneChatMessage *ChatRoom::findMessage (const string &messageId) { + L_D(ChatRoom); + LinphoneChatMessage *cm = nullptr; + list l = d->findMessages(messageId); + if (!l.empty()) { + cm = l.front(); + linphone_chat_message_ref(cm); + for (auto it = l.begin(); it != l.end(); it++) + linphone_chat_message_unref(*it); + } + return cm; +} + +uint32_t ChatRoom::getChar () const { + L_D(const ChatRoom); + if (!d->receivedRttCharacters.empty()) { + for (auto it = d->receivedRttCharacters.begin(); it != d->receivedRttCharacters.end(); it++) { + LinphoneChatMessageCharacter *cmc = *it; + if (!cmc->has_been_read) { + cmc->has_been_read = TRUE; + return cmc->value; + } + } + } + return 0; +} + +list ChatRoom::getHistory (int nbMessages) { + return getHistoryRange(0, nbMessages - 1); +} + +int ChatRoom::getHistorySize () { + L_D(ChatRoom); + return d->getMessagesCount(false); +} + +list ChatRoom::getHistoryRange (int startm, int endm) { + L_D(ChatRoom); + if (!d->core->db) return list(); + char *peer = linphone_address_as_string_uri_only(d->peerAddress); + d->messages.clear(); + + /* Since we want to append query parameters depending on arguments given, we use malloc instead of sqlite3_mprintf */ + const int bufMaxSize = 512; + char *buf = reinterpret_cast(ms_malloc(bufMaxSize)); + buf = sqlite3_snprintf(bufMaxSize - 1, buf, "SELECT * FROM history WHERE remoteContact = %Q ORDER BY id DESC", peer); + + if (startm < 0) startm = 0; + if (((endm > 0) && (endm >= startm)) || ((startm == 0) && (endm == 0))) { + char *buf2 = ms_strdup_printf("%s LIMIT %i ", buf, endm + 1 - startm); + ms_free(buf); + buf = buf2; + } else if (startm > 0) { + ms_message("%s(): end is lower than start (%d < %d). Assuming no end limit.", __FUNCTION__, endm, startm); + char *buf2 = ms_strdup_printf("%s LIMIT -1", buf); + ms_free(buf); + buf = buf2; + } + if (startm > 0) { + char *buf2 = ms_strdup_printf("%s OFFSET %i ", buf, startm); + ms_free(buf); + buf = buf2; + } + + uint64_t begin = ortp_get_cur_time_ms(); + d->sqlRequestMessage(d->core->db, buf); + uint64_t end = ortp_get_cur_time_ms(); + + if ((endm + 1 - startm) > 1) { + /* Display message only if at least 2 messages are loaded */ + ms_message("%s(): completed in %i ms", __FUNCTION__, (int)(end - begin)); + } + ms_free(buf); + + if (!d->messages.empty()) { + /* Fill local addr with core identity instead of per message */ + LinphoneAddress *localAddr = linphone_address_new(linphone_core_get_identity(d->core)); + for (auto it = d->messages.begin(); it != d->messages.end(); it++) { + LinphoneChatMessage *msg = *it; + if (msg->dir == LinphoneChatMessageOutgoing) { + if (msg->from != NULL) linphone_address_unref(msg->from); + msg->from = linphone_address_ref(localAddr); + } else { + msg->to = linphone_address_ref(localAddr); + } + } + linphone_address_unref(localAddr); + } + + list result = d->messages; + d->messages.clear(); + ms_free(peer); + return result; +} + +int ChatRoom::getUnreadMessagesCount () { + L_D(ChatRoom); + return d->getMessagesCount(true); +} + +bool ChatRoom::isRemoteComposing () const { + L_D(const ChatRoom); + return d->remoteIsComposing; +} + +void ChatRoom::markAsRead () { + L_D(ChatRoom); + + if (!d->core->db) return; + + /* Optimization: do not modify the database if no message is marked as unread */ + if (getUnreadMessagesCount() == 0) return; + + char *peer = linphone_address_as_string_uri_only(d->peerAddress); + char *buf = sqlite3_mprintf("SELECT * FROM history WHERE remoteContact = %Q AND direction = %i AND status != %i", peer, LinphoneChatMessageIncoming, LinphoneChatMessageStateDisplayed); + d->sqlRequestMessage(d->core->db, buf); + sqlite3_free(buf); + for (auto it = d->messages.begin(); it != d->messages.end(); it++) { + linphone_chat_message_send_display_notification(*it); + linphone_chat_message_unref(*it); + } + d->messages.clear(); + buf = sqlite3_mprintf("UPDATE history SET status=%i WHERE remoteContact=%Q AND direction=%i;", LinphoneChatMessageStateDisplayed, peer, LinphoneChatMessageIncoming); + d->sqlRequest(d->core->db, buf); + sqlite3_free(buf); + ms_free(peer); + + if (d->pendingMessage) { + linphone_chat_message_set_state(d->pendingMessage, LinphoneChatMessageStateDisplayed); + linphone_chat_message_send_display_notification(d->pendingMessage); + } + + d->unreadCount = 0; +} + +void ChatRoom::sendMessage (LinphoneChatMessage *msg) { + L_D(ChatRoom); + + /* Stubed rtt */ + if (d->call && linphone_call_params_realtime_text_enabled(linphone_call_get_current_params(d->call))) { + uint32_t new_line = 0x2028; + linphone_chat_message_put_char(msg, new_line); + linphone_chat_message_unref(msg); + return; + } + + msg->dir = LinphoneChatMessageOutgoing; + + /* Check if we shall upload a file to a server */ + if (msg->file_transfer_information && !msg->content_type) { + /* Open a transaction with the server and send an empty request(RCS5.1 section 3.5.4.8.3.1) */ + if (linphone_chat_room_upload_file(msg) == 0) { + /* Add to transient list only if message is going out */ + d->addTransientMessage(msg); + /* Store the message so that even if the upload is stopped, it can be done again */ + d->storeOrUpdateMessage(msg); + } else { + linphone_chat_message_unref(msg); + return; + } + } else { + SalOp *op = msg->op; + LinphoneCall *call = nullptr; + const char *identity = nullptr; + char *clearTextMessage = nullptr; + char *clearTextContentType = nullptr; + + if (msg->message) { + clearTextMessage = ms_strdup(msg->message); + } + if (msg->content_type) { + clearTextContentType = ms_strdup(msg->content_type); + } + + /* Add to transient list */ + d->addTransientMessage(msg); + msg->time = ms_time(0); + if (lp_config_get_int(d->core->config, "sip", "chat_use_call_dialogs", 0) != 0) { + call = linphone_core_get_call_by_remote_address(d->core, d->peer.c_str()); + if (call) { + if (call->state == LinphoneCallConnected || call->state == LinphoneCallStreamsRunning || + call->state == LinphoneCallPaused || call->state == LinphoneCallPausing || + call->state == LinphoneCallPausedByRemote) { + ms_message("send SIP msg through the existing call."); + op = call->op; + identity = linphone_core_find_best_identity(d->core, linphone_call_get_remote_address(call)); + } + } + } + + if (!identity) { + LinphoneProxyConfig *proxy = linphone_core_lookup_known_proxy(d->core, d->peerAddress); + if (proxy) { + identity = linphone_address_as_string(linphone_proxy_config_get_identity_address(proxy)); + } else { + identity = linphone_core_get_primary_contact(d->core); + } + } + if (msg->from) { + /* BUG: the file transfer message constructor sets the from, but doesn't do it as well as here */ + linphone_address_unref(msg->from); + } + msg->from = linphone_address_new(identity); + + int retval = -1; + LinphoneImEncryptionEngine *imee = d->core->im_encryption_engine; + if (imee) { + LinphoneImEncryptionEngineCbs *imeeCbs = linphone_im_encryption_engine_get_callbacks(imee); + LinphoneImEncryptionEngineCbsOutgoingMessageCb cbProcessOutgoingMessage = linphone_im_encryption_engine_cbs_get_process_outgoing_message(imeeCbs); + if (cbProcessOutgoingMessage) { + retval = cbProcessOutgoingMessage(imee, d->cBackPointer, msg); + if (retval == 0) { + msg->is_secured = TRUE; + } + } + } + + if (!op) { + /* Sending out of call */ + msg->op = op = sal_op_new(d->core->sal); + linphone_configure_op(d->core, op, d->peerAddress, msg->custom_headers, + lp_config_get_int(d->core->config, "sip", "chat_msg_with_contact", 0)); + sal_op_set_user_pointer(op, msg); /* If out of call, directly store msg */ + } + + if (retval > 0) { + sal_error_info_set((SalErrorInfo *)sal_op_get_error_info(op), SalReasonNotAcceptable, "SIP", retval, "Unable to encrypt IM", nullptr); + d->storeOrUpdateMessage(msg); + linphone_chat_message_update_state(msg, LinphoneChatMessageStateNotDelivered); + linphone_chat_message_unref(msg); + return; + } + + if (msg->external_body_url) { + char *content_type = ms_strdup_printf("message/external-body; access-type=URL; URL=\"%s\"", msg->external_body_url); + sal_message_send(op, identity, d->peer.c_str(), content_type, nullptr, nullptr); + ms_free(content_type); + } else { + char *peerUri = linphone_address_as_string_uri_only(d->peerAddress); + if (msg->content_type) { + sal_message_send(op, identity, d->peer.c_str(), msg->content_type, msg->message, peerUri); + } else { + sal_text_send(op, identity, d->peer.c_str(), msg->message); + } + ms_free(peerUri); + } + + if (msg->message && clearTextMessage && strcmp(msg->message, clearTextMessage) != 0) { + /* We replace the encrypted message by the original one so it can be correctly stored and displayed by the application */ + ms_free(msg->message); + msg->message = ms_strdup(clearTextMessage); + } + if (msg->content_type && clearTextContentType && (strcmp(msg->content_type, clearTextContentType) != 0)) { + /* We replace the encrypted content type by the original one */ + ms_free(msg->content_type); + msg->content_type = ms_strdup(clearTextContentType); + } + msg->message_id = ms_strdup(sal_op_get_call_id(op)); /* must be known at that time */ + d->storeOrUpdateMessage(msg); + + if (d->isComposing) + d->isComposing = false; + d->deleteComposingIdleTimer(); + d->deleteComposingRefreshTimer(); + + if (clearTextMessage) { + ms_free(clearTextMessage); + } + if (clearTextContentType) { + ms_free(clearTextContentType); + } + + if (call && call->op == op) { + /* In this case, chat delivery status is not notified, so unrefing chat message right now */ + /* Might be better fixed by delivering status, but too costly for now */ + linphone_chat_room_remove_transient_message(msg->chat_room, msg); + linphone_chat_message_unref(msg); + return; + } + } + + /* If operation failed, we should not change message state */ + if (msg->dir == LinphoneChatMessageOutgoing) { + linphone_chat_message_set_state(msg, LinphoneChatMessageStateInProgress); + } +} + +// ----------------------------------------------------------------------------- + +LinphoneCall *ChatRoom::getCall () const { + L_D(const ChatRoom); + return d->call; +} + +LinphoneCore *ChatRoom::getCore () const { + L_D(const ChatRoom); + return d->core; +} + +// ----------------------------------------------------------------------------- + +const LinphoneAddress *ChatRoom::getPeerAddress () const { + L_D(const ChatRoom); + return d->peerAddress; +} + +LINPHONE_END_NAMESPACE diff --git a/src/chat/chat-room.h b/src/chat/chat-room.h new file mode 100644 index 000000000..801896cf7 --- /dev/null +++ b/src/chat/chat-room.h @@ -0,0 +1,66 @@ +/* + * chat-room.h + * Copyright (C) 2017 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 3 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, see . + */ + +#ifndef _CHAT_ROOM_H_ +#define _CHAT_ROOM_H_ + +#include + +#include "object/object.h" + +#include "linphone/types.h" + +// ============================================================================= + +LINPHONE_BEGIN_NAMESPACE + +class ChatRoomPrivate; + +class ChatRoom : public Object { +public: + ChatRoom (LinphoneCore *core, LinphoneAddress *peerAddress); + virtual ~ChatRoom () = default; + + void compose (); + LinphoneChatMessage *createFileTransferMessage (const LinphoneContent *initialContent); + LinphoneChatMessage *createMessage (const std::string &msg); + void deleteHistory (); + void deleteMessage (LinphoneChatMessage *msg); + LinphoneChatMessage *findMessage (const std::string &messageId); + uint32_t getChar () const; + std::list getHistory (int nbMessages); + int getHistorySize (); + std::list getHistoryRange (int startm, int endm); + int getUnreadMessagesCount (); + bool isRemoteComposing () const; + void markAsRead (); + void sendMessage (LinphoneChatMessage *msg); + + LinphoneCall *getCall () const; + LinphoneCore *getCore () const; + + const LinphoneAddress *getPeerAddress () const; + +private: + L_DECLARE_PRIVATE(ChatRoom); + L_DISABLE_COPY(ChatRoom); +}; + +LINPHONE_END_NAMESPACE + +#endif // ifndef _CHAT_ROOM_H_ diff --git a/src/db/events-db.cpp b/src/db/events-db.cpp index 284fc3509..79e140e5c 100644 --- a/src/db/events-db.cpp +++ b/src/db/events-db.cpp @@ -252,17 +252,17 @@ EventsDb::EventsDb () : AbstractDb(*new EventsDbPrivate) {} bool EventsDb::addEvent (const EventLog &eventLog) { // TODO. switch (eventLog.getType()) { - case EventLog::NoneEvent: + case EventLog::TypeNone: return false; - case EventLog::MessageEvent: - case EventLog::CallStartEvent: - case EventLog::CallEndEvent: - case EventLog::ConferenceCreatedEvent: - case EventLog::ConferenceDestroyedEvent: - case EventLog::ConferenceParticipantAddedEvent: - case EventLog::ConferenceParticipantRemovedEvent: - case EventLog::ConferenceParticipantSetAdminEvent: - case EventLog::ConferenceParticipantUnsetAdminEvent: + case EventLog::TypeMessage: + case EventLog::TypeCallStart: + case EventLog::TypeCallEnd: + case EventLog::TypeConferenceCreated: + case EventLog::TypeConferenceDestroyed: + case EventLog::TypeConferenceParticipantAdded: + case EventLog::TypeConferenceParticipantRemoved: + case EventLog::TypeConferenceParticipantSetAdmin: + case EventLog::TypeConferenceParticipantUnsetAdmin: break; } diff --git a/src/enums.h b/src/enums.h index 78f0d1ed5..945e43995 100644 --- a/src/enums.h +++ b/src/enums.h @@ -25,7 +25,12 @@ LINPHONE_BEGIN_NAMESPACE -// Nothing. for the moment. +enum class Transport { + Udp, + Tcp, + Tls, + Dtls +}; LINPHONE_END_NAMESPACE diff --git a/src/event-log/call-event.cpp b/src/event-log/call-event.cpp index 0dde3edc9..1078606ec 100644 --- a/src/event-log/call-event.cpp +++ b/src/event-log/call-event.cpp @@ -36,7 +36,7 @@ public: CallEvent::CallEvent (Type type, const shared_ptr &call) : EventLog(*new CallEventPrivate, type) { L_D(CallEvent); L_ASSERT(call); - L_ASSERT(type == CallStartEvent || type == CallEndEvent); + L_ASSERT(type == TypeCallStart || type == TypeCallEnd); d->call = call; } diff --git a/src/event-log/conference-event.cpp b/src/event-log/conference-event.cpp index 169e9dfa5..cfb4ca69c 100644 --- a/src/event-log/conference-event.cpp +++ b/src/event-log/conference-event.cpp @@ -29,7 +29,7 @@ LINPHONE_BEGIN_NAMESPACE ConferenceEvent::ConferenceEvent (Type type, const shared_ptr &address) : EventLog(*new ConferenceEventPrivate, type) { L_D(ConferenceEvent); - L_ASSERT(type == ConferenceCreatedEvent || type == ConferenceDestroyedEvent); + L_ASSERT(type == TypeConferenceCreated || type == TypeConferenceDestroyed); L_ASSERT(address); // TODO: Duplicate address. d->address = address; diff --git a/src/event-log/conference-participant-event.cpp b/src/event-log/conference-participant-event.cpp index 4d3500144..0095da63a 100644 --- a/src/event-log/conference-participant-event.cpp +++ b/src/event-log/conference-participant-event.cpp @@ -40,10 +40,10 @@ ConferenceParticipantEvent::ConferenceParticipantEvent ( ) : ConferenceEvent(*new ConferenceParticipantEventPrivate, type, conferenceAddress) { L_D(ConferenceParticipantEvent); L_ASSERT( - type == ConferenceParticipantAddedEvent || - type == ConferenceParticipantRemovedEvent || - type == ConferenceParticipantSetAdminEvent || - type == ConferenceParticipantUnsetAdminEvent + type == TypeConferenceParticipantAdded || + type == TypeConferenceParticipantRemoved || + type == TypeConferenceParticipantSetAdmin || + type == TypeConferenceParticipantUnsetAdmin ); L_ASSERT(participantAddress); // TODO: Duplicate address. diff --git a/src/event-log/event-log-enums.h b/src/event-log/event-log-enums.h index 1542fdf54..878d1644b 100644 --- a/src/event-log/event-log-enums.h +++ b/src/event-log/event-log-enums.h @@ -19,25 +19,22 @@ #ifndef _EVENT_LOG_ENUMS_H_ #define _EVENT_LOG_ENUMS_H_ -#include "utils/general.h" +#include "utils/enum-generator.h" // ============================================================================= -L_DECLARE_ENUM(EventLog, Type) { - NoneEvent, - // MessageEvent. - MessageEvent, - // CallEvent. - CallStartEvent, - CallEndEvent, - // ConferenceEvent. - ConferenceCreatedEvent, - ConferenceDestroyedEvent, - // ConferenceParticipantEvent. - ConferenceParticipantAddedEvent, - ConferenceParticipantRemovedEvent, - ConferenceParticipantSetAdminEvent, - ConferenceParticipantUnsetAdminEvent -}; +#define L_ENUM_VALUES_EVENT_LOG_TYPE \ + L_DECLARE_ENUM_VALUES(EventLog, Type, \ + None, \ + Message, \ + CallStart, \ + CallEnd, \ + ConferenceCreated, \ + ConferenceDestroyed, \ + ConferenceParticipantAdded, \ + ConferenceParticipantRemoved, \ + ConferenceParticipantSetAdmin, \ + ConferenceParticipantUnsetAdmin \ + ) #endif // ifndef _EVENT_LOG_ENUMS_H_ diff --git a/src/event-log/event-log-p.h b/src/event-log/event-log-p.h index e9e266178..6a71e025f 100644 --- a/src/event-log/event-log-p.h +++ b/src/event-log/event-log-p.h @@ -28,7 +28,7 @@ LINPHONE_BEGIN_NAMESPACE class EventLogPrivate : public ClonableObjectPrivate { private: - EventLog::Type type = EventLog::NoneEvent; + EventLog::Type type = EventLog::TypeNone; L_DECLARE_PUBLIC(EventLog); }; diff --git a/src/event-log/event-log.h b/src/event-log/event-log.h index 180a09e44..831fca24f 100644 --- a/src/event-log/event-log.h +++ b/src/event-log/event-log.h @@ -20,6 +20,7 @@ #define _EVENT_LOG_H_ #include "object/clonable-object.h" +#include "event-log-enums.h" // ============================================================================= @@ -29,7 +30,9 @@ class EventLogPrivate; class LINPHONE_PUBLIC EventLog : public ClonableObject { public: - enum Type : int; + enum Type { + L_ENUM_VALUES_EVENT_LOG_TYPE + }; EventLog (); EventLog (const EventLog &src); @@ -46,8 +49,6 @@ private: L_DECLARE_PRIVATE(EventLog); }; -#include "event-log-enums.h" - LINPHONE_END_NAMESPACE #endif // ifndef _EVENT_LOG_H_ diff --git a/src/event-log/message-event.cpp b/src/event-log/message-event.cpp index d46af4102..cda82eb68 100644 --- a/src/event-log/message-event.cpp +++ b/src/event-log/message-event.cpp @@ -34,7 +34,7 @@ public: // ----------------------------------------------------------------------------- MessageEvent::MessageEvent (const shared_ptr &message) : - EventLog(*new MessageEventPrivate, EventLog::MessageEvent) { + EventLog(*new MessageEventPrivate, EventLog::TypeMessage) { L_D(MessageEvent); L_ASSERT(message); d->message = message; diff --git a/src/message/message.h b/src/message/message.h index 61754010b..3bb643315 100644 --- a/src/message/message.h +++ b/src/message/message.h @@ -23,7 +23,6 @@ #include #include -#include "enums.h" #include "object/object.h" // ============================================================================= diff --git a/src/utils/content-type.cpp b/src/utils/content-type.cpp new file mode 100644 index 000000000..ee1aac657 --- /dev/null +++ b/src/utils/content-type.cpp @@ -0,0 +1,43 @@ +/* + * content-type.cpp + * Copyright (C) 2017 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 3 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, see . + */ + +#include "content-type.h" + +// ============================================================================= + +using namespace std; + +LINPHONE_BEGIN_NAMESPACE + +bool ContentType::isFileTransfer (const string &contentType) { + return "application/vnd.gsma.rcs-ft-http+xml" == contentType; +} + +bool ContentType::isImIsComposing (const string &contentType) { + return "application/im-iscomposing+xml" == contentType; +} + +bool ContentType::isImdn (const string &contentType) { + return "message/imdn+xml" == contentType; +} + +bool ContentType::isText (const string &contentType) { + return "text/plain" == contentType; +} + +LINPHONE_END_NAMESPACE diff --git a/src/utils/content-type.h b/src/utils/content-type.h new file mode 100644 index 000000000..5d3eeeed5 --- /dev/null +++ b/src/utils/content-type.h @@ -0,0 +1,39 @@ +/* + * content-type.h + * Copyright (C) 2017 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 3 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, see . + */ + +#ifndef _CONTENT_TYPE_H_ +#define _CONTENT_TYPE_H_ + +#include + +#include "general.h" + +// ============================================================================= + +LINPHONE_BEGIN_NAMESPACE + +namespace ContentType { + bool isFileTransfer (const std::string &contentType); + bool isImIsComposing (const std::string &contentType); + bool isImdn (const std::string &contentType); + bool isText (const std::string &contentType); +} + +LINPHONE_END_NAMESPACE + +#endif // ifndef _CONTENT_TYPE_H_ diff --git a/src/utils/enum-generator.h b/src/utils/enum-generator.h new file mode 100644 index 000000000..994948d57 --- /dev/null +++ b/src/utils/enum-generator.h @@ -0,0 +1,40 @@ +/* + * enum-generator.h + * Copyright (C) 2017 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 3 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, see . + */ + +#ifndef _ENUM_GENERATOR_H_ +#define _ENUM_GENERATOR_H_ + +#include "magic-macros.h" + +// ============================================================================= + +LINPHONE_BEGIN_NAMESPACE + +#define L_ENUM_VALUE(C, VALUE) C ## VALUE + +#ifndef L_USE_C_ENUM + #define L_DECLARE_ENUM_VALUES(CLASS_NAME, ENUM_NAME, ...) \ + MM_APPLY_COMMA(L_ENUM_VALUE, ENUM_NAME, __VA_ARGS__) +#else + #define L_DECLARE_ENUM_VALUES(CLASS_NAME, ENUM_NAME, ...) \ + MM_APPLY_COMMA(L_ENUM_VALUE, Linphone ## CLASS_NAME ## ENUM_NAME, __VA_ARGS__) +#endif // ifndef L_USE_C_ENUM + +LINPHONE_END_NAMESPACE + +#endif // ifndef _ENUM_GENERATOR_H_ diff --git a/src/utils/general.h b/src/utils/general.h index 2291c43eb..6dcd120bd 100644 --- a/src/utils/general.h +++ b/src/utils/general.h @@ -55,10 +55,6 @@ LINPHONE_BEGIN_NAMESPACE #ifdef __cplusplus -#ifndef L_DECLARE_ENUM - #define L_DECLARE_ENUM(CLASS, ENUM) enum CLASS::ENUM : int -#endif - void l_assert (const char *condition, const char *file, int line); #ifdef DEBUG @@ -67,6 +63,8 @@ void l_assert (const char *condition, const char *file, int line); #define L_ASSERT(CONDITION) ((CONDITION) ? static_cast(0) : l_assert(#CONDITION, __FILE__, __LINE__)) #endif +// Allows access to private internal data. +// Gives a control to C Wrapper. #define L_DECLARE_PRIVATE(CLASS) \ inline CLASS ## Private * getPrivate() { \ return reinterpret_cast(mPrivate); \ @@ -74,7 +72,8 @@ void l_assert (const char *condition, const char *file, int line); inline const CLASS ## Private *getPrivate() const { \ return reinterpret_cast(mPrivate); \ } \ - friend class CLASS ## Private; + friend class CLASS ## Private; \ + friend class Wrapper; class ClonableObject; class ClonableObjectPrivate; diff --git a/src/utils/magic-macros.h b/src/utils/magic-macros.h new file mode 100644 index 000000000..95b2f9774 --- /dev/null +++ b/src/utils/magic-macros.h @@ -0,0 +1,240 @@ +/* + * magic-macros.h + * Copyright (C) 2017 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 3 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, see . + */ + +#ifndef _MAGIC_MACROS_H_ +#define _MAGIC_MACROS_H_ + +#include "general.h" + +// ============================================================================= +// Original header. +// Source: https://github.com/facebookresearch/ELF/blob/master/elf/pybind_helper.h +// ============================================================================= + +/** + * Copyright (c) 2017-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +// File: macros.h +// Author: Yuxin Wu + +// ============================================================================= + +LINPHONE_BEGIN_NAMESPACE + +#define MM_CONCAT__(A, B) A ## B +#define MM_CONCAT_(A, B) MM_CONCAT__(A, B) +#define MM_CONCAT(A, B) MM_CONCAT_(A, B) + +#define MM_INVOKE(MACRO, ARGS) MACRO ARGS +#define MM_INVOKE_B(MACRO, ARGS) MACRO ARGS + +#define MM_APPLY_1(MACRONAME, C, A1) \ + MM_INVOKE_B(MACRONAME, (C, A1)) + +#define MM_APPLY_2(MACRONAME, C, A1, A2) \ + MM_INVOKE_B(MACRONAME, (C, A1)) \ + MM_APPLY_1(MACRONAME, C, A2) + +#define MM_APPLY_3(MACRONAME, C, A1, A2, A3) \ + MM_INVOKE_B(MACRONAME, (C, A1)) \ + MM_APPLY_2(MACRONAME, C, A2, A3) + +#define MM_APPLY_4(MACRONAME, C, A1, A2, A3, A4) \ + MM_INVOKE_B(MACRONAME, (C, A1)) \ + MM_APPLY_3(MACRONAME, C, A2, A3, A4) + +#define MM_APPLY_5(MACRONAME, C, A1, A2, A3, A4, A5) \ + MM_INVOKE_B(MACRONAME, (C, A1)) \ + MM_APPLY_4(MACRONAME, C, A2, A3, A4, A5) + +#define MM_APPLY_6(MACRONAME, C, A1, A2, A3, A4, A5, A6) \ + MM_INVOKE_B(MACRONAME, (C, A1)) \ + MM_APPLY_5(MACRONAME, C, A2, A3, A4, A5, A6) + +#define MM_APPLY_7(MACRONAME, C, A1, A2, A3, A4, A5, A6, A7) \ + MM_INVOKE_B(MACRONAME, (C, A1)) \ + MM_APPLY_6(MACRONAME, C, A2, A3, A4, A5, A6, A7) + +#define MM_APPLY_8(MACRONAME, C, A1, A2, A3, A4, A5, A6, A7, A8) \ + MM_INVOKE_B(MACRONAME, (C, A1)) \ + MM_APPLY_7(MACRONAME, C, A2, A3, A4, A5, A6, A7, A8) + +#define MM_APPLY_9(MACRONAME, C, A1, A2, A3, A4, A5, A6, A7, A8, A9) \ + MM_INVOKE_B(MACRONAME, (C, A1)) \ + MM_APPLY_8(MACRONAME, C, A2, A3, A4, A5, A6, A7, A8, A9) + +#define MM_APPLY_10(MACRONAME, C, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10) \ + MM_INVOKE_B(MACRONAME, (C, A1)) \ + MM_APPLY_9(MACRONAME, C, A2, A3, A4, A5, A6, A7, A8, A9, A10) + +#define MM_APPLY_11(MACRONAME, C, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11) \ + MM_INVOKE_B(MACRONAME, (C, A1)) \ + MM_APPLY_10(MACRONAME, C, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11) + +#define MM_APPLY_12(MACRONAME, C, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12) \ + MM_INVOKE_B(MACRONAME, (C, A1)) \ + MM_APPLY_11(MACRONAME, C, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12) + +#define MM_APPLY_13(MACRONAME, C, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13) \ + MM_INVOKE_B(MACRONAME, (C, A1)) \ + MM_APPLY_12(MACRONAME, C, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13) + +#define MM_APPLY_14(MACRONAME, C, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14) \ + MM_INVOKE_B(MACRONAME, (C, A1)) \ + MM_APPLY_13(MACRONAME, C, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14) + +#define MM_APPLY_15(MACRONAME, C, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15) \ + MM_INVOKE_B(MACRONAME, (C, A1)) \ + MM_APPLY_14(MACRONAME, C, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15) + +#define MM_APPLY_16(MACRONAME, C, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16) \ + MM_INVOKE_B(MACRONAME, (C, A1)) \ + MM_APPLY_15(MACRONAME, C, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16) + +#define MM_APPLY_17(MACRONAME, C, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17) \ + MM_INVOKE_B(MACRONAME, (C, A1)) \ + MM_APPLY_16(MACRONAME, C, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17) + +#define MM_APPLY_18(MACRONAME, C, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18) \ + MM_INVOKE_B(MACRONAME, (C, A1)) \ + MM_APPLY_17(MACRONAME, C, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18) + +#define MM_APPLY_19(MACRONAME, C, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19) \ + MM_INVOKE_B(MACRONAME, (C, A1)) \ + MM_APPLY_18(MACRONAME, C, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19) + +#define MM_APPLY_20(MACRONAME, C, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20) \ + MM_INVOKE_B(MACRONAME, (C, A1)) \ + MM_APPLY_19(MACRONAME, C, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20) + +#define MM_APPLY_21(MACRONAME, C, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21) \ + MM_INVOKE_B(MACRONAME, (C, A1)) \ + MM_APPLY_20(MACRONAME, C, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21) + +#define MM_APPLY_22(MACRONAME, C, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22) \ + MM_INVOKE_B(MACRONAME, (C, A1)) \ + MM_APPLY_21(MACRONAME, C, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22) + +#define MM_NARG(...) \ + MM_NARG_(__VA_ARGS__, MM_RSEQ_N()) + +#define MM_NARG_(...) \ + MM_ARG_N(__VA_ARGS__) + +#define MM_ARG_N( \ + _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, \ + _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, \ + _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, \ + _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, \ + _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, \ + _61, _62, _63, N, ...) N + +#define MM_RSEQ_N() \ + 63, 62, 61, 60, \ + 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, \ + 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, \ + 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, \ + 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, \ + 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, \ + 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 + +#define MM_APPLY(MACRONAME, C, ...) \ + MM_INVOKE( \ + MM_CONCAT(MM_APPLY_, MM_NARG(__VA_ARGS__)), \ + (MACRONAME, C, __VA_ARGS__) \ + ) + +#define MM_APPLY_COMMA(MACRONAME, C, ...) \ + MM_INVOKE( \ + MM_CONCAT(MM_APPLY_COMMA_, MM_NARG(__VA_ARGS__)), \ + (MACRONAME, C, __VA_ARGS__) \ + ) + +#define MM_APPLY_COMMA_1(MACRONAME, C, A1) \ + MM_INVOKE_B(MACRONAME, (C, A1)) + +#define MM_APPLY_COMMA_2(MACRONAME, C, A1, A2) \ + MM_INVOKE_B(MACRONAME, (C, A1)), \ + MM_APPLY_COMMA_1(MACRONAME, C, A2) + +#define MM_APPLY_COMMA_3(MACRONAME, C, A1, A2, A3) \ + MM_INVOKE_B(MACRONAME, (C, A1)), \ + MM_APPLY_COMMA_2(MACRONAME, C, A2, A3) + +#define MM_APPLY_COMMA_4(MACRONAME, C, A1, A2, A3, A4) \ + MM_INVOKE_B(MACRONAME, (C, A1)), \ + MM_APPLY_COMMA_3(MACRONAME, C, A2, A3, A4) + +#define MM_APPLY_COMMA_5(MACRONAME, C, A1, A2, A3, A4, A5) \ + MM_INVOKE_B(MACRONAME, (C, A1)), \ + MM_APPLY_COMMA_4(MACRONAME, C, A2, A3, A4, A5) + +#define MM_APPLY_COMMA_6(MACRONAME, C, A1, A2, A3, A4, A5, A6) \ + MM_INVOKE_B(MACRONAME, (C, A1)), \ + MM_APPLY_COMMA_5(MACRONAME, C, A2, A3, A4, A5, A6) + +#define MM_APPLY_COMMA_7(MACRONAME, C, A1, A2, A3, A4, A5, A6, A7) \ + MM_INVOKE_B(MACRONAME, (C, A1)), \ + MM_APPLY_COMMA_6(MACRONAME, C, A2, A3, A4, A5, A6, A7) + +#define MM_APPLY_COMMA_8(MACRONAME, C, A1, A2, A3, A4, A5, A6, A7, A8) \ + MM_INVOKE_B(MACRONAME, (C, A1)), \ + MM_APPLY_COMMA_7(MACRONAME, C, A2, A3, A4, A5, A6, A7, A8) + +#define MM_APPLY_COMMA_9(MACRONAME, C, A1, A2, A3, A4, A5, A6, A7, A8, A9) \ + MM_INVOKE_B(MACRONAME, (C, A1)), \ + MM_APPLY_COMMA_8(MACRONAME, C, A2, A3, A4, A5, A6, A7, A8, A9) + +#define MM_APPLY_COMMA_10(MACRONAME, C, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10) \ + MM_INVOKE_B(MACRONAME, (C, A1)), \ + MM_APPLY_COMMA_9(MACRONAME, C, A2, A3, A4, A5, A6, A7, A8, A9, A10) + +#define MM_APPLY_COMMA_11(MACRONAME, C, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11) \ + MM_INVOKE_B(MACRONAME, (C, A1)), \ + MM_APPLY_COMMA_10(MACRONAME, C, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11) + +#define MM_APPLY_COMMA_12(MACRONAME, C, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12) \ + MM_INVOKE_B(MACRONAME, (C, A1)), \ + MM_APPLY_COMMA_11(MACRONAME, C, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12) + +#define MM_APPLY_COMMA_13(MACRONAME, C, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13) \ + MM_INVOKE_B(MACRONAME, (C, A1)), \ + MM_APPLY_COMMA_12(MACRONAME, C, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13) + +#define MM_APPLY_COMMA_14(MACRONAME, C, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14) \ + MM_INVOKE_B(MACRONAME, (C, A1)), \ + MM_APPLY_COMMA_13(MACRONAME, C, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14) + +#define MM_APPLY_COMMA_15(MACRONAME, C, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15) \ + MM_INVOKE_B(MACRONAME, (C, A1)), \ + MM_APPLY_COMMA_14(MACRONAME, C, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15) + +#define MM_APPLY_COMMA_16(MACRONAME, C, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16) \ + MM_INVOKE_B(MACRONAME, (C, A1)), \ + MM_APPLY_COMMA_15(MACRONAME, C, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16) + +LINPHONE_END_NAMESPACE + +#endif // ifndef _MAGIC_MACROS_H_ diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index 55e8d2026..1c205d3ed 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -17,6 +17,9 @@ */ #include +#include + +#include #include "utils.h" @@ -50,6 +53,30 @@ vector Utils::split (const string &str, const string &delimiter) { return out; } +#ifndef __ANDROID__ +#define TO_STRING_IMPL(TYPE) \ + string Utils::toString(TYPE val) { \ + return to_string(val); \ + } +#else +#define TO_STRING_IMPL(TYPE) \ + string Utils::toString(TYPE val) { \ + ostringstream os; \ + os << val; \ + return os.str(); \ + } +#endif // ifndef __ANDROID__ + +TO_STRING_IMPL(int) +TO_STRING_IMPL(long) +TO_STRING_IMPL(long long) +TO_STRING_IMPL(unsigned) +TO_STRING_IMPL(unsigned long) +TO_STRING_IMPL(unsigned long long) +TO_STRING_IMPL(float) +TO_STRING_IMPL(double) +TO_STRING_IMPL(long double) + int Utils::stoi (const string &str, size_t *idx, int base) { char *p; int v = strtol(str.c_str(), &p, base); @@ -60,4 +87,30 @@ int Utils::stoi (const string &str, size_t *idx, int base) { return v; } +char *Utils::utf8ToChar (uint32_t ic) { + char *result = new char[5]; + int size = 0; + if (ic < 0x80) { + result[0] = ic; + size = 1; + } else if (ic < 0x800) { + result[1] = 0x80 + ((ic & 0x3F)); + result[0] = 0xC0 + ((ic >> 6) & 0x1F); + size = 2; + } else if (ic < 0x100000) { + result[2] = 0x80 + (ic & 0x3F); + result[1] = 0x80 + ((ic >> 6) & 0x3F); + result[0] = 0xE0 + ((ic >> 12) & 0xF); + size = 3; + } else if (ic < 0x110000) { + result[3] = 0x80 + (ic & 0x3F); + result[2] = 0x80 + ((ic >> 6) & 0x3F); + result[1] = 0x80 + ((ic >> 12) & 0x3F); + result[0] = 0xF0 + ((ic >> 18) & 0x7); + size = 4; + } + result[size] = '\0'; + return result; +} + LINPHONE_END_NAMESPACE diff --git a/src/utils/utils.h b/src/utils/utils.h index e64dc4e7e..d75838826 100644 --- a/src/utils/utils.h +++ b/src/utils/utils.h @@ -37,7 +37,20 @@ namespace Utils { return split(str, std::string(1, delimiter)); } + LINPHONE_PUBLIC std::string toString (int val); + LINPHONE_PUBLIC std::string toString (long val); + LINPHONE_PUBLIC std::string toString (long long val); + LINPHONE_PUBLIC std::string toString (unsigned val); + LINPHONE_PUBLIC std::string toString (unsigned long val); + LINPHONE_PUBLIC std::string toString (unsigned long long val); + LINPHONE_PUBLIC std::string toString (float val); + LINPHONE_PUBLIC std::string toString (double val); + LINPHONE_PUBLIC std::string toString (long double val); + LINPHONE_PUBLIC int stoi (const std::string &str, size_t *idx = 0, int base = 10); + + // Return a buffer allocated with new. + LINPHONE_PUBLIC char *utf8ToChar (uint32_t ic); } LINPHONE_END_NAMESPACE diff --git a/tester/call_single_tester.c b/tester/call_single_tester.c index 089ac77d6..b10f01b2d 100644 --- a/tester/call_single_tester.c +++ b/tester/call_single_tester.c @@ -1044,6 +1044,7 @@ static void terminate_call_with_error(void) { BC_ASSERT_STRING_EQUAL(linphone_error_info_get_phrase(rei), "Call refused for security reason"); BC_ASSERT_STRING_EQUAL(linphone_error_info_get_protocol(ei), "SIP"); } + BC_ASSERT_EQUAL(linphone_call_log_get_status(linphone_call_get_call_log(call_callee)), LinphoneCallAcceptedElsewhere, int, "%d"); BC_ASSERT_TRUE(wait_for(caller_mgr->lc,callee_mgr->lc,&caller_mgr->stat.number_of_LinphoneCallReleased,1)); @@ -1094,6 +1095,7 @@ static void cancel_call_with_error(void) { BC_ASSERT_STRING_EQUAL(linphone_error_info_get_phrase(rei), "Call has been cancelled"); BC_ASSERT_STRING_EQUAL(linphone_error_info_get_protocol(rei), "SIP"); } + BC_ASSERT_EQUAL(linphone_call_log_get_status(linphone_call_get_call_log(call_callee)), LinphoneCallDeclinedElsewhere, int, "%d"); BC_ASSERT_TRUE(wait_for(caller_mgr->lc,callee_mgr->lc,&caller_mgr->stat.number_of_LinphoneCallReleased,1)); @@ -1142,6 +1144,7 @@ static void cancel_other_device_after_accept(void) { BC_ASSERT_STRING_EQUAL(linphone_error_info_get_phrase(rei), "Call completed elsewhere"); BC_ASSERT_STRING_EQUAL(linphone_error_info_get_protocol(rei), "SIP"); } + BC_ASSERT_EQUAL(linphone_call_log_get_status(linphone_call_get_call_log(call_callee_2)), LinphoneCallAcceptedElsewhere, int, "%d"); } linphone_call_terminate(out_call); BC_ASSERT_TRUE(wait_for(caller_mgr->lc,callee_mgr->lc,&caller_mgr->stat.number_of_LinphoneCallEnd,1)); @@ -1179,10 +1182,12 @@ static void cancel_other_device_after_decline(void) { BC_ASSERT_PTR_NOT_NULL(call_callee_2); BC_ASSERT_EQUAL(linphone_call_decline(call_callee, LinphoneReasonDeclined), 0 , int, "%d"); - BC_ASSERT_TRUE(wait_for(caller_mgr->lc,callee_mgr->lc,&caller_mgr->stat.number_of_LinphoneCallEnd,1)); + BC_ASSERT_TRUE(wait_for(caller_mgr->lc,callee_mgr->lc, &caller_mgr->stat.number_of_LinphoneCallEnd,1)); BC_ASSERT_TRUE(wait_for(caller_mgr->lc,callee_mgr->lc, &caller_mgr->stat.number_of_LinphoneCallReleased, 1)); - BC_ASSERT_TRUE(wait_for(caller_mgr->lc,callee_mgr_2->lc,&callee_mgr_2->stat.number_of_LinphoneCallEnd,1)); - BC_ASSERT_TRUE(wait_for(caller_mgr->lc,callee_mgr_2->lc,&callee_mgr_2->stat.number_of_LinphoneCallReleased,1)); + BC_ASSERT_TRUE(wait_for(caller_mgr->lc,callee_mgr->lc, &callee_mgr->stat.number_of_LinphoneCallEnd,1)); + BC_ASSERT_TRUE(wait_for(caller_mgr->lc,callee_mgr->lc, &callee_mgr->stat.number_of_LinphoneCallReleased, 1)); + BC_ASSERT_TRUE(wait_for(caller_mgr->lc,callee_mgr_2->lc, &callee_mgr_2->stat.number_of_LinphoneCallEnd,1)); + BC_ASSERT_TRUE(wait_for(caller_mgr->lc,callee_mgr_2->lc, &callee_mgr_2->stat.number_of_LinphoneCallReleased,1)); rei = linphone_call_get_error_info(call_callee_2); BC_ASSERT_PTR_NOT_NULL(rei); @@ -1192,6 +1197,7 @@ static void cancel_other_device_after_decline(void) { BC_ASSERT_STRING_EQUAL(linphone_error_info_get_phrase(rei), "Busy Everywhere"); BC_ASSERT_STRING_EQUAL(linphone_error_info_get_protocol(rei), "SIP"); } + BC_ASSERT_EQUAL(linphone_call_log_get_status(linphone_call_get_call_log(call_callee_2)), LinphoneCallDeclinedElsewhere, int, "%d"); } if (out_call) linphone_call_unref(out_call); if (call_callee) linphone_call_unref(call_callee); diff --git a/tester/message_tester.c b/tester/message_tester.c index cff9645fc..2bd560c2b 100644 --- a/tester/message_tester.c +++ b/tester/message_tester.c @@ -161,7 +161,7 @@ void file_transfer_progress_indication(LinphoneChatMessage *msg, const LinphoneC void is_composing_received(LinphoneCore *lc, LinphoneChatRoom *room) { stats *counters = get_stats(lc); - if (room->remote_is_composing == LinphoneIsComposingActive) { + if (linphone_chat_room_is_remote_composing(room)) { counters->number_of_LinphoneIsComposingActiveReceived++; } else { counters->number_of_LinphoneIsComposingIdleReceived++; @@ -236,7 +236,7 @@ LinphoneChatMessage* create_message_from_sintel_trailer(LinphoneChatRoom *chat_r file_size = ftell(file_to_send); fseek(file_to_send, 0, SEEK_SET); - content = linphone_core_create_content(chat_room->lc); + content = linphone_core_create_content(linphone_chat_room_get_core(chat_room)); belle_sip_object_set_name(&content->base, "sintel trailer content"); linphone_content_set_type(content,"video"); linphone_content_set_subtype(content,"mkv"); @@ -262,7 +262,7 @@ LinphoneChatMessage* create_file_transfer_message_from_sintel_trailer(LinphoneCh LinphoneChatMessage* msg; char *send_filepath = bc_tester_res("sounds/sintel_trailer_opus_h264.mkv"); - content = linphone_core_create_content(chat_room->lc); + content = linphone_core_create_content(linphone_chat_room_get_core(chat_room)); belle_sip_object_set_name(&content->base, "sintel trailer content"); linphone_content_set_type(content,"video"); linphone_content_set_subtype(content,"mkv"); @@ -413,15 +413,15 @@ static void text_message_with_send_error(void) { linphone_chat_room_send_chat_message(chat_room,msg); /* check transient msg list: the msg should be in it, and should be the only one */ - BC_ASSERT_EQUAL((unsigned int)bctbx_list_size(chat_room->transient_messages), 1, unsigned int, "%u"); - BC_ASSERT_PTR_EQUAL(bctbx_list_nth_data(chat_room->transient_messages,0), msg); + BC_ASSERT_EQUAL((unsigned int)bctbx_list_size(linphone_chat_room_get_transient_messages(chat_room)), 1, unsigned int, "%u"); + BC_ASSERT_PTR_EQUAL(bctbx_list_nth_data(linphone_chat_room_get_transient_messages(chat_room),0), msg); BC_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneMessageNotDelivered,1)); /*BC_ASSERT_EQUAL(marie->stat.number_of_LinphoneMessageInProgress,1, int, "%d");*/ BC_ASSERT_EQUAL(pauline->stat.number_of_LinphoneMessageReceived,0, int, "%d"); /* the msg should have been discarded from transient list after an error */ - BC_ASSERT_EQUAL((unsigned int)bctbx_list_size(chat_room->transient_messages), 0, unsigned int, "%u"); + BC_ASSERT_EQUAL((unsigned int)bctbx_list_size(linphone_chat_room_get_transient_messages(chat_room)), 0, unsigned int, "%u"); sal_set_send_error(marie->lc->sal, 0); @@ -446,8 +446,8 @@ static void text_message_with_external_body(void) { linphone_chat_room_send_chat_message(chat_room,msg); /* check transient msg list: the msg should be in it, and should be the only one */ - BC_ASSERT_EQUAL((unsigned int)bctbx_list_size(chat_room->transient_messages), 1, unsigned int, "%u"); - BC_ASSERT_PTR_EQUAL(bctbx_list_nth_data(chat_room->transient_messages,0), msg); + BC_ASSERT_EQUAL((unsigned int)bctbx_list_size(linphone_chat_room_get_transient_messages(chat_room)), 1, unsigned int, "%u"); + BC_ASSERT_PTR_EQUAL(bctbx_list_nth_data(linphone_chat_room_get_transient_messages(chat_room),0), msg); BC_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneMessageReceived,1)); BC_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&pauline->stat.number_of_LinphoneMessageDelivered,1)); @@ -455,7 +455,7 @@ static void text_message_with_external_body(void) { BC_ASSERT_EQUAL(pauline->stat.number_of_LinphoneMessageInProgress,1, int, "%d"); BC_ASSERT_EQUAL(marie->stat.number_of_LinphoneMessageExtBodyReceived,1, int, "%d"); - BC_ASSERT_EQUAL((unsigned int)bctbx_list_size(chat_room->transient_messages), 0, unsigned int, "%u"); + BC_ASSERT_EQUAL((unsigned int)bctbx_list_size(linphone_chat_room_get_transient_messages(chat_room)), 0, unsigned int, "%u"); linphone_core_manager_destroy(marie); linphone_core_manager_destroy(pauline);