[Telepathy-commits] [telepathy-glib/master] TpConnection: add handle ref/hold/release functionality

Simon McVittie simon.mcvittie at collabora.co.uk
Tue Oct 7 07:07:38 PDT 2008


This centralizes handle ref tracking, because otherwise calling
ReleaseHandles is inherently unsafe - you never know whether
another part of the process is still using the handles.

In this implementation the handles are stored in a data-slot on the
DBusConnection.

See mailing list thread "Uniqueness of objects", starting
with Message-ID: <20080930200125.GA15304 at carbon.pseudorandom.co.uk>
---
 docs/reference/telepathy-glib-sections.txt |    5 +
 telepathy-glib/Makefile.am                 |    1 +
 telepathy-glib/connection-handles.c        |  619 ++++++++++++++++++++++++++++
 telepathy-glib/connection-internal.h       |    2 +
 telepathy-glib/connection.c                |    3 +
 telepathy-glib/connection.h                |   25 ++
 6 files changed, 655 insertions(+), 0 deletions(-)
 create mode 100644 telepathy-glib/connection-handles.c

diff --git a/docs/reference/telepathy-glib-sections.txt b/docs/reference/telepathy-glib-sections.txt
index b6aa801..81bd554 100644
--- a/docs/reference/telepathy-glib-sections.txt
+++ b/docs/reference/telepathy-glib-sections.txt
@@ -2053,6 +2053,11 @@ TpConnectionWhenReadyCb
 tp_connection_call_when_ready
 tp_connection_is_ready
 tp_connection_get_status
+TpConnectionRequestHandlesCb
+tp_connection_request_handles
+TpConnectionHoldHandlesCb
+tp_connection_hold_handles
+tp_connection_unref_handles
 tp_connection_init_known_interfaces
 tp_connection_presence_type_cmp_availability
 TpConnection
diff --git a/telepathy-glib/Makefile.am b/telepathy-glib/Makefile.am
index f05ef50..821e4df 100644
--- a/telepathy-glib/Makefile.am
+++ b/telepathy-glib/Makefile.am
@@ -212,6 +212,7 @@ libtelepathy_glib_internal_la_SOURCES = \
     channel-manager.c \
     connection.c \
     connection-internal.h \
+    connection-handles.c \
     connection-manager.c \
     contacts-mixin.c \
     dbus.c \
