diff --git a/coreapi/chat.c b/coreapi/chat.c index 2d0a9dc18..530824512 100644 --- a/coreapi/chat.c +++ b/coreapi/chat.c @@ -29,22 +29,24 @@ #include "ortp/b64.h" #include "linphone/wrapper_utils.h" +#include "chat/chat-room.h" +#include "chat/chat-room-p.h" +#include "utils/content-type.h" +#include "utils/utils.h" + #include #include #include -#define COMPOSING_DEFAULT_IDLE_TIMEOUT 15 -#define COMPOSING_DEFAULT_REFRESH_TIMEOUT 60 -#define COMPOSING_DEFAULT_REMOTE_REFRESH_TIMEOUT 120 +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_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); 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 +118,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 +161,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); + cr->cr->getPrivate()->setCBackPointer(cr); return cr; } @@ -214,7 +192,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 +243,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 +255,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)); - } + cr->cr->getPrivate()->release(); } LinphoneChatRoom *linphone_chat_room_ref(LinphoneChatRoom *cr) { @@ -347,183 +280,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); - } + cr->cr->getPrivate()->removeTransientMessage(msg); } void linphone_chat_message_update_state(LinphoneChatMessage *msg, LinphoneChatMessageState new_state) { @@ -531,50 +289,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. */ - } + msg->chat_room->cr->getPrivate()->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; @@ -653,285 +376,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 = cr->cr->getPrivate()->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 +394,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 +430,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 +457,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 +468,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 +570,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) { + linphone_chat_message_get_chat_room(cm)->cr->getPrivate()->sendImdn(content, reason); ms_free(content); } - sal_op_unref(op); } void linphone_chat_message_send_delivery_notification(LinphoneChatMessage *cm, LinphoneReason reason) { @@ -1302,107 +596,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); - } - } + cr->cr->getPrivate()->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 +629,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 +643,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) { + cr->cr->getPrivate()->setCall(call); +} + +bctbx_list_t * linphone_chat_room_get_transient_messages(const LinphoneChatRoom *cr) { + std::list l = cr->cr->getPrivate()->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 +724,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 +771,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; } @@ -1717,7 +907,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 +915,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 +933,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..a7a9dd635 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); 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/src/CMakeLists.txt b/src/CMakeLists.txt index 053557107..499d32723 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -55,7 +55,11 @@ set(LINPHONE_CXX_OBJECTS_PRIVATE_HEADER_FILES object/object-p.h object/object.h object/singleton.h +<<<<<<< HEAD utils/enum-generator.h +======= + utils/content-type.h +>>>>>>> b7c744ef8... Use ChatRoom C++ class. utils/general.h utils/magic-macros.h utils/utils.h @@ -84,6 +88,7 @@ set(LINPHONE_CXX_OBJECTS_SOURCE_FILES message/message.cpp object/clonable-object.cpp object/object.cpp + utils/content-type.cpp utils/general.cpp utils/utils.cpp ) diff --git a/src/chat/chat-room-p.h b/src/chat/chat-room-p.h new file mode 100644 index 000000000..eae877b60 --- /dev/null +++ b/src/chat/chat-room-p.h @@ -0,0 +1,131 @@ +/* + * 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_ + +#include + +#include "object/object-p.h" + +#include "chat-room.h" + +#include "private.h" + +#include + +// ============================================================================= + +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; + +public: + 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..764fcbf79 --- /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 "chat-room.h" +#include "logger/logger.h" +#include "utils/content-type.h" +#include "utils/utils.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 *)to_string(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..4cb9e5628 --- /dev/null +++ b/src/chat/chat-room.h @@ -0,0 +1,94 @@ +/* + * 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 + +#include "object/object.h" + +#include "linphone/types.h" +#include "sal/sal.h" + +#include "private.h" + +#include + +extern "C" { +LinphoneChatRoom *_linphone_core_create_chat_room_base (LinphoneCore *lc, LinphoneAddress *addr); +int linphone_core_message_received (LinphoneCore *lc, SalOp *op, const SalMessage *sal_msg); +void linphone_core_real_time_text_received (LinphoneCore *lc, LinphoneChatRoom *cr, uint32_t character, LinphoneCall *call); +bctbx_list_t *linphone_chat_room_get_transient_messages (const LinphoneChatRoom *cr); +void linphone_chat_room_remove_transient_message (LinphoneChatRoom *cr, LinphoneChatMessage *msg); +void linphone_chat_room_set_call (LinphoneChatRoom *cr, LinphoneCall *call); +void linphone_chat_room_release (LinphoneChatRoom *cr); +void linphone_chat_message_send_imdn (LinphoneChatMessage *cm, ImdnType imdn_type, LinphoneReason reason); +void linphone_chat_message_update_state (LinphoneChatMessage *msg, LinphoneChatMessageState new_state); +} + +// ============================================================================= + +LINPHONE_BEGIN_NAMESPACE + +class ChatRoomPrivate; + +class ChatRoom : public Object { + friend LinphoneChatRoom *::_linphone_core_create_chat_room_base (LinphoneCore *lc, LinphoneAddress *addr); + friend int ::linphone_core_message_received(LinphoneCore * lc, SalOp * op, const SalMessage * sal_msg); + friend void ::linphone_core_real_time_text_received(LinphoneCore * lc, LinphoneChatRoom * cr, uint32_t character, LinphoneCall * call); + friend bctbx_list_t *::linphone_chat_room_get_transient_messages (const LinphoneChatRoom *cr); + friend void ::linphone_chat_room_remove_transient_message(LinphoneChatRoom * cr, LinphoneChatMessage * msg); + friend void ::linphone_chat_room_set_call(LinphoneChatRoom * cr, LinphoneCall * call); + friend void ::linphone_chat_room_release(LinphoneChatRoom * cr); + friend void ::linphone_chat_message_send_imdn(LinphoneChatMessage * cm, ImdnType imdn_type, LinphoneReason reason); + friend void ::linphone_chat_message_update_state(LinphoneChatMessage * msg, LinphoneChatMessageState new_state); + +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/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/utils.cpp b/src/utils/utils.cpp index 55e8d2026..aa21be3dd 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -18,6 +18,8 @@ #include +#include + #include "utils.h" // ============================================================================= @@ -60,4 +62,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..6779552a8 100644 --- a/src/utils/utils.h +++ b/src/utils/utils.h @@ -38,6 +38,9 @@ namespace Utils { } 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/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);