diff -r 000000000000 -r d0f3a028347a telepathygabble/src/gabble-presence-cache.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/telepathygabble/src/gabble-presence-cache.c Tue Feb 02 01:10:06 2010 +0200 @@ -0,0 +1,1267 @@ +/* + * gabble-presence-cache.c - Gabble's contact presence cache + * Copyright (C) 2005 Collabora Ltd. + * and/or its subsidiaries. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include + +#include "debug.h" +#include "disco.h" /* \o\ \o/ /o/ */ +#include "gabble-presence.h" +#include "namespaces.h" +#include "util.h" +#include "handle-set.h" +#include "gintset.h" + +#include "gabble-presence-cache.h" + +#include "gabble-presence-cache-signals-marshal.h" + +#include "gabble_enums.h" + +#ifndef EMULATOR +G_DEFINE_TYPE (GabblePresenceCache, gabble_presence_cache, G_TYPE_OBJECT); +#endif + +/* when five DIFFERENT guys report the same caps for a given bundle, it'll be enough */ +#define CAPABILITY_BUNDLE_ENOUGH_TRUST 5 +#define DEBUG_FLAG GABBLE_DEBUG_PRESENCE + +#ifdef DEBUG_FLAG +//#define DEBUG(format, ...) +#define DEBUGGING 0 +#define NODE_DEBUG(n, s) +#endif /* DEBUG_FLAG */ + +/* properties */ +enum +{ + PROP_CONNECTION = 1, + LAST_PROPERTY +}; + +/* signal enum */ +enum +{ + PRESENCE_UPDATE, + NICKNAME_UPDATE, + CAPABILITIES_UPDATE, + LAST_SIGNAL +#ifdef EMULATOR + = LAST_SIGNAL_PRE_CACHE +#endif + +}; + + +#ifdef EMULATOR +#include "libgabble_wsd_solution.h" + + GET_STATIC_ARRAY_FROM_TLS(signals,gabble_pre_cache,guint) + #define signals (GET_WSD_VAR_NAME(signals,gabble_pre_cache, s)()) + + GET_STATIC_VAR_FROM_TLS(gabble_presence_cache_parent_class,gabble_pre_cache,gpointer) + #define gabble_presence_cache_parent_class (*GET_WSD_VAR_NAME(gabble_presence_cache_parent_class,gabble_pre_cache,s)()) + + GET_STATIC_VAR_FROM_TLS(g_define_type_id,gabble_pre_cache,GType) + #define g_define_type_id (*GET_WSD_VAR_NAME(g_define_type_id,gabble_pre_cache,s)()) + + +static void gabble_presence_cache_init (GabblePresenceCache *self); +static void gabble_presence_cache_class_init (GabblePresenceCacheClass *klass); +static void gabble_presence_cache_class_intern_init (gpointer klass) + { + gabble_presence_cache_parent_class = g_type_class_peek_parent (klass); + gabble_presence_cache_class_init ((GabblePresenceCacheClass*) klass); + } + EXPORT_C GType gabble_presence_cache_get_type (void) + { + if ((g_define_type_id == 0)) + { static const GTypeInfo g_define_type_info = { sizeof (GabblePresenceCacheClass), (GBaseInitFunc) ((void *)0), (GBaseFinalizeFunc) ((void *)0), (GClassInitFunc) gabble_presence_cache_class_intern_init, (GClassFinalizeFunc) ((void *)0), ((void *)0), sizeof (GabblePresenceCache), 0, (GInstanceInitFunc) gabble_presence_cache_init, ((void *)0) }; g_define_type_id = g_type_register_static ( ((GType) ((20) << (2))), g_intern_static_string ("GabblePresenceCache"), &g_define_type_info, (GTypeFlags) 0); { {} ; } } return g_define_type_id; + }; + + +#else + + static guint signals[LAST_SIGNAL] = {0}; + +#endif + + +#define GABBLE_PRESENCE_CACHE_PRIV(account) ((GabblePresenceCachePrivate *)account->priv) + +typedef struct _GabblePresenceCachePrivate GabblePresenceCachePrivate; + +struct _GabblePresenceCachePrivate +{ + GabbleConnection *conn; + + gulong status_changed_cb; + LmMessageHandler *lm_message_cb; + + GHashTable *presence; + GabbleHandleSet *presence_handles; + + GHashTable *capabilities; + GHashTable *disco_pending; + guint caps_serial; + + gboolean dispose_has_run; +}; + +typedef struct _DiscoWaiter DiscoWaiter; + +struct _DiscoWaiter +{ + GabbleHandleRepo *repo; + GabbleHandle handle; + gchar *resource; + guint serial; + gboolean disco_requested; +}; + +/** + * disco_waiter_new () + */ +static DiscoWaiter * +disco_waiter_new (GabbleHandleRepo *repo, GabbleHandle handle, const gchar *resource, guint serial) +{ + DiscoWaiter *waiter; + + g_assert (repo); + gabble_handle_ref (repo, TP_HANDLE_TYPE_CONTACT, handle); + + waiter = g_new0 (DiscoWaiter, 1); + waiter->repo = repo; + waiter->handle = handle; + waiter->resource = g_strdup (resource); + waiter->serial = serial; + + gabble_debug (DEBUG_FLAG, "created waiter %p for handle %u with serial %u", waiter, handle, serial); + + return waiter; +} + +static void +disco_waiter_free (DiscoWaiter *waiter) +{ + g_assert (NULL != waiter); + + gabble_debug (DEBUG_FLAG, "freeing waiter %p for handle %u with serial %u", waiter, waiter->handle, waiter->serial); + + gabble_handle_unref (waiter->repo, TP_HANDLE_TYPE_CONTACT, waiter->handle); + + g_free (waiter->resource); + g_free (waiter); +} + +static void +disco_waiter_list_free (GSList *list) +{ + GSList *i; + + gabble_debug (DEBUG_FLAG, "list %p", list); + + for (i = list; NULL != i; i = i->next) + disco_waiter_free ((DiscoWaiter *) i->data); + + g_slist_free (list); +} + +static guint +disco_waiter_list_get_request_count (GSList *list) +{ + guint c = 0; + GSList *i; + + for (i = list; i; i = i->next) + { + DiscoWaiter *waiter = (DiscoWaiter *) i->data; + + if (waiter->disco_requested) + c++; + } + + return c; +} + +typedef struct _CapabilityInfo CapabilityInfo; + +struct _CapabilityInfo +{ + GabblePresenceCapabilities caps; + GIntSet *guys; + guint trust; +}; + +static CapabilityInfo * +capability_info_get (GabblePresenceCache *cache, const gchar *node, + GabblePresenceCapabilities caps) +{ + GabblePresenceCachePrivate *priv = GABBLE_PRESENCE_CACHE_PRIV (cache); + CapabilityInfo *info = g_hash_table_lookup (priv->capabilities, node); + + if (NULL == info) + { + info = g_new0 (CapabilityInfo, 1); + info->caps = caps; + info->guys = g_intset_new (); + g_hash_table_insert (priv->capabilities, g_strdup (node), info); + } + + return info; +} + +static guint +capability_info_recvd (GabblePresenceCache *cache, const gchar *node, + GabbleHandle handle, GabblePresenceCapabilities caps) +{ + CapabilityInfo *info = capability_info_get (cache, node, caps); + + /* Detect inconsistency in reported caps */ + if (info->caps != caps) + { + g_intset_clear (info->guys); + info->caps = caps; + info->trust = 0; + } + + if (!g_intset_is_member (info->guys, handle)) + { + g_intset_add (info->guys, handle); + info->trust++; + } + + return info->trust; +} + +static void gabble_presence_cache_init (GabblePresenceCache *presence_cache); +static GObject * gabble_presence_cache_constructor (GType type, guint n_props, + GObjectConstructParam *props); +static void gabble_presence_cache_dispose (GObject *object); +static void gabble_presence_cache_finalize (GObject *object); +static void gabble_presence_cache_set_property (GObject *object, guint + property_id, const GValue *value, GParamSpec *pspec); +static void gabble_presence_cache_get_property (GObject *object, guint + property_id, GValue *value, GParamSpec *pspec); +static GabblePresence *_cache_insert (GabblePresenceCache *cache, + GabbleHandle handle); + +static void gabble_presence_cache_status_changed_cb (GabbleConnection *, + TpConnectionStatus, TpConnectionStatusReason, gpointer); +static LmHandlerResult gabble_presence_cache_lm_message_cb (LmMessageHandler*, + LmConnection*, LmMessage*, gpointer); + +static void +gabble_presence_cache_class_init (GabblePresenceCacheClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GParamSpec *param_spec; + + g_type_class_add_private (object_class, sizeof (GabblePresenceCachePrivate)); + + object_class->constructor = gabble_presence_cache_constructor; + + object_class->dispose = gabble_presence_cache_dispose; + object_class->finalize = gabble_presence_cache_finalize; + + object_class->get_property = gabble_presence_cache_get_property; + object_class->set_property = gabble_presence_cache_set_property; + + param_spec = g_param_spec_object ("connection", "GabbleConnection object", + "Gabble connection object that owns this " + "presence cache.", + GABBLE_TYPE_CONNECTION, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_NICK | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, + PROP_CONNECTION, + param_spec); + + signals[PRESENCE_UPDATE] = g_signal_new ( + "presence-update", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT); + signals[NICKNAME_UPDATE] = g_signal_new ( + "nickname-update", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT); + signals[CAPABILITIES_UPDATE] = g_signal_new ( + "capabilities-update", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + gabble_presence_cache_marshal_VOID__UINT_UINT_UINT, G_TYPE_NONE, 3, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT); +} + +static void +gabble_presence_cache_init (GabblePresenceCache *cache) +{ + GabblePresenceCachePrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (cache, + GABBLE_TYPE_PRESENCE_CACHE, GabblePresenceCachePrivate); + + cache->priv = priv; + + priv->presence = g_hash_table_new_full (NULL, NULL, NULL, g_object_unref); + priv->capabilities = g_hash_table_new (g_str_hash, g_str_equal); + 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 * +gabble_presence_cache_constructor (GType type, guint n_props, + GObjectConstructParam *props) +{ + GObject *obj; + GabblePresenceCachePrivate *priv; + + obj = G_OBJECT_CLASS (gabble_presence_cache_parent_class)-> + constructor (type, n_props, props); + priv = GABBLE_PRESENCE_CACHE_PRIV (GABBLE_PRESENCE_CACHE (obj)); + + priv->status_changed_cb = g_signal_connect (priv->conn, "status-changed", + G_CALLBACK (gabble_presence_cache_status_changed_cb), obj); + + return obj; +} + +static void +gabble_presence_cache_dispose (GObject *object) +{ + GabblePresenceCache *self = GABBLE_PRESENCE_CACHE (object); + GabblePresenceCachePrivate *priv = GABBLE_PRESENCE_CACHE_PRIV (self); + + if (priv->dispose_has_run) + return; + + gabble_debug (DEBUG_FLAG, "dispose called"); + + priv->dispose_has_run = TRUE; + + g_assert (priv->lm_message_cb == NULL); + + g_signal_handler_disconnect (priv->conn, priv->status_changed_cb); + + g_hash_table_destroy (priv->presence); + priv->presence = NULL; + + handle_set_destroy (priv->presence_handles); + priv->presence_handles = NULL; + + if (G_OBJECT_CLASS (gabble_presence_cache_parent_class)->dispose) + G_OBJECT_CLASS (gabble_presence_cache_parent_class)->dispose (object); +} + +static void +gabble_presence_cache_finalize (GObject *object) +{ + gabble_debug (DEBUG_FLAG, "called with %p", object); + + G_OBJECT_CLASS (gabble_presence_cache_parent_class)->finalize (object); +} + +static void +gabble_presence_cache_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GabblePresenceCache *cache = GABBLE_PRESENCE_CACHE (object); + GabblePresenceCachePrivate *priv = GABBLE_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 +gabble_presence_cache_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GabblePresenceCache *cache = GABBLE_PRESENCE_CACHE (object); + GabblePresenceCachePrivate *priv = GABBLE_PRESENCE_CACHE_PRIV (cache); + GabbleHandleSet *new_presence_handles; + + switch (property_id) { + case PROP_CONNECTION: + priv->conn = g_value_get_object (value); + new_presence_handles = handle_set_new (priv->conn->handles, TP_HANDLE_TYPE_CONTACT); + + if (priv->presence_handles) + { + const GIntSet *add; + GIntSet *tmp; + add = handle_set_peek (priv->presence_handles); + tmp = handle_set_update (new_presence_handles, add); + handle_set_destroy (priv->presence_handles); + g_intset_destroy (tmp); + } + priv->presence_handles = new_presence_handles; + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +#if 0 +static gboolean +_presence_node_has_google_voice (LmMessageNode *pres_node) +{ + LmMessageNode *node; + const gchar *cap_ext; + gchar **features, **tmp; + gboolean found = FALSE; + + node = lm_message_node_get_child_with_namespace (pres_node, "c", NS_CAPS); + + if (node == NULL); + return FALSE; + + cap_ext = lm_message_node_get_attribute (node, "ext"); + + if (cap_ext == NULL); + return FALSE; + + features = g_strsplit (cap_ext, " ", 0); + + for (tmp = features; *tmp; tmp++) + { + if (!g_strdiff (tmp, "voice-v1")) + { + found = TRUE; + break; + } + } + + g_strfreev (features); + + return found; +} +#endif + +static void +gabble_presence_cache_status_changed_cb (GabbleConnection *conn, + TpConnectionStatus status, + TpConnectionStatusReason reason, + gpointer data) +{ + GabblePresenceCache *cache = GABBLE_PRESENCE_CACHE (data); + GabblePresenceCachePrivate *priv = GABBLE_PRESENCE_CACHE_PRIV (cache); + + g_assert (conn == priv->conn); + + switch (status) + { + case TP_CONN_STATUS_CONNECTING: + g_assert (priv->lm_message_cb == NULL); + + priv->lm_message_cb = lm_message_handler_new (gabble_presence_cache_lm_message_cb, + cache, NULL); + lm_connection_register_message_handler (priv->conn->lmconn, + priv->lm_message_cb, + LM_MESSAGE_TYPE_PRESENCE, + LM_HANDLER_PRIORITY_LAST); + lm_connection_register_message_handler (priv->conn->lmconn, + priv->lm_message_cb, + LM_MESSAGE_TYPE_MESSAGE, + LM_HANDLER_PRIORITY_FIRST); + break; + case TP_CONN_STATUS_CONNECTED: + /* TODO: emit self presence */ + break; + case TP_CONN_STATUS_DISCONNECTED: + g_assert (priv->lm_message_cb != NULL); + + lm_connection_unregister_message_handler (conn->lmconn, + priv->lm_message_cb, + LM_MESSAGE_TYPE_PRESENCE); + lm_connection_unregister_message_handler (conn->lmconn, + priv->lm_message_cb, + LM_MESSAGE_TYPE_MESSAGE); + lm_message_handler_unref (priv->lm_message_cb); + priv->lm_message_cb = NULL; + break; + default: + g_assert_not_reached (); + } +} + +static GabblePresenceId +_presence_node_get_status (LmMessageNode *pres_node) +{ + const gchar *presence_show; + LmMessageNode *child_node = lm_message_node_get_child (pres_node, "show"); + + if (!child_node) + { + /* + NODE_DEBUG (pres_node, + " without received from server, " + "setting presence to available"); + */ + return GABBLE_PRESENCE_AVAILABLE; + } + + presence_show = lm_message_node_get_value (child_node); + + if (!presence_show) + { + /* + NODE_DEBUG (pres_node, + "empty tag received from server, " + "setting presence to available"); + */ + return GABBLE_PRESENCE_AVAILABLE; + } + + if (0 == strcmp (presence_show, JABBER_PRESENCE_SHOW_AWAY)) + return GABBLE_PRESENCE_AWAY; + else if (0 == strcmp (presence_show, JABBER_PRESENCE_SHOW_CHAT)) + return GABBLE_PRESENCE_CHAT; + else if (0 == strcmp (presence_show, JABBER_PRESENCE_SHOW_DND)) + return GABBLE_PRESENCE_DND; + else if (0 == strcmp (presence_show, JABBER_PRESENCE_SHOW_XA)) + return GABBLE_PRESENCE_XA; + else + { + NODE_DEBUG (pres_node, + "unrecognised value received from server, " + "setting presence to available"); + return GABBLE_PRESENCE_AVAILABLE; + } +} + +static void +_grab_nickname (GabblePresenceCache *cache, + GabbleHandle handle, + const gchar *from, + LmMessageNode *node) +{ + const gchar *nickname; + GabblePresence *presence; + + node = lm_message_node_get_child_with_namespace (node, "nick", NS_NICK); + + if (NULL == node) + return; + + presence = gabble_presence_cache_get (cache, handle); + + if (NULL == presence) + return; + + nickname = lm_message_node_get_value (node); + gabble_debug (DEBUG_FLAG, "got nickname \"%s\" for %s", nickname, from); + + if (g_strdiff (presence->nickname, nickname)) + { + if (NULL != presence->nickname) + g_free (presence->nickname); + + presence->nickname = g_strdup (nickname); + g_signal_emit (cache, signals[NICKNAME_UPDATE], 0, handle); + } +} + +static GSList * +_extract_cap_bundles (LmMessageNode *lm_node) +{ + const gchar *node, *ver, *ext; + GSList *uris = NULL; + LmMessageNode *cap_node; + + cap_node = lm_message_node_get_child_with_namespace (lm_node, "c", NS_CAPS); + + if (NULL == cap_node) + return NULL; + + node = lm_message_node_get_attribute (cap_node, "node"); + + if (NULL == node) + return NULL; + + ver = lm_message_node_get_attribute (cap_node, "ver"); + + if (NULL != ver) + uris = g_slist_prepend (uris, g_strdup_printf ("%s#%s", node, ver)); + + ext = lm_message_node_get_attribute (cap_node, "ext"); + + if (NULL != ext) + { + gchar **exts, **i; + + exts = g_strsplit (ext, " ", 0); + + for (i = exts; NULL != *i; i++) + uris = g_slist_prepend (uris, g_strdup_printf ("%s#%s", node, *i)); + + g_strfreev (exts); + } + + return uris; +} + +static void +_caps_disco_cb (GabbleDisco *disco, + GabbleDiscoRequest *request, + const gchar *jid, + const gchar *node, + LmMessageNode *query_result, + GError *error, + gpointer user_data) +{ + GSList *waiters, *i; + LmMessageNode *child; + GabblePresenceCache *cache; + GabblePresenceCachePrivate *priv; + gchar *full_jid = NULL; + GabblePresenceCapabilities caps = 0; + guint trust; + GabbleHandle handle; + + cache = GABBLE_PRESENCE_CACHE (user_data); + priv = GABBLE_PRESENCE_CACHE_PRIV (cache); + + 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; + + gabble_debug (DEBUG_FLAG, "disco query failed: %s", error->message); + + for (i = waiters; NULL != i; i = i->next) + { + waiter = (DiscoWaiter *) i->data; + + if (!waiter->disco_requested) + { + const gchar *jid; + + jid = gabble_handle_inspect (priv->conn->handles, + TP_HANDLE_TYPE_CONTACT, + waiter->handle); + full_jid = g_strdup_printf ("%s/%s", jid, waiter->resource); + + gabble_disco_request (disco, GABBLE_DISCO_TYPE_INFO, full_jid, node, + _caps_disco_cb, cache, G_OBJECT(cache), NULL); + waiter->disco_requested = TRUE; + break; + } + } + + if (NULL != i) + { + gabble_debug (DEBUG_FLAG, "sent a retry disco request to %s for URI %s", full_jid, node); + } + else + { + gabble_debug (DEBUG_FLAG, "failed to find a suitable candidate to retry disco request for URI %s", node); + /* FIXME do something very clever here? */ + g_hash_table_remove (priv->disco_pending, node); + } + + goto OUT; + } + + for (child = query_result->children; NULL != child; child = child->next) + { + const gchar *var; + + if (0 != strcmp (child->name, "feature")) + continue; + + var = lm_message_node_get_attribute (child, "var"); + + if (NULL == var) + continue; + + /* TODO: use a table that equates disco features to caps */ + if (0 == strcmp (var, NS_GOOGLE_TRANSPORT_P2P)) + caps |= PRESENCE_CAP_GOOGLE_TRANSPORT_P2P; + else if (0 == strcmp (var, NS_GOOGLE_FEAT_VOICE)) + caps |= PRESENCE_CAP_GOOGLE_VOICE; + else if (0 == strcmp (var, NS_JINGLE)) + caps |= PRESENCE_CAP_JINGLE; + else if (0 == strcmp (var, NS_JINGLE_DESCRIPTION_AUDIO)) + caps |= PRESENCE_CAP_JINGLE_DESCRIPTION_AUDIO; + else if (0 == strcmp (var, NS_JINGLE_DESCRIPTION_VIDEO)) + caps |= PRESENCE_CAP_JINGLE_DESCRIPTION_VIDEO; + } + + handle = gabble_handle_for_contact (priv->conn->handles, jid, FALSE); + trust = capability_info_recvd (cache, node, handle, caps); + + for (i = waiters; NULL != i;) + { + DiscoWaiter *waiter; + GabblePresence *presence; + + waiter = (DiscoWaiter *) i->data; + + if (trust >= CAPABILITY_BUNDLE_ENOUGH_TRUST || waiter->handle == handle) + { + GSList *tmp; + /* trusted reply */ + presence = gabble_presence_cache_get (cache, waiter->handle); + + if (presence) + { + GabblePresenceCapabilities save_caps = presence->caps; + gabble_debug (DEBUG_FLAG, "setting caps for %d (%s) to %d", handle, jid, caps); + gabble_presence_set_capabilities (presence, waiter->resource,caps, + waiter->serial); + gabble_debug (DEBUG_FLAG, "caps for %d (%s) now %d", handle, jid, presence->caps); + g_signal_emit (cache, signals[CAPABILITIES_UPDATE], 0, + waiter->handle, save_caps, presence->caps); + } + + tmp = i; + i = i->next; + + waiters = g_slist_delete_link (waiters, tmp); + + g_hash_table_steal (priv->disco_pending, node); + g_hash_table_insert (priv->disco_pending, g_strdup (node), waiters); + + disco_waiter_free (waiter); + } + else if (trust + disco_waiter_list_get_request_count (waiters) - 1 + < CAPABILITY_BUNDLE_ENOUGH_TRUST) + { + /* 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) + { + const gchar *jid; + + jid = gabble_handle_inspect (priv->conn->handles, + TP_HANDLE_TYPE_CONTACT, waiter->handle); + full_jid = g_strdup_printf ("%s/%s", jid, waiter->resource); + + gabble_disco_request (disco, GABBLE_DISCO_TYPE_INFO, full_jid, + node, _caps_disco_cb, cache, G_OBJECT(cache), NULL); + waiter->disco_requested = TRUE; + + g_free (full_jid); + full_jid = NULL; + } + + i = i->next; + } + else + { + /* trust level still uncertain, don't do nothing */ + i = i->next; + } + } + + if (trust >= CAPABILITY_BUNDLE_ENOUGH_TRUST) + g_hash_table_remove (priv->disco_pending, node); + +OUT: + + g_free (full_jid); +} + +static void +_process_caps_uri (GabblePresenceCache *cache, + const gchar *from, + const gchar *uri, + GabbleHandle handle, + const gchar *resource, + guint serial) +{ + CapabilityInfo *info; + gpointer value; + GabblePresenceCachePrivate *priv; + + priv = GABBLE_PRESENCE_CACHE_PRIV (cache); + info = capability_info_get (cache, uri, 0); + + if (info->trust >= CAPABILITY_BUNDLE_ENOUGH_TRUST + || g_intset_is_member (info->guys, handle)) + { + /* we already have enough trust for this node; apply the cached value to + * the (handle, resource) */ + + GabblePresence *presence = gabble_presence_cache_get (cache, handle); + gabble_debug (DEBUG_FLAG, "enough trust for URI %s, setting caps for %u (%s) to %u", + uri, handle, from, info->caps); + + if (presence) + { + GabblePresenceCapabilities save_caps = presence->caps; + gabble_presence_set_capabilities (presence, resource, info->caps, serial); + g_signal_emit (cache, signals[CAPABILITIES_UPDATE], 0, + handle, save_caps, presence->caps); + gabble_debug (DEBUG_FLAG, "caps for %d (%s) now %d", handle, from, presence->caps); + } + else + { + gabble_debug (DEBUG_FLAG, "presence not found"); + } + } + else + { + /* Append the (handle, resource) pair to the list of such pairs + * waiting for capabilities for this uri, and send a disco request + * if we don't have enough possible trust yet */ + + GSList *waiters; + DiscoWaiter *waiter; + guint possible_trust; + + gabble_debug (DEBUG_FLAG, "not enough trust for URI %s", uri); + value = g_hash_table_lookup (priv->disco_pending, uri); + + if (value) + g_hash_table_steal (priv->disco_pending, uri); + + waiters = (GSList *) value; + waiter = disco_waiter_new (priv->conn->handles, handle, resource, serial); + waiters = g_slist_prepend (waiters, waiter); + g_hash_table_insert (priv->disco_pending, g_strdup (uri), waiters); + + possible_trust = disco_waiter_list_get_request_count (waiters); + + if (!value || info->trust + possible_trust < CAPABILITY_BUNDLE_ENOUGH_TRUST) + { + /* DISCO */ + gabble_debug (DEBUG_FLAG, "only %u trust out of %u possible thus far, sending disco for URI %s", + info->trust + possible_trust, CAPABILITY_BUNDLE_ENOUGH_TRUST, uri); + gabble_disco_request (priv->conn->disco, GABBLE_DISCO_TYPE_INFO, + from, uri, _caps_disco_cb, cache, G_OBJECT (cache), NULL); + /* enough DISCO for you, buddy */ + waiter->disco_requested = TRUE; + } + } +} + +static void +_process_caps (GabblePresenceCache *cache, + GabbleHandle handle, + const gchar *from, + LmMessageNode *lm_node) +{ + gchar *resource; + GSList *uris, *i; + GabblePresenceCachePrivate *priv; + guint serial; + + priv = GABBLE_PRESENCE_CACHE_PRIV (cache); + serial = priv->caps_serial++; + + gabble_decode_jid (from, NULL, NULL, &resource); + + if (NULL == resource) + return; + + uris = _extract_cap_bundles (lm_node); + + for (i = uris; NULL != i; i = i->next) + { + _process_caps_uri (cache, from, (gchar *) i->data, handle, resource, serial); + g_free (i->data); + } + + g_free (resource); + g_slist_free (uris); +} + +static void +_grab_avatar_sha1 (GabblePresenceCache *cache, + GabbleHandle handle, + const gchar *from, + LmMessageNode *node) +{ + const gchar *sha1; + LmMessageNode *x_node, *photo_node; + GabblePresence *presence; + //LIB_TRACE( ELibTraceTypeInfo, "%s", "Out _grab_avatar_sha1" ); + presence = gabble_presence_cache_get (cache, handle); + + if (NULL == presence) + return; + + x_node = lm_message_node_get_child_with_namespace (node, "x", + NS_VCARD_TEMP_UPDATE); + + if (NULL == x_node) + { +#if 0 + if (handle == priv->conn->parent.self_handle) + { + /* One of my other resources does not support XEP-0153. As per that + * XEP, I MUST stop advertising the image hash, at least until all + * instances of non-conforming resources have gone offline. + * However, we're going to ignore this requirement and hope that + * non-conforming clients won't alter the , which should + * in practice be true. + */ + presence->avatar_sha1 = NULL; + } +#endif + return; + } + + photo_node = lm_message_node_get_child (x_node, "photo"); + + /* If there is no photo node, the resource supports XEP-0153, but has + * nothing in particular to say about the avatar. */ + if (NULL == photo_node) + return; + + sha1 = lm_message_node_get_value (photo_node); + + if (g_strdiff (presence->avatar_sha1, sha1)) + { + g_free (presence->avatar_sha1); + presence->avatar_sha1 = g_strdup (sha1); + +#if 0 + if (handle == priv->conn->parent.self_handle) + { + /* that would be us, then. According to XEP-0153, we MUST + * immediately send a presence update with an empty update child + * element (no photo node), then re-download our own vCard; + * when that arrives, we may start setting the photo node in our + * presence again. + * + * For the moment I'm going to ignore that requirement and + * trust that our other resource is getting its sha1 right! + */ + /* TODO: I don't trust anyone to get XMPP right, so let's do + * this. :D */ + } +#endif + //LIB_TRACE( ELibTraceTypeInfo, "%s", "AVATAR_UPDATE _grab_avatar_sha1" ); + //g_signal_emit (cache, signals[AVATAR_UPDATE], 0, handle); + } + //LIB_TRACE( ELibTraceTypeInfo, "%s", "Out _grab_avatar_sha1" ); +} + +static LmHandlerResult +_parse_presence_message (GabblePresenceCache *cache, + GabbleHandle handle, + const gchar *from, + LmMessage *message) +{ + gint8 priority = 0; + gchar *resource = NULL; + const gchar *status_message = NULL; + LmMessageNode *presence_node, *child_node; + LmHandlerResult ret = LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; + GabblePresenceId presence_id; + GabblePresence *presence; + + presence_node = message->node; + g_assert (0 == strcmp (presence_node->name, "presence")); + + gabble_decode_jid (from, NULL, NULL, &resource); + + presence = gabble_presence_cache_get (cache, handle); + + if (NULL != presence) + presence->keep_unavailable = FALSE; + + child_node = lm_message_node_get_child (presence_node, "status"); + + if (child_node) + status_message = lm_message_node_get_value (child_node); + + child_node = lm_message_node_get_child (presence_node, "priority"); + + if (child_node) + { + const gchar *prio = lm_message_node_get_value (child_node); + + if (prio != NULL) + priority = CLAMP (atoi (prio), G_MININT8, G_MAXINT8); + } + + switch (lm_message_get_sub_type (message)) + { + case LM_MESSAGE_SUB_TYPE_NOT_SET: + case LM_MESSAGE_SUB_TYPE_AVAILABLE: + presence_id = _presence_node_get_status (presence_node); + gabble_presence_cache_update (cache, handle, resource, presence_id, + status_message, priority); + +#if 0 + if (_presence_node_has_google_voice (presence_node)) + { + presence = gabble_presence_cache_get (cache, handle); + g_assert (NULL != presence); + gabble_debug (DEBUG_FLAG, "%s has voice-v1 support", from); + gabble_presence_set_capabilities (presence, resource, + PRESENCE_CAP_GOOGLE_VOICE); + } +#endif + + ret = LM_HANDLER_RESULT_REMOVE_MESSAGE; + break; + + case LM_MESSAGE_SUB_TYPE_ERROR: + NODE_DEBUG (presence_node, "setting contact offline due to error"); + /* fall through */ + + case LM_MESSAGE_SUB_TYPE_UNAVAILABLE: + gabble_presence_cache_update (cache, handle, resource, + GABBLE_PRESENCE_OFFLINE, status_message, priority); + + ret = LM_HANDLER_RESULT_REMOVE_MESSAGE; + break; + + default: + break; + } + + _grab_avatar_sha1 (cache, handle, from, presence_node); + _grab_nickname (cache, handle, from, presence_node); + _process_caps (cache, handle, from, presence_node); + + g_free (resource); + + return ret; +} + +static LmHandlerResult +_parse_message_message (GabblePresenceCache *cache, + GabbleHandle handle, + const gchar *from, + LmMessage *message) +{ + LmMessageNode *node; + GabblePresence *presence; + + presence = gabble_presence_cache_get (cache, handle); + + if (NULL == presence) + { + presence = _cache_insert (cache, handle); + presence->keep_unavailable = TRUE; + } + + node = lm_message_get_node (message); + + _grab_nickname (cache, handle, from, node); + _process_caps (cache, handle, from, node); + + return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; +} + + +/** + * gabble_presence_cache_lm_message_cb: + * @handler: #LmMessageHandler for this message + * @connection: #LmConnection that originated the message + * @message: the presence message + * @user_data: callback data + * + * Called by loudmouth when we get an incoming . + */ +static LmHandlerResult +gabble_presence_cache_lm_message_cb (LmMessageHandler *handler, + LmConnection *lmconn, + LmMessage *message, + gpointer user_data) +{ + GabblePresenceCache *cache = GABBLE_PRESENCE_CACHE (user_data); + GabblePresenceCachePrivate *priv = GABBLE_PRESENCE_CACHE_PRIV (cache); + const char *from; + GabbleHandle handle; + + g_assert (lmconn == priv->conn->lmconn); + + from = lm_message_node_get_attribute (message->node, "from"); + + if (NULL == from) + { + NODE_DEBUG (message->node, "message without from attribute, ignoring"); + return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; + } + + handle = gabble_handle_for_contact (priv->conn->handles, from, FALSE); + + if (0 == handle) + { + NODE_DEBUG (message->node, "ignoring message from malformed jid"); + return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; + } + + if (handle == priv->conn->self_handle) + { + NODE_DEBUG (message->node, + "ignoring message from ourselves on another resource"); + return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; + } + + switch (lm_message_get_type (message)) + { + case LM_MESSAGE_TYPE_PRESENCE: + return _parse_presence_message (cache, handle, from, message); + case LM_MESSAGE_TYPE_MESSAGE: + return _parse_message_message (cache, handle, from, message); + default: + return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; + } +} + + +GabblePresenceCache * +gabble_presence_cache_new (GabbleConnection *conn) +{ + return g_object_new (GABBLE_TYPE_PRESENCE_CACHE, + "connection", conn, + NULL); +} + +GabblePresence * +gabble_presence_cache_get (GabblePresenceCache *cache, GabbleHandle handle) +{ + GabblePresenceCachePrivate *priv = GABBLE_PRESENCE_CACHE_PRIV (cache); + + +// g_assert (gabble_handle_is_valid (priv->conn->handles, +// TP_HANDLE_TYPE_CONTACT, handle, NULL)); + if(priv) + if(priv->conn) + if(priv->conn->handles){ + if ( gabble_handle_is_valid (priv->conn->handles, + TP_HANDLE_TYPE_CONTACT, handle, NULL) ) + { + return g_hash_table_lookup (priv->presence, GINT_TO_POINTER (handle)); + } + else + { + return NULL; + } + } + return NULL; +} + +void +gabble_presence_cache_maybe_remove ( + GabblePresenceCache *cache, + GabbleHandle handle) +{ + GabblePresenceCachePrivate *priv = GABBLE_PRESENCE_CACHE_PRIV (cache); + GabblePresence *presence; + + presence = gabble_presence_cache_get (cache, handle); + + if (NULL == presence) + return; + + if (presence->status == GABBLE_PRESENCE_OFFLINE && + presence->status_message == NULL && + !presence->keep_unavailable) + { + const gchar *jid; + + jid = gabble_handle_inspect (priv->conn->handles, TP_HANDLE_TYPE_CONTACT, + handle); + gabble_debug (DEBUG_FLAG, "discarding cached presence for unavailable jid %s", jid); + g_hash_table_remove (priv->presence, GINT_TO_POINTER (handle)); + handle_set_remove (priv->presence_handles, handle); + } +} + +static GabblePresence * +_cache_insert ( + GabblePresenceCache *cache, + GabbleHandle handle) +{ + GabblePresenceCachePrivate *priv = GABBLE_PRESENCE_CACHE_PRIV (cache); + GabblePresence *presence; + + presence = gabble_presence_new (); + g_hash_table_insert (priv->presence, GINT_TO_POINTER (handle), presence); + handle_set_add (priv->presence_handles, handle); + return presence; +} + +void +gabble_presence_cache_update ( + GabblePresenceCache *cache, + GabbleHandle handle, + const gchar *resource, + GabblePresenceId presence_id, + const gchar *status_message, + gint8 priority) +{ + GabblePresenceCachePrivate *priv = GABBLE_PRESENCE_CACHE_PRIV (cache); + const gchar *jid; + GabblePresence *presence; + + jid = gabble_handle_inspect (priv->conn->handles, TP_HANDLE_TYPE_CONTACT, + handle); + gabble_debug (DEBUG_FLAG, "%s (%d) resource %s prio %d presence %d message \"%s\"", + jid, handle, resource, priority, presence_id, status_message); + + presence = gabble_presence_cache_get (cache, handle); + + if (presence == NULL) + presence = _cache_insert (cache, handle); + + if (gabble_presence_update (presence, resource, presence_id, status_message, + priority)) + g_signal_emit (cache, signals[PRESENCE_UPDATE], 0, handle); + + gabble_presence_cache_maybe_remove (cache, handle); +} + +void gabble_presence_cache_add_bundle_caps (GabblePresenceCache *cache, + const gchar *node, GabblePresenceCapabilities new_caps) +{ + CapabilityInfo *info; + + info = capability_info_get (cache, node, 0); + info->trust = CAPABILITY_BUNDLE_ENOUGH_TRUST; + info->caps |= new_caps; +} +