[Telepathy-commits] [telepathy-salut/master] Caps: Add SalutPresenceCache and SalutDisco

Alban Crequy alban.crequy at collabora.co.uk
Thu Feb 26 11:20:09 PST 2009


---
 lib/gibber/gibber-namespaces.h |    6 +
 src/Makefile.am                |    2 +
 src/salut-avahi-contact.c      |   11 +
 src/salut-connection.c         |   32 ++
 src/salut-connection.h         |    7 +
 src/salut-contact.c            |   19 ++
 src/salut-contact.h            |   12 +
 src/salut-disco.c              |  500 ++++++++++++++++++++++++++++++++
 src/salut-disco.h              |  102 +++++++
 src/salut-presence-cache.c     |  625 +++++++++++++++++++++++++++++++++++++++-
 src/salut-presence-cache.h     |   49 ++++
 11 files changed, 1359 insertions(+), 6 deletions(-)
 create mode 100644 src/salut-disco.c
 create mode 100644 src/salut-disco.h

diff --git a/lib/gibber/gibber-namespaces.h b/lib/gibber/gibber-namespaces.h
index 6186cc9..cfa660e 100644
--- a/lib/gibber/gibber-namespaces.h
+++ b/lib/gibber/gibber-namespaces.h
@@ -11,6 +11,12 @@
 #define GIBBER_XMPP_NS_SASL_AUTH \
   (const gchar *)"urn:ietf:params:xml:ns:xmpp-sasl"
 
+#define NS_DISCO_INFO \
+  (const gchar *)"http://jabber.org/protocol/disco#info"
+
+#define NS_DISCO_ITEMS \
+  (const gchar *)"http://jabber.org/protocol/disco#items"
+
 #define GIBBER_XMPP_NS_XHTML_IM \
   (const gchar *)"http://jabber.org/protocol/xhtml-im"
 
diff --git a/src/Makefile.am b/src/Makefile.am
index 26747ab..909dd80 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -27,6 +27,8 @@ CORE_SOURCES =                                          \
     salut-connection-manager.h                          \
     salut-contact-manager.c                             \
     salut-contact-manager.h                             \
+    salut-disco.c                                       \
+    salut-disco.h                                       \
     salut-im-manager.c                                  \
     salut-im-manager.h                                  \
     salut-im-channel.c                                  \