diff --git a/telepathy-glib/connection-handles.c b/telepathy-glib/connection-handles.c
new file mode 100644
index 0000000..9b53959
--- /dev/null
+++ b/telepathy-glib/connection-handles.c
@@ -0,0 +1,619 @@
+/* Helper to hold Telepathy handles.
+ *
+ * Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 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
+ */
+
+#include "telepathy-glib/connection-internal.h"
+
+#include <dbus/dbus.h>
+#include <dbus/dbus-glib-lowlevel.h>
+
+#define DEBUG_FLAG TP_DEBUG_HANDLES
+#include "telepathy-glib/debug-internal.h"
+
+/*
+ * We allocate a global libdbus DBusConnection slot for this module. If
+ * used, it contains a
+ * GHashTable<gchar *Connection_object_path => Bucket>.
+ *
+ * A Bucket is basically just an array of GHashTable<handle => refcount>.
+ *
+ * This is external to the TpConnection because it has to be - if there
+ * are two TpConnection instances for the same Connection (perhaps they're
+ * of different subclasses), we need to share handle references between them.
+ */
+
+typedef struct {
+    GHashTable *refcounts[NUM_TP_HANDLE_TYPES];
+} Bucket;
+
+static inline void oom (void) G_GNUC_NORETURN;
+
+static inline void
+oom (void)
+{
+  g_error ("Out of memory in libdbus. Cannot have a bucket");
+}
+
+static void
+bucket_free (gpointer p)
+{
+  Bucket *b = p;
+  guint i;
+
+  /* [0] is never allocated (handle type NONE), so start at [1] */
+  g_assert (b->refcounts[0] == NULL);
+
+  for (i = 1; i < NUM_TP_HANDLE_TYPES; i++)
+    {
+      if (b->refcounts[i] != NULL)
+        g_hash_table_destroy (b->refcounts[i]);
+    }
+
+  g_slice_free (Bucket, p);
+}
+
+static Bucket *
+bucket_new (void)
+{
+  return g_slice_new0 (Bucket);
+}
+
+static dbus_int32_t connection_handle_refs_slot = -1;
+
+static void
+_tp_connection_ref_handles (TpConnection *connection,
+                            TpHandleType handle_type,
+                            const GArray *handles)
+{
+  TpProxy *as_proxy = (TpProxy *) connection;
+  DBusGConnection *g_connection = as_proxy->dbus_connection;
+  const gchar *object_path = as_proxy->object_path;
+  GHashTable *table, *refcounts;
+  Bucket *bucket = NULL;
+  guint i;
+
+  g_assert (as_proxy->invalidated == NULL);
+  g_assert (handle_type < NUM_TP_HANDLE_TYPES);
+
+  DEBUG ("%p: %u handles of type %u", connection, handles->len, handle_type);
+
+  /* MT: libdbus protects us, if so configured */
+  if (!dbus_connection_allocate_data_slot (&connection_handle_refs_slot))
+    oom ();
+
+  /* MT: if we become thread safe, the rest of this function needs a lock */
+  table = dbus_connection_get_data (dbus_g_connection_get_connection (
+        g_connection), connection_handle_refs_slot);
+
+  if (table == NULL)
+    {
+      table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
+          bucket_free);
+
+      if (!dbus_connection_set_data (dbus_g_connection_get_connection (
+              g_connection), connection_handle_refs_slot, table,
+            (DBusFreeFunction) g_hash_table_destroy))
+        oom ();
+    }
+  else
+    {
+      bucket = g_hash_table_lookup (table, object_path);
+    }
+
+  if (bucket == NULL)
+    {
+      bucket = bucket_new ();
+      g_hash_table_insert (table, g_strdup (object_path), bucket);
+    }
+
+  if (bucket->refcounts[handle_type] == NULL)
+    bucket->refcounts[handle_type] = g_hash_table_new (g_direct_hash,
+        g_direct_equal);
+
+  refcounts = bucket->refcounts[handle_type];
+
+  for (i = 0; i < handles->len; i++)
+    {
+      gpointer handle = GUINT_TO_POINTER (g_array_index (handles, guint, i));
+      gsize r = GPOINTER_TO_SIZE (g_hash_table_lookup (refcounts, handle));
+
+      g_hash_table_insert (refcounts, handle, GSIZE_TO_POINTER (r + 1));
+    }
+}
+
+
+static void
+post_unref (TpConnection *connection,
+            const GError *error,
+            gpointer user_data,
+            GObject *weak_object)
+{
+  GArray *arr = user_data;
+
+  if (error == NULL)
+    {
+      DEBUG ("Released %u handles", arr->len);
+    }
+  else
+    {
+      guint i;
+
+      g_message ("Failed to release %u handles: %s %u: %s",
+          arr->len, g_quark_to_string (error->domain), error->code,
+          error->message);
+
+      for (i = 0; i < arr->len; i++)
+        {
+          g_message ("   %u", g_array_index (arr, guint, i));
+        }
+    }
+}
+
+
+static void
+array_free_TRUE (gpointer p)
+{
+  g_array_free (p, TRUE);
+}
+
+
+/* Clean up after the connection is invalidated */
+void
+_tp_connection_clean_up_handle_refs (TpConnection *self)
+{
+  TpProxy *as_proxy = (TpProxy *) self;
+  DBusGConnection *g_connection = as_proxy->dbus_connection;
+  const gchar *object_path = as_proxy->object_path;
+  GHashTable *table;
+
+  DEBUG ("%p", self);
+
+  g_assert (as_proxy->invalidated != NULL);
+
+  if (connection_handle_refs_slot == -1)
+    return;
+
+  /* MT: if we become thread safe, the rest of this function needs a lock */
+  table = dbus_connection_get_data (dbus_g_connection_get_connection (
+        g_connection), connection_handle_refs_slot);
+
+  if (table == NULL)
+    return;
+
+  if (g_hash_table_remove (table, object_path) &&
+      g_hash_table_size (table) == 0)
+    {
+      /* this calls the destructor, g_hash_table_destroy */
+      dbus_connection_set_data (dbus_g_connection_get_connection (
+            g_connection), connection_handle_refs_slot, NULL, NULL);
+    }
+}
+
+
+/**
+ * tp_connection_unref_handles:
+ * @self: a connection
+ * @handle_type: a handle type
+ * @n_handles: the number of handles in @handles
+ * @handles: an array of @n_handles handles
+ *
+ * Release the reference to the handles in @handles that was obtained by
+ * calling tp_connection_hold_handles() or tp_connection_request_handles().
+ */
+void
+tp_connection_unref_handles (TpConnection *self,
+                             TpHandleType handle_type,
+                             guint n_handles,
+                             const TpHandle *handles)
+{
+  TpProxy *as_proxy = (TpProxy *) self;
+  DBusGConnection *g_connection = as_proxy->dbus_connection;
+  const gchar *object_path = as_proxy->object_path;
+  GHashTable *table, *refcounts;
+  Bucket *bucket = NULL;
+  guint i;
+  GArray *unref;
+
+  DEBUG ("%p: %u handles of type %u", self, n_handles, handle_type);
+
+  if (as_proxy->invalidated != NULL)
+    {
+      return;
+    }
+
+  g_assert (handle_type < NUM_TP_HANDLE_TYPES);
+
+  /* MT: libdbus protects us, if so configured */
+  if (!dbus_connection_allocate_data_slot (&connection_handle_refs_slot))
+    oom ();
+
+  /* MT: if we become thread safe, the rest of this function needs a lock */
+  table = dbus_connection_get_data (dbus_g_connection_get_connection (
+        g_connection), connection_handle_refs_slot);
+
+  /* if there's no table, then we can't have a ref to the handles -
+   * user error */
+  g_return_if_fail (table != NULL);
+
+  bucket = g_hash_table_lookup (table, object_path);
+
+  /* if there's no bucket, then we can't have a ref to the handles -
+   * user error */
+  g_return_if_fail (bucket != NULL);
+  g_return_if_fail (bucket->refcounts[handle_type] != NULL);
+
+  refcounts = bucket->refcounts[handle_type];
+
+  unref = g_array_sized_new (FALSE, FALSE, sizeof (guint), n_handles);
+
+  for (i = 0; i < n_handles; i++)
+    {
+      gpointer handle = GUINT_TO_POINTER (handles[i]);
+      gsize r = GPOINTER_TO_SIZE (g_hash_table_lookup (refcounts, handle));
+
+      g_return_if_fail (handles[i] != 0);
+      /* if we have no refs, it's user error */
+      g_return_if_fail (r != 0);
+
+      if (r == 1)
+        {
+          DEBUG ("releasing handle %u", handles[i]);
+          g_array_append_val (unref, handles[i]);
+          g_hash_table_remove (refcounts, handle);
+        }
+      else
+        {
+          DEBUG ("decrementing handle %u to %" G_GSIZE_FORMAT, handles[i],
+              r - 1);
+          g_hash_table_insert (refcounts, handle, GSIZE_TO_POINTER (r - 1));
+        }
+    }
+
+  /* fire off the unref call asynchronously, ignore error if any.
+   * FIXME: perhaps this should be done idly, so we can combine unrefs? */
+  if (unref->len > 0)
+    {
+      DEBUG ("releasing %u handles", unref->len);
+
+      tp_cli_connection_call_release_handles (self, -1,
+          handle_type, unref, post_unref, unref, array_free_TRUE, NULL);
+    }
+
+  if (g_hash_table_size (refcounts) == 0)
+    {
+      g_hash_table_destroy (refcounts);
+      bucket->refcounts[handle_type] = NULL;
+
+      for (i = 1; i < NUM_TP_HANDLE_TYPES; i++)
+        {
+          if (bucket->refcounts[handle_type] != NULL)
+            return;
+        }
+
+      /* if still here, then there are no refs left on this connection */
+      g_hash_table_remove (table, object_path);
+
+      if (g_hash_table_size (table) == 0)
+        {
+          /* this calls the destructor, g_hash_table_destroy */
+          dbus_connection_set_data (dbus_g_connection_get_connection (
+                g_connection), connection_handle_refs_slot, NULL, NULL);
+        }
+    }
+}
+
+
+typedef struct {
+    TpHandleType handle_type;
+    GArray *handles;
+    gpointer user_data;
+    GDestroyNotify destroy;
+    TpConnectionHoldHandlesCb callback;
+} HoldHandlesContext;
+
+
+static void
+hold_handles_context_free (gpointer p)
+{
+  HoldHandlesContext *context = p;
+
+  if (context->destroy != NULL)
+    context->destroy (context->user_data);
+
+  g_array_free (context->handles, TRUE);
+
+  g_slice_free (HoldHandlesContext, context);
+}
+
+/**
+ * TpConnectionHoldHandlesCb:
+ * @connection: the connection
+ * @handle_type: the handle type that was passed to
+ *  tp_connection_hold_handles()
+ * @n_handles: the number of handles that were passed to
+ *  tp_connection_hold_handles() on success, or 0 on failure
+ * @handles: the same @n_handles handles that were passed to
+ *  tp_connection_hold_handles() on success, or %NULL on failure
+ * @error: %NULL on success, or an error on failure
+ * @user_data: the same arbitrary pointer that was passed to
+ *  tp_connection_hold_handles()
+ * @weak_object: the same object that was passed to
+ *  tp_connection_hold_handles()
+ *
+ * Signature of the callback called when tp_connection_hold_handles() succeeds
+ * or fails.
+ *
+ * On success, the caller has one reference to each handle in @handles, which
+ * may be released later with tp_connection_unref_handles(). If not
+ * released, the handles will remain valid until @connection becomes invalid
+ * (signalled by TpProxy::invalidated).
+ *
+ * For convenience, the handle type and handles requested by the caller are
+ * passed through to this callback on success, so the caller does not have to
+ * include them in @user_data.
+ */
+
+static void
+connection_held_handles (TpConnection *self,
+                         const GError *error,
+                         gpointer user_data,
+                         GObject *weak_object)
+{
+  HoldHandlesContext *context = user_data;
+
+  g_object_ref (self);
+
+  if (error == NULL)
+    {
+      DEBUG ("%u handles of type %u", context->handles->len,
+          context->handle_type);
+      /* On the Telepathy side, we have held these handles (at least once).
+       * On the GLib side, record that we have one reference */
+      _tp_connection_ref_handles (self, context->handle_type,
+          context->handles);
+
+      context->callback (self, context->handle_type, context->handles->len,
+          (const TpHandle *) context->handles->data, NULL,
+          context->user_data, weak_object);
+    }
+  else
+    {
+      DEBUG ("%u handles of type %u failed: %s %u: %s",
+          context->handles->len, context->handle_type,
+          g_quark_to_string (error->domain), error->code, error->message);
+      context->callback (self, context->handle_type, 0, NULL, error,
+          context->user_data, weak_object);
+    }
+
+  g_object_unref (self);
+}
+
+
+/**
+ * tp_connection_hold_handles:
+ * @self: a connection
+ * @timeout_ms: the timeout in milliseconds, or -1 to use the default
+ * @handle_type: the handle type
+ * @n_handles: the number of handles in @handles
+ * @handles: an array of handles
+ * @callback: called on success or failure (unless @weak_object has become
+ *  unreferenced)
+ * @user_data: arbitrary user-supplied data
+ * @destroy: called to destroy @user_data after calling @callback, or when
+ *  @weak_object becomes unreferenced (whichever occurs sooner)
+ * @weak_object: if not %NULL, an object to be weakly referenced: if it is
+ *  destroyed, @callback will not be called
+ *
+ * Hold (ensure a reference to) the given handles, if they are valid.
+ *
+ * If they are valid, the callback will later be called with the given
+ * handles; if not all of them are valid, the callback will be called with
+ * an error.
+ */
+void
+tp_connection_hold_handles (TpConnection *self,
+                            gint timeout_ms,
+                            TpHandleType handle_type,
+                            guint n_handles,
+                            const TpHandle *handles,
+                            TpConnectionHoldHandlesCb callback,
+                            gpointer user_data,
+                            GDestroyNotify destroy,
+                            GObject *weak_object)
+{
+  HoldHandlesContext *context;
+
+  context = g_slice_new0 (HoldHandlesContext);
+  context->handle_type = handle_type;
+  context->user_data = user_data;
+  context->destroy = destroy;
+  context->handles = g_array_sized_new (FALSE, FALSE, sizeof (guint),
+      n_handles);
+  g_array_append_vals (context->handles, handles, n_handles);
+  context->callback = callback;
+
+  tp_cli_connection_call_hold_handles (self, timeout_ms, handle_type,
+      context->handles, connection_held_handles,
+      context, hold_handles_context_free, weak_object);
+}
+
+
+typedef struct {
+    TpHandleType handle_type;
+    guint n_ids;
+    gchar **ids;
+    gpointer user_data;
+    GDestroyNotify destroy;
+    TpConnectionRequestHandlesCb callback;
+} RequestHandlesContext;
+
+
+static void
+request_handles_context_free (gpointer p)
+{
+  RequestHandlesContext *context = p;
+
+  g_strfreev (context->ids);
+
+  if (context->destroy != NULL)
+    context->destroy (context->user_data);
+
+  g_slice_free (RequestHandlesContext, context);
+}
+
+
+/**
+ * TpConnectionRequestHandlesCb:
+ * @connection: the connection
+ * @handle_type: the handle type that was passed to
+ *  tp_connection_request_handles()
+ * @n_handles: the number of IDs that were passed to
+ *  tp_connection_request_handles() on success, or 0 on failure
+ * @handles: the @n_handles handles corresponding to @ids, in the same order,
+ *  or %NULL on failure
+ * @ids: the same @n_handles IDs that were passed to
+ *  tp_connection_request_handles() on success, or %NULL on failure
+ * @error: %NULL on success, or an error on failure
+ * @user_data: the same arbitrary pointer that was passed to
+ *  tp_connection_request_handles()
+ * @weak_object: the same object that was passed to
+ *  tp_connection_request_handles()
+ *
+ * Signature of the callback called when tp_connection_request_handles()
+ * succeeds or fails.
+ *
+ * On success, the caller has one reference to each handle in @handles, which
+ * may be released later with tp_connection_unref_handles(). If not
+ * released, the handles will remain valid until @connection becomes invalid
+ * (signalled by TpProxy::invalidated).
+ *
+ * For convenience, the handle type and IDs requested by the caller are
+ * passed through to this callback, so the caller does not have to include
+ * them in @user_data.
+ */
+
+
+static void
+connection_requested_handles (TpConnection *self,
+                              const GArray *handles,
+                              const GError *error,
+                              gpointer user_data,
+                              GObject *weak_object)
+{
+  RequestHandlesContext *context = user_data;
+
+  g_object_ref (self);
+
+  if (error == NULL)
+    {
+      if (G_UNLIKELY (g_strv_length (context->ids) != handles->len))
+        {
+          const gchar *cm = tp_proxy_get_bus_name ((TpProxy *) self);
+          GError *e = g_error_new (TP_DBUS_ERRORS, TP_DBUS_ERROR_INCONSISTENT,
+              "Connection manager %s is broken: we asked for %u "
+              "handles but RequestHandles returned %u",
+              cm, g_strv_length (context->ids), handles->len);
+
+          /* This CM is bad and wrong. We can't trust it to get anything
+           * right, so we'd probably better leak the handles, hence this
+           * early-return comes before recording that we have a ref to them. */
+          g_warning ("%s", e->message);
+
+          context->callback (self, context->handle_type, 0, NULL, NULL,
+              error, context->user_data, weak_object);
+          return;
+        }
+
+      DEBUG ("%u handles of type %u", handles->len,
+          context->handle_type);
+      /* On the Telepathy side, we have held these handles (at least once).
+       * On the GLib side, record that we have one reference */
+      _tp_connection_ref_handles (self, context->handle_type,
+          handles);
+
+      context->callback (self, context->handle_type, handles->len,
+          (const TpHandle *) handles->data,
+          (const gchar * const *) context->ids,
+          NULL, context->user_data, weak_object);
+    }
+  else
+    {
+      DEBUG ("%u handles of type %u failed: %s %u: %s",
+          g_strv_length (context->ids), context->handle_type,
+          g_quark_to_string (error->domain), error->code, error->message);
+      context->callback (self, context->handle_type, 0, NULL, NULL, error,
+          context->user_data, weak_object);
+    }
+
+  g_object_unref (self);
+}
+
+
+/**
+ * tp_connection_request_handles:
+ * @self: a connection
+ * @timeout_ms: the timeout in milliseconds, or -1 to use the default
+ * @handle_type: the handle type
+ * @ids: an array of string identifiers for which handles are required,
+ *  terminated by %NULL
+ * @callback: called on success or failure (unless @weak_object has become
+ *  unreferenced)
+ * @user_data: arbitrary user-supplied data
+ * @destroy: called to destroy @user_data after calling @callback, or when
+ *  @weak_object becomes unreferenced (whichever occurs sooner)
+ * @weak_object: if not %NULL, an object to be weakly referenced: if it is
+ *  destroyed, @callback will not be called
+ *
+ * Request the handles corresponding to the given identifiers, and if they
+ * are valid, hold (ensure a reference to) the corresponding handles.
+ *
+ * If they are valid, the callback will later be called with the given
+ * handles; if not all of them are valid, the callback will be called with
+ * an error.
+ */
+void
+tp_connection_request_handles (TpConnection *self,
+                               gint timeout_ms,
+                               TpHandleType handle_type,
+                               const gchar * const *ids,
+                               TpConnectionRequestHandlesCb callback,
+                               gpointer user_data,
+                               GDestroyNotify destroy,
+                               GObject *weak_object)
+{
+  RequestHandlesContext *context;
+
+  context = g_slice_new0 (RequestHandlesContext);
+  context->handle_type = handle_type;
+
+  /* g_strv_length is not NULL-safe - it's simpler to ensure that
+   * context->ids is not NULL */
+  if (ids == NULL)
+    context->ids = g_new0 (gchar *, 1);
+  else
+    context->ids = g_strdupv ((GStrv) ids);
+
+  context->user_data = user_data;
+  context->destroy = destroy;
+  context->callback = callback;
+
+  tp_cli_connection_call_request_handles (self, timeout_ms, handle_type,
+      (const gchar **) context->ids, connection_requested_handles,
+      context, request_handles_context_free, weak_object);
+}
diff --git a/telepathy-glib/connection-internal.h b/telepathy-glib/connection-internal.h
index 10d2b49..11f17cd 100644
--- a/telepathy-glib/connection-internal.h
+++ b/telepathy-glib/connection-internal.h
@@ -29,6 +29,8 @@ G_BEGIN_DECLS
 const GArray *_tp_connection_get_contact_attribute_interfaces (
     TpConnection *self);
 
