From 91b852d54264a37b8b2d88e5d2c084325f6b30a6 Mon Sep 17 00:00:00 2001 From: Pekka Pessi Date: Mon, 7 Feb 2011 21:30:34 +0200 Subject: Move src/text-factory to tpsip/text-manager --- src/Makefile.am | 2 - src/protocol.c | 4 +- src/sip-connection.c | 4 +- src/text-factory.c | 640 -------------------------------------------------- src/text-factory.h | 57 ----- tpsip/Makefile.am | 2 + tpsip/text-manager.c | 642 +++++++++++++++++++++++++++++++++++++++++++++++++++ tpsip/text-manager.h | 57 +++++ 8 files changed, 705 insertions(+), 703 deletions(-) delete mode 100644 src/text-factory.c delete mode 100644 src/text-factory.h create mode 100644 tpsip/text-manager.c create mode 100644 tpsip/text-manager.h diff --git a/src/Makefile.am b/src/Makefile.am index 62ad84f..345dbbd 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -88,8 +88,6 @@ libtpsip_convenience_la_SOURCES = \ media-factory.c \ protocol.h \ protocol.c \ - text-factory.h \ - text-factory.c \ sip-connection-helpers.h \ sip-connection-helpers.c \ sip-connection-private.h diff --git a/src/protocol.c b/src/protocol.c index f4aa767..e52d689 100644 --- a/src/protocol.c +++ b/src/protocol.c @@ -27,6 +27,7 @@ #include #include +#include #include #define DEBUG_FLAG TPSIP_DEBUG_CONNECTION @@ -34,7 +35,6 @@ #include "media-factory.h" #include "sip-connection.h" #include "sip-connection-helpers.h" -#include "text-factory.h" #define PROTOCOL_NAME "sip" #define ICON_NAME "im-" PROTOCOL_NAME @@ -417,7 +417,7 @@ get_connection_details (TpBaseProtocol *self, if (channel_managers != NULL) { GType types[] = { - TPSIP_TYPE_TEXT_FACTORY, + TPSIP_TYPE_TEXT_MANAGER, TPSIP_TYPE_MEDIA_FACTORY, G_TYPE_INVALID }; diff --git a/src/sip-connection.c b/src/sip-connection.c index 866aba7..8eacf28 100644 --- a/src/sip-connection.c +++ b/src/sip-connection.c @@ -38,10 +38,10 @@ #include #include #include +#include #include "sip-connection.h" #include "media-factory.h" -#include "text-factory.h" #include "sip-connection-enumtypes.h" #include "sip-connection-helpers.h" @@ -137,7 +137,7 @@ tpsip_connection_create_channel_managers (TpBaseConnection *conn) GPtrArray *channel_managers = g_ptr_array_sized_new (2); g_ptr_array_add (channel_managers, - g_object_new (TPSIP_TYPE_TEXT_FACTORY, + g_object_new (TPSIP_TYPE_TEXT_MANAGER, "connection", self, NULL)); priv->media_factory = g_object_new (TPSIP_TYPE_MEDIA_FACTORY, diff --git a/src/text-factory.c b/src/text-factory.c deleted file mode 100644 index 04d0e68..0000000 --- a/src/text-factory.c +++ /dev/null @@ -1,640 +0,0 @@ -/* - * text-factory.c - Text channel factory for SIP connection manager - * Copyright (C) 2007-2008 Collabora Ltd. - * Copyright (C) 2007-2009 Nokia Corporation - * - * This work is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This work 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this work; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#include "text-factory.h" - -#include - -#include -#include -#include - -#include "sip-text-channel.h" -#include "tpsip/base-connection.h" -#include "tpsip/handles.h" - -#include -#include -#include - -#define DEBUG_FLAG TPSIP_DEBUG_IM -#include "tpsip/debug.h" - - -static void channel_manager_iface_init (gpointer g_iface, gpointer iface_data); -static void connection_status_changed_cb (TpBaseConnection *conn, - guint status, guint reason, TpsipTextFactory *self); -static void tpsip_text_factory_close_all (TpsipTextFactory *self); - -G_DEFINE_TYPE_WITH_CODE (TpsipTextFactory, tpsip_text_factory, - G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_MANAGER, - channel_manager_iface_init)) - -enum -{ - PROP_CONNECTION = 1, - LAST_PROPERTY -}; - -typedef struct _TpsipTextFactoryPrivate TpsipTextFactoryPrivate; -struct _TpsipTextFactoryPrivate -{ - TpBaseConnection *conn; - /* guint handle => TpsipTextChannel *channel */ - GHashTable *channels; - - gulong status_changed_id; - gulong message_received_id; - - gboolean dispose_has_run; -}; - -#define TPSIP_TEXT_FACTORY_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TPSIP_TYPE_TEXT_FACTORY, TpsipTextFactoryPrivate)) - -static void -tpsip_text_factory_init (TpsipTextFactory *fac) -{ - TpsipTextFactoryPrivate *priv = TPSIP_TEXT_FACTORY_GET_PRIVATE (fac); - - priv->conn = NULL; - priv->channels = g_hash_table_new_full (g_direct_hash, g_direct_equal, - NULL, g_object_unref); - - priv->dispose_has_run = FALSE; -} - -static void -tpsip_text_factory_constructed (GObject *object) -{ - TpsipTextFactory *fac = TPSIP_TEXT_FACTORY (object); - TpsipTextFactoryPrivate *priv = TPSIP_TEXT_FACTORY_GET_PRIVATE (fac); - GObjectClass *parent_object_class = - G_OBJECT_CLASS (tpsip_text_factory_parent_class); - - if (parent_object_class->constructed != NULL) - parent_object_class->constructed (object); - - priv->status_changed_id = g_signal_connect (priv->conn, - "status-changed", (GCallback) connection_status_changed_cb, object); -} - -static void -tpsip_text_factory_dispose (GObject *object) -{ - TpsipTextFactory *fac = TPSIP_TEXT_FACTORY (object); - TpsipTextFactoryPrivate *priv = TPSIP_TEXT_FACTORY_GET_PRIVATE (fac); - - if (priv->dispose_has_run) - return; - - priv->dispose_has_run = TRUE; - - tpsip_text_factory_close_all (fac); - g_assert (priv->channels == NULL); - - if (G_OBJECT_CLASS (tpsip_text_factory_parent_class)->dispose) - G_OBJECT_CLASS (tpsip_text_factory_parent_class)->dispose (object); -} - -static void -tpsip_text_factory_get_property (GObject *object, - guint property_id, - GValue *value, - GParamSpec *pspec) -{ - TpsipTextFactory *fac = TPSIP_TEXT_FACTORY (object); - TpsipTextFactoryPrivate *priv = TPSIP_TEXT_FACTORY_GET_PRIVATE (fac); - - switch (property_id) { - case PROP_CONNECTION: - g_value_set_object (value, priv->conn); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); - break; - } -} - -static void -tpsip_text_factory_set_property (GObject *object, - guint property_id, - const GValue *value, - GParamSpec *pspec) -{ - TpsipTextFactory *fac = TPSIP_TEXT_FACTORY (object); - TpsipTextFactoryPrivate *priv = TPSIP_TEXT_FACTORY_GET_PRIVATE (fac); - - switch (property_id) { - case PROP_CONNECTION: - priv->conn = TP_BASE_CONNECTION (g_value_get_object (value)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); - break; - } -} - -static void -tpsip_text_factory_class_init (TpsipTextFactoryClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GParamSpec *param_spec; - - g_type_class_add_private (klass, sizeof (TpsipTextFactoryPrivate)); - - object_class->constructed = tpsip_text_factory_constructed; - object_class->get_property = tpsip_text_factory_get_property; - object_class->set_property = tpsip_text_factory_set_property; - object_class->dispose = tpsip_text_factory_dispose; - - param_spec = g_param_spec_object ("connection", - "TpsipBaseConnection object", - "SIP connection that owns this text channel factory", - TPSIP_TYPE_BASE_CONNECTION, - G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); - g_object_class_install_property (object_class, PROP_CONNECTION, param_spec); -} - -static void -tpsip_text_factory_close_all (TpsipTextFactory *fac) -{ - TpsipTextFactoryPrivate *priv = TPSIP_TEXT_FACTORY_GET_PRIVATE (fac); - GHashTable *channels; - - if (priv->status_changed_id != 0) - { - g_signal_handler_disconnect (priv->conn, - priv->status_changed_id); - priv->status_changed_id = 0; - } - - if (!priv->channels) - return; - - channels = priv->channels; - priv->channels = NULL; - - g_hash_table_destroy (channels); -} - -struct _ForeachData -{ - TpExportableChannelFunc func; - gpointer user_data; -}; - -static void -_foreach_slave (gpointer key, gpointer value, gpointer user_data) -{ - struct _ForeachData *data = (struct _ForeachData *)user_data; - TpExportableChannel *chan = TP_EXPORTABLE_CHANNEL (value); - - data->func (chan, data->user_data); -} - -static void -tpsip_text_factory_foreach_channel (TpChannelManager *manager, - TpExportableChannelFunc func, - gpointer user_data) -{ - TpsipTextFactory *fac = TPSIP_TEXT_FACTORY (manager); - TpsipTextFactoryPrivate *priv = TPSIP_TEXT_FACTORY_GET_PRIVATE (fac); - struct _ForeachData data; - - data.func = func; - data.user_data = user_data; - - g_hash_table_foreach (priv->channels, _foreach_slave, &data); -} - -/** - * text_channel_closed_cb: - * - * Signal callback for when a text channel is closed. Removes the references - * that #TpsipChannelFactory holds to them. - */ -static void -channel_closed (TpsipTextChannel *chan, gpointer user_data) -{ - TpsipTextFactory *self = TPSIP_TEXT_FACTORY (user_data); - TpsipTextFactoryPrivate *priv = TPSIP_TEXT_FACTORY_GET_PRIVATE (self); - TpHandle contact_handle; - gboolean really_destroyed = TRUE; - - tp_channel_manager_emit_channel_closed_for_object (self, - (TpExportableChannel *) chan); - - if (priv->channels == NULL) - return; - - g_object_get (chan, - "handle", &contact_handle, - "channel-destroyed", &really_destroyed, - NULL); - - if (really_destroyed) - { - DEBUG ("removing text channel with handle %u", contact_handle); - g_hash_table_remove (priv->channels, GINT_TO_POINTER (contact_handle)); - } - else - { - DEBUG ("reopening channel with handle %u due to pending messages", - contact_handle); - tp_channel_manager_emit_new_channel (self, - (TpExportableChannel *) chan, NULL); - } -} - -/** - * new_text_channel - * - * Creates a new empty TpsipTextChannel. - */ -static TpsipTextChannel * -tpsip_text_factory_new_channel (TpsipTextFactory *fac, - TpHandle handle, - TpHandle initiator, - gpointer request_token) -{ - TpsipTextFactoryPrivate *priv; - TpsipTextChannel *chan; - gchar *object_path; - TpBaseConnection *conn; - GSList *request_tokens; - - priv = TPSIP_TEXT_FACTORY_GET_PRIVATE (fac); - conn = priv->conn; - - object_path = g_strdup_printf ("%s/TextChannel%u", - conn->object_path, handle); - - DEBUG ("object path %s", object_path); - - chan = g_object_new (TPSIP_TYPE_TEXT_CHANNEL, - "connection", priv->conn, - "object-path", object_path, - "handle", handle, - "initiator-handle", initiator, - NULL); - - g_free (object_path); - - g_signal_connect (chan, "closed", (GCallback) channel_closed, fac); - - g_hash_table_insert (priv->channels, GUINT_TO_POINTER (handle), chan); - - if (request_token != NULL) - request_tokens = g_slist_prepend (NULL, request_token); - else - request_tokens = NULL; - - tp_channel_manager_emit_new_channel (fac, - (TpExportableChannel *) chan, request_tokens); - - g_slist_free (request_tokens); - - return chan; -} - - -static const gchar * const text_channel_fixed_properties[] = { - TP_IFACE_CHANNEL ".ChannelType", - TP_IFACE_CHANNEL ".TargetHandleType", - NULL -}; - -static const gchar * const text_channel_allowed_properties[] = { - TP_IFACE_CHANNEL ".TargetHandle", - TP_IFACE_CHANNEL ".TargetID", - NULL -}; - -static void -tpsip_text_factory_type_foreach_channel_class (GType type, - TpChannelManagerTypeChannelClassFunc func, - gpointer user_data) -{ - GHashTable *table = g_hash_table_new_full (g_str_hash, g_str_equal, - NULL, (GDestroyNotify) tp_g_value_slice_free); - GValue *value; - - value = tp_g_value_slice_new (G_TYPE_STRING); - g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_TEXT); - g_hash_table_insert (table, (gchar *) text_channel_fixed_properties[0], - value); - - value = tp_g_value_slice_new (G_TYPE_UINT); - g_value_set_uint (value, TP_HANDLE_TYPE_CONTACT); - g_hash_table_insert (table, (gchar *) text_channel_fixed_properties[1], - value); - - func (type, table, text_channel_allowed_properties, user_data); - - g_hash_table_destroy (table); -} - - -static gboolean -tpsip_text_factory_requestotron (TpsipTextFactory *self, - gpointer request_token, - GHashTable *request_properties, - gboolean require_new) -{ - TpsipTextFactoryPrivate *priv = TPSIP_TEXT_FACTORY_GET_PRIVATE (self); - TpBaseConnection *base_conn = (TpBaseConnection *) priv->conn; - TpHandle handle; - GError *error = NULL; - TpExportableChannel *channel; - - if (tp_strdiff (tp_asv_get_string (request_properties, - TP_IFACE_CHANNEL ".ChannelType"), TP_IFACE_CHANNEL_TYPE_TEXT)) - return FALSE; - - if (tp_asv_get_uint32 (request_properties, - TP_IFACE_CHANNEL ".TargetHandleType", NULL) != TP_HANDLE_TYPE_CONTACT) - return FALSE; - - /* validity already checked by TpBaseConnection */ - handle = tp_asv_get_uint32 (request_properties, - TP_IFACE_CHANNEL ".TargetHandle", NULL); - g_assert (handle != 0); - - if (tp_channel_manager_asv_has_unknown_properties (request_properties, - text_channel_fixed_properties, text_channel_allowed_properties, - &error)) - goto error; - - channel = g_hash_table_lookup (priv->channels, - GUINT_TO_POINTER (handle)); - - if (channel == NULL) - { - tpsip_text_factory_new_channel (self, - handle, base_conn->self_handle, request_token); - return TRUE; - } - - if (require_new) - { - g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE, - "Already chatting with contact #%u in another channel", handle); - goto error; - } - - tp_channel_manager_emit_request_already_satisfied (self, request_token, - channel); - return TRUE; - -error: - tp_channel_manager_emit_request_failed (self, request_token, - error->domain, error->code, error->message); - g_error_free (error); - return TRUE; -} - - -static gboolean -tpsip_text_factory_create_channel (TpChannelManager *manager, - gpointer request_token, - GHashTable *request_properties) -{ - TpsipTextFactory *self = TPSIP_TEXT_FACTORY (manager); - - return tpsip_text_factory_requestotron (self, request_token, - request_properties, TRUE); -} - - -static gboolean -tpsip_text_factory_request_channel (TpChannelManager *manager, - gpointer request_token, - GHashTable *request_properties) -{ - TpsipTextFactory *self = TPSIP_TEXT_FACTORY (manager); - - return tpsip_text_factory_requestotron (self, request_token, - request_properties, FALSE); -} - - -static gboolean -tpsip_text_factory_ensure_channel (TpChannelManager *manager, - gpointer request_token, - GHashTable *request_properties) -{ - TpsipTextFactory *self = TPSIP_TEXT_FACTORY (manager); - - return tpsip_text_factory_requestotron (self, request_token, - request_properties, FALSE); -} - -static inline TpsipTextChannel * -tpsip_text_factory_lookup_channel (TpsipTextFactory *fac, - TpHandle handle) -{ - TpsipTextFactoryPrivate *priv = TPSIP_TEXT_FACTORY_GET_PRIVATE (fac); - - return g_hash_table_lookup (priv->channels, - GUINT_TO_POINTER(handle)); -} - -static gboolean -tpsip_nua_i_message_cb (TpBaseConnection *conn, - const TpsipNuaEvent *ev, - tagi_t tags[], - TpsipTextFactory *fac) -{ - TpsipTextChannel *channel; - TpHandle handle; - const sip_t *sip = ev->sip; - const char *text = ""; - gsize len = 0; - char *allocated_text = NULL; - - /* Block anything else except text/plain messages (like isComposings) */ - if (sip->sip_content_type - && (g_ascii_strcasecmp ("text/plain", sip->sip_content_type->c_type))) - { - nua_respond (ev->nua_handle, - SIP_415_UNSUPPORTED_MEDIA, - SIPTAG_ACCEPT_STR("text/plain"), - NUTAG_WITH_THIS(ev->nua), - TAG_END()); - goto end; - } - - /* If there is some text, assure it's in UTF-8 encoding */ - if (sip->sip_payload && sip->sip_payload->pl_len > 0) - { - const char *charset = NULL; - if (sip->sip_content_type) - { - charset = msg_header_find_param ( - (msg_common_t *) sip->sip_content_type, "charset"); - } - - /* Default charset is UTF-8, we only need to convert if it's a different one */ - if (charset && g_ascii_strcasecmp (charset, "UTF-8")) - { - GError *error; - gsize in_len; - allocated_text = g_convert ( - sip->sip_payload->pl_data, sip->sip_payload->pl_len, - "UTF-8", charset, &in_len, &len, &error); - - if (allocated_text == NULL) - { - gint status; - const char *message = NULL; - - MESSAGE ("character set conversion failed for the message body: %s", error->message); - - if (error->code == G_CONVERT_ERROR_ILLEGAL_SEQUENCE) - { - status = 400; - message = "Invalid character sequence in the message body"; - } - else - { - status = 500; - message = "Character set conversion failed for the message body"; - } - nua_respond (ev->nua_handle, - status, message, - NUTAG_WITH_THIS(ev->nua), - TAG_END()); - - g_error_free (error); - goto end; - } - - text = allocated_text; - - if (in_len != sip->sip_payload->pl_len) - { - nua_respond (ev->nua_handle, - 400, "Incomplete character sequence at the " - "end of the message body", - NUTAG_WITH_THIS(ev->nua), - TAG_END()); - goto end; - } - } - else - { - if (!g_utf8_validate (sip->sip_payload->pl_data, - sip->sip_payload->pl_len, - NULL)) - { - nua_respond (ev->nua_handle, - 400, "Invalid character sequence in the message body", - NUTAG_WITH_THIS(ev->nua), - TAG_END()); - goto end; - } - text = sip->sip_payload->pl_data; - len = (gsize) sip->sip_payload->pl_len; - } - } - - handle = tpsip_handle_by_requestor (conn, sip); - - if (!handle) - { - nua_respond (ev->nua_handle, - 400, "Invalid From address", - NUTAG_WITH_THIS(ev->nua), - TAG_END()); - goto end; - } - - /* Send the final response immediately as recommended by RFC 3428 */ - nua_respond (ev->nua_handle, - SIP_200_OK, - NUTAG_WITH_THIS(ev->nua), - TAG_END()); - - DEBUG("Got incoming message from <%s>", - tpsip_handle_inspect (conn, handle)); - - channel = tpsip_text_factory_lookup_channel (fac, handle); - - if (!channel) - channel = tpsip_text_factory_new_channel (fac, - handle, handle, NULL); - - tpsip_text_channel_receive (channel, - sip, handle, text, len); - - tpsip_handle_unref (conn, handle); - -end: - g_free (allocated_text); - - return TRUE; -} - -static void -connection_status_changed_cb (TpBaseConnection *conn, - guint status, - guint reason, - TpsipTextFactory *self) -{ - TpsipTextFactoryPrivate *priv = TPSIP_TEXT_FACTORY_GET_PRIVATE (self); - - switch (status) - { - case TP_CONNECTION_STATUS_CONNECTING: - - priv->message_received_id = g_signal_connect (conn, - "nua-event::nua_i_message", - G_CALLBACK (tpsip_nua_i_message_cb), - self); - - break; - case TP_CONNECTION_STATUS_DISCONNECTED: - tpsip_text_factory_close_all (self); - - if (priv->message_received_id != 0) - { - g_signal_handler_disconnect (conn, priv->message_received_id); - priv->message_received_id = 0; - } - - break; - default: - break; - } -} - -static void -channel_manager_iface_init (gpointer g_iface, gpointer iface_data) -{ - TpChannelManagerIface *iface = g_iface; - - iface->foreach_channel = tpsip_text_factory_foreach_channel; - iface->type_foreach_channel_class = - tpsip_text_factory_type_foreach_channel_class; - iface->create_channel = tpsip_text_factory_create_channel; - iface->request_channel = tpsip_text_factory_request_channel; - iface->ensure_channel = tpsip_text_factory_ensure_channel; -} diff --git a/src/text-factory.h b/src/text-factory.h deleted file mode 100644 index 8a998d7..0000000 --- a/src/text-factory.h +++ /dev/null @@ -1,57 +0,0 @@ -/* - * text-factory.h - Text channel factory for SIP connection manager - * Copyright (C) 2007 Collabora Ltd. - * Copyright (C) 2007-2008 Nokia Corporation - * - * This work is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This work 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this work; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#ifndef __TPSIP_TEXT_FACTORY_H__ -#define __TPSIP_TEXT_FACTORY_H__ - -#include - -G_BEGIN_DECLS - -typedef struct _TpsipTextFactory TpsipTextFactory; -typedef struct _TpsipTextFactoryClass TpsipTextFactoryClass; - -struct _TpsipTextFactoryClass { - GObjectClass parent_class; -}; - -struct _TpsipTextFactory { - GObject parent; -}; - -GType tpsip_text_factory_get_type(void); - -/* TYPE MACROS */ -#define TPSIP_TYPE_TEXT_FACTORY \ - (tpsip_text_factory_get_type()) -#define TPSIP_TEXT_FACTORY(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST((obj), TPSIP_TYPE_TEXT_FACTORY, TpsipTextFactory)) -#define TPSIP_TEXT_FACTORY_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_CAST((klass), TPSIP_TYPE_TEXT_FACTORY, TpsipTextFactoryClass)) -#define TPSIP_IS_TEXT_FACTORY(obj) \ - (G_TYPE_CHECK_INSTANCE_TYPE((obj), TPSIP_TYPE_TEXT_FACTORY)) -#define TPSIP_IS_TEXT_FACTORY_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_TYPE((klass), TPSIP_TYPE_TEXT_FACTORY)) -#define TPSIP_TEXT_FACTORY_GET_CLASS(obj) \ - (G_TYPE_INSTANCE_GET_CLASS ((obj), TPSIP_TYPE_TEXT_FACTORY, TpsipTextFactoryClass)) - -G_END_DECLS - -#endif diff --git a/tpsip/Makefile.am b/tpsip/Makefile.am index f12ea1a..cf07c99 100644 --- a/tpsip/Makefile.am +++ b/tpsip/Makefile.am @@ -23,6 +23,7 @@ tpsip_include_HEADERS = \ event-target.h \ handles.h \ debug.h \ + text-manager.h \ util.h BUILT_SOURCES = \ @@ -39,6 +40,7 @@ libtpsip_la_SOURCES = \ debug.c \ text-channel.h \ text-channel.c \ + text-manager.c \ util.c nodist_libtpsip_la_SOURCES = \ diff --git a/tpsip/text-manager.c b/tpsip/text-manager.c new file mode 100644 index 0000000..f3ae185 --- /dev/null +++ b/tpsip/text-manager.c @@ -0,0 +1,642 @@ +/* + * text-manager.c - Text channel manager for SIP + * Copyright (C) 2007-2008 Collabora Ltd. + * Copyright (C) 2007-2011 Nokia Corporation + * + * This work is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This work 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this work; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include "tpsip/text-manager.h" + +#include + +#include +#include +#include + +#include "tpsip/text-channel.h" +#include "tpsip/base-connection.h" +#include "tpsip/handles.h" + +#include +#include +#include + +#define DEBUG_FLAG TPSIP_DEBUG_IM +#include "tpsip/debug.h" + + +static void channel_manager_iface_init (gpointer g_iface, gpointer iface_data); +static void connection_status_changed_cb (TpBaseConnection *conn, + guint status, guint reason, TpsipTextManager *self); +static void tpsip_text_manager_close_all (TpsipTextManager *self); + +G_DEFINE_TYPE_WITH_CODE (TpsipTextManager, tpsip_text_manager, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_MANAGER, + channel_manager_iface_init)) + +enum +{ + PROP_CONNECTION = 1, + LAST_PROPERTY +}; + +typedef struct _TpsipTextManagerPrivate TpsipTextManagerPrivate; +struct _TpsipTextManagerPrivate +{ + TpBaseConnection *conn; + /* guint handle => TpsipTextChannel *channel */ + GHashTable *channels; + + gulong status_changed_id; + gulong message_received_id; + + gboolean dispose_has_run; +}; + +#define TPSIP_TEXT_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TPSIP_TYPE_TEXT_MANAGER, TpsipTextManagerPrivate)) + +static void +tpsip_text_manager_init (TpsipTextManager *fac) +{ + TpsipTextManagerPrivate *priv = TPSIP_TEXT_MANAGER_GET_PRIVATE (fac); + + priv->conn = NULL; + priv->channels = g_hash_table_new_full (g_direct_hash, g_direct_equal, + NULL, g_object_unref); + + priv->dispose_has_run = FALSE; +} + +static void +tpsip_text_manager_constructed (GObject *object) +{ + TpsipTextManager *fac = TPSIP_TEXT_MANAGER (object); + TpsipTextManagerPrivate *priv = TPSIP_TEXT_MANAGER_GET_PRIVATE (fac); + GObjectClass *parent_object_class = + G_OBJECT_CLASS (tpsip_text_manager_parent_class); + + if (parent_object_class->constructed != NULL) + parent_object_class->constructed (object); + + priv->status_changed_id = g_signal_connect (priv->conn, + "status-changed", (GCallback) connection_status_changed_cb, object); +} + +static void +tpsip_text_manager_dispose (GObject *object) +{ + TpsipTextManager *fac = TPSIP_TEXT_MANAGER (object); + TpsipTextManagerPrivate *priv = TPSIP_TEXT_MANAGER_GET_PRIVATE (fac); + + if (priv->dispose_has_run) + return; + + priv->dispose_has_run = TRUE; + + tpsip_text_manager_close_all (fac); + g_assert (priv->channels == NULL); + + if (G_OBJECT_CLASS (tpsip_text_manager_parent_class)->dispose) + G_OBJECT_CLASS (tpsip_text_manager_parent_class)->dispose (object); +} + +static void +tpsip_text_manager_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + TpsipTextManager *fac = TPSIP_TEXT_MANAGER (object); + TpsipTextManagerPrivate *priv = TPSIP_TEXT_MANAGER_GET_PRIVATE (fac); + + switch (property_id) { + case PROP_CONNECTION: + g_value_set_object (value, priv->conn); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +tpsip_text_manager_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + TpsipTextManager *fac = TPSIP_TEXT_MANAGER (object); + TpsipTextManagerPrivate *priv = TPSIP_TEXT_MANAGER_GET_PRIVATE (fac); + + switch (property_id) { + case PROP_CONNECTION: + priv->conn = TP_BASE_CONNECTION (g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +tpsip_text_manager_class_init (TpsipTextManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GParamSpec *param_spec; + + g_type_class_add_private (klass, sizeof (TpsipTextManagerPrivate)); + + object_class->constructed = tpsip_text_manager_constructed; + object_class->get_property = tpsip_text_manager_get_property; + object_class->set_property = tpsip_text_manager_set_property; + object_class->dispose = tpsip_text_manager_dispose; + + param_spec = g_param_spec_object ("connection", + "TpsipBaseConnection object", + "SIP connection that owns this text channel manager", + TPSIP_TYPE_BASE_CONNECTION, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_CONNECTION, param_spec); +} + +static void +tpsip_text_manager_close_all (TpsipTextManager *fac) +{ + TpsipTextManagerPrivate *priv = TPSIP_TEXT_MANAGER_GET_PRIVATE (fac); + GHashTable *channels; + + if (priv->status_changed_id != 0) + { + g_signal_handler_disconnect (priv->conn, + priv->status_changed_id); + priv->status_changed_id = 0; + } + + if (!priv->channels) + return; + + channels = priv->channels; + priv->channels = NULL; + + g_hash_table_destroy (channels); +} + +struct _ForeachData +{ + TpExportableChannelFunc func; + gpointer user_data; +}; + +static void +_foreach_slave (gpointer key, gpointer value, gpointer user_data) +{ + struct _ForeachData *data = (struct _ForeachData *)user_data; + TpExportableChannel *chan = TP_EXPORTABLE_CHANNEL (value); + + data->func (chan, data->user_data); +} + +static void +tpsip_text_manager_foreach_channel (TpChannelManager *manager, + TpExportableChannelFunc func, + gpointer user_data) +{ + TpsipTextManager *fac = TPSIP_TEXT_MANAGER (manager); + TpsipTextManagerPrivate *priv = TPSIP_TEXT_MANAGER_GET_PRIVATE (fac); + struct _ForeachData data; + + data.func = func; + data.user_data = user_data; + + g_hash_table_foreach (priv->channels, _foreach_slave, &data); +} + +/** + * text_channel_closed_cb: + * + * Signal callback for when a text channel is closed. Removes the references + * that #TpsipChannelManager holds to them. + */ +static void +channel_closed (TpsipTextChannel *chan, gpointer user_data) +{ + TpsipTextManager *self = TPSIP_TEXT_MANAGER (user_data); + TpsipTextManagerPrivate *priv = TPSIP_TEXT_MANAGER_GET_PRIVATE (self); + TpHandle contact_handle; + gboolean really_destroyed = TRUE; + + tp_channel_manager_emit_channel_closed_for_object (self, + (TpExportableChannel *) chan); + + if (priv->channels == NULL) + return; + + g_object_get (chan, + "handle", &contact_handle, + "channel-destroyed", &really_destroyed, + NULL); + + if (really_destroyed) + { + DEBUG ("removing text channel with handle %u", contact_handle); + g_hash_table_remove (priv->channels, GINT_TO_POINTER (contact_handle)); + } + else + { + DEBUG ("reopening channel with handle %u due to pending messages", + contact_handle); + tp_channel_manager_emit_new_channel (self, + (TpExportableChannel *) chan, NULL); + } +} + +/** + * new_text_channel + * + * Creates a new empty TpsipTextChannel. + */ +static TpsipTextChannel * +tpsip_text_manager_new_channel (TpsipTextManager *fac, + TpHandle handle, + TpHandle initiator, + gpointer request_token) +{ + TpsipTextManagerPrivate *priv; + TpsipTextChannel *chan; + gchar *object_path; + TpBaseConnection *conn; + GSList *request_tokens; + + priv = TPSIP_TEXT_MANAGER_GET_PRIVATE (fac); + conn = priv->conn; + + object_path = g_strdup_printf ("%s/TextChannel%u", + conn->object_path, handle); + + DEBUG ("object path %s", object_path); + + chan = g_object_new (TPSIP_TYPE_TEXT_CHANNEL, + "connection", priv->conn, + "object-path", object_path, + "handle", handle, + "initiator-handle", initiator, + NULL); + + g_free (object_path); + + g_signal_connect (chan, "closed", (GCallback) channel_closed, fac); + + g_hash_table_insert (priv->channels, GUINT_TO_POINTER (handle), chan); + + if (request_token != NULL) + request_tokens = g_slist_prepend (NULL, request_token); + else + request_tokens = NULL; + + tp_channel_manager_emit_new_channel (fac, + (TpExportableChannel *) chan, request_tokens); + + g_slist_free (request_tokens); + + return chan; +} + + +static const gchar * const text_channel_fixed_properties[] = { + TP_IFACE_CHANNEL ".ChannelType", + TP_IFACE_CHANNEL ".TargetHandleType", + NULL +}; + +static const gchar * const text_channel_allowed_properties[] = { + TP_IFACE_CHANNEL ".TargetHandle", + TP_IFACE_CHANNEL ".TargetID", + NULL +}; + +static void +tpsip_text_manager_type_foreach_channel_class (GType type, + TpChannelManagerTypeChannelClassFunc func, + gpointer user_data) +{ + GHashTable *table = g_hash_table_new_full (g_str_hash, g_str_equal, + NULL, (GDestroyNotify) tp_g_value_slice_free); + GValue *value; + + value = tp_g_value_slice_new (G_TYPE_STRING); + g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_TEXT); + g_hash_table_insert (table, (gchar *) text_channel_fixed_properties[0], + value); + + value = tp_g_value_slice_new (G_TYPE_UINT); + g_value_set_uint (value, TP_HANDLE_TYPE_CONTACT); + g_hash_table_insert (table, (gchar *) text_channel_fixed_properties[1], + value); + + func (type, table, text_channel_allowed_properties, user_data); + + g_hash_table_destroy (table); +} + + +static gboolean +tpsip_text_manager_requestotron (TpsipTextManager *self, + gpointer request_token, + GHashTable *request_properties, + gboolean require_new) +{ + TpsipTextManagerPrivate *priv = TPSIP_TEXT_MANAGER_GET_PRIVATE (self); + TpBaseConnection *base_conn = (TpBaseConnection *) priv->conn; + TpHandle handle; + GError *error = NULL; + TpExportableChannel *channel; + + if (tp_strdiff (tp_asv_get_string (request_properties, + TP_IFACE_CHANNEL ".ChannelType"), TP_IFACE_CHANNEL_TYPE_TEXT)) + return FALSE; + + if (tp_asv_get_uint32 (request_properties, + TP_IFACE_CHANNEL ".TargetHandleType", NULL) != TP_HANDLE_TYPE_CONTACT) + return FALSE; + + /* validity already checked by TpBaseConnection */ + handle = tp_asv_get_uint32 (request_properties, + TP_IFACE_CHANNEL ".TargetHandle", NULL); + g_assert (handle != 0); + + if (tp_channel_manager_asv_has_unknown_properties (request_properties, + text_channel_fixed_properties, text_channel_allowed_properties, + &error)) + goto error; + + channel = g_hash_table_lookup (priv->channels, + GUINT_TO_POINTER (handle)); + + if (channel == NULL) + { + tpsip_text_manager_new_channel (self, + handle, base_conn->self_handle, request_token); + return TRUE; + } + + if (require_new) + { + g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE, + "Already chatting with contact #%u in another channel", handle); + goto error; + } + + tp_channel_manager_emit_request_already_satisfied (self, request_token, + channel); + return TRUE; + +error: + tp_channel_manager_emit_request_failed (self, request_token, + error->domain, error->code, error->message); + g_error_free (error); + return TRUE; +} + + +static gboolean +tpsip_text_manager_create_channel (TpChannelManager *manager, + gpointer request_token, + GHashTable *request_properties) +{ + TpsipTextManager *self = TPSIP_TEXT_MANAGER (manager); + + return tpsip_text_manager_requestotron (self, request_token, + request_properties, TRUE); +} + + +static gboolean +tpsip_text_manager_request_channel (TpChannelManager *manager, + gpointer request_token, + GHashTable *request_properties) +{ + TpsipTextManager *self = TPSIP_TEXT_MANAGER (manager); + + return tpsip_text_manager_requestotron (self, request_token, + request_properties, FALSE); +} + + +static gboolean +tpsip_text_manager_ensure_channel (TpChannelManager *manager, + gpointer request_token, + GHashTable *request_properties) +{ + TpsipTextManager *self = TPSIP_TEXT_MANAGER (manager); + + return tpsip_text_manager_requestotron (self, request_token, + request_properties, FALSE); +} + +static inline TpsipTextChannel * +tpsip_text_manager_lookup_channel (TpsipTextManager *fac, + TpHandle handle) +{ + TpsipTextManagerPrivate *priv = TPSIP_TEXT_MANAGER_GET_PRIVATE (fac); + + return g_hash_table_lookup (priv->channels, + GUINT_TO_POINTER(handle)); +} + +static gboolean +tpsip_nua_i_message_cb (TpBaseConnection *conn, + const TpsipNuaEvent *ev, + tagi_t tags[], + TpsipTextManager *fac) +{ + TpsipTextChannel *channel; + TpHandle handle; + const sip_t *sip = ev->sip; + const char *text = ""; + gsize len = 0; + char *allocated_text = NULL; + + /* Block anything else except text/plain messages (like isComposings) */ + if (sip->sip_content_type + && (g_ascii_strcasecmp ("text/plain", sip->sip_content_type->c_type))) + { + nua_respond (ev->nua_handle, + SIP_415_UNSUPPORTED_MEDIA, + SIPTAG_ACCEPT_STR("text/plain"), + NUTAG_WITH_THIS(ev->nua), + TAG_END()); + goto end; + } + + /* If there is some text, assure it's in UTF-8 encoding */ + if (sip->sip_payload && sip->sip_payload->pl_len > 0) + { + const char *charset = NULL; + if (sip->sip_content_type) + { + charset = msg_header_find_param ( + (msg_common_t *) sip->sip_content_type, "charset"); + } + + /* Default charset is UTF-8, we only need to convert if it's a different one */ + if (charset && g_ascii_strcasecmp (charset, "UTF-8")) + { + GError *error; + gsize in_len; + allocated_text = g_convert ( + sip->sip_payload->pl_data, sip->sip_payload->pl_len, + "UTF-8", charset, &in_len, &len, &error); + + if (allocated_text == NULL) + { + gint status; + const char *message = NULL; + + MESSAGE ("character set conversion failed for the message body: %s", error->message); + + if (error->code == G_CONVERT_ERROR_ILLEGAL_SEQUENCE) + { + status = 400; + message = "Invalid character sequence in the message body"; + } + else + { + status = 500; + message = "Character set conversion failed for the message body"; + } + nua_respond (ev->nua_handle, + status, message, + NUTAG_WITH_THIS(ev->nua), + TAG_END()); + + g_error_free (error); + goto end; + } + + text = allocated_text; + + if (in_len != sip->sip_payload->pl_len) + { + nua_respond (ev->nua_handle, + 400, "Incomplete character sequence at the " + "end of the message body", + NUTAG_WITH_THIS(ev->nua), + TAG_END()); + goto end; + } + } + else + { + if (!g_utf8_validate (sip->sip_payload->pl_data, + sip->sip_payload->pl_len, + NULL)) + { + nua_respond (ev->nua_handle, + 400, "Invalid character sequence in the message body", + NUTAG_WITH_THIS(ev->nua), + TAG_END()); + goto end; + } + text = sip->sip_payload->pl_data; + len = (gsize) sip->sip_payload->pl_len; + } + } + + handle = tpsip_handle_by_requestor (conn, sip); + + if (!handle) + { + nua_respond (ev->nua_handle, + 400, "Invalid From address", + NUTAG_WITH_THIS(ev->nua), + TAG_END()); + goto end; + } + + /* Send the final response immediately as recommended by RFC 3428 */ + nua_respond (ev->nua_handle, + SIP_200_OK, + NUTAG_WITH_THIS(ev->nua), + TAG_END()); + + DEBUG("Got incoming message from <%s>", + tpsip_handle_inspect (conn, handle)); + + channel = tpsip_text_manager_lookup_channel (fac, handle); + + if (!channel) + channel = tpsip_text_manager_new_channel (fac, + handle, handle, NULL); + + tpsip_text_channel_receive (channel, + sip, handle, text, len); + + tpsip_handle_unref (conn, handle); + +end: + g_free (allocated_text); + + return TRUE; +} + +static void +connection_status_changed_cb (TpBaseConnection *conn, + guint status, + guint reason, + TpsipTextManager *self) +{ + TpsipTextManagerPrivate *priv = TPSIP_TEXT_MANAGER_GET_PRIVATE (self); + + switch (status) + { + case TP_CONNECTION_STATUS_CONNECTING: + + priv->message_received_id = g_signal_connect (conn, + "nua-event::nua_i_message", + G_CALLBACK (tpsip_nua_i_message_cb), + self); + + break; + case TP_CONNECTION_STATUS_DISCONNECTED: + tpsip_text_manager_close_all (self); + + if (priv->message_received_id != 0) + { + g_signal_handler_disconnect (conn, priv->message_received_id); + priv->message_received_id = 0; + } + + break; + default: + break; + } +} + +static void +channel_manager_iface_init (gpointer g_iface, gpointer iface_data) +{ + TpChannelManagerIface *iface = g_iface; + + iface->foreach_channel = tpsip_text_manager_foreach_channel; + iface->type_foreach_channel_class = + tpsip_text_manager_type_foreach_channel_class; + iface->create_channel = tpsip_text_manager_create_channel; + iface->request_channel = tpsip_text_manager_request_channel; + iface->ensure_channel = tpsip_text_manager_ensure_channel; +} diff --git a/tpsip/text-manager.h b/tpsip/text-manager.h new file mode 100644 index 0000000..cd4ea76 --- /dev/null +++ b/tpsip/text-manager.h @@ -0,0 +1,57 @@ +/* + * tpsip/text-manager.h - Text channel manager for SIP + * Copyright (C) 2007 Collabora Ltd. + * Copyright (C) 2007-2011 Nokia Corporation + * + * This work is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This work 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this work; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __TPSIP_TEXT_MANAGER_H__ +#define __TPSIP_TEXT_MANAGER_H__ + +#include + +G_BEGIN_DECLS + +typedef struct _TpsipTextManager TpsipTextManager; +typedef struct _TpsipTextManagerClass TpsipTextManagerClass; + +struct _TpsipTextManagerClass { + GObjectClass parent_class; +}; + +struct _TpsipTextManager { + GObject parent; +}; + +GType tpsip_text_manager_get_type(void); + +/* TYPE MACROS */ +#define TPSIP_TYPE_TEXT_MANAGER \ + (tpsip_text_manager_get_type()) +#define TPSIP_TEXT_MANAGER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), TPSIP_TYPE_TEXT_MANAGER, TpsipTextManager)) +#define TPSIP_TEXT_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), TPSIP_TYPE_TEXT_MANAGER, TpsipTextManagerClass)) +#define TPSIP_IS_TEXT_MANAGER(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), TPSIP_TYPE_TEXT_MANAGER)) +#define TPSIP_IS_TEXT_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), TPSIP_TYPE_TEXT_MANAGER)) +#define TPSIP_TEXT_MANAGER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), TPSIP_TYPE_TEXT_MANAGER, TpsipTextManagerClass)) + +G_END_DECLS + +#endif -- cgit v1.2.3