diff options
Diffstat (limited to 'src/im')
-rw-r--r-- | src/im/Makefile.am | 9 | ||||
-rw-r--r-- | src/im/im_iscomposing_body.cpp | 182 | ||||
-rw-r--r-- | src/im/im_iscomposing_body.h | 89 | ||||
-rw-r--r-- | src/im/msg_session.cpp | 423 | ||||
-rw-r--r-- | src/im/msg_session.h | 347 |
5 files changed, 1050 insertions, 0 deletions
diff --git a/src/im/Makefile.am b/src/im/Makefile.am new file mode 100644 index 0000000..300a43a --- /dev/null +++ b/src/im/Makefile.am @@ -0,0 +1,9 @@ +AM_CPPFLAGS = -Wall $(CCRTP_CFLAGS) $(XML2_CFLAGS) -I$(top_srcdir)/src + +noinst_LIBRARIES = libim.a + +libim_a_SOURCES =\ + im_iscomposing_body.cpp\ + msg_session.cpp\ + im_iscomposing_body.h\ + msg_session.h diff --git a/src/im/im_iscomposing_body.cpp b/src/im/im_iscomposing_body.cpp new file mode 100644 index 0000000..a24fc6e --- /dev/null +++ b/src/im/im_iscomposing_body.cpp @@ -0,0 +1,182 @@ +/* + Copyright (C) 2005-2009 Michel de Boer <michel@twinklephone.com> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "im_iscomposing_body.h" + +#include <cassert> +#include <libxml/parser.h> + +#include "log.h" +#include "util.h" +#include "audits/memman.h" + +#define IM_ISCOMPOSING_NAMESPACE "urn:ietf:params:xml:ns:im-iscomposing" + +#define IS_IM_ISCOMPOSING_TAG(node, tag) IS_XML_TAG(node, tag, IM_ISCOMPOSING_NAMESPACE) + +#define IS_IM_ISCOMPOSING_ATTR(attr, attr_name) IS_XML_ATTR(attr, attr_name, IM_ISCOMPOSING_NAMESPACE) + +bool t_im_iscomposing_xml_body::extract_data(void) { + assert(xml_doc); + + state_.clear(); + refresh_ = 0; + + xmlNode *root_element = NULL; + + // Get root + root_element = xmlDocGetRootElement(xml_doc); + if (!root_element) { + log_file->write_report("im-iscomposing document has no root element.", + "t_im_iscomposing_xml_body::extract_data", + LOG_NORMAL, LOG_WARNING); + return false; + } + + // Check if root is <isComposing> + if (!IS_IM_ISCOMPOSING_TAG(root_element, "isComposing")) { + log_file->write_report("im-iscomposing document has invalid root element.", + "t_im_iscomposing_xml_body::extract_data", + LOG_NORMAL, LOG_WARNING); + return false; + } + + xmlNode *child = root_element->children; + + // Process children of root. + bool state_present = false; + + for (xmlNode *cur_node = child; cur_node; cur_node = cur_node->next) { + if (IS_IM_ISCOMPOSING_TAG(cur_node, "state")) { + state_present = process_node_state(cur_node); + } else if (IS_IM_ISCOMPOSING_TAG(cur_node, "refresh")) { + process_node_refresh(cur_node); + } + } + + // The state node is mandatory, so return only true if it is present. + return state_present; +} + +bool t_im_iscomposing_xml_body::process_node_state(xmlNode *node) { + assert(node); + + xmlNode *child = node->children; + if (child && child->type == XML_TEXT_NODE) { + state_ = tolower((char*)child->content); + } else { + log_file->write_report("<state> element has no content.", + "t_im_iscomposing_xml_body::process_node_state", + LOG_NORMAL, LOG_WARNING); + + return false; + } + + return true; +} + +void t_im_iscomposing_xml_body::process_node_refresh(xmlNode *node) { + assert(node); + + xmlNode *child = node->children; + if (child && child->type == XML_TEXT_NODE) { + refresh_ = atoi((char*)child->content); + } else { + log_file->write_report("<refresh> element has no content.", + "t_im_iscomposing_xml_body::process_node_refresh", + LOG_NORMAL, LOG_WARNING); + } +} + +void t_im_iscomposing_xml_body::create_xml_doc( + const string &xml_version, + const string &charset) +{ + t_sip_body_xml::create_xml_doc(xml_version, charset); + + // isComposing + xmlNode *node_iscomposing = xmlNewNode(NULL, BAD_CAST "isComposing"); + xmlNs *ns_im_iscomposing = xmlNewNs(node_iscomposing, BAD_CAST IM_ISCOMPOSING_NAMESPACE, NULL); + xmlDocSetRootElement(xml_doc, node_iscomposing); + + // state + xmlNewChild(node_iscomposing, ns_im_iscomposing, + BAD_CAST "state", + BAD_CAST state_.c_str()); + + // refresh + if (refresh_ > 0) { + xmlNewChild(node_iscomposing, ns_im_iscomposing, + BAD_CAST "refresh", + BAD_CAST int2str(refresh_).c_str()); + } +} + +t_im_iscomposing_xml_body::t_im_iscomposing_xml_body() : t_sip_body_xml (), + state_(IM_ISCOMPOSING_STATE_IDLE), + refresh_(0) +{} + +t_sip_body *t_im_iscomposing_xml_body::copy(void) const { + t_im_iscomposing_xml_body *body = new t_im_iscomposing_xml_body(*this); + MEMMAN_NEW(body); + + // Clear the xml_doc pointer in the new body, as a copy of the + // XML document must be copied to the body. + body->xml_doc = NULL; + + copy_xml_doc(body); + + return body; +} + +t_body_type t_im_iscomposing_xml_body::get_type(void) const { + return BODY_IM_ISCOMPOSING_XML; +} + +t_media t_im_iscomposing_xml_body::get_media(void) const { + return t_media("application", "im-iscomposing+xml"); +} + +bool t_im_iscomposing_xml_body::parse(const string &s) { + if (t_sip_body_xml::parse(s)) { + if (!extract_data()) { + MEMMAN_DELETE(xml_doc); + xmlFreeDoc(xml_doc); + xml_doc = NULL; + } + } + + return (xml_doc != NULL); +} + +string t_im_iscomposing_xml_body::get_state(void) const { + return state_; +} + +time_t t_im_iscomposing_xml_body::get_refresh(void) const { + return refresh_; +} + +void t_im_iscomposing_xml_body::set_state(const string &state) { + state_ = state; +} + +void t_im_iscomposing_xml_body::set_refresh(time_t refresh) { + refresh_ = refresh; +} diff --git a/src/im/im_iscomposing_body.h b/src/im/im_iscomposing_body.h new file mode 100644 index 0000000..57dbe62 --- /dev/null +++ b/src/im/im_iscomposing_body.h @@ -0,0 +1,89 @@ +/* + Copyright (C) 2005-2009 Michel de Boer <michel@twinklephone.com> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @file + * RFC 3994 im-iscomposing+xml body + */ + +#ifndef _IM_ISCOMPOSING_BODY_H +#define _IM_ISCOMPOSING_BODY_H + +#include <string> +#include <time.h> +#include <libxml/tree.h> +#include "parser/sip_body.h" + +using namespace std; + +//@{ +/** @name Message composition states */ +#define IM_ISCOMPOSING_STATE_IDLE "idle" +#define IM_ISCOMPOSING_STATE_ACTIVE "active" +//@} + +/** RFC 3994 im-iscomposing+xml body */ +class t_im_iscomposing_xml_body : public t_sip_body_xml { +private: + string state_; /**< Composition state */ + time_t refresh_; /**< Refresh interval in seconds */ + + /** Extract information elements from the XML document. */ + bool extract_data(void); + + /** + * Process the state element. + * @param node [in] The state element. + */ + bool process_node_state(xmlNode *node); + + /** + * Process the refresh element. + * @param node [in] The refresh element. + */ + void process_node_refresh(xmlNode *node); + +protected: + /** + * Create a im-iscomposing document from the values stored in the attributes. + */ + virtual void create_xml_doc(const string &xml_version = "1.0", const string &charset = "UTF-8"); + +public: + /** Constructor */ + t_im_iscomposing_xml_body(); + + virtual t_sip_body *copy(void) const; + virtual t_body_type get_type(void) const; + virtual t_media get_media(void) const; + virtual bool parse(const string &s); + + /** @name Getters */ + //@{ + string get_state(void) const; + time_t get_refresh(void) const; + //@} + + /** @name Setters */ + //@{ + void set_state(const string &state); + void set_refresh(time_t refresh); + //@} +}; + +#endif diff --git a/src/im/msg_session.cpp b/src/im/msg_session.cpp new file mode 100644 index 0000000..640012f --- /dev/null +++ b/src/im/msg_session.cpp @@ -0,0 +1,423 @@ +/* + Copyright (C) 2005-2009 Michel de Boer <michel@twinklephone.com> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "msg_session.h" + +#include <cassert> +#include <sys/time.h> + +#include "im_iscomposing_body.h" +#include "log.h" +#include "phone.h" +#include "translator.h" +#include "parser/media_type.h" +#include "utils/file_utils.h" + +#define COMPOSING_LOCAL_IDLE_TIMEOUT 15 +#define COMPOSING_LOCAL_REFRESH_TIMEOUT 90 + +extern t_phone *phone; + +using namespace im; +using namespace utils; + +t_composing_state im::string2composing_state(const string &state_name) { + if (state_name == IM_ISCOMPOSING_STATE_ACTIVE) { + return COMPOSING_STATE_ACTIVE; + } + + return COMPOSING_STATE_IDLE; +} + +string im::composing_state2string(t_composing_state state) { + switch (state) { + case COMPOSING_STATE_IDLE: + return "idle"; + case COMPOSING_STATE_ACTIVE: + return "active"; + default: + assert(false); + } + + return "idle"; +} + +// class t_msg + +t_msg::t_msg() : + has_attachment(false) +{ + struct timeval t; + + gettimeofday(&t, NULL); + timestamp = t.tv_sec; +} + +t_msg::t_msg(const string &msg, t_direction dir, t_text_format fmt) : + message(msg), + direction(dir), + format(fmt), + has_attachment(false) +{ + struct timeval t; + + gettimeofday(&t, NULL); + timestamp = t.tv_sec; +} + +void t_msg::set_attachment(const string &filename, const t_media &media, const string &save_as) { + attachment_filename = filename; + attachment_media = media; + attachment_save_as_name = save_as; + has_attachment = true; +} + +// class t_msg_session + +t_msg_session::t_msg_session(t_user *u) : + user_config(u), + new_message_added(false), + error_recvd(false), + delivery_notification_recvd(false), + msg_in_flight(false), + send_composing_state(u->get_im_send_iscomposing()), + local_composing_state(COMPOSING_STATE_IDLE), + local_idle_timeout(0), + local_refresh_timeout(0), + remote_composing_state(COMPOSING_STATE_IDLE), + remote_idle_timeout(0) +{} + +t_msg_session::t_msg_session(t_user *u, t_display_url _remote_party) : + user_config(u), + remote_party(_remote_party), + new_message_added(false), + error_recvd(false), + delivery_notification_recvd(false), + msg_in_flight(false), + send_composing_state(u->get_im_send_iscomposing()), + local_composing_state(COMPOSING_STATE_IDLE), + local_idle_timeout(0), + local_refresh_timeout(0), + remote_composing_state(COMPOSING_STATE_IDLE), + remote_idle_timeout(0) +{} + +t_msg_session::~t_msg_session() { + // Remove temporary files. + for (list<t_msg>::iterator it = messages.begin(); it != messages.end(); ++it) { + // Temporary files are created for incoming messages only. + if (it->has_attachment && it->direction == MSG_DIR_IN) { + // Defensive check to make sure we are deleting tmp files only. + if (sys_config->is_tmpfile(it->attachment_filename)) { + log_file->write_header("t_msg_session::~t_msg_session"); + log_file->write_raw("Remove tmp file "); + log_file->write_raw(it->attachment_filename); + log_file->write_endl(); + log_file->write_footer(); + + unlink(it->attachment_filename.c_str()); + } + } + } +} + +t_user *t_msg_session::get_user(void) const { + return user_config; +} + +t_display_url t_msg_session::get_remote_party(void) const { + return remote_party; +} + +t_composing_state t_msg_session::get_remote_composing_state(void) const { + return remote_composing_state; +} + +void t_msg_session::set_user(t_user *u) { + user_config = u; +} + +void t_msg_session::set_remote_party(const t_display_url &du) { + remote_party = du; +} + +void t_msg_session::set_send_composing_state(bool enable) { + send_composing_state = enable; +} + +t_msg t_msg_session::get_last_message(void) { + new_message_added = false; + if (messages.empty()) { + throw empty_list_exception(); + } + return messages.back(); +} + +bool t_msg_session::is_new_message_added(void) const { + return new_message_added; +} + +void t_msg_session::set_display_if_empty(const string &display) { + if (remote_party.display.empty()) { + remote_party.display = display; + } +} + +const list<t_msg> &t_msg_session::get_messages(void) const { + return messages; +} + +void t_msg_session::recv_msg(const t_msg &msg) { + // RFC 3994 3.3 + // The composing state of the remote party transitions to idle + // when a message is received. + remote_composing_state = COMPOSING_STATE_IDLE; + remote_idle_timeout = 0; + + messages.push_back(msg); + new_message_added = true; + notify(); +} + +void t_msg_session::send_msg(const string &message, t_text_format format) { + // RFC 3994 3.2 + // If a content message is sent before the idle threshold expires, no + // "idle" state indication is needed. + // The local state is set to idle without sending an indication to the + // remote party. + local_composing_state = COMPOSING_STATE_IDLE; + local_idle_timeout = 0; + local_refresh_timeout = 0; + + t_msg msg(message, im::MSG_DIR_OUT, format); + messages.push_back(msg); + new_message_added = true; + + bool ret = phone->pub_send_message(user_config, remote_party.url, remote_party.display, msg); + + if (ret) { + msg_in_flight = true; + } else { + set_error(TRANSLATE("Failed to send message.")); + } + + notify(); +} + +void t_msg_session::send_file(const string &filename, const t_media &media, const string &subject) { + // RFC 3994 3.2 + // If a content message is sent before the idle threshold expires, no + // "idle" state indication is needed. + // The local state is set to idle without sending an indication to the + // remote party. + local_composing_state = COMPOSING_STATE_IDLE; + local_idle_timeout = 0; + local_refresh_timeout = 0; + + t_msg msg; + msg.set_attachment(filename, media, strip_path_from_filename(filename)); + msg.subject = subject; + msg.direction = MSG_DIR_OUT; + messages.push_back(msg); + new_message_added = true; + + bool ret = phone->pub_send_message(user_config, remote_party.url, remote_party.display, msg); + + if (ret) { + msg_in_flight = true; + notify(); + } else { + // Notify user interface about the sent message before + // setting the error. + notify(); + + set_error(TRANSLATE("Failed to send message.")); + } +} + +void t_msg_session::set_error(const string &message) { + error_msg = message; + error_recvd = true; + notify(); +} + +bool t_msg_session::error_received(void) const { + return error_recvd; +} + +string t_msg_session::take_error(void) { + if (!error_recvd) return ""; + error_recvd = false; + return error_msg; +} + +void t_msg_session::set_delivery_notification(const string ¬ification) { + delivery_notification = notification; + delivery_notification_recvd = true; + notify(); +} + +bool t_msg_session::delivery_notification_received(void) const { + return delivery_notification_recvd; +} + +string t_msg_session::take_delivery_notification(void) { + if (!delivery_notification_recvd) return ""; + delivery_notification_recvd = false; + return delivery_notification; +} + +bool t_msg_session::match(t_user *user, t_url _remote_party) { + return user == user_config && _remote_party == remote_party.url; +} + +void t_msg_session::set_msg_in_flight(bool in_flight) { + msg_in_flight = in_flight; + notify(); +} + +bool t_msg_session::is_msg_in_flight(void) const { + return msg_in_flight; +} + +void t_msg_session::set_local_composing_state(t_composing_state state) { + if (!remote_party.is_valid()) { + // The session is not yet established + return; + } + + switch (local_composing_state) { + case COMPOSING_STATE_IDLE: + switch (state) { + case COMPOSING_STATE_IDLE: + break; + case COMPOSING_STATE_ACTIVE: + local_composing_state = state; + local_idle_timeout = COMPOSING_LOCAL_IDLE_TIMEOUT; + local_refresh_timeout = COMPOSING_LOCAL_REFRESH_TIMEOUT - 10; + + if (send_composing_state) { + (void)phone->pub_send_im_iscomposing( + user_config, remote_party.url, + remote_party.display, + IM_ISCOMPOSING_STATE_ACTIVE, + COMPOSING_LOCAL_REFRESH_TIMEOUT); + } + + break; + default: + assert(false); + } + + break; + case COMPOSING_STATE_ACTIVE: + switch (state) { + case COMPOSING_STATE_IDLE: + local_composing_state = state; + local_idle_timeout = 0; + local_refresh_timeout = 0; + + if (send_composing_state) { + (void)phone->pub_send_im_iscomposing( + user_config, remote_party.url, + remote_party.display, + IM_ISCOMPOSING_STATE_IDLE, + COMPOSING_LOCAL_REFRESH_TIMEOUT); + } + + break; + case COMPOSING_STATE_ACTIVE: + local_idle_timeout = COMPOSING_LOCAL_IDLE_TIMEOUT; + break; + default: + assert(false); + } + + break; + default: + assert(false); + } +} + +void t_msg_session::set_remote_composing_state(t_composing_state state, time_t idle_timeout) { + switch (remote_composing_state) { + case COMPOSING_STATE_IDLE: + switch (state) { + case COMPOSING_STATE_IDLE: + break; + case COMPOSING_STATE_ACTIVE: + remote_composing_state = state; + remote_idle_timeout = idle_timeout; + notify(); + + break; + default: + assert(false); + } + + break; + case COMPOSING_STATE_ACTIVE: + switch (state) { + case COMPOSING_STATE_IDLE: + remote_composing_state = state; + remote_idle_timeout = 0; + notify(); + + break; + case COMPOSING_STATE_ACTIVE: + remote_idle_timeout = idle_timeout; + break; + } + + break; + default: + assert(false); + } +} + +void t_msg_session::dec_local_composing_timeout(void) { + if (local_composing_state == COMPOSING_STATE_IDLE) return; + + local_idle_timeout--; + if (local_idle_timeout == 0) { + set_local_composing_state(COMPOSING_STATE_IDLE); + } else { + local_refresh_timeout--; + if (local_refresh_timeout == 0) { + local_refresh_timeout = COMPOSING_LOCAL_REFRESH_TIMEOUT - 10; + + if (send_composing_state) { + (void)phone->pub_send_im_iscomposing( + user_config, remote_party.url, + remote_party.display, + IM_ISCOMPOSING_STATE_ACTIVE, + COMPOSING_LOCAL_REFRESH_TIMEOUT); + } + } + } +} + +void t_msg_session::dec_remote_composing_timeout(void) { + if (remote_composing_state == COMPOSING_STATE_IDLE) return; + + remote_idle_timeout--; + if (remote_idle_timeout == 0) { + set_remote_composing_state(COMPOSING_STATE_IDLE); + } +} diff --git a/src/im/msg_session.h b/src/im/msg_session.h new file mode 100644 index 0000000..bf19d29 --- /dev/null +++ b/src/im/msg_session.h @@ -0,0 +1,347 @@ +/* + Copyright (C) 2005-2009 Michel de Boer <michel@twinklephone.com> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @file + * Instant message session. + * SIP does not have a concept of message sessions. It's up to the + * user interface to create the illusion of a session by grouping + * messages between 2 users. + */ + +#ifndef _MSG_SESSION_H +#define _MSG_SESSION_H + +#include <string> +#include <time.h> + +#include "id_object.h" +#include "exceptions.h" +#include "user.h" +#include "sockets/url.h" +#include "parser/media_type.h" +#include "patterns/observer.h" + +using namespace std; + +namespace im { + +/** + * Maximum length for inline text messages. Longer texts are shown + * as attachments. + */ +const size_t MAX_INLINE_TEXT_LEN = 10240; + +/** Message direction. */ +enum t_direction { + MSG_DIR_IN, /**< Incoming. */ + MSG_DIR_OUT /**< Outgoing. */ +}; + +/** Text format. */ +enum t_text_format { + TXT_PLAIN, /**< Plain */ + TXT_HTML, /**< HTML */ +}; + +/** Message composing state. */ +enum t_composing_state { + COMPOSING_STATE_IDLE, + COMPOSING_STATE_ACTIVE +}; + +/** + * Convert a state name a conveyed in an im_iscomposing body to a state. + * @param state_name [in] The state name to convert. + * @return The composing state. If the state name is unknown, then + * COMPOSING_STATE_IDE is returned. + */ +t_composing_state string2composing_state(const string &state_name); + +/** + * Convert a composing state to a string for an im_iscomposing body. + * @param state [in] The composing state to convert. + * @return The string. + */ +string composing_state2string(t_composing_state state); + +/** + * Single message with meta information. + * In the current implementation a message either contains a text message + * or an attachment. + * TODO: use multipart MIME to send a text message with an attachment. + */ +class t_msg : public t_id_object { +public: + string subject; /**< Subject of the message. */ + string message; /**< The message text. */ + t_direction direction; /**< Direction of the message. */ + time_t timestamp; /**< Timestamp of the message. */ + t_text_format format; /**< Text format. */ + bool has_attachment; /**< Indicates if an attachment is present. */ + string attachment_filename; /**< File name of stored attachment. */ + t_media attachment_media; /**< Media type of attachment */ + string attachment_save_as_name; /**< Suggested 'save as' filename */ + + /** Constructor. */ + t_msg(); + + /** + * Constructor. + * Sets the timestamp to the current time. + * @param msg [in] The message. + * @param dir [in] Direction of the message. + * @param fmt [in] Text format of the message. + */ + t_msg(const string &msg, t_direction dir, t_text_format fmt); + + /** + * Add an attachment to the message. + * @param filename [in] File name (full path) of the attachment. + * @param media [in] Media type of the attachment. + * @param save_as [in] Suggested file name for saving. + */ + void set_attachment(const string &filename, const t_media &media, const string &save_as); +}; + +/** + * Message session. + * It's up to the user interface to create a message session and store + * all messages in it. The session is just a container to store a + * collection of page-mode messages. + */ +class t_msg_session : public patterns::t_subject { +private: + t_user *user_config; /**< User profile of the local user. */ + t_display_url remote_party; /**< Remote party. */ + list<t_msg> messages; /**< Messages sent/received. */ + + /** Indicates if a new message has been added to the list of message. */ + bool new_message_added; + + bool error_recvd; /**< Indicates that an error has been received. */ + string error_msg; /**< Received error message. */ + + /** Indicates that a delivery notification has been received. */ + bool delivery_notification_recvd; + + /** Received delivery notification. */ + string delivery_notification; + + bool msg_in_flight; /**< Indicates if an outgoing message is in flight. */ + + /** Indicates if a composing state indication must be sent to the remote party. */ + bool send_composing_state; + + /** Message composing state of the local party. */ + t_composing_state local_composing_state; + + /** Timeout in seconds till the active state of the local party expires. */ + time_t local_idle_timeout; + + /** Timeout in seconds till a refresh of the active state indication must be sent. */ + time_t local_refresh_timeout; + + /** Message composing state of the remote party. */ + t_composing_state remote_composing_state; + + /** Timeout in seconds till the active state of the remote party expires. */ + time_t remote_idle_timeout; + +public: + /** + * Constructor. + * @param u [in] User profile of the local user. + */ + t_msg_session(t_user *u); + + /** + * Constructor. + * @param u [in] User profile of the local user. + * @param _remote_party [in] URL of remote party. + */ + t_msg_session(t_user *u, t_display_url _remote_party); + + /** Destructor. */ + ~t_msg_session(); + + /** @name getters */ + //@{ + t_user *get_user(void) const; + t_display_url get_remote_party(void) const; + const list<t_msg> &get_messages(void) const; + t_composing_state get_remote_composing_state(void) const; + //@} + + /** @name setters */ + //@{ + void set_user(t_user *u); + void set_remote_party(const t_display_url &du); + void set_send_composing_state(bool enable); + //@} + + /** + * Get the last message of the session. + * @return The last message. + * @throws empty_list_exception There are no messages. + */ + t_msg get_last_message(void); + + /** Check if a new message has been added. */ + bool is_new_message_added(void) const; + + /** + * Set the display name of the remote party if it is not yet set. + * @param display [in] The display name to set. + */ + void set_display_if_empty(const string &display); + + /** + * Add a received message to the session. + * @param msg [in] The message to add. + */ + void recv_msg(const t_msg &msg); + + /** + * Send a message to the remote party. + * The message will be added to the list of messages. + * @param message [in] Message to be sent. + * @param format [in] Text format of the message. + */ + void send_msg(const string &message, t_text_format format); + + /** + * Send a message with file attachment to the remote party. + * The message will be added to the list of messages. + * @param filename [in] Name of file to be sent. + * @param media [in] Mime type of the file. + * @param subject [in] Subject of message. + */ + void send_file(const string &filename, const t_media &media, const string &subject); + + /** + * Set the error message of the session. + * @param message [in] Error message. + * @post @ref error_msg == message + * @post @ref error_recvd == true + */ + void set_error(const string &message); + + /** + * Check if an error has been received. + * @return true, if an error has been received. + * @return false, otherwise + */ + bool error_received(void) const; + + /** + * Take the error message from the session. + * @return Error message. + * @pre @ref error_received() == true + * @post @ref error_received() == false + */ + string take_error(void); + + /** + * Set the delivery notification of the session. + * @param notification [in] Delivery notification. + * @post @ref delivery_notification == notification + * @post @ref delivery_notification_recvd == true + */ + void set_delivery_notification(const string ¬ification); + + /** + * Check if a delivery notification has been received. + * @return true, if an error has been received. + * @return false, otherwise + */ + bool delivery_notification_received(void) const; + + /** + * Take the delivery notification from the session. + * @return Delivery notification. + * @pre @ref delivery_notification_received() == true + * @post @ref delivery_notification_received() == false + */ + string take_delivery_notification(void); + + /** + * Check if the session matches with a particular user and + * remote party. + * @param user [in] The user + * @param remote_party [in] URL of the remote party + * @return true, if there is a match + * @return false, otherwise + */ + bool match(t_user *user, t_url _remote_party); + + /** + * Set the message in flight indicator. + * @param in_flight [in] Indicator value to set. + */ + void set_msg_in_flight(bool in_flight); + + /** + * Check if a message is in flight. + * @return true, message is in flight. + * @return false, no message is in flight. + */ + bool is_msg_in_flight(void) const; + + /** + * Set the local composing state. + * If the state transitions to a new state, then a composing indication + * is sent to the remote party. + * The local idle timeout and refresh timeout timers are updated depending + * on the current state. + * @param state [in] The new local composing state. + */ + void set_local_composing_state(t_composing_state state); + + /** + * Set the remote composing state. + * The remote idle timeout timer is updated depending on the state. + * @param state [in] The new remote composing state. + * @param idle_timeout [in] The idle timeout value when state == active. + * @note When state == idle, then the idle_timout argument has no meaning. + */ + void set_remote_composing_state(t_composing_state state, time_t idle_timeout = 120); + + /** + * Decrement the timeout values for the local composing state if + * the current state is active. + * If the idle timeout reaches zero, then the state transitions + * to idle, and an idle indication is sent to the remote party. + * If the refresh timeout reaches zero, then an active indication + * is sent to the remote party. + * If the current state is idle, then nothing is done. + */ + void dec_local_composing_timeout(void); + + /** Decrement the timeout values for the remote composing state if + * the current state is active. + * If the idle timeout reaches zero, then the state transitions + * to idle. + * If the current state is idle, then nothing is done. + */ + void dec_remote_composing_timeout(void); +}; + +}; // end namespace + +#endif |