diff --git a/src/salut-avahi-contact.c b/src/salut-avahi-contact.c
index 0aba7bf..2595c5f 100644
--- a/src/salut-avahi-contact.c
+++ b/src/salut-avahi-contact.c
@@ -606,6 +606,8 @@ contact_resolved_cb (GaServiceResolver *resolver,
   SalutContact *contact = SALUT_CONTACT (self);
   char *s;
   char *nick, *first, *last;
+  /* node, hash and ver as defined by XEP-0115 */
+  char *node, *hash, *ver;
 #ifdef ENABLE_OLPC
   char *activity_id, *room_id;
   char *olpc_key_part;
@@ -654,6 +656,15 @@ contact_resolved_cb (GaServiceResolver *resolver,
   avahi_free (first);
   avahi_free (last);
 
+  /* capabilities */
+  hash = _avahi_txt_get_keyval (txt, "hash");
+  node = _avahi_txt_get_keyval (txt, "node");
+  ver = _avahi_txt_get_keyval (txt, "ver");
+  salut_contact_change_capabilities (contact, hash, node, ver);
+  avahi_free (node);
+  avahi_free (hash);
+  avahi_free (ver);
+
   /* avatar token */
   s = _avahi_txt_get_keyval (txt, "phsh");
   salut_contact_change_avatar_token (contact, s);
diff --git a/src/salut-connection.c b/src/salut-connection.c
index 492bd33..1be7fb3 100644
--- a/src/salut-connection.c
+++ b/src/salut-connection.c
@@ -49,6 +49,7 @@
 #include "salut-contact.h"
 #include "salut-contact-manager.h"
 #include "salut-direct-bytestream-manager.h"
+#include "salut-disco.h"
 #include "salut-discovery-client.h"
 #include "salut-im-manager.h"
 #include "salut-muc-manager.h"
@@ -258,6 +259,10 @@ static void salut_connection_avatars_fill_contact_attributes (GObject *obj,
 static void salut_connection_aliasing_fill_contact_attributes (GObject *obj,
     const GArray *contacts, GHashTable *attributes_hash);
 
+static void connection_capabilities_update_cb (SalutPresenceCache *cache,
+    TpHandle handle, GHashTable *old_enhanced_caps,
+    GHashTable *new_enhanced_caps, gpointer user_data);
+
 static void
 salut_connection_init (SalutConnection *obj)
 {
@@ -296,9 +301,16 @@ salut_connection_constructor (GType type,
                               GObjectConstructParam *props)
 {
   GObject *obj;
+  SalutConnection *self;
 
   obj = G_OBJECT_CLASS (salut_connection_parent_class)->
            constructor (type, n_props, props);
+  self = SALUT_CONNECTION (obj);
+
+  self->disco = salut_disco_new (self);
+  self->presence_cache = salut_presence_cache_new (self);
+  g_signal_connect (self->presence_cache, "capabilities-update", G_CALLBACK
+      (connection_capabilities_update_cb), self);
 
   tp_contacts_mixin_init (obj,
       G_STRUCT_OFFSET (SalutConnection, contacts_mixin));
@@ -803,6 +815,12 @@ salut_connection_dispose (GObject *object)
 
   priv->dispose_has_run = TRUE;
 
+  g_object_unref (self->disco);
+  self->disco = NULL;
+
+  g_object_unref (self->presence_cache);
+  self->presence_cache = NULL;
+
   if (priv->self) {
     g_object_unref (priv->self);
     priv->self = NULL;
@@ -1787,6 +1805,20 @@ _emit_contact_capabilities_changed (SalutConnection *conn,
   salut_free_enhanced_contact_capabilities (ret);
 }
 
+static void
+connection_capabilities_update_cb (SalutPresenceCache *cache,
+                                   TpHandle handle,
+                                   GHashTable *old_enhanced_caps,
+                                   GHashTable *new_enhanced_caps,
+                                   gpointer user_data)
+{
+  SalutConnection *conn = SALUT_CONNECTION (user_data);
+
+  if (old_enhanced_caps != NULL || new_enhanced_caps != NULL)
+    _emit_contact_capabilities_changed (conn, handle,
+        old_enhanced_caps, new_enhanced_caps);
+}
+
 /**
  * salut_connection_set_self_capabilities
  *
diff --git a/src/salut-connection.h b/src/salut-connection.h
index f581ea4..e088e75 100644
--- a/src/salut-connection.h
+++ b/src/salut-connection.h
@@ -35,8 +35,12 @@
 
 #include <gibber/gibber-xmpp-stanza.h>
 
+
 G_BEGIN_DECLS
 
+typedef struct _SalutPresenceCache SalutPresenceCache;
+typedef struct _SalutDisco SalutDisco;
+
 typedef struct _SalutConnection SalutConnection;
 typedef struct _SalutConnectionClass SalutConnectionClass;
 
@@ -52,6 +56,9 @@ struct _SalutConnection {
   TpPresenceMixin presence_mixin;
   TpContactsMixin contacts_mixin;
 
+  SalutPresenceCache *presence_cache;
+  SalutDisco *disco;
+
   /* Our name on the network */
   gchar *name;
 
diff --git a/src/salut-contact.c b/src/salut-contact.c
index 882f896..5cb386a 100644
--- a/src/salut-contact.c
+++ b/src/salut-contact.c
@@ -24,6 +24,7 @@
 #include "salut-contact.h"
 #include "salut-signals-marshal.h"
 #include "salut-presence.h"
+#include "salut-presence-cache.h"
 #include "salut-presence-enumtypes.h"
 
 #include <telepathy-glib/util.h>
@@ -446,6 +447,15 @@ salut_contact_get_avatar (SalutContact *contact,
     SALUT_CONTACT_GET_CLASS (contact)->retrieve_avatar (contact);
 }
 
+void
+salut_contact_set_capabilities (SalutContact *contact,
+                                GHashTable *per_channel_manager_caps)
+{
+  salut_presence_cache_free_cache_entry (contact->per_channel_manager_caps);
+  salut_presence_cache_copy_cache_entry (&contact->per_channel_manager_caps,
+      per_channel_manager_caps);
+}
+
 static void
 salut_contact_change (SalutContact *self, guint changes)
 {
@@ -521,6 +531,15 @@ salut_contact_change_jid (SalutContact *self, gchar *jid)
     }
 }
 
+void salut_contact_change_capabilities (SalutContact *self,
+                                        const gchar *hash,
+                                        const gchar *node,
+                                        const gchar *ver)
+{
+  salut_presence_cache_process_caps (self->connection->presence_cache, self,
+      hash, node, ver);
+}
+
 #ifdef ENABLE_OLPC
 void
 salut_contact_change_olpc_color (SalutContact *self, const gchar *olpc_color)
diff --git a/src/salut-contact.h b/src/salut-contact.h
index 13ac1df..82f84b3 100644
--- a/src/salut-contact.h
+++ b/src/salut-contact.h
@@ -66,6 +66,13 @@ struct _SalutContact {
     gchar *avatar_token;
     gchar *status_message;
     gchar *jid;
+
+    /* XEP-0115 Capabilities */
+    gchar *hash;
+    gchar *node;
+    gchar *ver;
+    GHashTable *per_channel_manager_caps;
+
     TpHandle handle;
 #ifdef ENABLE_OLPC
     GArray *olpc_key;
@@ -117,6 +124,9 @@ void salut_contact_get_avatar (SalutContact *contact,
                                salut_contact_get_avatar_callback callback,
                                gpointer user_data1);
 
+void salut_contact_set_capabilities (SalutContact *contact,
+    GHashTable *per_channel_manager_caps);
+
 #ifdef ENABLE_OLPC
 typedef void (*SalutContactOLPCActivityFunc)
     (SalutOlpcActivity *activity, gpointer user_data);
@@ -139,6 +149,8 @@ void salut_contact_change_status_message (SalutContact *self,
 void salut_contact_change_avatar_token (SalutContact *self,
   const gchar *avatar_token);
 void salut_contact_change_jid (SalutContact *self, gchar *jid);
+void salut_contact_change_capabilities (SalutContact *self,
+  const gchar *hash, const gchar *node, const gchar *ver);
 
 #ifdef ENABLE_OLPC
 void salut_contact_change_olpc_color (SalutContact *self,
diff --git a/src/salut-disco.c b/src/salut-disco.c
new file mode 100644
index 0000000..ec3d7f6
--- /dev/null
+++ b/src/salut-disco.c
@@ -0,0 +1,500 @@
+/*
+ * disco.c - Source for Salut service discovery
+ *
+ * Copyright (C) 2006-2008 Collabora Ltd.
+ * Copyright (C) 2006-2008 Nokia Corporation
+ *
+ * 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
+ *
+ * -- LET'S DISCO!!!  \o/ \o_ _o/ /\o/\ _/o/- -\o\_ --
+ */
+
+#include "config.h"
+#include "salut-disco.h"
+
+#include <string.h>
+
+#define DBUS_API_SUBJECT_TO_CHANGE
+
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-lowlevel.h>
+#include <telepathy-glib/dbus.h>
+#include <gibber/gibber-namespaces.h>
+
+#define DEBUG_FLAG DEBUG_DISCO
+
+#include "debug.h"
+#include "salut-connection.h"
+#include "signals-marshal.h"
+
+#define DEFAULT_REQUEST_TIMEOUT 20000
+
+/* signals */
+enum
+{
+  ITEM_FOUND,
+  LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+/* Properties */
+enum
+{
+  PROP_CONNECTION = 1,
+  LAST_PROPERTY
+};
+
+G_DEFINE_TYPE(SalutDisco, salut_disco, G_TYPE_OBJECT);
+
+struct _SalutDiscoPrivate
+{
+  SalutConnection *connection;
+  GList *requests;
+  gboolean dispose_has_run;
+};
+
+struct _SalutDiscoRequest
+{
+  SalutDisco *disco;
+  guint timer_id;
+
+  SalutDiscoType type;
+  SalutContact *contact;
+  gchar *node;
+  SalutDiscoCb callback;
+  gpointer user_data;
+  GObject *bound_object;
+};
+
+GQuark
+salut_disco_error_quark (void)
+{
+  static GQuark quark = 0;
+  if (!quark)
+    quark = g_quark_from_static_string ("salut-disco-error");
+  return quark;
+}
+
+#define SALUT_DISCO_GET_PRIVATE(o) ((o)->priv)
+
+static void
+salut_disco_init (SalutDisco *obj)
+{
+  SalutDiscoPrivate *priv =
+     G_TYPE_INSTANCE_GET_PRIVATE (obj, SALUT_TYPE_DISCO, SalutDiscoPrivate);
+  obj->priv = priv;
+}
+
+static GObject *salut_disco_constructor (GType type, guint n_props,
+    GObjectConstructParam *props);
+static void salut_disco_set_property (GObject *object, guint property_id,
+    const GValue *value, GParamSpec *pspec);
+static void salut_disco_get_property (GObject *object, guint property_id,
+    GValue *value, GParamSpec *pspec);
+static void salut_disco_dispose (GObject *object);
+static void salut_disco_finalize (GObject *object);
+
+static void
+salut_disco_class_init (SalutDiscoClass *salut_disco_class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (salut_disco_class);
+  GParamSpec *param_spec;
+
+  g_type_class_add_private (salut_disco_class, sizeof (SalutDiscoPrivate));
+
+  object_class->constructor = salut_disco_constructor;
+
+  object_class->get_property = salut_disco_get_property;
+  object_class->set_property = salut_disco_set_property;
+
+  object_class->dispose = salut_disco_dispose;
+  object_class->finalize = salut_disco_finalize;
+
+  param_spec = g_param_spec_object ("connection", "SalutConnection object",
+                                    "Salut connection object that owns this "
+                                    "XMPP Discovery object.",
+                                    SALUT_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);
+
+  signals[ITEM_FOUND] =
+    g_signal_new ("item-found",
+                  G_OBJECT_CLASS_TYPE (salut_disco_class),
+                  G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+                  0,
+                  NULL, NULL,
+                  salut_signals_marshal_VOID__POINTER,
+                  G_TYPE_NONE, 1, G_TYPE_POINTER);
+}
+
+static void
+salut_disco_get_property (GObject    *object,
+                                guint       property_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+  SalutDisco *chan = SALUT_DISCO (object);
+  SalutDiscoPrivate *priv = SALUT_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
+salut_disco_set_property (GObject     *object,
+                           guint        property_id,
+                           const GValue *value,
+                           GParamSpec   *pspec)
+{
+  SalutDisco *chan = SALUT_DISCO (object);
+  SalutDiscoPrivate *priv = SALUT_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 GObject *
+salut_disco_constructor (GType type, guint n_props,
+                          GObjectConstructParam *props)
+{
+  GObject *obj;
+  SalutDisco *disco;
+  SalutDiscoPrivate *priv;
+
+  obj = G_OBJECT_CLASS (salut_disco_parent_class)-> constructor (type,
+      n_props, props);
+  disco = SALUT_DISCO (obj);
+  priv = SALUT_DISCO_GET_PRIVATE (disco);
+
+  return obj;
+}
+
+static void cancel_request (SalutDiscoRequest *request);
+
+static void
+salut_disco_dispose (GObject *object)
+{
+  SalutDisco *self = SALUT_DISCO (object);
+  SalutDiscoPrivate *priv = SALUT_DISCO_GET_PRIVATE (self);
+
+  if (priv->dispose_has_run)
+    return;
+
+  priv->dispose_has_run = TRUE;
+
+  DEBUG ("dispose called");
+
+  /* cancel request removes the element from the list after cancelling */
+  while (priv->requests)
+    cancel_request (priv->requests->data);
+
+  if (G_OBJECT_CLASS (salut_disco_parent_class)->dispose)
+    G_OBJECT_CLASS (salut_disco_parent_class)->dispose (object);
+}
+
+static void
+salut_disco_finalize (GObject *object)
+{
+  DEBUG ("called with %p", object);
+
+  G_OBJECT_CLASS (salut_disco_parent_class)->finalize (object);
+}
+
+/**
+ * salut_disco_new:
+ * @conn: The #SalutConnection to use for service discovery
+ *
+ * Creates an object to use for Jabber service discovery (DISCO)
+ * There should be one of these per connection
+ */
+SalutDisco *
+salut_disco_new (SalutConnection *conn)
+{
+  SalutDisco *disco;
+
+  g_return_val_if_fail (SALUT_IS_CONNECTION (conn), NULL);
+
+  disco = SALUT_DISCO (g_object_new (SALUT_TYPE_DISCO,
+        "connection", conn,
+        NULL));
+
+  return disco;
+}
+
+
+static void notify_delete_request (gpointer data, GObject *obj);
+
+static void
+delete_request (SalutDiscoRequest *request)
+{
+  SalutDisco *disco = request->disco;
+  SalutDiscoPrivate *priv;
+
+  g_assert (NULL != request);
+  g_assert (SALUT_IS_DISCO (disco));
+
+  priv = SALUT_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_object_unref (request->contact);
+  g_free (request->node);
+  g_slice_free (SalutDiscoRequest, request);
+}
+
+//static gboolean
+//timeout_request (gpointer data)
+//{
+//  SalutDiscoRequest *request = (SalutDiscoRequest *) data;
+//  SalutDisco *disco;
+//  GError *err /* doesn't need initializing */;
+//  g_return_val_if_fail (data != NULL, FALSE);
+//
+//  err = g_error_new (SALUT_DISCO_ERROR, SALUT_DISCO_ERROR_TIMEOUT,
+//      "Request for %s on %s timed out",
+//      (request->type == SALUT_DISCO_TYPE_INFO)?"info":"items",
+//      request->jid);
+//
+//  /* Temporarily ref the disco object to avoid crashing if the callback
+//   * destroys us (as seen in test-disco-no-reply.py) */
+//  disco = g_object_ref (request->disco);
+//
+//  /* also, we're about to run the callback, so it's too late to cancel it -
+//   * avoid crashing if running the callback destroys the bound object */
+//  if (NULL != request->bound_object)
+//    {
+//      g_object_weak_unref (request->bound_object, notify_delete_request,
+//          request);
+//      request->bound_object = NULL;
+//    }
+//
+//  (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);
+//
+//  g_object_unref (disco);
+//
+//  return FALSE;
+//}
+
+static void
+cancel_request (SalutDiscoRequest *request)
+{
+  GError *err /* doesn't need initializing */;
+
+  g_assert (request != NULL);
+
+  err = g_error_new (SALUT_DISCO_ERROR, SALUT_DISCO_ERROR_CANCELLED,
+      "Request for %s on %s cancelled",
+      (request->type == SALUT_DISCO_TYPE_INFO)?"info":"items",
+      request->contact->name);
+  (request->callback)(request->disco, request, request->contact, request->node,
+                      NULL, err, request->user_data);
+  g_error_free (err);
+
+  delete_request (request);
+}
+
+/*
+static const char *
+disco_type_to_xmlns (SalutDiscoType type)
+{
+  switch (type) {
+    case SALUT_DISCO_TYPE_INFO:
+      return NS_DISCO_INFO;
+    case SALUT_DISCO_TYPE_ITEMS:
+      return NS_DISCO_ITEMS;
+    default:
+      g_assert_not_reached ();
+  }
+
+  return NULL;
+}
+
+static LmHandlerResult
+request_reply_cb (SalutConnection *conn, LmMessage *sent_msg,
+                  LmMessage *reply_msg, GObject *object, gpointer user_data)
+{
+  SalutDiscoRequest *request = (SalutDiscoRequest *) user_data;
+  SalutDisco *disco = SALUT_DISCO (object);
+  SalutDiscoPrivate *priv = SALUT_DISCO_GET_PRIVATE (disco);
+  LmMessageNode *query_node;
+  GError *err = NULL;
+
+  g_assert (request);
+
+  if (!g_list_find (priv->requests, request))
+    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+
+  query_node = lm_message_node_get_child_with_namespace (reply_msg->node,
+      "query", disco_type_to_xmlns (request->type));
+
+  if (lm_message_get_sub_type (reply_msg) == LM_MESSAGE_SUB_TYPE_ERROR)
+    {
+      err = salut_message_get_xmpp_error (reply_msg);
+
+      if (err == NULL)
+        {
+          err = g_error_new (SALUT_DISCO_ERROR,
+                             SALUT_DISCO_ERROR_UNKNOWN,
+                             "an unknown error occurred");
+        }
+    }
+  else if (NULL == query_node)
+    {
+      err = g_error_new (SALUT_DISCO_ERROR, SALUT_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)
+{
+  SalutDiscoRequest *request = (SalutDiscoRequest *) data;
+  request->bound_object = NULL;
+  delete_request (request);
+}
+
+/**
+ * salut_disco_request:
+ * @self: #SalutDisco object to use for request
+ * @type: type of request
+ * @jid: Jabber ID to request on
+ * @node: node to request on @jid, or NULL
+ * @callback: #SalutDiscoCb 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.
+ */
+SalutDiscoRequest *
+salut_disco_request (SalutDisco *self, SalutDiscoType type,
+                     SalutContact *contact, const char *node,
+                     SalutDiscoCb callback,
+                     gpointer user_data, GObject *object,
+                     GError **error)
+{
+  SalutDiscoPrivate *priv = SALUT_DISCO_GET_PRIVATE (self);
+  SalutDiscoRequest *request;
+  //LmMessage *msg;
+  //LmMessageNode *lm_node;
+
+  request = g_slice_new0 (SalutDiscoRequest);
+  request->disco = self;
+  request->type = type;
+  request->contact = g_object_ref (contact);
+  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);
+
+  DEBUG ("Creating disco request %p for %s",
+           request, request->contact->name);
+
+  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);
+
+  lm_message_node_set_attribute (lm_node, "xmlns", disco_type_to_xmlns (type));
+
+  if (node)
+    {
+      lm_message_node_set_attribute (lm_node, "node", node);
+    }
+
+  if (! _salut_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 (DEFAULT_REQUEST_TIMEOUT, timeout_request, request);
+      lm_message_unref (msg);
+      return request;
+    }
+*/
+/**/ return NULL;
+}
+
+void
+salut_disco_cancel_request (SalutDisco *disco, SalutDiscoRequest *request)
+{
+  SalutDiscoPrivate *priv;
+
+  g_return_if_fail (SALUT_IS_DISCO (disco));
+  g_return_if_fail (NULL != request);
+
+  priv = SALUT_DISCO_GET_PRIVATE (disco);
+
+  g_return_if_fail (NULL != g_list_find (priv->requests, request));
+
+  cancel_request (request);
+}
+
diff --git a/src/salut-disco.h b/src/salut-disco.h
new file mode 100644
index 0000000..0c22272
--- /dev/null
+++ b/src/salut-disco.h
@@ -0,0 +1,102 @@
+/*
+ * disco.h - Headers for Salut service discovery
+ *
+ * Copyright (C) 2006-2008 Collabora Ltd.
+ * Copyright (C) 2006-2008 Nokia Corporation
+ *
+ * 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
+ *
+ * -- LET'S DISCO!!!  \o/ \o_ _o/ /\o/\ _/o/- -\o\_ --
+ */
+
+#ifndef __SALUT_DISCO_H__
+#define __SALUT_DISCO_H__
+
+#include <glib-object.h>
+#include <gibber/gibber-xmpp-stanza.h>
+
+#include "salut-contact.h"
+#include "salut-connection.h"
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+  SALUT_DISCO_TYPE_INFO,
+  SALUT_DISCO_TYPE_ITEMS
+} SalutDiscoType;
+
+typedef struct _SalutDiscoClass SalutDiscoClass;
+typedef struct _SalutDiscoPrivate SalutDiscoPrivate;
+typedef struct _SalutDiscoRequest SalutDiscoRequest;
+
+/**
+ * SalutDiscoError:
+ * @SALUT_DISCO_ERROR_CANCELLED: The DISCO request was cancelled
+ * @SALUT_DISCO_ERROR_TIMEOUT: The DISCO request timed out
+ * @SALUT_DISCO_ERROR_UNKNOWN: An unknown error occured
+ */
+typedef enum
+{
+  SALUT_DISCO_ERROR_CANCELLED,
+  SALUT_DISCO_ERROR_TIMEOUT,
+  SALUT_DISCO_ERROR_UNKNOWN
+} SalutDiscoError;
+
+GQuark salut_disco_error_quark (void);
+#define SALUT_DISCO_ERROR salut_disco_error_quark ()
+
+GType salut_disco_get_type (void);
+
+/* TYPE MACROS */
+#define SALUT_TYPE_DISCO \
+  (salut_disco_get_type ())
+#define SALUT_DISCO(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj), SALUT_TYPE_DISCO, SalutDisco))
+#define SALUT_DISCO_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass), SALUT_TYPE_DISCO, SalutDiscoClass))
+#define SALUT_IS_DISCO(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj), SALUT_TYPE_DISCO))
+#define SALUT_IS_DISCO_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass), SALUT_TYPE_DISCO))
+#define SALUT_DISCO_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), SALUT_TYPE_DISCO, SalutDiscoClass))
+
+struct _SalutDiscoClass {
+    GObjectClass parent_class;
+};
+
+struct _SalutDisco {
+    GObject parent;
+    SalutDiscoPrivate *priv;
+};
+
+typedef void (*SalutDiscoCb)(SalutDisco *self, SalutDiscoRequest *request,
+    SalutContact *contact, const gchar *node, GibberXmppStanza *query_result,
+    GError* error, gpointer user_data);
+
+SalutDisco *salut_disco_new (SalutConnection *);
+
+SalutDiscoRequest *salut_disco_request (SalutDisco *self,
+    SalutDiscoType type, SalutContact *contact, const char *node,
+    SalutDiscoCb callback, gpointer user_data, GObject *object,
+    GError **error);
+
+void salut_disco_cancel_request (SalutDisco *, SalutDiscoRequest *);
+
+
+G_END_DECLS
+
+#endif
diff --git a/src/salut-presence-cache.c b/src/salut-presence-cache.c
index c00b269..cf87b32 100644
--- a/src/salut-presence-cache.c
+++ b/src/salut-presence-cache.c
@@ -1,7 +1,7 @@
 /*
  * salut-presence-cache.c - Salut's contact presence cache
- * Copyright (C) 2005 Collabora Ltd.
- * Copyright (C) 2005 Nokia Corporation
+ * Copyright (C) 2005-2008 Collabora Ltd.
+ * Copyright (C) 2005-2008 Nokia Corporation
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -25,17 +25,630 @@
 #include <string.h>
 #include <glib.h>
 
-#define DEBUG_FLAG SALUT_DEBUG_PRESENCE
-
 #include <gibber/gibber-namespaces.h>
 #include <telepathy-glib/channel-manager.h>
 #include <telepathy-glib/intset.h>
 
-#define DEBUG_FLAG SALUT_DEBUG_PRESENCE
+#define DEBUG_FLAG DEBUG_PRESENCE
 
+#include "debug.h"
 #include "salut-caps-channel-manager.h"
 #include "salut-caps-hash.h"
-#include "debug.h"
+#include "salut-disco.h"
+#include "signals-marshal.h"
+
+G_DEFINE_TYPE (SalutPresenceCache, salut_presence_cache, G_TYPE_OBJECT);
+
+/* properties */
+enum
+{
+  PROP_CONNECTION = 1,
+  LAST_PROPERTY
+};
+
+/* signal enum */
+enum
+{
+  CAPABILITIES_UPDATE,
+  LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+#define SALUT_PRESENCE_CACHE_PRIV(account) ((account)->priv)
+
+struct _SalutPresenceCachePrivate
+{
+  SalutConnection *conn;
+
+  /* gchar *uri -> CapabilityInfo */
+  GHashTable *capabilities;
+
+  /* gchar *uri -> GSList* of DiscoWaiter* */
+  GHashTable *disco_pending;
+
+  guint caps_serial;
+
+  gboolean dispose_has_run;
+};
+
+typedef struct _DiscoWaiter DiscoWaiter;
+
+struct _DiscoWaiter
+{
+  SalutContact *contact;
+  gchar *hash;
+  gchar *ver;
+  /* if a discovery request fails, we will ask another contact */
+  gboolean disco_requested;
+};
+
+static DiscoWaiter *
+disco_waiter_new (SalutContact *contact,
+                  const gchar *hash,
+                  const gchar *ver)
+{
+  DiscoWaiter *waiter;
+
+  g_object_ref (contact);
+
+  waiter = g_slice_new0 (DiscoWaiter);
+  waiter->contact = contact;
+  waiter->hash = g_strdup (hash);
+  waiter->ver = g_strdup (ver);
+
+  DEBUG ("created waiter %p for contact %s", waiter, contact->name);
+
+  return waiter;
+}
+
+static void
+disco_waiter_free (DiscoWaiter *waiter)
+{
+  g_assert (NULL != waiter);
+
+  DEBUG ("freeing waiter %p for contact %s", waiter,
+      waiter->contact->name);
+
+  g_object_unref (waiter->contact);
+  g_free (waiter->hash);
+  g_free (waiter->ver);
+  g_slice_free (DiscoWaiter, waiter);
+}
+
+static void
+disco_waiter_list_free (GSList *list)
+{
+  GSList *i;
+
+  DEBUG ("list %p", list);
+
+  for (i = list; NULL != i; i = i->next)
+    disco_waiter_free ((DiscoWaiter *) i->data);
+
+  g_slist_free (list);
+}
+
+typedef struct _CapabilityInfo CapabilityInfo;
+
+struct _CapabilityInfo
+{
+  /* struct _CapabilityInfo can be allocated before receiving the contact's
+   * caps. In this case, caps_set is FALSE and set to TRUE when the caps are
+   * received */
+  gboolean caps_set;
+
+  /* key: SalutCapsChannelFactory -> value: gpointer
+   *
+   * The type of the value depends on the SalutCapsChannelFactory. It is an
+   * opaque pointer used by the channel manager to store the capabilities.
+   * Some channel manager do not need to store anything, in this case the
+   * value can just be NULL.
+   *
+   * Since the type of the value is not public, the value is allocated, copied
+   * and freed by helper functions on the SalutCapsChannelManager interface.
+   *
+   * For example:
+   *   * SalutPrivateTubesFactory -> TubesCapabilities
+   *
+   * At the moment, only SalutPrivateTubesFactory use this mechanism to store
+   * the list of supported tube types (example: stream tube for daap).
+   */
+  GHashTable *per_channel_manager_caps;
+
+  /* SalutContact -> NULL */
+  GHashTable *guys;
+};
+
+static CapabilityInfo *
+capability_info_get (SalutPresenceCache *cache, const gchar *uri)
+{
+  SalutPresenceCachePrivate *priv = SALUT_PRESENCE_CACHE_PRIV (cache);
+  CapabilityInfo *info = g_hash_table_lookup (priv->capabilities, uri);
+
+  if (NULL == info)
+    {
+      info = g_slice_new0 (CapabilityInfo);
+      info->caps_set = FALSE;
+      info->guys = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+          g_object_unref, NULL);
+      g_hash_table_insert (priv->capabilities, g_strdup (uri), info);
+    }
+
+  return info;
+}
+
+static void
+capability_info_free (CapabilityInfo *info)
+{
+  g_hash_table_destroy (info->guys);
+  g_slice_free (CapabilityInfo, info);
+}
+
+static void
+capability_info_recvd (SalutPresenceCache *cache, const gchar *node,
+        SalutContact *contact, GHashTable *per_channel_manager_caps)
+{
+  CapabilityInfo *info = capability_info_get (cache, node);
+  gpointer dummy_key, dummy_value;
+
+  if (! info->caps_set)
+    {
+      /* The caps are not valid because this is the first caps report and the
+       * caps were never set.
+       */
+      info->per_channel_manager_caps = per_channel_manager_caps;
+      info->caps_set = TRUE;
+    }
+
+  if (!g_hash_table_lookup_extended (info->guys, contact, &dummy_key,
+        &dummy_value))
+    {
+      g_hash_table_insert (info->guys, contact, NULL);
+    }
+}
+
+static void salut_presence_cache_init (SalutPresenceCache *presence_cache);
+static GObject * salut_presence_cache_constructor (GType type, guint n_props,
+    GObjectConstructParam *props);
+static void salut_presence_cache_dispose (GObject *object);
+static void salut_presence_cache_finalize (GObject *object);
+static void salut_presence_cache_set_property (GObject *object, guint
+    property_id, const GValue *value, GParamSpec *pspec);
+static void salut_presence_cache_get_property (GObject *object, guint
+    property_id, GValue *value, GParamSpec *pspec);
+
+static void
+salut_presence_cache_class_init (SalutPresenceCacheClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GParamSpec *param_spec;
+
+  g_type_class_add_private (object_class, sizeof (SalutPresenceCachePrivate));
+
+  object_class->constructor = salut_presence_cache_constructor;
+
+  object_class->dispose = salut_presence_cache_dispose;
+  object_class->finalize = salut_presence_cache_finalize;
+
+  object_class->get_property = salut_presence_cache_get_property;
+  object_class->set_property = salut_presence_cache_set_property;
+
+  param_spec = g_param_spec_object ("connection", "SalutConnection object",
+                                    "Salut connection object that owns this "
+                                    "presence cache.",
+                                    SALUT_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);
+
+  signals[CAPABILITIES_UPDATE] = g_signal_new (
+    "capabilities-update",
+    G_TYPE_FROM_CLASS (klass),
+    G_SIGNAL_RUN_LAST,
+    0,
+    NULL, NULL,
+    salut_signals_marshal_VOID__UINT_UINT_UINT_POINTER_POINTER, G_TYPE_NONE,
+    5, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_POINTER, G_TYPE_POINTER);
+}
+
+static void
+salut_presence_cache_init (SalutPresenceCache *cache)
+{
+  SalutPresenceCachePrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (cache,
+      SALUT_TYPE_PRESENCE_CACHE, SalutPresenceCachePrivate);
+
+  cache->priv = priv;
+
+  priv->capabilities = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
+      (GDestroyNotify) capability_info_free);
+  priv->disco_pending = g_hash_table_new_full (g_str_hash, g_str_equal,
+    g_free, (GDestroyNotify) disco_waiter_list_free);
+  priv->caps_serial = 1;
+}
+
+static GObject *
+salut_presence_cache_constructor (GType type, guint n_props,
+                                   GObjectConstructParam *props)
+{
+  GObject *obj;
+  SalutPresenceCachePrivate *priv;
+
+  obj = G_OBJECT_CLASS (salut_presence_cache_parent_class)->
+           constructor (type, n_props, props);
+  priv = SALUT_PRESENCE_CACHE_PRIV (SALUT_PRESENCE_CACHE (obj));
+
+  return obj;
+}
+
+static void
+salut_presence_cache_dispose (GObject *object)
+{
+  SalutPresenceCache *self = SALUT_PRESENCE_CACHE (object);
+  SalutPresenceCachePrivate *priv = SALUT_PRESENCE_CACHE_PRIV (self);
+
+  if (priv->dispose_has_run)
+    return;
+
+  DEBUG ("dispose called");
+
+  priv->dispose_has_run = TRUE;
+
+  g_hash_table_destroy (priv->capabilities);
+  priv->capabilities = NULL;
+
+  g_hash_table_destroy (priv->disco_pending);
+  priv->disco_pending = NULL;
+
+  if (G_OBJECT_CLASS (salut_presence_cache_parent_class)->dispose)
+    G_OBJECT_CLASS (salut_presence_cache_parent_class)->dispose (object);
+}
+
+static void
+salut_presence_cache_finalize (GObject *object)
+{
+  DEBUG ("called with %p", object);
+
+  G_OBJECT_CLASS (salut_presence_cache_parent_class)->finalize (object);
+}
+
+static void
+salut_presence_cache_get_property (GObject    *object,
+                                    guint       property_id,
+                                    GValue     *value,
+                                    GParamSpec *pspec)
+{
+  SalutPresenceCache *cache = SALUT_PRESENCE_CACHE (object);
+  SalutPresenceCachePrivate *priv = SALUT_PRESENCE_CACHE_PRIV (cache);
+
+  switch (property_id) {
+    case PROP_CONNECTION:
+      g_value_set_object (value, priv->conn);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+  }
+}
+
+static void
+salut_presence_cache_set_property (GObject     *object,
+                                    guint        property_id,
+                                    const GValue *value,
+                                    GParamSpec   *pspec)
+{
+  SalutPresenceCache *cache = SALUT_PRESENCE_CACHE (object);
+  SalutPresenceCachePrivate *priv = SALUT_PRESENCE_CACHE_PRIV (cache);
+
+  switch (property_id) {
+    case PROP_CONNECTION:
+      priv->conn = g_value_get_object (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+  }
+}
+
+static void
+_caps_disco_cb (SalutDisco *disco,
+                SalutDiscoRequest *request,
+                SalutContact *contact,
+                const gchar *node,
+                GibberXmppStanza *query_result,
+                GError *error,
+                gpointer user_data)
+{
+  GSList *waiters, *i;
+  DiscoWaiter *waiter_self;
+  SalutPresenceCache *cache;
+  SalutPresenceCachePrivate *priv;
+  TpHandleRepoIface *contact_repo;
+  GHashTable *per_channel_manager_caps;
+  gboolean bad_hash = FALSE;
+  TpBaseConnection *base_conn;
+  TpChannelManagerIter iter;
+  TpChannelManager *manager;
+
+  cache = SALUT_PRESENCE_CACHE (user_data);
+  priv = SALUT_PRESENCE_CACHE_PRIV (cache);
+  base_conn = TP_BASE_CONNECTION (priv->conn);
+  contact_repo = tp_base_connection_get_handles (
+      (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+  if (NULL == node)
+    {
+      g_warning ("got disco response with NULL node, ignoring");
+      return;
+    }
+
+  waiters = g_hash_table_lookup (priv->disco_pending, node);
+
+  if (NULL != error)
+    {
+      DiscoWaiter *waiter = NULL;
+
+      DEBUG ("disco query failed: %s", error->message);
+
+      for (i = waiters; NULL != i; i = i->next)
+        {
+          waiter = (DiscoWaiter *) i->data;
+
+          if (!waiter->disco_requested)
+            {
+              salut_disco_request (disco, SALUT_DISCO_TYPE_INFO,
+                  waiter->contact, node, _caps_disco_cb, cache,
+                  G_OBJECT(cache), NULL);
+              waiter->disco_requested = TRUE;
+              break;
+            }
+        }
+
+      if (NULL != i)
+        {
+          DEBUG ("sent a retry disco request to %s for URI %s",
+              contact->name, node);
+        }
+      else
+        {
+          /* The contact sends us an error and we don't have any other
+           * contacts to send the discovery request on the same node. We
+           * cannot get the caps for this node. */
+          DEBUG ("failed to find a suitable candidate to retry disco "
+              "request for URI %s", node);
+          g_hash_table_remove (priv->disco_pending, node);
+        }
+
+      goto OUT;
+    }
+
+  per_channel_manager_caps = g_hash_table_new (NULL, NULL);
+
+  /* parsing for Connection.Interface.ContactCapabilities.DRAFT */
+  tp_base_connection_channel_manager_iter_init (&iter, base_conn);
+  while (tp_base_connection_channel_manager_iter_next (&iter, &manager))
+    {
+      gpointer *factory_caps;
+
+      /* all channel managers must implement the capability interface */
+      g_assert (SALUT_IS_CAPS_CHANNEL_MANAGER (manager));
+
+      factory_caps = salut_caps_channel_manager_parse_capabilities
+          (SALUT_CAPS_CHANNEL_MANAGER (manager), query_result);
+      if (factory_caps != NULL)
+        g_hash_table_insert (per_channel_manager_caps,
+            SALUT_CAPS_CHANNEL_MANAGER (manager), factory_caps);
+    }
+
+
+  waiter_self = NULL;
+  for (i = waiters; NULL != i;  i = i->next)
+    {
+      DiscoWaiter *waiter;
+
+      waiter = (DiscoWaiter *) i->data;
+      if (waiter->contact == contact)
+        {
+          waiter_self = waiter;
+          break;
+        }
+    }
+  if (NULL == waiter_self)
+    {
+      DEBUG ("Ignoring non requested disco reply");
+      salut_presence_cache_free_cache_entry (per_channel_manager_caps);
+      per_channel_manager_caps = NULL;
+      goto OUT;
+    }
+
+  /* Only 'sha-1' is mandatory to implement by XEP-0115. If the remote contact
+   * uses another hash algorithm, don't check the hash and fallback to the old
+   * method. The hash method is not included in the discovery request nor
+   * response but we saved it in disco_pending when we received the presence
+   * stanza. */
+  if (!tp_strdiff (waiter_self->hash, "sha-1"))
+    {
+      gchar *computed_hash;
+
+      computed_hash = caps_hash_compute_from_stanza (query_result);
+
+      if (!g_str_equal (waiter_self->ver, computed_hash))
+        bad_hash = TRUE;
+
+      if (!bad_hash)
+        {
+          capability_info_recvd (cache, node, contact,
+              per_channel_manager_caps);
+        }
+      else
+        {
+          /* The received reply does not match the */
+          g_warning ("The announced verification string '%s' does not match "
+              "our hash '%s'.", waiter_self->ver, computed_hash);
+          salut_presence_cache_free_cache_entry (per_channel_manager_caps);
+          per_channel_manager_caps = NULL;
+        }
+
+      g_free (computed_hash);
+    }
+  else
+    {
+      /* Do not allow tubes caps if the contact does not observe XEP-0115
+       * version 1.5: we don't need to bother being compatible with both version
+       * 1.3 and tubes caps */
+      salut_presence_cache_free_cache_entry (per_channel_manager_caps);
+      per_channel_manager_caps = NULL;
+    }
+
+  for (i = waiters; NULL != i;)
+    {
+      DiscoWaiter *waiter;
+
+      waiter = (DiscoWaiter *) i->data;
+
+      if (!bad_hash || waiter->contact == contact)
+        {
+          GSList *tmp;
+          gpointer key;
+          gpointer value;
+
+          if (!bad_hash)
+            {
+              GHashTable *save_enhanced_caps;
+              salut_presence_cache_copy_cache_entry (&save_enhanced_caps,
+                  waiter->contact->per_channel_manager_caps);
+
+              DEBUG ("setting caps for %s (thanks to %s)",
+                  waiter->contact->name, contact->name);
+              salut_contact_set_capabilities (waiter->contact,
+                  per_channel_manager_caps);
+              g_signal_emit (cache, signals[CAPABILITIES_UPDATE], 0,
+                contact, save_enhanced_caps,
+                waiter->contact->per_channel_manager_caps);
+              salut_presence_cache_free_cache_entry (save_enhanced_caps);
+            }
+
+          tmp = i;
+          i = i->next;
+
+          waiters = g_slist_delete_link (waiters, tmp);
+
+          if (!g_hash_table_lookup_extended (priv->disco_pending, node, &key,
+                &value))
+            g_assert_not_reached ();
+
+          g_hash_table_steal (priv->disco_pending, node);
+          g_hash_table_insert (priv->disco_pending, key, waiters);
+
+          disco_waiter_free (waiter);
+        }
+      else
+        {
+          /* if the possible trust, not counting this guy, is too low,
+           * we have been poisoned and reset our trust meters - disco
+           * anybody we still haven't to be able to get more trusted replies */
+
+          if (!waiter->disco_requested)
+            {
+              salut_disco_request (disco, SALUT_DISCO_TYPE_INFO,
+                  waiter->contact, node, _caps_disco_cb, cache,
+                  G_OBJECT(cache), NULL);
+              waiter->disco_requested = TRUE;
+            }
+
+          i = i->next;
+        }
+    }
+
+  if (!bad_hash)
+    g_hash_table_remove (priv->disco_pending, node);
+
+OUT:
+  g_object_unref (contact);
+}
+
+
+void
+salut_presence_cache_process_caps (SalutPresenceCache *self,
+                                   SalutContact *contact,
+                                   const gchar *hash,
+                                   const gchar *node,
+                                   const gchar *ver)
+{
+  gchar *uri = g_strdup_printf ("%s#%s", node, ver);
+  CapabilityInfo *info;
+  SalutPresenceCachePrivate *priv;
+  TpHandleRepoIface *contact_repo;
+
+  priv = SALUT_PRESENCE_CACHE_PRIV (self);
+  contact_repo = tp_base_connection_get_handles (
+      (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_CONTACT);
+  info = capability_info_get (self, uri);
+
+  if (info->caps_set)
+    {
+      /* we already have enough trust for this node; apply the cached value to
+       * the contact */
+      DEBUG ("enough trust for URI %s, setting caps for %s",
+          uri, contact->name);
+
+      salut_contact_set_capabilities (contact, info->per_channel_manager_caps);
+    }
+  else
+    {
+      /* Append the contact to the list of such contacts waiting for
+       * capabilities for this uri, and send a disco request if we don't
+       * have enough possible trust yet */
+
+      GSList *waiters;
+      DiscoWaiter *waiter;
+      gpointer key;
+      gpointer value = NULL;
+
+      /* If the URI is in the hash table, steal it and its value; we can
+       * reuse the same URI for the following insertion. Otherwise, make a
+       * copy of the URI for use as a key.
+       */
+      if (g_hash_table_lookup_extended (priv->disco_pending, uri, &key,
+            &value))
+        {
+          g_hash_table_steal (priv->disco_pending, key);
+        }
+      else
+        {
+          key = g_strdup (uri);
+        }
+
+      waiters = (GSList *) value;
+      waiter = disco_waiter_new (contact, hash, ver);
+      waiters = g_slist_prepend (waiters, waiter);
+      g_hash_table_insert (priv->disco_pending, key, waiters);
+
+
+      if (!value)
+        {
+          /* Nobody was asked for this uri so far. Do it now. */
+          salut_disco_request (priv->conn->disco, SALUT_DISCO_TYPE_INFO,
+              contact, uri, _caps_disco_cb, self, G_OBJECT (self), NULL);
+          waiter->disco_requested = TRUE;
+        }
+    }
+}
+
+SalutPresenceCache *
+salut_presence_cache_new (SalutConnection *conn)
+{
+  return g_object_new (SALUT_TYPE_PRESENCE_CACHE,
+                       "connection", conn,
+                       NULL);
+}
+
+
+/* helper functions */
 
 static void
 free_caps_helper (gpointer key, gpointer value, gpointer user_data)
diff --git a/src/salut-presence-cache.h b/src/salut-presence-cache.h
index 40de8cc..594b59c 100644
--- a/src/salut-presence-cache.h
+++ b/src/salut-presence-cache.h
@@ -22,9 +22,58 @@
 #define __SALUT_PRESENCE_CACHE_H__
 
 #include <glib.h>
+#include <glib-object.h>
+
+#include "salut-connection.h"
+#include "salut-contact.h"
 
 G_BEGIN_DECLS
 
+#define SALUT_TYPE_PRESENCE_CACHE salut_presence_cache_get_type ()
+
+#define SALUT_PRESENCE_CACHE(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+  SALUT_TYPE_PRESENCE_CACHE, SalutPresenceCache))
+
+#define SALUT_PRESENCE_CACHE_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), \
+  SALUT_TYPE_PRESENCE_CACHE, SalutPresenceCacheClass))
+
+#define SALUT_IS_PRESENCE_CACHE(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+  SALUT_TYPE_PRESENCE_CACHE))
+
+#define SALUT_IS_PRESENCE_CACHE_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+  SALUT_TYPE_PRESENCE_CACHE))
+
+#define SALUT_PRESENCE_CACHE_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+  SALUT_TYPE_PRESENCE_CACHE, SalutPresenceCacheClass))
+
+
+typedef struct _SalutPresenceCachePrivate SalutPresenceCachePrivate;
+
+struct _SalutPresenceCache {
+    GObject parent;
+    SalutPresenceCachePrivate *priv;
+};
+
+typedef struct _SalutPresenceCacheClass SalutPresenceCacheClass;
+
+struct _SalutPresenceCacheClass {
+    GObjectClass parent_class;
+};
+
+GType salut_presence_cache_get_type (void);
+
+SalutPresenceCache *salut_presence_cache_new (SalutConnection *conn);
+
+void salut_presence_cache_process_caps (SalutPresenceCache *self,
+    SalutContact *contact, const gchar *hash, const gchar *node,
+    const gchar *ver);
+
+
 /* loop on CapabilityInfo::per_channel_manager_caps and call
  * salut_caps_channel_manager_free_capabilities */
 void salut_presence_cache_free_cache_entry (
-- 
1.5.6.5




More information about the telepathy-commits mailing list