summaryrefslogtreecommitdiffstats
path: root/src/im
diff options
context:
space:
mode:
Diffstat (limited to 'src/im')
-rw-r--r--src/im/Makefile.am9
-rw-r--r--src/im/im_iscomposing_body.cpp182
-rw-r--r--src/im/im_iscomposing_body.h89
-rw-r--r--src/im/msg_session.cpp423
-rw-r--r--src/im/msg_session.h347
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 &notification) {
+ 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 &notification);
+
+ /**
+ * 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