diff options
author | Jonny Lamb <jonny.lamb@collabora.co.uk> | 2011-09-06 13:24:39 +0100 |
---|---|---|
committer | Jonny Lamb <jonny.lamb@collabora.co.uk> | 2011-09-07 14:56:56 +0100 |
commit | 42069fb5754f1ed1d146a660d9528e92bfc52200 (patch) | |
tree | ea93bbd3dd5b5ba362364e9cfbd45808b0de99d3 | |
parent | 59cc9b7c289b902580e3adbc6af08fe50f6bce75 (diff) |
gabble: add beginning of gabble plugin
Signed-off-by: Jonny Lamb <jonny.lamb@collabora.co.uk>
-rw-r--r-- | Makefile.am | 1 | ||||
-rw-r--r-- | configure.ac | 19 | ||||
-rw-r--r-- | gabble/Makefile.am | 31 | ||||
-rw-r--r-- | gabble/message-channel.c | 895 | ||||
-rw-r--r-- | gabble/message-channel.h | 80 | ||||
-rw-r--r-- | gabble/status.c | 752 | ||||
-rw-r--r-- | gabble/status.h | 70 |
7 files changed, 1848 insertions, 0 deletions
diff --git a/Makefile.am b/Makefile.am index cc5edb5..9a537d3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3,4 +3,5 @@ SUBDIRS = \ mission-control \ plugin-base \ salut \ + gabble \ tests diff --git a/configure.ac b/configure.ac index e6bd745..c7feb23 100644 --- a/configure.ac +++ b/configure.ac @@ -88,6 +88,24 @@ AC_SUBST(TEST_PYTHON) AM_CONDITIONAL([WANT_TWISTED_TESTS], test false != "$TEST_PYTHON") # ------------------------------------------------------------------------------ +# GABBLE PLUGIN + +PKG_CHECK_MODULES(GABBLE, telepathy-gabble, HAVE_GABBLE=yes, HAVE_GABBLE=no) +AC_SUBST(GABBLE_LIBS) +AC_SUBST(GABBLE_CFLAGS) +gabbleplugindir=`pkg-config --variable=plugindir telepathy-gabble` +AC_SUBST(gabbleplugindir) + +AC_MSG_CHECKING([telepathy-gabble executable]) +GABBLE_EXECUTABLE=`pkg-config --variable=gabblepath telepathy-gabble` +if test "$GABBLE_EXECUTABLE" = ""; then + AC_MSG_ERROR([could not determine location of telepathy-gabble executable]) +else + AC_MSG_RESULT([$GABBLE_EXECUTABLE]) +fi +AC_SUBST(GABBLE_EXECUTABLE) + +# ------------------------------------------------------------------------------ AC_ARG_ENABLE(debug, AC_HELP_STRING([--enable-debug], @@ -113,6 +131,7 @@ AC_OUTPUT([ mission-control/Makefile plugin-base/Makefile salut/Makefile + gabble/Makefile tests/Makefile tests/twisted/Makefile tests/twisted/tools/Makefile diff --git a/gabble/Makefile.am b/gabble/Makefile.am new file mode 100644 index 0000000..e0ad1bf --- /dev/null +++ b/gabble/Makefile.am @@ -0,0 +1,31 @@ +AM_CFLAGS = \ + -DG_LOG_DOMAIN=\"ytstenut\" \ + -DGABBLE \ + -I$(top_srcdir)/plugin-base \ + $(GABBLE_CFLAGS) \ + $(TELEPATHY_YTSTENUT_CFLAGS) + +plugindir = $(gabbleplugindir) + +plugin_LTLIBRARIES = ytstenut-gabble.la + +AM_LDFLAGS = -module -avoid-version -shared + +ytstenut_gabble_la_LIBADD = \ + $(GABBLE_LIBS) \ + $(TELEPATHY_YTSTENUT_LIBS) + +ytstenut_gabble_la_SOURCES = \ + $(top_srcdir)/plugin-base/ytstenut.c \ + $(top_srcdir)/plugin-base/ytstenut.h \ + $(top_srcdir)/plugin-base/caps-manager.c \ + $(top_srcdir)/plugin-base/caps-manager.h \ + status.c \ + status.h \ + message-channel.c \ + message-channel.h \ + $(top_srcdir)/plugin-base/channel-manager.c \ + $(top_srcdir)/plugin-base/channel-manager.h \ + $(top_srcdir)/plugin-base/utils.c \ + $(top_srcdir)/plugin-base/utils.h + diff --git a/gabble/message-channel.c b/gabble/message-channel.c new file mode 100644 index 0000000..f72eddc --- /dev/null +++ b/gabble/message-channel.c @@ -0,0 +1,895 @@ +/* + * message-channel.c - Source for YtstMessageChannel + * Copyright (C) 2005-2008, 2010, 2011 Collabora Ltd. + * Copyright (C) 2011 Intel, Corp. + * @author: Sjoerd Simons <sjoerd@luon.net> + * @author: Stef Walter <stefw@collabora.co.uk> + * + * This library 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 library 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 library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "message-channel.h" + +#include <errno.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> + +#include <dbus/dbus-glib.h> +#include <telepathy-glib/channel.h> +#include <telepathy-glib/channel-iface.h> +#include <telepathy-glib/dbus.h> +#include <telepathy-glib/exportable-channel.h> +#include <telepathy-glib/gtypes.h> +#include <telepathy-glib/interfaces.h> +#include <telepathy-glib/svc-generic.h> +#include <telepathy-glib/svc-channel.h> +#include <telepathy-glib/util.h> + +#include <telepathy-ytstenut-glib/telepathy-ytstenut-glib.h> + +#include <wocky/wocky-namespaces.h> +#include <wocky/wocky-utils.h> +#include <wocky/wocky-xmpp-reader.h> +#include <wocky/wocky-xmpp-writer.h> +#include <wocky/wocky-xmpp-error-enumtypes.h> +#include <wocky/wocky-session.h> + +#include <gabble/connection.h> + +#define DEBUG(msg, ...) \ + g_debug ("%s: " msg, G_STRFUNC, ##__VA_ARGS__) + +#include "utils.h" + +#define EL_YTSTENUT_MESSAGE "message" + +static void channel_ytstenut_iface_init (gpointer g_iface, + gpointer iface_data); + +G_DEFINE_TYPE_WITH_CODE (YtstMessageChannel, ytst_message_channel, + TP_TYPE_BASE_CHANNEL, + G_IMPLEMENT_INTERFACE (TP_TYPE_YTS_SVC_CHANNEL, + channel_ytstenut_iface_init); +); + +static const gchar *ytst_message_channel_interfaces[] = { + TP_IFACE_CHANNEL, + TP_YTS_IFACE_CHANNEL, + NULL +}; + +/* properties */ +enum +{ + PROP_CONTACT = 1, + PROP_TARGET_SERVICE, + PROP_INITIATOR_SERVICE, + PROP_REQUEST, + PROP_REQUEST_TYPE, + PROP_REQUEST_ATTRIBUTES, + PROP_REQUEST_BODY, + LAST_PROPERTY +}; + +/* private structure */ +struct _YtstMessageChannelPrivate +{ + gboolean dispose_has_run; + gchar *contact; + + GCancellable *cancellable; + + /* TRUE if Request() has been called. */ + gboolean requested; + + /* TRUE when either the other side has replied (and Replied/Failed + * has been fired), or one of Fail()/Reply() has been called + * locally. */ + gboolean replied; + + WockyStanza *request; +}; + +/* ----------------------------------------------------------------------------- + * INTERNAL + */ + +static guint32 +channel_get_message_type (WockyStanza *message) +{ + WockyStanzaSubType sub_type; + + wocky_stanza_get_type_info (message, NULL, &sub_type); + switch (sub_type) + { + case WOCKY_STANZA_SUB_TYPE_GET: + return TP_YTS_REQUEST_TYPE_GET; + case WOCKY_STANZA_SUB_TYPE_SET: + return TP_YTS_REQUEST_TYPE_SET; + case WOCKY_STANZA_SUB_TYPE_RESULT: + return TP_YTS_REPLY_TYPE_RESULT; + case WOCKY_STANZA_SUB_TYPE_ERROR: + return TP_YTS_REPLY_TYPE_ERROR; + default: + return 0; + } +} + +static gchar * +channel_get_message_body (WockyStanza *message) +{ + WockyXmppWriter *writer; + WockyNode *top, *body; + WockyNodeTree *tree; + const guint8 *output; + gsize length; + gchar *result; + + top = wocky_stanza_get_top_node (message); + body = wocky_node_get_first_child (top); + + writer = wocky_xmpp_writer_new_no_stream (); + tree = wocky_node_tree_new_from_node (body); + wocky_xmpp_writer_write_node_tree (writer, tree, &output, &length); + result = g_strndup ((const gchar*) output, length); + g_object_unref (writer); + g_object_unref (tree); + + return result; +} + +static GHashTable * +channel_new_message_attributes (void) +{ + return g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); +} + +static gboolean +attribute_to_hashtable (const gchar *key, + const gchar *value, + const gchar *pref, + const gchar *ns, + gpointer user_data) +{ + /* We only expose non namespace attributes in these properties */ + if (ns == NULL && wocky_strdiff (key, "from-service") + && wocky_strdiff (key, "to-service")) + g_hash_table_insert (user_data, g_strdup (key), g_strdup (value)); + return TRUE; +} + +static GHashTable * +channel_get_message_attributes (WockyStanza *message) +{ + WockyNode *top, *body; + GHashTable *attributes; + + top = wocky_stanza_get_top_node (message); + body = wocky_node_get_first_child (top); + + attributes = channel_new_message_attributes (); + wocky_node_each_attribute (body, attribute_to_hashtable, attributes); + return attributes; +} + +static const gchar * +channel_get_message_attribute (WockyStanza *message, + const gchar *key) +{ + WockyNode *top, *body; + + top = wocky_stanza_get_top_node (message); + body = wocky_node_get_first_child (top); + + return wocky_node_get_attribute (body, key); +} + +static void +channel_message_stanza_callback (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + WockyPorter *porter = WOCKY_PORTER (source_object); + YtstMessageChannel *self = YTST_MESSAGE_CHANNEL (user_data); + YtstMessageChannelPrivate *priv = self->priv; + WockyStanza *stanza; + WockyXmppErrorType error_type; + GError *core_error = NULL; + WockyNode *specialized_node = NULL; + GHashTable *attributes; + gchar *body; + GError *error = NULL; + + stanza = wocky_porter_send_iq_finish (porter, result, &error); + if (stanza == NULL) + { + DEBUG ("Failed to send IQ: %s", error->message); + g_clear_error (&error); + goto out; + } + + priv->replied = TRUE; + + if (wocky_stanza_extract_errors (stanza, &error_type, &core_error, + NULL, &specialized_node)) + { + g_assert (core_error != NULL); + tp_yts_svc_channel_emit_failed (self, + ytst_message_error_type_from_wocky (error_type), + wocky_enum_to_nick (WOCKY_TYPE_XMPP_ERROR, core_error->code), + specialized_node ? specialized_node->name : "", + core_error->message ? core_error->message : ""); + g_clear_error (&core_error); + } + else + { + attributes = channel_get_message_attributes (stanza); + body = channel_get_message_body (stanza); + tp_yts_svc_channel_emit_replied (self, attributes, body); + g_hash_table_destroy (attributes); + g_free (body); + } + +out: + g_object_unref (self); +} + +static void +set_attributes_on_body (WockyNodeTree *body, + GHashTable *attributes) +{ + GHashTableIter iter; + WockyNode *node; + const gchar *name, *value; + + node = wocky_node_tree_get_top_node (body); + g_hash_table_iter_init (&iter, attributes); + while (g_hash_table_iter_next (&iter, (gpointer *) &name, + (gpointer *) &value)) + wocky_node_set_attribute (node, name, value); +} + +static WockyNodeTree * +parse_message_body (const gchar *body, + GError **error) +{ + WockyXmppReader *reader; + WockyNodeTree *tree; + WockyNode *node; + GError *err = NULL; + + if (body == NULL || *body == '\0') + body = "<ytstenut:message xmlns:ytstenut=\"" YTST_MESSAGE_NS "\" />"; + + reader = wocky_xmpp_reader_new_no_stream (); + wocky_xmpp_reader_push (reader, (guint8 *) body, strlen (body)); + tree = WOCKY_NODE_TREE (wocky_xmpp_reader_pop_stanza (reader)); + g_object_unref (reader); + + if (tree == NULL) + { + err = wocky_xmpp_reader_get_error (reader); + g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, + "Invalid XML%s%s", + err && err->message ? ": " : ".", + err && err->message ? err->message : ""); + g_clear_error (&err); + return NULL; + } + + /* Make sure it smells right */ + node = wocky_node_tree_get_top_node (tree); + if (!wocky_node_has_ns (node, YTST_MESSAGE_NS)) + { + g_set_error_literal (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, + "Must be a of the ytstenut namespace"); + node = NULL; + } + if (node != NULL && wocky_strdiff (node->name, EL_YTSTENUT_MESSAGE)) + { + g_set_error_literal (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, + "Must be a <ytstenut:message> element"); + node = NULL; + } + + if (node == NULL) + { + g_object_unref (tree); + tree = NULL; + } + + return tree; +} + +/* ----------------------------------------------------------------------------- + * OBJECT + */ + +static void +ytst_message_channel_close (TpBaseChannel *chan) +{ + YtstMessageChannel *self = YTST_MESSAGE_CHANNEL (chan); + YtstMessageChannelPrivate *priv = self->priv; + + DEBUG ("called\n"); + + /* Need to send an item-not-found reply */ + if (!tp_base_channel_is_requested (chan) && !priv->replied) + { + TpBaseConnection *conn = tp_base_channel_get_connection ( + TP_BASE_CHANNEL (self)); + WockySession *session = gabble_connection_get_session ( + GABBLE_CONNECTION (conn)); + + wocky_porter_send_iq_error ( + wocky_session_get_porter (session), + priv->request, WOCKY_XMPP_ERROR_ITEM_NOT_FOUND, + "channel closed before reply was sent; possibly " + "no handler found?"); + } + + if (!g_cancellable_is_cancelled (priv->cancellable)) + g_cancellable_cancel (priv->cancellable); + + tp_base_channel_destroyed (chan); +} + +static gchar * +ytst_message_channel_get_path (TpBaseChannel *chan) +{ + return g_strdup_printf ("YtstenutChannel/%p", chan); +} + +static void +ytst_message_channel_fill_immutable_properties ( + TpBaseChannel *chan, + GHashTable *properties) +{ + TpBaseChannelClass *klass = TP_BASE_CHANNEL_CLASS ( + ytst_message_channel_parent_class); + + klass->fill_immutable_properties (chan, properties); + + tp_dbus_properties_mixin_fill_properties_hash ( + G_OBJECT (chan), properties, + TP_YTS_IFACE_CHANNEL, "RequestType", + TP_YTS_IFACE_CHANNEL, "RequestAttributes", + TP_YTS_IFACE_CHANNEL, "RequestBody", + TP_YTS_IFACE_CHANNEL, "TargetService", + TP_YTS_IFACE_CHANNEL, "InitiatorService", + NULL); +} + +static void +ytst_message_channel_init (YtstMessageChannel *self) +{ + YtstMessageChannelPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + YTST_TYPE_MESSAGE_CHANNEL, YtstMessageChannelPrivate); + self->priv = priv; + priv->cancellable = g_cancellable_new (); +} + +static void +ytst_message_channel_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + YtstMessageChannel *self = YTST_MESSAGE_CHANNEL (object); + YtstMessageChannelPrivate *priv = self->priv; + + switch (property_id) + { + case PROP_CONTACT: + g_value_set_string (value, priv->contact); + break; + case PROP_TARGET_SERVICE: + g_value_set_string (value, channel_get_message_attribute (priv->request, + "to-service")); + break; + case PROP_INITIATOR_SERVICE: + g_value_set_string (value, channel_get_message_attribute (priv->request, + "from-service")); + break; + case PROP_REQUEST: + g_value_set_object (value, priv->request); + break; + case PROP_REQUEST_TYPE: + g_value_set_uint (value, channel_get_message_type (priv->request)); + break; + case PROP_REQUEST_ATTRIBUTES: + g_value_take_boxed (value, + channel_get_message_attributes (priv->request)); + break; + case PROP_REQUEST_BODY: + g_value_take_string (value, channel_get_message_body (priv->request)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +ytst_message_channel_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + YtstMessageChannel *self = YTST_MESSAGE_CHANNEL (object); + YtstMessageChannelPrivate *priv = self->priv; + + switch (property_id) + { + case PROP_CONTACT: + priv->contact = g_value_dup_string (value); + break; + case PROP_REQUEST: + g_assert (priv->request == NULL); + priv->request = g_value_dup_object (value); + g_assert (priv->request != NULL); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +ytst_message_channel_dispose (GObject *object) +{ + YtstMessageChannel *self = YTST_MESSAGE_CHANNEL (object); + YtstMessageChannelPrivate *priv = self->priv; + + if (priv->dispose_has_run) + return; + + priv->dispose_has_run = TRUE; + + if (priv->cancellable != NULL) + { + if (!g_cancellable_is_cancelled (priv->cancellable)) + g_cancellable_cancel (priv->cancellable); + g_object_unref (priv->cancellable); + priv->cancellable = NULL; + } + + tp_clear_pointer (&priv->contact, g_free); + + if (priv->request != NULL) + { + g_object_unref (priv->request); + priv->request = NULL; + } + + if (G_OBJECT_CLASS (ytst_message_channel_parent_class)->dispose) + G_OBJECT_CLASS (ytst_message_channel_parent_class)->dispose (object); +} + +static void +ytst_message_channel_class_init (YtstMessageChannelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + TpBaseChannelClass *base_class = TP_BASE_CHANNEL_CLASS (klass); + + GParamSpec *param_spec; + + static TpDBusPropertiesMixinPropImpl ytstenut_props[] = { + { "TargetService", "target-service", NULL }, + { "InitiatorService", "initiator-service", NULL }, + { "RequestType", "request-type", NULL }, + { "RequestBody", "request-body", NULL }, + { "RequestAttributes", "request-attributes", NULL }, + { NULL } + }; + + g_type_class_add_private (klass, sizeof (YtstMessageChannelPrivate)); + + object_class->dispose = ytst_message_channel_dispose; + object_class->get_property = ytst_message_channel_get_property; + object_class->set_property = ytst_message_channel_set_property; + + base_class->channel_type = TP_YTS_IFACE_CHANNEL; + base_class->interfaces = ytst_message_channel_interfaces; + base_class->target_handle_type = TP_HANDLE_TYPE_CONTACT; + base_class->close = ytst_message_channel_close; + base_class->get_object_path_suffix = ytst_message_channel_get_path; + base_class->fill_immutable_properties = + ytst_message_channel_fill_immutable_properties; + + param_spec = g_param_spec_string ( + "contact", + "Contact", + "Contact to which this channel is dedicated", + "", + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_CONTACT, param_spec); + + param_spec = g_param_spec_object ("request", "Request Stanza", + "The stanza of the request iq", WOCKY_TYPE_STANZA, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_REQUEST, + param_spec); + + param_spec = g_param_spec_string ("target-service", "Target Service", + "Target Ytstenut Service Name", "", + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_TARGET_SERVICE, + param_spec); + + param_spec = g_param_spec_string ("initiator-service", "Initiator Service", + "Initiator Ytstenut Service Name", "", + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_INITIATOR_SERVICE, + param_spec); + + param_spec = g_param_spec_uint ("request-type", "Request Type", + "The type of the ytstenut request message", TP_YTS_REQUEST_TYPE_GET, + NUM_TP_YTS_REQUEST_TYPES, TP_YTS_REQUEST_TYPE_GET, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_REQUEST_TYPE, param_spec); + + param_spec = g_param_spec_string ("request-body", "Request Body", + "The UTF-8 encoded XML body of request message", "", + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_REQUEST_BODY, param_spec); + + param_spec = g_param_spec_boxed ("request-attributes", "Request Attributes", + "The attributes of the ytstenut request message", + TP_HASH_TYPE_STRING_STRING_MAP, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_REQUEST_ATTRIBUTES, + param_spec); + + tp_dbus_properties_mixin_implement_interface (object_class, + TP_YTS_IFACE_QUARK_CHANNEL, + tp_dbus_properties_mixin_getter_gobject_properties, NULL, + ytstenut_props); + + wocky_xmpp_error_register_domain (ytst_message_error_get_domain ()); +} + +static void +ytst_message_channel_request (TpYtsSvcChannel *channel, + DBusGMethodInvocation *context) +{ + YtstMessageChannel *self = YTST_MESSAGE_CHANNEL (channel); + YtstMessageChannelPrivate *priv = self->priv; + WockySession *session; + GError *error = NULL; + + /* Can't call this method from this side */ + if (!tp_base_channel_is_requested (TP_BASE_CHANNEL (channel))) + { + g_set_error_literal (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE, + "Request() may not be called on the reply side of a channel"); + goto done; + } + + if (priv->requested) + { + g_set_error_literal (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE, + "Request() has already been called"); + dbus_g_method_return_error (context, error); + g_clear_error (&error); + return; + } + + session = gabble_connection_get_session (GABBLE_CONNECTION ( + tp_base_channel_get_connection (TP_BASE_CHANNEL (self)))); + + wocky_porter_send_iq_async (wocky_session_get_porter (session), + priv->request, priv->cancellable, + channel_message_stanza_callback, g_object_ref (self)); + priv->requested = TRUE; + +done: + if (error != NULL) + { + dbus_g_method_return_error (context, error); + g_clear_error (&error); + } + else + { + tp_yts_svc_channel_return_from_request (context); + } +} + +static void +ytst_message_channel_reply (TpYtsSvcChannel *channel, + GHashTable *attributes, + const gchar *body, + DBusGMethodInvocation *context) +{ + YtstMessageChannel *self = YTST_MESSAGE_CHANNEL (channel); + YtstMessageChannelPrivate *priv = self->priv; + GabbleConnection *conn = GABBLE_CONNECTION (tp_base_channel_get_connection ( + TP_BASE_CHANNEL (self))); + WockySession *session = gabble_connection_get_session (conn); + WockyNodeTree *body_tree = NULL; + WockyNode *msg_node; + WockyStanza *reply; + GError *error = NULL; + + /* Can't call this method from this side */ + if (tp_base_channel_is_requested (TP_BASE_CHANNEL (channel))) + { + g_set_error_literal (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE, + "Reply() may not be called on the request side of a channel"); + goto done; + } + + /* Can't call this after a successful call */ + if (priv->replied) + { + g_set_error_literal (&error, TP_ERROR, TP_ERROR_NOT_AVAILABLE, + "Fail() or Reply() has already been successfully called"); + goto done; + } + + body_tree = parse_message_body (body, &error); + if (body_tree == NULL) + goto done; + + /* All attributes override anything in the body */ + set_attributes_on_body (body_tree, attributes); + + /* Add the from and to service properties as well */ + msg_node = wocky_node_tree_get_top_node (body_tree); + wocky_node_set_attribute (msg_node, "to-service", + channel_get_message_attribute (priv->request, "from-service")); + wocky_node_set_attribute (msg_node, "from-service", + channel_get_message_attribute (priv->request, "to-service")); + + reply = wocky_stanza_build_iq_result (priv->request, NULL); + + /* Now append the message node */ + wocky_node_add_node_tree (wocky_stanza_get_top_node (reply), body_tree); + g_object_unref (body_tree); + + wocky_porter_send (wocky_session_get_porter (session), reply); + g_object_unref (reply); + +done: + if (error != NULL) + { + dbus_g_method_return_error (context, error); + g_clear_error (&error); + } + else + { + priv->replied = TRUE; + tp_yts_svc_channel_return_from_reply (context); + } +} + +static void +ytst_message_channel_fail (TpYtsSvcChannel *channel, + guint error_type, + const gchar *stanza_error_name, + const gchar *ytstenut_error_name, + const gchar *text, + DBusGMethodInvocation *context) +{ + YtstMessageChannel *self = YTST_MESSAGE_CHANNEL (channel); + YtstMessageChannelPrivate *priv = self->priv; + const gchar *type; + GError *error = NULL; + GabbleConnection *conn = GABBLE_CONNECTION (tp_base_channel_get_connection ( + TP_BASE_CHANNEL (self))); + WockySession *session = gabble_connection_get_session (conn); + WockyStanza *reply; + + /* Can't call this method from this side */ + if (tp_base_channel_is_requested (TP_BASE_CHANNEL (channel))) + { + g_set_error_literal (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE, + "Fail() may not be called on the request side of a channel"); + goto done; + } + + /* Can't call this after a successful call */ + if (priv->replied) + { + g_set_error_literal (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE, + "Fail() or Reply() has already been called"); + goto done; + } + + /* Must be one of the valid error types */ + type = wocky_enum_to_nick (WOCKY_TYPE_XMPP_ERROR_TYPE, + ytst_message_error_type_to_wocky (error_type)); + if (type == NULL) + { + g_set_error_literal (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, + "ErrorType is set to an invalid value."); + goto done; + } + + reply = wocky_stanza_build_iq_error (priv->request, + '(', "error", + '@', "type", type, + '(', stanza_error_name, ':', WOCKY_XMPP_NS_STANZAS, ')', + '(', ytstenut_error_name, ':', YTST_MESSAGE_NS, ')', + '(', "text", + ':', WOCKY_XMPP_NS_STANZAS, + '$', text, + ')', + ')', + NULL); + + wocky_porter_send (wocky_session_get_porter (session), reply); + g_object_unref (reply); + +done: + if (error != NULL) + { + dbus_g_method_return_error (context, error); + g_error_free (error); + } + else + { + priv->replied = TRUE; + tp_yts_svc_channel_return_from_fail (context); + } +} + +static void +channel_ytstenut_iface_init (gpointer g_iface, + gpointer iface_data) +{ + TpYtsSvcChannelClass *klass = (TpYtsSvcChannelClass *) g_iface; + +#define IMPLEMENT(x) tp_yts_svc_channel_implement_##x (\ + klass, ytst_message_channel_##x) + IMPLEMENT(request); + IMPLEMENT(reply); + IMPLEMENT(fail); +#undef IMPLEMENT +} + +/* ----------------------------------------------------------------------------- + * PUBLIC METHODS + */ + +YtstMessageChannel * +ytst_message_channel_new (GabbleConnection *connection, + const gchar *contact, + WockyStanza *request, + TpHandle handle, + TpHandle initiator, + gboolean requested) +{ + YtstMessageChannel *channel; + + g_return_val_if_fail (GABBLE_IS_CONNECTION (connection), NULL); + g_return_val_if_fail (!tp_str_empty (contact), NULL); + g_return_val_if_fail (WOCKY_IS_STANZA (request), NULL); + + channel = g_object_new (YTST_TYPE_MESSAGE_CHANNEL, + "connection", connection, + "contact", contact, + "request", request, + "handle", handle, + "requested", requested, + "initiator-handle", initiator, + NULL); + + tp_base_channel_register (TP_BASE_CHANNEL (channel)); + + return channel; +} + +WockyStanza * +ytst_message_channel_build_request (GHashTable *request_props, + const gchar *from, + const gchar *to, + GError **error) +{ + WockyStanzaSubType sub_type = WOCKY_STANZA_SUB_TYPE_NONE; + TpYtsRequestType request_type; + WockyStanza *request; + const gchar *body; + WockyNodeTree *tree; + GHashTable *attributes; + const gchar *initiator_service; + const gchar *target_service; + + request_type = tp_asv_get_uint32 (request_props, + TP_YTS_IFACE_CHANNEL ".RequestType", NULL); + switch (request_type) + { + case TP_YTS_REQUEST_TYPE_GET: + sub_type = WOCKY_STANZA_SUB_TYPE_GET; + break; + case TP_YTS_REQUEST_TYPE_SET: + sub_type = WOCKY_STANZA_SUB_TYPE_SET; + break; + default: + g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, + "The RequestType property is invalid."); + return NULL; + } + + target_service = tp_asv_get_string (request_props, + TP_YTS_IFACE_CHANNEL ".TargetService"); + if (target_service == NULL) + { + g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, + "The TargetService property must be set."); + return NULL; + } + else if (!tp_dbus_check_valid_bus_name (target_service, + TP_DBUS_NAME_TYPE_WELL_KNOWN, NULL)) + { + g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, + "The TargetService property has an invalid syntax."); + return NULL; + } + + initiator_service = tp_asv_get_string (request_props, + TP_YTS_IFACE_CHANNEL ".InitiatorService"); + if (initiator_service == NULL) + { + g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, + "The InitiatorService property must be set."); + return NULL; + } + else if (!tp_dbus_check_valid_bus_name (initiator_service, + TP_DBUS_NAME_TYPE_WELL_KNOWN, NULL)) + { + g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, + "The InitiatorService property has an invalid syntax."); + return NULL; + } + + attributes = tp_asv_get_boxed (request_props, + TP_YTS_IFACE_CHANNEL ".RequestAttributes", + TP_HASH_TYPE_STRING_STRING_MAP); + if (!attributes && tp_asv_lookup (request_props, + TP_YTS_IFACE_CHANNEL ".RequestAttributes")) + { + g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, + "The RequestAttributes property is invalid."); + return NULL; + } + + body = tp_asv_get_string (request_props, + TP_YTS_IFACE_CHANNEL ".RequestBody"); + tree = parse_message_body (body, error); + if (!tree) + { + g_prefix_error (error, "The RequestBody property is invalid: "); + return NULL; + } + + if (attributes != NULL) + set_attributes_on_body (tree, attributes); + + wocky_node_set_attribute (wocky_node_tree_get_top_node (tree), + "from-service", initiator_service); + wocky_node_set_attribute (wocky_node_tree_get_top_node (tree), + "to-service", target_service); + + request = wocky_stanza_build (WOCKY_STANZA_TYPE_IQ, sub_type, + from, to, NULL); + wocky_node_add_node_tree (wocky_stanza_get_top_node (request), tree); + g_object_unref (tree); + + return request; +} diff --git a/gabble/message-channel.h b/gabble/message-channel.h new file mode 100644 index 0000000..f9e3821 --- /dev/null +++ b/gabble/message-channel.h @@ -0,0 +1,80 @@ +/* + * message-channel.h - Header for YtstMessageChannel + * Copyright (C) 2011 Intel, Corp. + * Copyright (C) 2005, 2011 Collabora Ltd. + * + * This library 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 library 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 library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __YTST_MESSAGE_CHANNEL_H__ +#define __YTST_MESSAGE_CHANNEL_H__ + +#include <glib-object.h> + +#include <telepathy-glib/base-channel.h> + +#include <gabble/connection.h> + +G_BEGIN_DECLS + +typedef struct _YtstMessageChannel YtstMessageChannel; +typedef struct _YtstMessageChannelClass YtstMessageChannelClass; +typedef struct _YtstMessageChannelPrivate YtstMessageChannelPrivate; + +struct _YtstMessageChannelClass { + TpBaseChannelClass parent_class; +}; + +struct _YtstMessageChannel { + TpBaseChannel parent; + YtstMessageChannelPrivate *priv; +}; + +GType ytst_message_channel_get_type (void); + +/* TYPE MACROS */ +#define YTST_TYPE_MESSAGE_CHANNEL \ + (ytst_message_channel_get_type ()) +#define YTST_MESSAGE_CHANNEL(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), YTST_TYPE_MESSAGE_CHANNEL, YtstMessageChannel)) +#define YTST_MESSAGE_CHANNEL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), YTST_TYPE_MESSAGE_CHANNEL, \ + YtstMessageChannelClass)) +#define YTST_IS_MESSAGE_CHANNEL(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), YTST_TYPE_MESSAGE_CHANNEL)) +#define YTST_IS_MESSAGE_CHANNEL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), YTST_TYPE_MESSAGE_CHANNEL)) +#define YTST_MESSAGE_CHANNEL_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), YTST_TYPE_MESSAGE_CHANNEL, \ + YtstMessageChannelClass)) + +YtstMessageChannel* ytst_message_channel_new (GabbleConnection *connection, + const gchar *contact, + WockyStanza *request, + TpHandle handle, + TpHandle initiator, + gboolean requested); + +gboolean ytst_message_channel_is_ytstenut_request_with_id ( + WockyStanza *stanza, gchar **id); + +WockyStanza * ytst_message_channel_build_request (GHashTable *request_props, + const gchar *from, + const gchar *to, + GError **error); + +G_END_DECLS + +#endif /* #ifndef __YTST_MESSAGE_CHANNEL_H__*/ diff --git a/gabble/status.c b/gabble/status.c new file mode 100644 index 0000000..c61cee2 --- /dev/null +++ b/gabble/status.c @@ -0,0 +1,752 @@ +/* + * status.c - Header for YtstStatus + * + * Copyright (C) 2011 Intel Corp. + * @author: Stef Walter <stefw@collabora.co.uk> + * + * This library 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 library 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 library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include "status.h" + +#include <string.h> + +#include <gabble/plugin.h> + +#include <telepathy-glib/svc-generic.h> +#include <telepathy-glib/gtypes.h> + +#include <wocky/wocky-pubsub-helpers.h> +#include <wocky/wocky-xmpp-reader.h> +#include <wocky/wocky-xmpp-writer.h> +#include <wocky/wocky-xep-0115-capabilities.h> +#include <wocky/wocky-data-form.h> + +#include <telepathy-ytstenut-glib/telepathy-ytstenut-glib.h> + +#include "utils.h" + +#define DEBUG(msg, ...) \ + g_debug ("%s: " msg, G_STRFUNC, ##__VA_ARGS__) + +static void sidecar_iface_init (GabbleSidecarInterface *iface); + +static void ytst_status_iface_init (TpYtsSvcStatusClass *iface); + +G_DEFINE_TYPE_WITH_CODE (YtstStatus, ytst_status, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GABBLE_TYPE_SIDECAR, sidecar_iface_init); + G_IMPLEMENT_INTERFACE (TP_TYPE_YTS_SVC_STATUS, ytst_status_iface_init); + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES, + tp_dbus_properties_mixin_iface_init); +); + +/* properties */ +enum +{ + PROP_SESSION = 1, + PROP_CONNECTION, + PROP_DISCOVERED_STATUSES, + PROP_DISCOVERED_SERVICES, + LAST_PROPERTY +}; + +/* private structure */ +struct _YtstStatusPrivate +{ + WockySession *session; + GabbleConnection *connection; + + guint handler_id; + gulong capabilities_changed_id; + + /* GHashTable<gchar*, + * GHashTable<gchar*, + * GHashTable<gchar*,gchar*>>> + */ + GHashTable *discovered_statuses; + + /* GHashTable<gchar*, + * GHashTable<gchar*, + * GValueArray(gchar*,GHashTable<gchar*,gchar*>,GPtrArray<gchar*>)>> + */ + GHashTable *discovered_services; + + gboolean dispose_has_run; +}; + +/* ----------------------------------------------------------------------------- + * INTERNAL + */ + + +/* ----------------------------------------------------------------------------- + * OBJECT + */ + +static void +ytst_status_init (YtstStatus *self) +{ + YtstStatusPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + YTST_TYPE_STATUS, YtstStatusPrivate); + self->priv = priv; +} + +static void +ytst_status_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + YtstStatus *self = YTST_STATUS (object); + YtstStatusPrivate *priv = self->priv; + + switch (property_id) + { + case PROP_SESSION: + g_value_set_object (value, priv->session); + break; + case PROP_CONNECTION: + g_value_set_object (value, priv->connection); + break; + case PROP_DISCOVERED_STATUSES: + g_value_set_boxed (value, priv->discovered_statuses); + break; + case PROP_DISCOVERED_SERVICES: + g_value_set_boxed (value, priv->discovered_services); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +ytst_status_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + YtstStatus *self = YTST_STATUS (object); + YtstStatusPrivate *priv = self->priv; + + switch (property_id) + { + case PROP_SESSION: + priv->session = g_value_dup_object (value); + break; + case PROP_CONNECTION: + priv->connection = g_value_dup_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +update_contact_status (YtstStatus *self, + const gchar *from, + const gchar *capability, + const gchar *service_name, + const gchar *status_str) +{ + YtstStatusPrivate *priv = self->priv; + const gchar *old_status; + gboolean emit = FALSE; + + GHashTable *capability_service_map; + GHashTable *service_status_map; + + capability_service_map = g_hash_table_lookup (priv->discovered_statuses, from); + + if (capability_service_map == NULL) + { + capability_service_map = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, (GDestroyNotify) g_hash_table_unref); + g_hash_table_insert (priv->discovered_statuses, g_strdup (from), + capability_service_map); + } + + service_status_map = g_hash_table_lookup (capability_service_map, + capability); + + if (service_status_map == NULL) + { + service_status_map = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, g_free); + g_hash_table_insert (capability_service_map, + g_strdup (capability), service_status_map); + } + + old_status = g_hash_table_lookup (service_status_map, service_name); + + /* Save this value as old_status will be freed when we call + * g_hash_table_insert next and the spec says we need to update the + * property before emitting the signal. In reality this wouldn't be + * a problem, but let's be nice. */ + emit = tp_strdiff (old_status, status_str); + + if (status_str != NULL) + { + g_hash_table_insert (service_status_map, g_strdup (service_name), + g_strdup (status_str)); + } + else + { + /* remove the service from the service status map */ + g_hash_table_remove (service_status_map, service_name); + + /* now run along up the hash table cleaning up */ + if (g_hash_table_size (service_status_map) == 0) + g_hash_table_remove (capability_service_map, capability); + + if (g_hash_table_size (capability_service_map) == 0) + g_hash_table_remove (priv->discovered_statuses, from); + } + + if (emit) + tp_yts_svc_status_emit_status_changed (self, from, capability, + service_name, status_str); +} + +static gchar * +get_node_body (WockyNode *node) +{ + WockyXmppWriter *writer; + WockyNodeTree *tree; + const guint8 *output; + gsize length; + gchar *result; + + writer = wocky_xmpp_writer_new_no_stream (); + tree = wocky_node_tree_new_from_node (node); + wocky_xmpp_writer_write_node_tree (writer, tree, &output, &length); + result = g_strndup ((const gchar*) output, length); + g_object_unref (writer); + g_object_unref (tree); + + return result; +} + +static gboolean +pep_event_cb (WockyPorter *porter, + WockyStanza *stanza, + gpointer user_data) +{ + YtstStatus *self = user_data; + WockyNode *message, *event, *items, *item, *status; + gchar *status_str = NULL; + const gchar *from, *capability, *service_name; + + message = wocky_stanza_get_top_node (stanza); + + event = wocky_node_get_first_child (message); + + if (event == NULL || tp_strdiff (event->name, "event")) + return FALSE; + + items = wocky_node_get_first_child (event); + if (items == NULL || tp_strdiff (items->name, "items")) + return TRUE; + + item = wocky_node_get_first_child (items); + if (item == NULL || tp_strdiff (item->name, "item")) + return FALSE; + + status = wocky_node_get_first_child (item); + if (status == NULL || tp_strdiff (status->name, "status")) + return FALSE; + + /* looks good */ + + from = wocky_stanza_get_from (stanza); + capability = wocky_node_get_ns (items); + service_name = wocky_node_get_attribute (status, "from-service"); + + if (wocky_node_get_attribute (status, "activity") != NULL) + status_str = get_node_body (status); + + update_contact_status (self, from, capability, service_name, status_str); + + g_free (status_str); + + return TRUE; +} + +static GHashTable * +get_name_map_from_strv (const gchar **strv) +{ + GHashTable *out = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, g_free); + const gchar **s; + + for (s = strv; s != NULL && *s != NULL; s++) + { + gchar **parts = g_strsplit (*s, "/", 2); + + g_hash_table_insert (out, + g_strdup (parts[0]), g_strdup(parts[1])); + + g_strfreev (parts); + } + + return out; +} + +static void +contact_capabilities_changed (YtstStatus *self, + gpointer contact, + gboolean do_signal) +{ + YtstStatusPrivate *priv = self->priv; + const GPtrArray *data_forms; + guint i; + GHashTable *old, *new; + GHashTableIter iter; + gpointer key, value; + const gchar *jid; + + data_forms = wocky_xep_0115_capabilities_get_data_forms ( + WOCKY_XEP_0115_CAPABILITIES (contact)); + + jid = gabble_connection_get_jid_for_caps (priv->connection, + WOCKY_XEP_0115_CAPABILITIES (contact)); + + if (jid == NULL) + return; + + old = g_hash_table_lookup (priv->discovered_services, jid); + + new = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, (GDestroyNotify) g_value_array_free); + + for (i = 0; i < data_forms->len; i++) + { + WockyDataForm *form = g_ptr_array_index (data_forms, i); + WockyDataFormField *type, *tmp; + const gchar *form_type; + const gchar *service; + GValueArray *details; + + gchar *yts_service_name; + GHashTable *yts_name_map; + gchar **yts_caps; + + type = g_hash_table_lookup (form->fields, "FORM_TYPE"); + form_type = g_value_get_string (type->default_value); + + if (type == NULL + || !g_str_has_prefix (form_type, SERVICE_PREFIX)) + { + continue; + } + + service = form_type + strlen (SERVICE_PREFIX); + + /* service type */ + tmp = g_hash_table_lookup (form->fields, "type"); + if (tmp == NULL) + continue; + yts_service_name = g_value_dup_string (tmp->default_value); + + /* name map */ + tmp = g_hash_table_lookup (form->fields, "name"); + if (tmp != NULL && tmp->default_value != NULL) + { + yts_name_map = get_name_map_from_strv ( + g_value_get_boxed (tmp->default_value)); + } + else + { + yts_name_map = g_hash_table_new (g_str_hash, g_str_equal); + } + + /* caps */ + tmp = g_hash_table_lookup (form->fields, "capabilities"); + if (tmp != NULL && tmp->default_value != NULL) + { + yts_caps = g_strdupv (tmp->raw_value_contents); + } + else + { + gchar *caps_tmp[] = { NULL }; + yts_caps = g_strdupv (caps_tmp); + } + + /* now build the value array and add it to the new hash table */ + details = tp_value_array_build (3, + G_TYPE_STRING, yts_service_name, + TP_HASH_TYPE_STRING_STRING_MAP, yts_name_map, + G_TYPE_STRV, yts_caps, + G_TYPE_INVALID); + + g_hash_table_insert (new, g_strdup (service), details); + + g_free (yts_service_name); + g_hash_table_unref (yts_name_map); + g_strfreev (yts_caps); + } + + if (do_signal) + { + /* first check for services in old but not in new; they've been + * removed. old can be NULL. */ + if (old != NULL) + { + g_hash_table_iter_init (&iter, old); + while (g_hash_table_iter_next (&iter, &key, NULL)) + { + if (g_hash_table_lookup (new, key) == NULL) + tp_yts_svc_status_emit_service_removed (self, jid, key); + } + } + + /* next check for services in new but not in old; they've been + * added */ + g_hash_table_iter_init (&iter, new); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + if (old == NULL || g_hash_table_lookup (old, key) == NULL) + tp_yts_svc_status_emit_service_added (self, jid, key, value); + } + } + + if (g_hash_table_size (new) > 0) + { + g_hash_table_replace (priv->discovered_services, + g_strdup (jid), new); + } + else + { + g_hash_table_remove (priv->discovered_services, jid); + g_hash_table_unref (new); + } +} + +static gboolean +capabilities_changed_cb (GSignalInvocationHint *ihint, + guint n_param_values, + const GValue *param_values, + gpointer user_data) +{ + YtstStatus *self = YTST_STATUS (user_data); + + contact_capabilities_changed (self, + g_value_get_object (param_values), TRUE); + + return TRUE; +} + +static gboolean +capabilities_idle_cb (gpointer data) +{ + YtstStatus *self = YTST_STATUS (data); + YtstStatusPrivate *priv = self->priv; + /* TODO + WockyContactFactory *factory; + GList *contacts, *l; + */ + + /* connect to all capabilities-changed signals */ + priv->capabilities_changed_id = g_signal_add_emission_hook ( + g_signal_lookup ("capabilities-changed", WOCKY_TYPE_XEP_0115_CAPABILITIES), + 0, capabilities_changed_cb, self, NULL); + + /* and now look through all the contacts that had caps before this + * sidecar was ensured */ + /* TODO + factory = wocky_session_get_contact_factory (priv->session); + contacts = wocky_contact_factory_get_ll_contacts (factory); + + for (l = contacts; l != NULL; l = l->next) + { + if (WOCKY_IS_XEP_0115_CAPABILITIES (l->data)) + contact_capabilities_changed (self, l->data, FALSE); + } + + g_list_free (contacts); + */ + + return FALSE; +} + +static void +ytst_status_constructed (GObject *object) +{ + YtstStatus *self = YTST_STATUS (object); + YtstStatusPrivate *priv = self->priv; + WockyPorter *porter; + + priv->discovered_statuses = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, (GDestroyNotify) g_hash_table_unref); + + priv->discovered_services = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, (GDestroyNotify) g_hash_table_unref); + + porter = wocky_session_get_porter (priv->session); + priv->handler_id = wocky_porter_register_handler_from_anyone ( + porter, WOCKY_STANZA_TYPE_MESSAGE, WOCKY_STANZA_SUB_TYPE_HEADLINE, + WOCKY_PORTER_HANDLER_PRIORITY_MAX, pep_event_cb, self, + '(', "event", + ':', "http://jabber.org/protocol/pubsub#event", + ')', NULL); + + /* we need an idle for this otherwise the g_signal_lookup fails + * giving this (not entirely sure why): + * + * unable to lookup signal "capabilities-changed" for non + * instantiatable type `WockyXep0115Capabilities' + */ + g_idle_add_full (G_PRIORITY_HIGH_IDLE, capabilities_idle_cb, + self, NULL); +} + +static void +ytst_status_dispose (GObject *object) +{ + YtstStatus *self = YTST_STATUS (object); + YtstStatusPrivate *priv = self->priv; + WockyPorter *porter; + + if (priv->dispose_has_run) + return; + + priv->dispose_has_run = TRUE; + + /* release any references held by the object here */ + + porter = wocky_session_get_porter (priv->session); + wocky_porter_unregister_handler (porter, priv->handler_id); + priv->handler_id = 0; + + if (priv->capabilities_changed_id > 0) + g_signal_remove_emission_hook ( + g_signal_lookup ("capabilities-changed", WOCKY_TYPE_XEP_0115_CAPABILITIES), + priv->capabilities_changed_id); + + tp_clear_pointer (&priv->discovered_statuses, g_hash_table_unref); + tp_clear_pointer (&priv->discovered_services, g_hash_table_unref); + + tp_clear_object (&priv->session); + tp_clear_object (&priv->connection); + + if (G_OBJECT_CLASS (ytst_status_parent_class)->dispose) + G_OBJECT_CLASS (ytst_status_parent_class)->dispose (object); +} + +static void +ytst_status_finalize (GObject *object) +{ +#if 0 + YtstStatus *self = YTST_STATUS (object); + YtstStatusPrivate *priv = self->priv; +#endif + + /* free any data held directly by the object here */ + + G_OBJECT_CLASS (ytst_status_parent_class)->finalize (object); +} + +static void +ytst_status_class_init (YtstStatusClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + GParamSpec *param_spec; + + static TpDBusPropertiesMixinPropImpl ytstenut_props[] = { + { "DiscoveredStatuses", "discovered-statuses", NULL }, + { "DiscoveredServices", "discovered-services", NULL }, + { NULL } + }; + + g_type_class_add_private (klass, sizeof (YtstStatusPrivate)); + + object_class->dispose = ytst_status_dispose; + object_class->finalize = ytst_status_finalize; + object_class->constructed = ytst_status_constructed; + object_class->get_property = ytst_status_get_property; + object_class->set_property = ytst_status_set_property; + + param_spec = g_param_spec_object ( + "session", + "Session object", + "WockySession object", + WOCKY_TYPE_SESSION, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_SESSION, + param_spec); + + param_spec = g_param_spec_object ( + "connection", + "Gabble connection", + "GabbleConnection object", + GABBLE_TYPE_CONNECTION, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_CONNECTION, + param_spec); + + param_spec = g_param_spec_boxed ( + "discovered-statuses", + "Discovered Statuses", + "Discovered Ytstenut statuses", + TP_YTS_HASH_TYPE_CONTACT_CAPABILITY_MAP, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_DISCOVERED_STATUSES, + param_spec); + + param_spec = g_param_spec_boxed ( + "discovered-services", + "Discovered Services", + "Discovered Ytstenut services", + TP_YTS_HASH_TYPE_CONTACT_SERVICE_MAP, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_DISCOVERED_SERVICES, + param_spec); + + tp_dbus_properties_mixin_class_init (object_class, + G_STRUCT_OFFSET (YtstStatusClass, dbus_props_class)); + + tp_dbus_properties_mixin_implement_interface (object_class, + TP_YTS_IFACE_QUARK_STATUS, + tp_dbus_properties_mixin_getter_gobject_properties, NULL, + ytstenut_props); +} + +static WockyNodeTree * +parse_status_body (const gchar *body, + GError **error) +{ + WockyXmppReader *reader; + WockyNodeTree *tree; + GError *err = NULL; + + reader = wocky_xmpp_reader_new_no_stream (); + wocky_xmpp_reader_push (reader, (guint8 *) body, strlen (body)); + tree = WOCKY_NODE_TREE (wocky_xmpp_reader_pop_stanza (reader)); + g_object_unref (reader); + + if (tree == NULL) + { + err = wocky_xmpp_reader_get_error (reader); + g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, + "Invalid XML%s%s", + err != NULL && err->message != NULL ? ": " : ".", + err != NULL && err->message != NULL ? err->message : ""); + g_clear_error (&err); + } + + return tree; +} + +static void +ytst_status_advertise_status (TpYtsSvcStatus *svc, + const gchar *capability, + const gchar *service_name, + const gchar *status, + DBusGMethodInvocation *context) +{ + YtstStatus *self = YTST_STATUS (svc); + YtstStatusPrivate *priv = self->priv; + WockyNodeTree *status_tree = NULL; + GError *error = NULL; + WockyStanza *stanza; + WockyNode *item, *status_node; + + if (tp_str_empty (capability)) + { + g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, + "Capability argument must be set"); + goto out; + } + + if (tp_str_empty (service_name)) + { + g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, + "Service name argument must be set"); + goto out; + } + + if (!tp_str_empty (status)) + { + status_tree = parse_status_body (status, &error); + if (status_tree == NULL) + goto out; + } + else + { + status_tree = wocky_node_tree_new ("status", YTST_STATUS_NS, NULL); + } + + status_node = wocky_node_tree_get_top_node (status_tree); + + wocky_node_set_attribute (status_node, "from-service", + service_name); + wocky_node_set_attribute (status_node, "capability", + capability); + + stanza = wocky_pubsub_make_publish_stanza (NULL, capability, + NULL, NULL, &item); + + wocky_node_add_node_tree (item, status_tree); + g_object_unref (status_tree); + + wocky_porter_send_async (wocky_session_get_porter (priv->session), + stanza, NULL, NULL, NULL); + g_object_unref (stanza); + +out: + if (error == NULL) + { + tp_yts_svc_status_return_from_advertise_status (context); + } + else + { + dbus_g_method_return_error (context, error); + g_clear_error (&error); + } +} + +static void +ytst_status_iface_init (TpYtsSvcStatusClass *iface) +{ +#define IMPLEMENT(x) tp_yts_svc_status_implement_##x (\ + iface, ytst_status_##x) + IMPLEMENT(advertise_status); +#undef IMPLEMENT +} + +static void +sidecar_iface_init (GabbleSidecarInterface *iface) +{ + iface->interface = TP_YTS_IFACE_STATUS; + iface->get_immutable_properties = NULL; +} + +/* ----------------------------------------------------------------------------- + * PUBLIC METHODS + */ + +YtstStatus * +ytst_status_new (WockySession *session, + GabbleConnection *connection) +{ + return g_object_new (YTST_TYPE_STATUS, + "session", session, + "connection", connection, + NULL); +} diff --git a/gabble/status.h b/gabble/status.h new file mode 100644 index 0000000..24adbc6 --- /dev/null +++ b/gabble/status.h @@ -0,0 +1,70 @@ +/* + * status.h - Header for YtstStatus + * + * Copyright (C) 2011 Intel Corp. + * + * This library 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 library 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 library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef YTST_STATUS_H +#define YTST_STATUS_H + +#include <glib-object.h> + +#include <telepathy-glib/base-channel.h> + +#include <wocky/wocky-session.h> + +#include <gabble/connection.h> + +G_BEGIN_DECLS + +typedef struct _YtstStatus YtstStatus; +typedef struct _YtstStatusClass YtstStatusClass; +typedef struct _YtstStatusPrivate YtstStatusPrivate; + +struct _YtstStatusClass { + GObjectClass parent_class; + + TpDBusPropertiesMixinClass dbus_props_class; +}; + +struct _YtstStatus { + GObject parent; + YtstStatusPrivate *priv; +}; + +GType ytst_status_get_type (void); + +/* TYPE MACROS */ +#define YTST_TYPE_STATUS \ + (ytst_status_get_type ()) +#define YTST_STATUS(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), YTST_TYPE_STATUS, YtstStatus)) +#define YTST_STATUS_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), YTST_TYPE_STATUS, YtstStatusClass)) +#define YTST_IS_STATUS(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), YTST_TYPE_STATUS)) +#define YTST_IS_STATUS_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), YTST_TYPE_STATUS)) +#define YTST_STATUS_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), YTST_TYPE_STATUS, YtstStatusClass)) + +YtstStatus * ytst_status_new (WockySession *session, + GabbleConnection *connection); + +G_END_DECLS + +#endif /* #ifndef YTST_STATUS_H*/ |