telepathygabble/src/handles.c
changeset 0 d0f3a028347a
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/telepathygabble/src/handles.c	Tue Feb 02 01:10:06 2010 +0200
@@ -0,0 +1,985 @@
+/*
+ * handles.c - mechanism to store and retrieve handles on a connection
+ * Copyright (C) 2005 Collabora Ltd.
+ * 
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <glib.h>
+#include <dbus/dbus-glib.h>
+#include <string.h>
+
+#include "gheap.h"
+#include "handles.h"
+#include "handle-set.h"
+#include "telepathy-errors.h"
+#include "telepathy-helpers.h"
+#include "util.h"
+
+#include "config.h"
+
+#ifdef ENABLE_HANDLE_LEAK_DEBUG
+#include <stdlib.h>
+#include <stdio.h>
+#include <execinfo.h>
+
+	
+typedef struct _HandleLeakTrace HandleLeakTrace;
+
+struct _HandleLeakTrace
+{
+  char **trace;
+  int len;
+};
+
+static void
+handle_leak_trace_free (HandleLeakTrace *hltrace)
+{
+  free (hltrace->trace);
+  g_free (hltrace);
+}
+
+static void
+handle_leak_trace_free_gfunc (gpointer data, gpointer user_data)
+{
+  return handle_leak_trace_free ((HandleLeakTrace *) data);
+}
+
+#endif /* ENABLE_HANDLE_LEAK_DEBUG */
+
+/*#ifdef EMULATOR
+#include "libgabble_wsd_solution.h"
+	
+	gchar** _s_handles_list_handle_strings() { return (gchar**)((libgabble_ImpurePtr()->_s_handles_list_handle_strings)); }
+
+	#define list_handle_strings (GET_WSD_VAR_NAME(list_handle_strings,handles, s)())	
+	
+#endif*/
+	
+	
+typedef struct _GabbleHandlePriv GabbleHandlePriv;
+
+struct _GabbleHandlePriv
+{
+  guint refcount;
+  gchar *string;
+#ifdef ENABLE_HANDLE_LEAK_DEBUG
+  GSList *traces;
+#endif /* ENABLE_HANDLE_LEAK_DEBUG */
+  GData *datalist;
+};
+
+struct _GabbleHandleRepo
+{
+  GHashTable *contact_handles;
+  GHashTable *room_handles;
+  GData *list_handles;
+  GHashTable *contact_strings;
+  GHashTable *room_strings;
+  GHeap *free_contact_handles;
+  GHeap *free_room_handles;
+  guint contact_serial;
+  guint room_serial;
+  GData *client_contact_handle_sets;
+  GData *client_room_handle_sets;
+  DBusGProxy *bus_service_proxy;
+};
+
+//#ifndef EMULATOR
+static const char *list_handle_strings[GABBLE_LIST_HANDLE_DENY] =
+{
+    "publish",      /* GABBLE_LIST_HANDLE_PUBLISH */
+    "subscribe",    /* GABBLE_LIST_HANDLE_SUBSCRIBE */
+    "known",        /* GABBLE_LIST_HANDLE_KNOWN */
+    "deny"          /* GABBLE_LIST_HANDLE_DENY */
+};
+//#endif
+
+/* private functions */
+
+static GabbleHandlePriv *
+handle_priv_new ()
+{
+  GabbleHandlePriv *priv;
+
+  priv = g_new0 (GabbleHandlePriv, 1);
+
+  g_datalist_init (&(priv->datalist));
+  return priv;
+}
+
+static void
+handle_priv_free (GabbleHandlePriv *priv)
+{
+  g_assert (priv != NULL);
+
+  g_free(priv->string);
+  g_datalist_clear (&(priv->datalist));
+#ifdef ENABLE_HANDLE_LEAK_DEBUG
+  g_slist_foreach (priv->traces, handle_leak_trace_free_gfunc, NULL);
+  g_slist_free (priv->traces);
+#endif /* ENABLE_HANDLE_LEAK_DEBUG */
+  g_free (priv);
+}
+
+static GabbleHandlePriv *
+handle_priv_lookup (GabbleHandleRepo *repo,
+                    TpHandleType type,
+                    GabbleHandle handle)
+{
+  GabbleHandlePriv *priv = NULL;
+
+  g_assert (repo != NULL);
+  g_assert (gabble_handle_type_is_valid (type, NULL));
+  g_assert (handle != 0);
+
+  switch (type) {
+    case TP_HANDLE_TYPE_CONTACT:
+      priv = g_hash_table_lookup (repo->contact_handles, GINT_TO_POINTER (handle));
+      break;
+    case TP_HANDLE_TYPE_ROOM:
+      priv = g_hash_table_lookup (repo->room_handles, GINT_TO_POINTER (handle));
+      break;
+    case TP_HANDLE_TYPE_LIST:
+      priv = g_datalist_id_get_data (&repo->list_handles, handle);
+      break;
+    default:
+      g_assert_not_reached();
+    }
+
+  return priv;
+}
+
+static GabbleHandle
+gabble_handle_alloc (GabbleHandleRepo *repo, TpHandleType type)
+{
+  GabbleHandle ret = 0;
+
+  g_assert (repo != NULL);
+  g_assert (gabble_handle_type_is_valid (type, NULL));
+
+  switch (type) {
+    case TP_HANDLE_TYPE_CONTACT:
+      if (g_heap_size (repo->free_contact_handles))
+        ret = GPOINTER_TO_UINT (g_heap_extract_first (repo->free_contact_handles));
+      else
+        ret = repo->contact_serial++;
+      break;
+    case TP_HANDLE_TYPE_ROOM:
+      if (g_heap_size (repo->free_room_handles))
+        ret = GPOINTER_TO_UINT (g_heap_extract_first (repo->free_room_handles));
+      else
+        ret = repo->room_serial++;
+      break;
+    default:
+      g_assert_not_reached();
+    }
+
+  return ret;
+}
+
+static gint
+handle_compare_func (gconstpointer a, gconstpointer b)
+{
+  GabbleHandle first = GPOINTER_TO_UINT (a);
+  GabbleHandle second = GPOINTER_TO_UINT (b);
+
+  return (first == second) ? 0 : ((first < second) ? -1 : 1);
+}
+
+static void
+handle_priv_remove (GabbleHandleRepo *repo,
+                    TpHandleType type,
+                    GabbleHandle handle)
+{
+  GabbleHandlePriv *priv;
+  const gchar *string;
+
+  g_assert (gabble_handle_type_is_valid (type, NULL));
+  g_assert (handle != 0);
+  g_assert (repo != NULL);
+
+  priv = handle_priv_lookup (repo, type, handle);
+
+  g_assert (priv != NULL);
+
+  string = priv->string;
+
+  switch (type) {
+    case TP_HANDLE_TYPE_CONTACT:
+      g_hash_table_remove (repo->contact_strings, string);
+      g_hash_table_remove (repo->contact_handles, GINT_TO_POINTER (handle));
+      if (handle == repo->contact_serial-1)
+        repo->contact_serial--;
+      else
+        g_heap_add (repo->free_contact_handles, GUINT_TO_POINTER (handle));
+      break;
+    case TP_HANDLE_TYPE_ROOM:
+      g_hash_table_remove (repo->room_strings, string);
+      g_hash_table_remove (repo->room_handles, GINT_TO_POINTER (handle));
+      if (handle == repo->room_serial-1)
+        repo->room_serial--;
+      else
+        g_heap_add (repo->free_room_handles, GUINT_TO_POINTER (handle));
+      break;
+    case TP_HANDLE_TYPE_LIST:
+      g_dataset_id_remove_data (&repo->list_handles, handle);
+      break;
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+static void
+handles_name_owner_changed_cb (DBusGProxy *proxy,
+                               const gchar *name,
+                               const gchar *old_owner,
+                               const gchar *new_owner,
+                               gpointer data)
+{
+  GabbleHandleRepo *repo = (GabbleHandleRepo *) data;
+
+  if (old_owner && strlen (old_owner))
+    {
+      if (!new_owner || !strlen (new_owner))
+        {
+          g_datalist_remove_data (&repo->client_contact_handle_sets, old_owner);
+          g_datalist_remove_data (&repo->client_room_handle_sets, old_owner);
+        }
+    }
+}
+
+/* public API */
+
+/**
+ * gabble_handle_jid_is_valid
+ *
+ * Validates a jid for given handle type and returns TRUE/FALSE
+ * on success/failure. In the latter case further information is
+ * provided through error if set.
+ */
+gboolean
+gabble_handle_jid_is_valid (TpHandleType type, const gchar *jid, GError **error)
+{
+  if (type == TP_HANDLE_TYPE_CONTACT || type == TP_HANDLE_TYPE_ROOM)
+    {
+      if (!strchr (jid, '@'))
+        {
+          g_debug ("%s: jid %s has no @", G_STRFUNC, jid);
+
+          g_set_error (error, TELEPATHY_ERRORS, InvalidArgument,
+              "jid %s has no @", jid);
+
+          return FALSE;
+        }
+
+      /* FIXME: do more extensive checking */
+    }
+  else
+    {
+      g_assert_not_reached ();
+      /* FIXME: add checking for other types here */
+    }
+
+  return TRUE;
+}
+
+gboolean
+gabble_handle_type_is_valid (TpHandleType type, GError **error)
+{
+  gboolean ret;
+
+  if (type > TP_HANDLE_TYPE_NONE && type <= TP_HANDLE_TYPE_LIST)
+    {
+      ret = TRUE;
+    }
+  else
+    {
+      g_set_error (error, TELEPATHY_ERRORS, InvalidArgument,
+          "invalid handle type %u", type);
+      ret = FALSE;
+    }
+
+  return ret;
+}
+
+
+GabbleHandleRepo *
+gabble_handle_repo_new ()
+{
+  GabbleHandleRepo *repo;
+  GabbleHandle publish, subscribe, known, deny;
+
+  repo = g_new0 (GabbleHandleRepo, 1);
+
+  repo->contact_handles = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) handle_priv_free);
+
+  repo->room_handles = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) handle_priv_free);
+
+  repo->contact_strings = g_hash_table_new (g_str_hash, g_str_equal);
+  repo->room_strings = g_hash_table_new (g_str_hash, g_str_equal);
+
+  repo->free_contact_handles = g_heap_new (handle_compare_func);
+  repo->free_room_handles = g_heap_new (handle_compare_func);
+
+  repo->contact_serial = 1;
+  repo->room_serial = 1;
+
+  g_datalist_init (&repo->list_handles);
+
+  publish = GABBLE_LIST_HANDLE_PUBLISH;
+  g_datalist_id_set_data_full (&repo->list_handles, (GQuark) publish,
+      handle_priv_new(), (GDestroyNotify) handle_priv_free);
+
+  subscribe = GABBLE_LIST_HANDLE_SUBSCRIBE;
+  g_datalist_id_set_data_full (&repo->list_handles, (GQuark) subscribe,
+      handle_priv_new(), (GDestroyNotify) handle_priv_free);
+
+  known = GABBLE_LIST_HANDLE_KNOWN;
+  g_datalist_id_set_data_full (&repo->list_handles, (GQuark) known,
+      handle_priv_new(), (GDestroyNotify) handle_priv_free);
+
+  deny = GABBLE_LIST_HANDLE_DENY;
+  g_datalist_id_set_data_full (&repo->list_handles, (GQuark) deny,
+      handle_priv_new(), (GDestroyNotify) handle_priv_free);
+
+  g_datalist_init (&repo->client_contact_handle_sets);
+  g_datalist_init (&repo->client_room_handle_sets);
+
+  repo->bus_service_proxy = dbus_g_proxy_new_for_name (tp_get_bus(),
+                                                       DBUS_SERVICE_DBUS,
+                                                       DBUS_PATH_DBUS,
+                                                       DBUS_INTERFACE_DBUS);
+
+  dbus_g_proxy_add_signal (repo->bus_service_proxy,
+                           "NameOwnerChanged",
+                           G_TYPE_STRING,
+                           G_TYPE_STRING,
+                           G_TYPE_STRING,
+                           G_TYPE_INVALID);
+  dbus_g_proxy_connect_signal (repo->bus_service_proxy,
+                               "NameOwnerChanged",
+                               G_CALLBACK (handles_name_owner_changed_cb),
+                               repo,
+                               NULL);
+
+  return repo;
+}
+
+#ifdef ENABLE_HANDLE_LEAK_DEBUG
+
+static void
+handle_leak_debug_printbt_foreach (gpointer data, gpointer user_data)
+{
+  HandleLeakTrace *hltrace = (HandleLeakTrace *) data;
+  int i;
+
+  for (i = 1; i < hltrace->len; i++)
+    {
+      g_message ("\t\t%s\n", hltrace->trace[i]);
+    }
+
+  g_message ("\n");
+}
+
+static void
+handle_leak_debug_printhandles_foreach (gpointer key, gpointer value, gpointer ignore)
+{
+  GabbleHandle handle = GPOINTER_TO_UINT (key);
+  GabbleHandlePriv *priv = (GabbleHandlePriv *) value;
+
+  g_message ("\t%5u: %s (%u refs), traces:\n", handle, priv->string, priv->refcount);
+  
+  g_slist_foreach (priv->traces, handle_leak_debug_printbt_foreach, NULL);
+}
+
+static void
+handle_leak_debug_print_report (GabbleHandleRepo *repo)
+{
+  g_assert (repo != NULL);
+
+  g_message ("The following contact handles were not freed:\n");
+  g_hash_table_foreach (repo->contact_handles, handle_leak_debug_printhandles_foreach, NULL);
+  g_message ("The following room handles were not freed:\n");
+  g_hash_table_foreach (repo->room_handles, handle_leak_debug_printhandles_foreach, NULL);
+}
+
+static HandleLeakTrace *
+handle_leak_debug_bt ()
+{
+  void *bt_addresses[16];
+  HandleLeakTrace *ret = g_new0 (HandleLeakTrace, 1);
+  
+  ret->len = backtrace (bt_addresses, 16);
+  ret->trace = backtrace_symbols (bt_addresses, ret->len);
+
+  return ret;
+}
+
+#define HANDLE_LEAK_DEBUG_DO(traces_slist) \
+  { (traces_slist) =  g_slist_append ((traces_slist), handle_leak_debug_bt ()); }
+
+#else /* !ENABLE_HANDLE_LEAK_DEBUG */
+
+#define HANDLE_LEAK_DEBUG_DO(traces_slist) {}
+
+#endif /* ENABLE_HANDLE_LEAK_DEBUG */
+
+
+void
+gabble_handle_repo_destroy (GabbleHandleRepo *repo)
+{
+  g_assert (repo != NULL);
+  g_assert (repo->contact_handles);
+  g_assert (repo->room_handles);
+  g_assert (repo->contact_strings);
+  g_assert (repo->room_strings);
+
+  g_datalist_clear (&repo->client_contact_handle_sets);
+  g_datalist_clear (&repo->client_room_handle_sets);
+
+#ifdef ENABLE_HANDLE_LEAK_DEBUG
+  handle_leak_debug_print_report (repo);
+#endif /* ENABLE_HANDLE_LEAK_DEBUG */
+
+  g_hash_table_destroy (repo->contact_handles);
+  g_hash_table_destroy (repo->room_handles);
+  g_hash_table_destroy (repo->contact_strings);
+  g_hash_table_destroy (repo->room_strings);
+  g_heap_destroy (repo->free_contact_handles);
+  g_heap_destroy (repo->free_room_handles);
+  g_datalist_clear (&repo->list_handles);
+
+  dbus_g_proxy_disconnect_signal (repo->bus_service_proxy,
+                                  "NameOwnerChanged",
+                                  G_CALLBACK (handles_name_owner_changed_cb),
+                                  repo);
+  g_object_unref (G_OBJECT (repo->bus_service_proxy));
+
+  g_free (repo);
+}
+
+
+gboolean
+gabble_handle_is_valid (GabbleHandleRepo *repo, TpHandleType type, GabbleHandle handle, GError **error)
+{
+  GArray *arr;
+  gboolean ret;
+
+  arr = g_array_new (FALSE, FALSE, sizeof (GabbleHandle));
+  g_array_insert_val (arr, 0, handle);
+
+  ret = gabble_handles_are_valid (repo, type, arr, FALSE, error);
+
+  g_array_free (arr, TRUE);
+
+  return ret;
+}
+
+gboolean
+gabble_handles_are_valid (GabbleHandleRepo *repo,
+                          TpHandleType type,
+                          const GArray *array,
+                          gboolean allow_zero,
+                          GError **error)
+{
+  guint i;
+
+  g_return_val_if_fail (repo != NULL, FALSE);
+  g_return_val_if_fail (array != NULL, FALSE);
+
+  if (!gabble_handle_type_is_valid (type, error))
+    return FALSE;
+
+  for (i = 0; i < array->len; i++)
+    {
+      GabbleHandle handle = g_array_index (array, GabbleHandle, i);
+
+      if (handle == 0)
+        {
+          if (allow_zero)
+              continue;
+
+          g_debug ("someone tried to validate handle zero");
+
+          g_set_error (error, TELEPATHY_ERRORS, InvalidArgument,
+              "invalid handle %u", handle);
+          return FALSE;
+        }
+
+      if (handle_priv_lookup (repo, type, handle) == NULL)
+        {
+          g_set_error (error, TELEPATHY_ERRORS, InvalidArgument,
+              "invalid handle %u", handle);
+          return FALSE;
+        }
+    }
+
+  return TRUE;
+}
+
+
+gboolean
+gabble_handle_ref (GabbleHandleRepo *repo,
+                   TpHandleType type,
+                   GabbleHandle handle)
+{
+  GabbleHandlePriv *priv;
+
+  if (type == TP_HANDLE_TYPE_LIST)
+    {
+      if (handle >= GABBLE_LIST_HANDLE_PUBLISH && handle <= GABBLE_LIST_HANDLE_DENY)
+        return TRUE;
+      else
+        return FALSE;
+    }
+
+  priv = handle_priv_lookup (repo, type, handle);
+
+  if (priv == NULL)
+    return FALSE;
+
+  priv->refcount++;
+
+  HANDLE_LEAK_DEBUG_DO (priv->traces);
+
+  return TRUE;
+}
+
+
+gboolean
+gabble_handle_unref (GabbleHandleRepo *repo,
+                     TpHandleType type,
+                     GabbleHandle handle)
+{
+  GabbleHandlePriv *priv;
+
+  if (type == TP_HANDLE_TYPE_LIST)
+    {
+      if (handle >= GABBLE_LIST_HANDLE_PUBLISH && handle <= GABBLE_LIST_HANDLE_DENY)
+        return TRUE;
+      else
+        return FALSE;
+    }
+
+  priv = handle_priv_lookup (repo, type, handle);
+
+  if (priv == NULL)
+    return FALSE;
+
+  HANDLE_LEAK_DEBUG_DO (priv->traces);
+
+  g_assert (priv->refcount > 0);
+
+  priv->refcount--;
+
+  if (priv->refcount == 0)
+    handle_priv_remove (repo, type, handle);
+
+  return TRUE;
+}
+
+
+const char *
+gabble_handle_inspect (GabbleHandleRepo *repo,
+                       TpHandleType type,
+                       GabbleHandle handle)
+{
+  GabbleHandlePriv *priv;
+
+  if (type == TP_HANDLE_TYPE_LIST)
+    {
+      g_assert (handle >= GABBLE_LIST_HANDLE_PUBLISH
+                  && handle <= GABBLE_LIST_HANDLE_DENY);
+      return list_handle_strings[handle-1];
+    }
+
+  priv = handle_priv_lookup (repo, type, handle);
+
+  if (priv == NULL)
+    return NULL;
+  else
+    return priv->string;
+}
+
+static GabbleHandle
+_handle_lookup_by_jid (GabbleHandleRepo *repo,
+                       const gchar *jid)
+{
+  GabbleHandle handle;
+
+  handle = GPOINTER_TO_UINT (g_hash_table_lookup (repo->contact_strings, jid));
+
+  if (0 == handle)
+    return 0;
+
+  return handle;
+}
+
+
+GabbleHandle
+gabble_handle_for_contact (GabbleHandleRepo *repo,
+                           const char *jid,
+                           gboolean with_resource)
+{
+  char *username = NULL;
+  char *server = NULL;
+  char *resource = NULL;
+  char *clean_jid = NULL;
+  GabbleHandle handle = 0;
+  GabbleHandlePriv *priv;
+
+  g_assert (repo != NULL);
+  g_assert (jid != NULL);
+  g_assert (*jid != '\0');
+
+  gabble_decode_jid (jid, &username, &server, &resource);
+
+  if (NULL == username || '\0' == *username)
+    goto OUT;
+
+  if (NULL == resource && with_resource)
+    goto OUT;
+
+  if (NULL != resource)
+    {
+      clean_jid = g_strdup_printf ("%s@%s/%s", username, server, resource);
+      handle = _handle_lookup_by_jid (repo, clean_jid);
+
+      if (0 != handle)
+        goto OUT;
+    }
+
+  if (!with_resource)
+    {
+      g_free (clean_jid);
+      clean_jid = g_strdup_printf ("%s@%s", username, server);
+      handle = _handle_lookup_by_jid (repo, clean_jid);
+
+      if (0 != handle)
+        goto OUT;
+    }
+
+  handle = gabble_handle_alloc (repo, TP_HANDLE_TYPE_CONTACT);
+  priv = handle_priv_new ();
+  priv->string = clean_jid;
+  clean_jid = NULL;
+  g_hash_table_insert (repo->contact_handles, GINT_TO_POINTER (handle), priv);
+  g_hash_table_insert (repo->contact_strings, priv->string, GUINT_TO_POINTER (handle));
+
+  HANDLE_LEAK_DEBUG_DO (priv->traces);
+
+OUT:
+
+  g_free (clean_jid);
+  g_free (username);
+  g_free (server);
+  g_free (resource);
+  return handle;
+}
+
+gboolean
+gabble_handle_for_room_exists (GabbleHandleRepo *repo,
+                               const gchar *jid,
+                               gboolean ignore_nick)
+{
+  GabbleHandle handle;
+  gchar *room, *service, *nick;
+  gchar *clean_jid;
+
+  gabble_decode_jid (jid, &room, &service, &nick);
+
+  if (!room || !service || room[0] == '\0')
+    return FALSE;
+
+  if (ignore_nick || !nick)
+    clean_jid = g_strdup_printf ("%s@%s", room, service);
+  else
+    clean_jid = g_strdup_printf ("%s@%s/%s", room, service, nick);
+
+  handle = GPOINTER_TO_UINT (g_hash_table_lookup (repo->room_strings,
+                                                  clean_jid));
+  
+  g_free (clean_jid);
+  g_free (room);
+  g_free (service);
+  g_free (nick);
+
+  if (handle == 0)
+    return FALSE;
+
+  return (handle_priv_lookup (repo, TP_HANDLE_TYPE_ROOM, handle) != NULL);
+}
+
+
+GabbleHandle
+gabble_handle_for_room (GabbleHandleRepo *repo,
+                        const gchar *jid)
+{
+  GabbleHandle handle;
+  gchar *room, *service, *clean_jid;
+
+  g_assert (repo != NULL);
+  g_assert (jid != NULL);
+  g_assert (*jid != '\0');
+
+  handle = 0;
+
+  room = service = NULL;
+  gabble_decode_jid (jid, &room, &service, NULL);
+
+  if (room && service && *room != '\0')
+    {
+      clean_jid = g_strdup_printf ("%s@%s", room, service);
+
+      handle = GPOINTER_TO_UINT (g_hash_table_lookup (repo->room_strings, clean_jid));
+
+      if (handle == 0)
+        {
+          GabbleHandlePriv *priv;
+          handle = gabble_handle_alloc (repo, TP_HANDLE_TYPE_ROOM);
+          priv = handle_priv_new ();
+          priv->string = clean_jid;
+          g_hash_table_insert (repo->room_handles, GUINT_TO_POINTER (handle), priv);
+          g_hash_table_insert (repo->room_strings, clean_jid, GUINT_TO_POINTER (handle));
+          HANDLE_LEAK_DEBUG_DO (priv->traces);
+        }
+      else
+        {
+          g_free (clean_jid);
+        }
+    }
+
+  g_free (room);
+  g_free (service);
+
+  return handle;
+}
+
+
+GabbleHandle
+gabble_handle_for_list (GabbleHandleRepo *repo,
+                        const gchar *list)
+{
+  GabbleHandle handle = 0;
+  int i;
+
+  g_assert (repo != NULL);
+  g_assert (list != NULL);
+
+  for (i = 0; i < GABBLE_LIST_HANDLE_DENY; i++)
+    {
+      if (0 == strcmp (list_handle_strings[i], list))
+        handle = (GabbleHandle) i + 1;
+    }
+
+  return handle;
+}
+
+/**
+ * gabble_handle_set_qdata:
+ * @repo: A #GabbleHandleRepo
+ * @type: The handle type
+ * @handle: A handle to set data on
+ * @key_id: Key id to associate data with
+ * @data: data to associate with handle
+ * @destroy: A #GDestroyNotify to call to detroy the data,
+ *           or NULL if not needed.
+ *
+ * Associates a blob of data with a given handle and a given key
+ *
+ * If @destroy is set, then the data is freed when the handle is freed.
+ */
+
+gboolean
+gabble_handle_set_qdata (GabbleHandleRepo *repo,
+                         TpHandleType type, GabbleHandle handle,
+                         GQuark key_id, gpointer data, GDestroyNotify destroy)
+{
+  GabbleHandlePriv *priv;
+  priv = handle_priv_lookup (repo, type, handle);
+
+  if (!priv)
+    return FALSE;
+
+  g_datalist_id_set_data_full (&priv->datalist, key_id, data, destroy);
+  return TRUE;
+}
+
+/**
+ * gabble_handle_get_qdata:
+ * @repo: A #GabbleHandleRepo
+ * @type: The handle type
+ * @handle: A handle to get data from
+ * @key_id: Key id of data to fetch
+ *
+ * Gets the data associated with a given key on a given handle
+ */
+gpointer
+gabble_handle_get_qdata (GabbleHandleRepo *repo,
+                         TpHandleType type, GabbleHandle handle,
+                         GQuark key_id)
+{
+  GabbleHandlePriv *priv;
+  priv = handle_priv_lookup (repo, type, handle);
+
+  if (!priv)
+    return NULL;
+
+  return g_datalist_id_get_data(&priv->datalist, key_id);
+}
+
+/**
+ * gabble_handle_client_hold:
+ * @repo: a #GabbleHandleRepo
+ * @client_name: D-Bus bus name of client to hold the handle for
+ * @handle: the handle to hold
+ * @type: type of handle to hold
+ * @error: used to return a pointer to a GError detailing any error that occurred
+ *
+ * Marks a handle as held by a given client.
+ *
+ * Returns: Whether the handle was succesfully marked as held or an error occurred.
+ */
+
+gboolean
+gabble_handle_client_hold (GabbleHandleRepo *repo,
+                           const gchar *client_name,
+                           GabbleHandle handle,
+                           TpHandleType type,
+                           GError **error)
+{
+  GData **handle_set_list;
+  GabbleHandleSet *handle_set;
+
+  g_assert (repo != NULL);
+
+  switch (type)
+    {
+    case TP_HANDLE_TYPE_CONTACT:
+      handle_set_list = &repo->client_contact_handle_sets;
+      break;
+    case TP_HANDLE_TYPE_ROOM:
+      handle_set_list = &repo->client_room_handle_sets;
+      break;
+    case TP_HANDLE_TYPE_LIST:
+      /* no-op */
+      return TRUE;
+    default:
+      g_critical ("%s: called with invalid handle type %u", G_STRFUNC, type);
+      g_set_error (error, TELEPATHY_ERRORS, InvalidArgument,
+          "invalid handle type %u", type);
+      return FALSE;
+    }
+
+  if (!client_name || *client_name == '\0')
+    {
+      g_critical ("%s: called with invalid client name", G_STRFUNC);
+      g_set_error (error, TELEPATHY_ERRORS, InvalidArgument,
+          "invalid client name");
+      return FALSE;
+    }
+
+  handle_set = (GabbleHandleSet*) g_datalist_get_data (handle_set_list, client_name);
+
+  if (!handle_set)
+    {
+      handle_set = handle_set_new (repo, type);
+      g_datalist_set_data_full (handle_set_list,
+                                client_name,
+                                handle_set,
+                                (GDestroyNotify) handle_set_destroy);
+    }
+
+  handle_set_add (handle_set, handle);
+
+  return TRUE;
+}
+
+/**
+ * gabble_handle_client_release:
+ * @repo: a #GabbleHandleRepo
+ * @client_name: D-Bus bus name of client to release the handle for
+ * @handle: the handle to release
+ * @type: type of handle to release
+ * @error: used to return a pointer to a GError detailing any error that occurred
+ *
+ * Unmarks a handle as held by a given client.
+ *
+ * Returns: Whether the handle had been marked as held by the given client and now unmarked or not.
+ */
+
+gboolean
+gabble_handle_client_release (GabbleHandleRepo *repo,
+                           const gchar *client_name,
+                           GabbleHandle handle,
+                           TpHandleType type,
+                           GError **error)
+{
+  GData **handle_set_list;
+  GabbleHandleSet *handle_set;
+
+  g_assert (repo != NULL);
+
+  switch (type)
+    {
+    case TP_HANDLE_TYPE_CONTACT:
+      handle_set_list = &repo->client_contact_handle_sets;
+      break;
+    case TP_HANDLE_TYPE_ROOM:
+      handle_set_list = &repo->client_room_handle_sets;
+      break;
+    case TP_HANDLE_TYPE_LIST:
+      /* no-op */
+      return TRUE;
+    default:
+      g_critical ("%s: called with invalid handle type %u", G_STRFUNC, type);
+      g_set_error (error, TELEPATHY_ERRORS, InvalidArgument,
+          "invalid handle type %u", type);
+      return FALSE;
+    }
+
+  if (!client_name || *client_name == '\0')
+    {
+      g_critical ("%s: called with invalid client name", G_STRFUNC);
+      g_set_error (error, TELEPATHY_ERRORS, InvalidArgument,
+          "invalid client name");
+      return FALSE;
+    }
+
+  handle_set = (GabbleHandleSet*) g_datalist_get_data (handle_set_list, client_name);
+
+  if (!handle_set)
+    {
+      g_critical ("%s: no handle set found for the given client %s", G_STRFUNC, client_name);
+      g_set_error (error, TELEPATHY_ERRORS, NotAvailable,
+          "the given client %s wasn't holding any handles", client_name);
+      return FALSE;
+    }
+
+  if (!handle_set_remove (handle_set, handle))
+    {
+      g_critical ("%s: the client %s wasn't holding the handle %u", G_STRFUNC,
+          client_name, handle);
+      g_set_error (error, TELEPATHY_ERRORS, NotAvailable,
+          "the given client %s wasn't holding the handle %u", client_name,
+          handle);
+      return FALSE;
+    }
+
+  return TRUE;
+}
+