telepathygabble/src/disco.c
changeset 10 59927b2d3b75
parent 0 d0f3a028347a
--- a/telepathygabble/src/disco.c	Tue Feb 02 01:10:06 2010 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,981 +0,0 @@
-/*
- * gabble-media-stream-glue.h
- *
- * Copyright (C) 2006 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
- *
-
- */
-
-
-
-#include <dbus/dbus-glib.h>
-#include <dbus/dbus-glib-lowlevel.h>
-#include <stdlib.h>
-#include <string.h>
-#include "loudmouth/loudmouth.h"
-
-#include "debug.h"
-#include "disco.h"
-#include "gabble-connection.h"
-#include "gabble-error.h"
-#include "namespaces.h"
-#include "telepathy-helpers.h"
-#include "util.h"
-#include "search-keys-info.h"
-
-
-#ifdef EMULATOR
-#include "libgabble_wsd_solution.h"
-
-	GET_STATIC_VAR_FROM_TLS(quark,gabble_disco,GQuark)
-	#define quark (*GET_WSD_VAR_NAME(quark,gabble_disco,s)())
-	
-	GET_STATIC_VAR_FROM_TLS(gabble_disco_parent_class,gabble_disco,gpointer)
-	#define gabble_disco_parent_class (*GET_WSD_VAR_NAME(gabble_disco_parent_class,gabble_disco,s)())
-	
-	GET_STATIC_VAR_FROM_TLS(g_define_type_id,gabble_disco,GType)
-	#define g_define_type_id (*GET_WSD_VAR_NAME(g_define_type_id,gabble_disco,s)())
-	
-	
-	
-static void gabble_disco_init (GabbleDisco *self); 
-static void gabble_disco_class_init (GabbleDiscoClass *klass); 
-static void gabble_disco_class_intern_init (gpointer klass) 
-{ 
-gabble_disco_parent_class = g_type_class_peek_parent (klass); 
-gabble_disco_class_init ((GabbleDiscoClass*) klass); 
-} 
-EXPORT_C GType gabble_disco_get_type (void) 
-{ 
-
-if ((g_define_type_id == 0)) 
-{ 
-static const GTypeInfo g_define_type_info = 
-	{ sizeof (GabbleDiscoClass), (GBaseInitFunc) ((void *)0), (GBaseFinalizeFunc) ((void *)0), (GClassInitFunc) gabble_disco_class_intern_init, (GClassFinalizeFunc) ((void *)0), ((void *)0), sizeof (GabbleDisco), 0, (GInstanceInitFunc) gabble_disco_init, ((void *)0) }; g_define_type_id = g_type_register_static ( ((GType) ((20) << (2))), g_intern_static_string ("GabbleDisco"), &g_define_type_info, (GTypeFlags) 0); { {} ; } } return g_define_type_id; 
-	} ;
-
-
-#endif
-
-
-#define DBUS_API_SUBJECT_TO_CHANGE
-#define DEBUG_FLAG GABBLE_DEBUG_DISCO
-#define DEFAULT_REQUEST_TIMEOUT 20000
-#define DISCO_PIPELINE_SIZE 10
-
-
-/* Properties */
-enum
-{
-  PROP_CONNECTION = 1,
-  LAST_PROPERTY
-};
-
-#ifndef EMULATOR
-G_DEFINE_TYPE(GabbleDisco, gabble_disco, G_TYPE_OBJECT);
-#endif
-
-typedef struct _GabbleDiscoPrivate GabbleDiscoPrivate;
-struct _GabbleDiscoPrivate
-{
-  GabbleConnection *connection;
-  GSList *service_cache;
-  GList *requests;
-  gboolean dispose_has_run;
-};
-
-struct _GabbleDiscoRequest
-{
-  GabbleDisco *disco;
-  guint timer_id;
-
-  GabbleDiscoType type;
-  gchar *jid;
-  gchar *node;
-  GabbleDiscoCb callback;
-  gpointer user_data;
-  GObject *bound_object;
-};
-
-GQuark
-gabble_disco_error_quark (void)
-{
-
-#ifndef EMULATOR
-  static GQuark quark = 0;
-#endif
-
-  if (!quark)
-    quark = g_quark_from_static_string ("gabble-disco-error");
-  return quark;
-}
-
-#define GABBLE_DISCO_GET_PRIVATE(o)     ((GabbleDiscoPrivate*)((o)->priv));
-
-
-
-
-
-
-static void
-gabble_disco_init (GabbleDisco *obj)
-{
-  GabbleDiscoPrivate *priv =
-     G_TYPE_INSTANCE_GET_PRIVATE (obj, GABBLE_TYPE_DISCO, GabbleDiscoPrivate);
-  obj->priv = priv;
-}
-
-
-
-
-static GObject *gabble_disco_constructor (GType type, guint n_props,
-    GObjectConstructParam *props);
-static void gabble_disco_set_property (GObject *object, guint property_id,
-    const GValue *value, GParamSpec *pspec);
-static void gabble_disco_get_property (GObject *object, guint property_id,
-    GValue *value, GParamSpec *pspec);
-static void gabble_disco_dispose (GObject *object);
-static void gabble_disco_finalize (GObject *object);
-
-static void
-gabble_disco_class_init (GabbleDiscoClass *gabble_disco_class)
-{
-  GObjectClass *object_class = G_OBJECT_CLASS (gabble_disco_class);
-  GParamSpec *param_spec;  
-  
-  g_type_class_add_private (gabble_disco_class, sizeof (GabbleDiscoPrivate));
-
-  object_class->constructor = gabble_disco_constructor;
-
-  object_class->get_property = gabble_disco_get_property;
-  object_class->set_property = gabble_disco_set_property;
-
-  object_class->dispose = gabble_disco_dispose;
-  object_class->finalize = gabble_disco_finalize;
-
-  param_spec = g_param_spec_object ("connection", "GabbleConnection object",
-                                    "Gabble connection object that owns this "
-                                    "XMPP Discovery object.",
-                                    GABBLE_TYPE_CONNECTION,
-                                    G_PARAM_CONSTRUCT_ONLY |
-                                    G_PARAM_READWRITE |
-                                    G_PARAM_STATIC_NICK |
-                                    G_PARAM_STATIC_BLURB);
-  g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
-}
-
-static void
-gabble_disco_get_property (GObject    *object,
-                                guint       property_id,
-                                GValue     *value,
-                                GParamSpec *pspec)
-{
-  GabbleDisco *chan = GABBLE_DISCO (object);
-  GabbleDiscoPrivate *priv = GABBLE_DISCO_GET_PRIVATE (chan);
-
-  switch (property_id) {
-    case PROP_CONNECTION:
-      g_value_set_object (value, priv->connection);
-      break;
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
-      break;
-  }
-}
-
-static void
-gabble_disco_set_property (GObject     *object,
-                           guint        property_id,
-                           const GValue *value,
-                           GParamSpec   *pspec)
-{
-  GabbleDisco *chan = GABBLE_DISCO (object);
-  GabbleDiscoPrivate *priv = GABBLE_DISCO_GET_PRIVATE (chan);
-
-  switch (property_id) {
-    case PROP_CONNECTION:
-      priv->connection = g_value_get_object (value);
-      break;
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
-      break;
-  }
-}
-
-static void gabble_disco_conn_status_changed_cb (GabbleConnection *conn,
-    TpConnectionStatus status, TpConnectionStatusReason reason, gpointer data);
-
-static GObject *
-gabble_disco_constructor (GType type, guint n_props,
-                          GObjectConstructParam *props)
-{
-  GObject *obj;
-  GabbleDisco *disco;
-  GabbleDiscoPrivate *priv;
-
-  obj = G_OBJECT_CLASS (gabble_disco_parent_class)-> constructor (type,
-      n_props, props);
-  disco = GABBLE_DISCO (obj);
-  priv = GABBLE_DISCO_GET_PRIVATE (disco);
-
-  g_signal_connect (priv->connection, "status-changed",
-      G_CALLBACK (gabble_disco_conn_status_changed_cb), disco);
-
-  return obj;
-}
-
-static void cancel_request (GabbleDiscoRequest *request);
-
-void
-gabble_disco_dispose (GObject *object)
-{
-  GSList *l;
-  DBusGProxy *bus_proxy;
-
-  GabbleDisco *self = GABBLE_DISCO (object);
-  GabbleDiscoPrivate *priv = GABBLE_DISCO_GET_PRIVATE (self);
-  bus_proxy = tp_get_bus_proxy ();
-
-  if (priv->dispose_has_run)
-    return;
-
-  priv->dispose_has_run = TRUE;
-
-  gabble_debug (DEBUG_FLAG, "dispose called");
-
-  /* cancel request removes the element from the list after cancelling */
-  while (priv->requests)
-    cancel_request (priv->requests->data);
-
-  for (l = priv->service_cache; l; l = g_slist_next (l))
-    {
-      GabbleDiscoItem *item = (GabbleDiscoItem *) l->data;
-      g_free ((char *) item->jid);
-      g_free ((char *) item->name);
-      g_free ((char *) item->type);
-      g_free ((char *) item->category);
-      g_hash_table_destroy (item->features);
-      g_free (item);
-    }
-
-  g_slist_free (priv->service_cache);
-  priv->service_cache = NULL;
-
-  if (G_OBJECT_CLASS (gabble_disco_parent_class)->dispose)
-    G_OBJECT_CLASS (gabble_disco_parent_class)->dispose (object);
-}
-
-void
-gabble_disco_finalize (GObject *object)
-{
-  gabble_debug (DEBUG_FLAG, "called with %p", object);
-
-  G_OBJECT_CLASS (gabble_disco_parent_class)->finalize (object);
-}
-
-/**
- * gabble_disco_new:
- * @conn: The #GabbleConnection to use for service discovery
- *
- * Creates an object to use for Jabber service discovery (DISCO)
- * There should be one of these per connection
- */
-GabbleDisco *
-gabble_disco_new (GabbleConnection *conn)
-{
-  GabbleDisco *disco;
-
-  g_return_val_if_fail (GABBLE_IS_CONNECTION (conn), NULL);
-
-  disco = GABBLE_DISCO (g_object_new (GABBLE_TYPE_DISCO,
-        "connection", conn,
-        NULL));
-
-  return disco;
-}
-
-
-static void notify_delete_request (gpointer data, GObject *obj);
-
-static void
-delete_request (GabbleDiscoRequest *request)
-{
-  GabbleDisco *disco = request->disco;
-  GabbleDiscoPrivate *priv;
-
-  g_assert (NULL != request);
-    /* fix: Application Crashed: Main */
-  if ( disco == NULL )
-      {
-      g_debug ("%s: accesing after dereferenced connection", G_STRFUNC);
-      return;
-      }
-  g_assert (GABBLE_IS_DISCO (disco));
-
-  priv = GABBLE_DISCO_GET_PRIVATE (disco);
-
-  g_assert (NULL != g_list_find (priv->requests, request));
-
-  priv->requests = g_list_remove (priv->requests, request);
-
-  if (NULL != request->bound_object)
-    {
-      g_object_weak_unref (request->bound_object, notify_delete_request, request);
-    }
-
-  if (0 != request->timer_id)
-    {
-      g_source_remove (request->timer_id);
-    }
-
-  g_free (request->jid);
-  g_free (request);
-}
-
-static gboolean
-timeout_request (gpointer data)
-{
-  GabbleDiscoRequest *request = (GabbleDiscoRequest*) data;
-  GError *err;
-  g_return_val_if_fail (data != NULL, FALSE);
-
-  err = g_error_new (GABBLE_DISCO_ERROR, GABBLE_DISCO_ERROR_TIMEOUT,
-      "Request for %s on %s timed out",
-      (request->type == GABBLE_DISCO_TYPE_INFO)?"info":"items",
-      request->jid);
-  (request->callback)(request->disco, request, request->jid, request->node,
-                      NULL, err, request->user_data);
-  g_error_free (err);
-
-  request->timer_id = 0;
-  delete_request (request);
-  return FALSE;
-}
-
-static void
-cancel_request (GabbleDiscoRequest *request)
-{
-  GError *err;
-
-  g_assert (request != NULL);
-
-  err = g_error_new (GABBLE_DISCO_ERROR, GABBLE_DISCO_ERROR_CANCELLED,
-      "Request for %s on %s cancelled",
-      (request->type == GABBLE_DISCO_TYPE_INFO)?"info":"items",
-      request->jid);
-  (request->callback)(request->disco, request, request->jid, request->node,
-                      NULL, err, request->user_data);
-  g_error_free (err);
-
-  delete_request (request);
-}
-
-static LmHandlerResult
-request_reply_cb (GabbleConnection *conn, LmMessage *sent_msg,
-                  LmMessage *reply_msg, GObject *object, gpointer user_data)
-{
-  LmMessageNode *query_node;
-  GError *err = NULL;
-
-  GabbleDiscoRequest *request = (GabbleDiscoRequest*) user_data;
-  GabbleDisco *disco = GABBLE_DISCO (object);
-  GabbleDiscoPrivate *priv = GABBLE_DISCO_GET_PRIVATE (disco);
-
-  g_assert (request);
-
-  if (!g_list_find (priv->requests, request))
-    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
-
-  query_node = lm_message_node_get_child (reply_msg->node, "query");
-
-  if (lm_message_get_sub_type (reply_msg) == LM_MESSAGE_SUB_TYPE_ERROR)
-    {
-      LmMessageNode *error_node;
-
-      error_node = lm_message_node_get_child (reply_msg->node, "error");
-      if (error_node)
-        {
-          err = gabble_xmpp_error_to_g_error (
-              gabble_xmpp_error_from_node (error_node));
-        }
-
-      if (err == NULL)
-        {
-          err = g_error_new (GABBLE_DISCO_ERROR,
-                             GABBLE_DISCO_ERROR_UNKNOWN,
-                             "an unknown error occurred");
-        }
-    }
-  else if (NULL == query_node)
-    {
-      err = g_error_new (GABBLE_DISCO_ERROR, GABBLE_DISCO_ERROR_UNKNOWN,
-          "disco response contained no <query> node");
-    }
-
-  request->callback (request->disco, request, request->jid, request->node,
-                     query_node, err, request->user_data);
-  delete_request (request);
-
-  if (err)
-    g_error_free (err);
-
-  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-static void
-notify_delete_request (gpointer data, GObject *obj)
-{
-  GabbleDiscoRequest *request = (GabbleDiscoRequest *) data;
-  request->bound_object = NULL;
-  delete_request (request);
-}
-
-/**
- * gabble_disco_request:
- * @self: #GabbleDisco object to use for request
- * @type: type of request
- * @jid: Jabber ID to request on
- * @node: node to request on @jid, or NULL
- * @callback: #GabbleDiscoCb to call on request fullfilment
- * @object: GObject to bind request to. the callback will not be
- *          called if this object has been unrefed. NULL if not needed
- * @error: #GError to return a telepathy error in if unable to make
- *         request, NULL if unneeded.
- *
- * Make a disco request on the given jid with the default timeout.
- */
-GabbleDiscoRequest *
-gabble_disco_request (GabbleDisco *self, GabbleDiscoType type,
-                      const gchar *jid, const char *node,
-                      GabbleDiscoCb callback, gpointer user_data,
-                      GObject *object, GError **error)
-{
-  return gabble_disco_request_with_timeout (self, type, jid, node,
-                                            DEFAULT_REQUEST_TIMEOUT,
-                                            callback, user_data,
-                                            object, error);
-}
-
-/**
- * gabble_disco_request_with_timeout:
- * @self: #GabbleDisco object to use for request
- * @type: type of request
- * @jid: Jabber ID to request on
- * @node: node to request on @jid, or NULL
- * @timeout: the time until the request fails, in milliseconds (1/1000ths of a second)
- * @callback: #GabbleDiscoCb to call on request fullfilment
- * @object: GObject to bind request to. the callback will not be
- *          called if this object has been unrefed. NULL if not needed
- * @error: #GError to return a telepathy error in if unable to make
- *         request, NULL if unneeded.
- *
- * Make a disco request on the given jid, which will fail unless a reply
- * is received within the given timeout interval.
- */
-GabbleDiscoRequest *
-gabble_disco_request_with_timeout (GabbleDisco *self, GabbleDiscoType type,
-                                   const gchar *jid, const char *node,
-                                   guint timeout, GabbleDiscoCb callback,
-                                   gpointer user_data, GObject *object,
-                                   GError **error)
-{
-  const gchar *xmlns;
-
-  LmMessage *msg;
-  LmMessageNode *lm_node;
-  GabbleDiscoRequest *request;
-  GabbleDiscoPrivate *priv = GABBLE_DISCO_GET_PRIVATE (self);
-
-
-  request = g_new0 (GabbleDiscoRequest, 1);
-  request->disco = self;
-  request->type = type;
-  request->jid = g_strdup (jid);
-  if (node)
-    request->node = g_strdup (node);
-  request->callback = callback;
-  request->user_data = user_data;
-  request->bound_object = object;
-
-  if (NULL != object)
-    g_object_weak_ref (object, notify_delete_request, request);
-
-  gabble_debug (DEBUG_FLAG, "Creating disco request %p for %s",
-           request, request->jid);
-
-  priv->requests = g_list_prepend (priv->requests, request);
-  msg = lm_message_new_with_sub_type (jid, LM_MESSAGE_TYPE_IQ,
-                                           LM_MESSAGE_SUB_TYPE_GET);
-  lm_node = lm_message_node_add_child (msg->node, "query", NULL);
-
-  switch (type) {
-    case GABBLE_DISCO_TYPE_INFO:
-      xmlns = NS_DISCO_INFO;
-      break;
-    case GABBLE_DISCO_TYPE_ITEMS:
-      xmlns = NS_DISCO_ITEMS;
-      break;
-    default:
-      g_assert_not_reached ();
-      return NULL;
-  }
-
-  lm_message_node_set_attribute (lm_node, "xmlns", xmlns);
-
-  if (node)
-    {
-      lm_message_node_set_attribute (lm_node, "node", node);
-    }
-
-  if (! _gabble_connection_send_with_reply (priv->connection, msg,
-        request_reply_cb, G_OBJECT(self), request, error))
-    {
-      delete_request (request);
-      lm_message_unref (msg);
-      return NULL;
-    }
-  else
-    {
-      request->timer_id =
-          g_timeout_add (timeout, timeout_request, request);
-      lm_message_unref (msg);
-      return request;
-    }
-}
-
-void
-gabble_disco_cancel_request (GabbleDisco *disco, GabbleDiscoRequest *request)
-{
-  GabbleDiscoPrivate *priv;
-
-  g_return_if_fail (GABBLE_IS_DISCO (disco));
-  g_return_if_fail (NULL != request);
-
-  priv = GABBLE_DISCO_GET_PRIVATE (disco);
-
-  g_return_if_fail (NULL != g_list_find (priv->requests, request));
-
-  cancel_request (request);
-}
-
-/* Disco pipeline */
-
-
-typedef struct _GabbleDiscoPipeline GabbleDiscoPipeline;
-struct _GabbleDiscoPipeline {
-    GabbleDisco *disco;
-    gpointer user_data;
-    GabbleDiscoPipelineCb callback;
-    GabbleDiscoEndCb end_callback;
-    GPtrArray *disco_pipeline;
-    GHashTable *remaining_items;
-    GabbleDiscoRequest *list_request;
-    gboolean running;
-};
-
-static void
-gabble_disco_fill_pipeline (GabbleDisco *disco, GabbleDiscoPipeline *pipeline);
-
-static void
-item_info_cb (GabbleDisco *disco,
-              GabbleDiscoRequest *request,
-              const gchar *jid,
-              const gchar *node,
-              LmMessageNode *result,
-              GError *error,
-              gpointer user_data)
-{
-  LmMessageNode *identity, *feature, *field, *value_node;
-  const char *category, *type, *var, *name, *value;
-  GHashTable *keys;
-  GabbleDiscoItem item;
-
-  GabbleDiscoPipeline *pipeline = (GabbleDiscoPipeline *) user_data;
-  GabbleDiscoPrivate *priv = GABBLE_DISCO_GET_PRIVATE (disco);
-
-  g_ptr_array_remove_fast (pipeline->disco_pipeline, request);
-
-  if (error)
-    {
-      gabble_debug (DEBUG_FLAG, "got error %s", error->message);
-      goto done;
-    }
-
-  identity = lm_message_node_get_child (result, "identity");
-  if (NULL == identity)
-    goto done;
-
-  name = lm_message_node_get_attribute (identity, "name");
-  if (NULL == name)
-    goto done;
-
-  category = lm_message_node_get_attribute (identity, "category");
-  if (NULL == category)
-    goto done;
-
-  type = lm_message_node_get_attribute (identity, "type");
-  if (NULL == type)
-    goto done;
-
-  gabble_debug (DEBUG_FLAG, "got item identity, jid=%s, name=%s, category=%s, type=%s",
-      jid, name, category, type);
-
-  keys = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
-
-  for (feature = result->children; feature; feature = feature->next)
-    {
-      if (0 == strcmp (feature->name, "feature"))
-        {
-          var = lm_message_node_get_attribute (feature, "var");
-          if (0 == strcmp (var, NS_SEARCH))
-	          {
-	          priv->connection->features |= GABBLE_CONNECTION_FEATURES_SEARCH;
-	          get_search_keys_info(priv->connection,jid);	
-	          }
-          if (var)
-            g_hash_table_insert (keys, g_strdup (var), NULL);
-        }
-      else if (0 == strcmp (feature->name, "x"))
-        {
-          if (lm_message_node_has_namespace (feature, NS_X_DATA, NULL))
-            {
-              for (field = feature->children;
-                   field; field = field->next)
-                {
-                  if (0 != strcmp (field->name, "field"))
-                    continue;
-
-                  var = lm_message_node_get_attribute (field, "var");
-                  if (NULL == var)
-                    continue;
-
-                  value_node = lm_message_node_get_child (field, "value");
-                  if (NULL == value_node)
-                    continue;
-
-                  value = lm_message_node_get_value (value_node);
-                  if (NULL == value)
-                    continue;
-
-                  g_hash_table_insert (keys, g_strdup (var), g_strdup (value));
-                }
-            }
-        }
-    }
-
-  item.jid = jid;
-  item.name = name;
-  item.category = category;
-  item.type = type;
-  item.features = keys;
-
-  pipeline->callback (pipeline, &item, pipeline->user_data);
-  g_hash_table_destroy (keys);
-
-done:
-  gabble_disco_fill_pipeline (disco, pipeline);
-
-  return;
-}
-
-
-static gboolean
-return_true (gpointer key, gpointer value, gpointer data)
-{
-  return TRUE;
-}
-
-static void
-gabble_disco_fill_pipeline (GabbleDisco *disco, GabbleDiscoPipeline *pipeline)
-{
-  if (!pipeline->running)
-    {
-      gabble_debug (DEBUG_FLAG, "pipeline not running, not refilling");
-    }
-  else
-    {
-      /* send disco requests for the JIDs in the remaining_items hash table
-       * until there are DISCO_PIPELINE_SIZE requests in progress */
-      while (pipeline->disco_pipeline->len < DISCO_PIPELINE_SIZE)
-        {
-          gchar *jid;
-          GabbleDiscoRequest *request;
-
-          jid = (gchar *) g_hash_table_find (pipeline->remaining_items,
-              return_true, NULL);
-          if (NULL == jid)
-            break;
-
-          request = gabble_disco_request (disco,
-              GABBLE_DISCO_TYPE_INFO, jid, NULL, item_info_cb, pipeline,
-              G_OBJECT(disco), NULL);
-
-          g_ptr_array_add (pipeline->disco_pipeline, request);
-
-          /* frees jid */
-          g_hash_table_remove (pipeline->remaining_items, jid);
-        }
-
-      if (0 == pipeline->disco_pipeline->len)
-        {
-          /* signal that the pipeline has finished */
-          pipeline->running = FALSE;
-          pipeline->end_callback (pipeline, pipeline->user_data);
-        }
-    }
-}
-
-
-static void
-disco_items_cb (GabbleDisco *disco,
-          GabbleDiscoRequest *request,
-          const gchar *jid,
-          const gchar *node,
-          LmMessageNode *result,
-          GError *error,
-          gpointer user_data)
-{
-  LmMessageNode *iter;
-  const char *item_jid;
-  gpointer key, value;
-  GabbleDiscoPipeline *pipeline = (GabbleDiscoPipeline *) user_data;
-
-  pipeline->list_request = NULL;
-
-  if (error)
-    {
-      gabble_debug (DEBUG_FLAG, "Got error on items request: %s", error->message);
-      goto out;
-    }
-
-  iter = result->children;
-
-  for (; iter; iter = iter->next)
-    {
-      if (0 != strcmp (iter->name, "item"))
-        continue;
-
-      item_jid = lm_message_node_get_attribute (iter, "jid");
-
-      if (NULL != item_jid &&
-          !g_hash_table_lookup_extended (pipeline->remaining_items, item_jid, &key, &value))
-        {
-          gchar *tmp = g_strdup (item_jid);
-          gabble_debug (DEBUG_FLAG, "discovered service item: %s", tmp);
-          g_hash_table_insert (pipeline->remaining_items, tmp, tmp);
-        }
-    }
-
-out:
-  gabble_disco_fill_pipeline (disco, pipeline);
-}
-
-/**
- * gabble_disco_pipeline_init:
- * @disco: disco object to use in the pipeline
- * @callback: GFunc to call on request fullfilment
- * @user_data: the usual
- *
- * Prepares the pipeline for making the ITEM request on the server and
- * subsequent INFO elements on returned items.
- *
- * GabbleDiscoPipeline is opaque structure for the user.
- */
-gpointer gabble_disco_pipeline_init (GabbleDisco *disco,
-                                     GabbleDiscoPipelineCb callback,
-                                     GabbleDiscoEndCb end_callback,
-                                     gpointer user_data)
-{
-  GabbleDiscoPipeline *pipeline = g_new (GabbleDiscoPipeline, 1);
-  pipeline->user_data = user_data;
-  pipeline->callback = callback;
-  pipeline->end_callback = end_callback;
-  pipeline->disco_pipeline = g_ptr_array_sized_new (DISCO_PIPELINE_SIZE);
-  pipeline->remaining_items = g_hash_table_new_full (g_str_hash, g_str_equal,
-      g_free, NULL);
-  pipeline->running = TRUE;
-  pipeline->disco = disco;
-
-  return pipeline;
-}
-
-/**
- * gabble_disco_pipeline_run:
- * @self: reference to the pipeline structure
- * @server: server to query
- *
- * Makes ITEMS request on the server, and afterwards queries for INFO
- * on each item. INFO queries are pipelined. The item properties are stored
- * in hash table parameter to the callback function. The user is responsible
- * for destroying the hash table after it's done with.
- *
- * Upon returning all the results, the end_callback is called with
- * reference to the pipeline.
- */
-void
-gabble_disco_pipeline_run (gpointer self, const char *server)
-{
-  GabbleDiscoPipeline *pipeline = (GabbleDiscoPipeline *) self;
-
-  pipeline->running = TRUE;
-
-  pipeline->list_request = gabble_disco_request (pipeline->disco,
-      GABBLE_DISCO_TYPE_ITEMS, server, NULL, disco_items_cb, pipeline,
-      G_OBJECT (pipeline->disco), NULL);
-}
-
-
-/**
- * gabble_disco_pipeline_cancel:
- * @pipeline: pipeline to cancel
- *
- * Flushes the pipeline (cancels all pending disco requests) and
- * destroys it.
- */
-void
-gabble_disco_pipeline_destroy (gpointer self)
-{
-  GabbleDiscoPipeline *pipeline = (GabbleDiscoPipeline *) self;
-
-  pipeline->running = FALSE;
-
-  if (pipeline->list_request != NULL)
-    {
-      gabble_disco_cancel_request (pipeline->disco, pipeline->list_request);
-      pipeline->list_request = NULL;
-    }
-
-  /* iterate using a while loop otherwise we're modifying
-   * the array as we iterate it, and miss things! */
-  while (pipeline->disco_pipeline->len > 0)
-    {
-      GabbleDiscoRequest *request =
-        g_ptr_array_index (pipeline->disco_pipeline, 0);
-      gabble_disco_cancel_request (pipeline->disco, request);
-    }
-
-  g_hash_table_destroy (pipeline->remaining_items);
-  g_ptr_array_free (pipeline->disco_pipeline, TRUE);
-  g_free (pipeline);
-}
-
-
-static void
-service_feature_copy_one (gpointer k, gpointer v, gpointer user_data)
-{
-  char *key = (char *) k;
-  char *value = (char *) v;
-
-  GHashTable *target = (GHashTable *) user_data;
-  g_hash_table_insert (target, g_strdup (key), g_strdup (value));
-}
-
-/* Service discovery */
-static void
-services_cb (gpointer pipeline, GabbleDiscoItem *item, gpointer user_data)
-{
-  GabbleDiscoItem *my_item = g_new0 (GabbleDiscoItem, 1);
-
-  GabbleDisco *disco = GABBLE_DISCO (user_data);
-  GabbleDiscoPrivate *priv = GABBLE_DISCO_GET_PRIVATE (disco);
-
-  my_item->jid = g_strdup (item->jid);
-  my_item->name = g_strdup (item->name);
-  my_item->type = g_strdup (item->type);
-  my_item->category = g_strdup (item->category);
-
-  my_item->features = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
-  g_hash_table_foreach  (item->features, service_feature_copy_one, my_item->features);
-
-  priv->service_cache = g_slist_prepend (priv->service_cache, my_item);
-}
-
-static void
-end_cb (gpointer pipeline, gpointer user_data)
-{
-  GabbleDisco *disco = GABBLE_DISCO (user_data);
-  GabbleDiscoPrivate *priv = GABBLE_DISCO_GET_PRIVATE (disco);
-
-  gabble_disco_pipeline_destroy (pipeline);
-  priv->service_cache = g_slist_reverse (priv->service_cache);
-
-  /* FIXME - service discovery done - signal that somehow */
-}
-
-static void
-gabble_disco_conn_status_changed_cb (GabbleConnection *conn,
-                                     TpConnectionStatus status,
-                                     TpConnectionStatusReason reason,
-                                     gpointer data)
-{
-  GabbleDisco *disco = GABBLE_DISCO (data);
-  GabbleDiscoPrivate *priv = GABBLE_DISCO_GET_PRIVATE (disco);
-
-  if (status == TP_CONN_STATUS_CONNECTED)
-    {
-      char *server;
-      gpointer pipeline = gabble_disco_pipeline_init (disco, services_cb,
-          end_cb, disco);
-
-      g_object_get (priv->connection, "stream-server", &server, NULL);
-
-      g_assert (server != NULL);
-
-      gabble_debug (DEBUG_FLAG, "connected, initiating service discovery on %s", server);
-      gabble_disco_pipeline_run (pipeline, server);
-
-      g_free (server);
-    }
-}
-
-const GabbleDiscoItem *
-gabble_disco_service_find (GabbleDisco *disco,
-                           const char *type,
-                           const char *category,
-                           const char *feature)
-{
-  GabbleDiscoPrivate *priv;
-  GSList *l;
-
-  g_assert (GABBLE_IS_DISCO (disco));
-  priv = GABBLE_DISCO_GET_PRIVATE (disco);
-
-  for (l = priv->service_cache; l; l = g_slist_next (l))
-    {
-      GabbleDiscoItem *item = (GabbleDiscoItem *) l->data;
-      gboolean selected = TRUE;
-
-      if (type != NULL && g_strdiff (type, item->type))
-        selected = FALSE;
-
-      if (category != NULL && g_strdiff (category, item->category))
-        selected = FALSE;
-
-      if (feature != NULL)
-        {
-          gpointer k, v;
-          if (!g_hash_table_lookup_extended (item->features, feature, &k, &v))
-            selected = FALSE;
-        }
-
-      if (selected)
-        return item;
-    }
-
-  return NULL;
-}