summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonny Lamb <jonny.lamb@collabora.co.uk>2011-09-06 13:24:39 +0100
committerJonny Lamb <jonny.lamb@collabora.co.uk>2011-09-07 14:56:56 +0100
commit42069fb5754f1ed1d146a660d9528e92bfc52200 (patch)
treeea93bbd3dd5b5ba362364e9cfbd45808b0de99d3
parent59cc9b7c289b902580e3adbc6af08fe50f6bce75 (diff)
gabble: add beginning of gabble plugin
Signed-off-by: Jonny Lamb <jonny.lamb@collabora.co.uk>
-rw-r--r--Makefile.am1
-rw-r--r--configure.ac19
-rw-r--r--gabble/Makefile.am31
-rw-r--r--gabble/message-channel.c895
-rw-r--r--gabble/message-channel.h80
-rw-r--r--gabble/status.c752
-rw-r--r--gabble/status.h70
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*/