[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