+void _tp_connection_clean_up_handle_refs (TpConnection *self);
+
 G_END_DECLS
 
 #endif
diff --git a/telepathy-glib/connection.c b/telepathy-glib/connection.c
index 538f624..4f45f1c 100644
--- a/telepathy-glib/connection.c
+++ b/telepathy-glib/connection.c
@@ -460,6 +460,9 @@ tp_connection_constructor (GType type,
   tp_cli_connection_call_get_status (self, -1,
       tp_connection_got_status_cb, NULL, NULL, NULL);
 
+  g_signal_connect (self, "invalidated",
+      G_CALLBACK (_tp_connection_clean_up_handle_refs), NULL);
+
   DEBUG ("Returning %p", self);
   return (GObject *) self;
 }
diff --git a/telepathy-glib/connection.h b/telepathy-glib/connection.h
index 8dc2bcf..557b464 100644
--- a/telepathy-glib/connection.h
+++ b/telepathy-glib/connection.h
@@ -23,6 +23,7 @@
 #define __TP_CONNECTION_H__
 
 #include <telepathy-glib/enums.h>
+#include <telepathy-glib/handle.h>
 #include <telepathy-glib/proxy.h>
 
 G_BEGIN_DECLS
@@ -102,6 +103,30 @@ void tp_connection_init_known_interfaces (void);
 gint tp_connection_presence_type_cmp_availability (TpConnectionPresenceType p1,
   TpConnectionPresenceType p2);
 
+/* connection-handles.c */
+
+typedef void (*TpConnectionHoldHandlesCb) (TpConnection *connection,
+    TpHandleType handle_type, guint n_handles, const TpHandle *handles,
+    const GError *error, gpointer user_data, GObject *weak_object);
+
+void tp_connection_hold_handles (TpConnection *self, gint timeout_ms,
+    TpHandleType handle_type, guint n_handles, const TpHandle *handles,
+    TpConnectionHoldHandlesCb callback,
+    gpointer user_data, GDestroyNotify destroy, GObject *weak_object);
+
+typedef void (*TpConnectionRequestHandlesCb) (TpConnection *connection,
+    TpHandleType handle_type,
+    guint n_handles, const TpHandle *handles, const gchar * const *ids,
+    const GError *error, gpointer user_data, GObject *weak_object);
+
+void tp_connection_request_handles (TpConnection *self, gint timeout_ms,
+    TpHandleType handle_type, const gchar * const *ids,
+    TpConnectionRequestHandlesCb callback,
+    gpointer user_data, GDestroyNotify destroy, GObject *weak_object);
+
+void tp_connection_unref_handles (TpConnection *self,
+    TpHandleType handle_type, guint n_handles, const TpHandle *handles);
+
 G_END_DECLS
 
 #include <telepathy-glib/_gen/tp-cli-connection.h>
-- 
1.5.6.5




More information about the Telepathy-commits mailing list