/*
 * proxy.c - Base class for Telepathy client proxies
 *
 * Copyright (C) 2007-2008 Collabora Ltd. <http://www.collabora.co.uk/>
 * Copyright (C) 2007-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/proxy-subclass.h"
#include "telepathy-glib/proxy-internal.h"

#include <string.h>

#include <telepathy-glib/dbus.h>
#include <telepathy-glib/errors.h>
#include <telepathy-glib/interfaces.h>

#include "dbus-internal.h"
#define DEBUG_FLAG TP_DEBUG_PROXY
#include "debug-internal.h"

#include "_gen/signals-marshal.h"

#include "_gen/tp-cli-generic-body.h"

#if 0
#define MORE_DEBUG DEBUG
#else
#define MORE_DEBUG(...) G_STMT_START {} G_STMT_END
#endif

/**
 * TP_DBUS_ERRORS:
 *
 * #GError domain representing D-Bus errors not directly related to
 * Telepathy, for use by #TpProxy. The @code in a #GError with this
 * domain must be a member of #TpDBusError.
 *
 * This macro expands to a function call returning a #GQuark.
 *
 * Since: 0.7.1
 */
GQuark
tp_dbus_errors_quark (void)
{
  static GQuark q = 0;

  if (q == 0)
    q = g_quark_from_static_string ("tp_dbus_errors_quark");

  return q;
}

/**
 * TpDBusError:
 * @TP_DBUS_ERROR_UNKNOWN_REMOTE_ERROR: Raised if the error raised by
 *  a remote D-Bus object is not recognised
 * @TP_DBUS_ERROR_PROXY_UNREFERENCED: Emitted in #TpProxy::invalidated
 *  when the #TpProxy has lost its last reference
 * @TP_DBUS_ERROR_NO_INTERFACE: Raised by #TpProxy methods if the remote
 *  object does not appear to have the required interface
 * @TP_DBUS_ERROR_NAME_OWNER_LOST: Emitted in #TpProxy::invalidated if the
 *  remote process loses ownership of its bus name, and raised by
 *  any #TpProxy methods that have not had a reply at that time or are called
 *  after the proxy becomes invalid in this way (usually meaning it crashed)
 * @TP_DBUS_ERROR_INVALID_BUS_NAME: Raised if a D-Bus bus name given is not
 *  valid, or is of an unacceptable type (e.g. well-known vs. unique)
 * @TP_DBUS_ERROR_INVALID_INTERFACE_NAME: Raised if a D-Bus interface or
 *  error name given is not valid
 * @TP_DBUS_ERROR_INVALID_OBJECT_PATH: Raised if a D-Bus object path
 *  given is not valid
 * @TP_DBUS_ERROR_INVALID_MEMBER_NAME: Raised if a D-Bus method or signal
 *  name given is not valid
 * @TP_DBUS_ERROR_OBJECT_REMOVED: A generic error which can be used with
 *  #TpProxy::invalidated to indicate an application-specific indication
 *  that the remote object no longer exists, if no more specific error
 *  is available.
 * @TP_DBUS_ERROR_CANCELLED: Raised from calls that re-enter the main
 *  loop (*_run_*) if they are cancelled
 * @TP_DBUS_ERROR_INCONSISTENT: Raised if information received from a remote
 *  object is inconsistent or otherwise obviously wrong (added in 0.7.17)
 * @NUM_TP_DBUS_ERRORS: 1 more than the highest valid #TpDBusError at the
 *  time of compilation
 *
 * #GError codes for use with the %TP_DBUS_ERRORS domain.
 *
 * Since: 0.7.1
 */

/**
 * SECTION:proxy
 * @title: TpProxy
 * @short_description: base class for Telepathy client proxy objects
 * @see_also: #TpChannel, #TpConnection, #TpConnectionManager
 *
 * #TpProxy is a base class for Telepathy client-side proxies, which represent
 * an object accessed via D-Bus and provide access to its methods and signals.
 *
 * Since: 0.7.1
 */

/**
 * SECTION:proxy-dbus-core
 * @title: TpProxy D-Bus core methods
 * @short_description: The D-Bus Introspectable, Peer and Properties interfaces
 * @see_also: #TpProxy
 *
 * All D-Bus objects support the Peer interface, and many support the
 * Introspectable and Properties interfaces.
 *
 * Since: 0.7.2
 */

/**
 * SECTION:proxy-tp-properties
 * @title: TpProxy Telepathy Properties
 * @short_description: The Telepathy Properties interface
 * @see_also: #TpProxy
 *
 * As well as #TpProxy, proxy.h includes auto-generated client wrappers for the
 * Telepathy Properties interface, which can be implemented by any type of
 * object.
 *
 * The Telepathy Properties interface should not be confused with the D-Bus
 * core Properties interface.
 *
 * Since: 0.7.1
 */

/**
 * SECTION:proxy-subclass
 * @title: TpProxy subclasses and mixins
 * @short_description: Providing extra functionality for a #TpProxy or
 *  subclass, or subclassing it
 * @see_also: #TpProxy
 *
 * The implementations of #TpProxy subclasses and "mixin" functions need
 * access to the underlying dbus-glib objects used to implement the
 * #TpProxy API.
 *
 * Mixin functions to implement particular D-Bus interfaces should usually
 * be auto-generated, by copying tools/glib-client-gen.py from telepathy-glib.
 *
 * Since: 0.7.1
 */

/**
 * TpProxy:
 * @parent: parent object
 *
 * Structure representing a Telepathy client-side proxy.
 *
 * Since: 0.7.1
 */

/**
 * TpProxyInterfaceAddedCb:
 * @self: the proxy
 * @quark: a quark whose string value is the interface being added
 * @proxy: the #DBusGProxy for the added interface
 * @unused: unused
 *
 * The signature of a #TpProxy::interface-added signal callback.
 *
 * Since: 0.7.1
 */

/**
 * TpProxyClass:
 * @parent_class: The parent class structure
 * @interface: If set non-zero by a subclass, #TpProxy will
 *    automatically add this interface in its constructor
 * @must_have_unique_name: If set %TRUE by a subclass, the #TpProxy
 *    constructor will fail if a well-known bus name is given
 * @_reserved_flags: Reserved for future expansion
 * @_reserved: Reserved for future expansion
 * @priv: Opaque pointer for private data
 *
 * The class of a #TpProxy.
 *
 * Since: 0.7.1
 */

typedef struct _TpProxyErrorMappingLink TpProxyErrorMappingLink;

struct _TpProxyErrorMappingLink {
    const gchar *prefix;
    GQuark domain;
    GEnumClass *code_enum_class;
    TpProxyErrorMappingLink *next;
};

typedef struct _TpProxyInterfaceAddLink TpProxyInterfaceAddLink;

struct _TpProxyInterfaceAddLink {
    TpProxyInterfaceAddedCb callback;
    TpProxyInterfaceAddLink *next;
};

/**
 * TpProxyInvokeFunc:
 * @self: the #TpProxy on which the D-Bus method was invoked
 * @error: %NULL if the method call succeeded, or a non-%NULL error if the
 *  method call failed
 * @args: array of "out" arguments (return values) for the D-Bus method,
 *  or %NULL if an error occurred or if there were no "out" arguments
 * @callback: the callback that should be invoked, as passed to
 *  tp_proxy_pending_call_v0_new()
 * @user_data: user-supplied data to pass to the callback, as passed to
 *  tp_proxy_pending_call_v0_new()
 * @weak_object: user-supplied object to pass to the callback, as passed to
 *  tp_proxy_pending_call_v0_new()
 *
 * Signature of a callback invoked by the #TpProxy machinery after a D-Bus
 * method call has succeeded or failed. It is responsible for calling the
 * user-supplied callback.
 *
 * Because parts of dbus-glib aren't reentrant, this callback may be called
 * from an idle handler shortly after the method call reply is received,
 * rather than from the callback for the reply.
 *
 * At most one of @args and @error can be non-%NULL (implementations may
 * assert this). @args and @error may both be %NULL if a method with no
 * "out" arguments (i.e. a method that returns nothing) was called
 * successfully.
 *
 * The #TpProxyInvokeFunc must call callback with @user_data, @weak_object,
 * and appropriate arguments derived from @error and @args. It is responsible
 * for freeing @error and @args, if their ownership has not been transferred.
 *
 * Since: 0.7.1
 */

struct _TpProxyPrivate {
    /* GQuark for interface => either a ref'd DBusGProxy *,
     * or the TpProxy itself used as a dummy value to indicate that
     * the DBusGProxy has not been needed yet */
    GData *interfaces;

    unsigned dispose_has_run:1;
};

G_DEFINE_TYPE (TpProxy,
    tp_proxy,
    G_TYPE_OBJECT);

enum
{
  PROP_DBUS_DAEMON = 1,
  PROP_DBUS_CONNECTION,
  PROP_BUS_NAME,
  PROP_OBJECT_PATH,
  PROP_INTERFACES,
  N_PROPS
};

enum {
    SIGNAL_INTERFACE_ADDED,
    SIGNAL_INVALIDATED,
    N_SIGNALS
};

static guint signals[N_SIGNALS] = {0};

static void tp_proxy_iface_destroyed_cb (DBusGProxy *dgproxy, TpProxy *self);

/**
 * tp_proxy_borrow_interface_by_id:
 * @self: the TpProxy
 * @iface: quark representing the interface required
 * @error: used to raise an error in the #TP_DBUS_ERRORS domain if @iface
 *         is invalid, @self has been invalidated or @self does not implement
 *         @iface
 *
 * <!-- -->
 *
 * Returns: a borrowed reference to a #DBusGProxy
 * for which the bus name and object path are the same as for @self, but the
 * interface is as given (or %NULL if an @error is raised).
 * The reference is only valid as long as @self is.
 *
 * Since: 0.7.1
 */
DBusGProxy *
tp_proxy_borrow_interface_by_id (TpProxy *self,
                                 GQuark iface,
                                 GError **error)
{
  gpointer dgproxy;

  if (self->invalidated != NULL)
    {
      g_set_error (error, self->invalidated->domain, self->invalidated->code,
          "%s", self->invalidated->message);
      return NULL;
    }

  if (!tp_dbus_check_valid_interface_name (g_quark_to_string (iface),
        error))
      return NULL;

  dgproxy = g_datalist_id_get_data (&self->priv->interfaces, iface);

  if (dgproxy == self)
    {
      /* dummy value - we've never actually needed the interface, so we
       * didn't create it, to avoid binding to all the signals */

      dgproxy = dbus_g_proxy_new_for_name (self->dbus_connection,
          self->bus_name, self->object_path, g_quark_to_string (iface));
      DEBUG ("%p: %s DBusGProxy is %p", self, g_quark_to_string (iface),
          dgproxy);

      g_signal_connect (dgproxy, "destroy",
          G_CALLBACK (tp_proxy_iface_destroyed_cb), self);

      g_datalist_id_set_data_full (&self->priv->interfaces, iface,
          dgproxy, g_object_unref);

      g_signal_emit (self, signals[SIGNAL_INTERFACE_ADDED], 0,
          (guint) iface, dgproxy);
    }

  if (dgproxy != NULL)
    {
      return dgproxy;
    }

  g_set_error (error, TP_DBUS_ERRORS, TP_DBUS_ERROR_NO_INTERFACE,
      "Object %s does not have interface %s",
      self->object_path, g_quark_to_string (iface));

  return NULL;
}

/**
 * tp_proxy_has_interface_by_id:
 * @self: the #TpProxy (or subclass)
 * @iface: quark representing the interface required
 *
 * <!-- -->
 *
 * Returns: %TRUE if this proxy implements the given interface.
 *
 * Since: 0.7.1
 */
gboolean
tp_proxy_has_interface_by_id (gpointer self,
                              GQuark iface)
{
  TpProxy *proxy = TP_PROXY (self);

  return (g_datalist_id_get_data (&proxy->priv->interfaces, iface)
      != NULL);
}

/**
 * tp_proxy_has_interface:
 * @self: the #TpProxy (or subclass)
 * @iface: the interface required, as a string
 *
 * A macro wrapping tp_proxy_has_interface_by_id(). Returns %TRUE if this
 * proxy implements the given interface.
 *
 * Since: 0.7.1
 */

static void
tp_proxy_lose_interface (GQuark unused,
                         gpointer dgproxy_or_self,
                         gpointer self)
{
  if (dgproxy_or_self != self)
    g_signal_handlers_disconnect_by_func (dgproxy_or_self,
        G_CALLBACK (tp_proxy_iface_destroyed_cb), self);
}

static void
tp_proxy_lose_interfaces (TpProxy *self)
{
  g_datalist_foreach (&self->priv->interfaces,
      tp_proxy_lose_interface, self);

  g_datalist_clear (&self->priv->interfaces);
}

/* This signature is chosen to match GSourceFunc */
static gboolean
tp_proxy_emit_invalidated (gpointer p)
{
  TpProxy *self = p;

  g_signal_emit (self, signals[SIGNAL_INVALIDATED], 0,
      self->invalidated->domain, self->invalidated->code,
      self->invalidated->message);

  /* Don't clear the datalist until after we've emitted the signal, so
   * the pending call and signal connection friend classes can still get
   * to the proxies */
  tp_proxy_lose_interfaces (self);

  if (self->dbus_connection != NULL)
    {
      dbus_g_connection_unref (self->dbus_connection);
      self->dbus_connection = NULL;
    }

  return FALSE;
}

/**
 * tp_proxy_invalidate:
 * @self: a proxy
 * @error: an error causing the invalidation
 *
 * Mark @self as having been invalidated - no further calls will work, and
 * if not already invalidated, the #TpProxy::invalidated signal will be emitted
 * with the given error.
 *
 * Since: 0.7.1
 */
void
tp_proxy_invalidate (TpProxy *self, const GError *error)
{
  g_return_if_fail (self != NULL);
  g_return_if_fail (error != NULL);

  if (self->invalidated == NULL)
    {
      DEBUG ("%p: %s", self, error->message);
      self->invalidated = g_error_copy (error);

      tp_proxy_emit_invalidated (self);
    }
}

static void
tp_proxy_iface_destroyed_cb (DBusGProxy *dgproxy,
                             TpProxy *self)
{
  /* We can't call any API on the proxy now. Because the proxies are all
   * for the same bus name, we can assume that all of them are equally
   * useless now */
  tp_proxy_lose_interfaces (self);

  /* We need to be able to delay emitting the invalidated signal, so that
   * any queued-up method calls and signal handlers will run first, and so
   * it doesn't try to reenter libdbus.
   */
  if (self->invalidated == NULL)
    {
      DEBUG ("%p", self);
      self->invalidated = g_error_new_literal (TP_DBUS_ERRORS,
          TP_DBUS_ERROR_NAME_OWNER_LOST, "Name owner lost (service crashed?)");

      g_idle_add_full (G_PRIORITY_HIGH, tp_proxy_emit_invalidated,
          g_object_ref (self), g_object_unref);
    }
}

/**
 * tp_proxy_add_interface_by_id:
 * @self: the TpProxy, which must not have become #TpProxy::invalidated.
 * @iface: quark representing the interface to be added
 *
 * Declare that this proxy supports a given interface.
 *
 * To use methods and signals of that interface, either call
 * tp_proxy_borrow_interface_by_id() to get the #DBusGProxy, or use the
 * tp_cli_* wrapper functions (strongly recommended).
 *
 * If the interface is the proxy's "main interface", or has already been
 * added, then do nothing.
 *
 * Returns: either %NULL or a borrowed #DBusGProxy corresponding to @iface,
 * depending on implementation details. To reliably borrow the #DBusGProxy, use
 * tp_proxy_borrow_interface_by_id(). (This method should probably have
 * returned void; sorry.)
 *
 * Since: 0.7.1
 */
DBusGProxy *
tp_proxy_add_interface_by_id (TpProxy *self,
                              GQuark iface)
{
  DBusGProxy *iface_proxy = g_datalist_id_get_data (&self->priv->interfaces,
      iface);

  g_return_val_if_fail
      (tp_dbus_check_valid_interface_name (g_quark_to_string (iface),
          NULL),
       NULL);

  g_return_val_if_fail (tp_proxy_get_invalidated (self) == NULL, NULL);

  if (iface_proxy == NULL)
    {
      /* we don't want to actually create it just yet - dbus-glib will
       * helpfully wake us up on every signal, if we do. So we set a
       * dummy value (self), and replace it with the real value in
       * tp_proxy_borrow_interface_by_id */
      g_datalist_id_set_data_full (&self->priv->interfaces, iface,
          self, NULL);
    }

  return iface_proxy;
}

static GQuark
error_mapping_quark (void)
{
  static GQuark q = 0;

  if (G_UNLIKELY (q == 0))
    {
      q = g_quark_from_static_string ("TpProxyErrorMappingCb_0.7.1");
    }

  return q;
}

/**
 * tp_proxy_dbus_error_to_gerror:
 * @self: a #TpProxy or subclass
 * @dbus_error: a D-Bus error name, for instance from the callback for
 *              tp_cli_connection_connect_to_connection_error()
 * @debug_message: a debug message that accompanied the error name, or %NULL
 * @error: used to return the corresponding #GError
 *
 * Convert a D-Bus error name into a GError as if it was returned by a method
 * on this proxy. This method is useful when D-Bus error names are emitted in
 * signals, such as Connection.ConnectionError and
 * Group.MembersChangedDetailed.
 *
 * Since: 0.7.24
 */
void
tp_proxy_dbus_error_to_gerror (gpointer self,
                               const char *dbus_error,
                               const char *debug_message,
                               GError **error)
{
  GType proxy_type = TP_TYPE_PROXY;
  GType type;

  g_return_if_fail (TP_IS_PROXY (self));

  if (error == NULL)
    return;

  g_return_if_fail (*error == NULL);

  if (!tp_dbus_check_valid_interface_name (dbus_error, error))
    {
      return;
    }

  if (debug_message == NULL)
    debug_message = "";

  for (type = G_TYPE_FROM_INSTANCE (self);
       type != proxy_type;
       type = g_type_parent (type))
    {
      TpProxyErrorMappingLink *iter;

      for (iter = g_type_get_qdata (type, error_mapping_quark ());
           iter != NULL;
           iter = iter->next)
        {
          size_t prefix_len = strlen (iter->prefix);

          if (!strncmp (dbus_error, iter->prefix, prefix_len)
              && dbus_error[prefix_len] == '.')
            {
              GEnumValue *code =
                g_enum_get_value_by_nick (iter->code_enum_class,
                    dbus_error + prefix_len + 1);

              if (code != NULL)
                {
                  g_set_error (error, iter->domain, code->value,
                      "%s", debug_message);
                  return;
                }
            }
        }
    }

  /* we don't have an error mapping - so let's just paste the
   * error name and message into TP_DBUS_ERROR_UNKNOWN_REMOTE_ERROR */
  g_set_error (error, TP_DBUS_ERRORS,
      TP_DBUS_ERROR_UNKNOWN_REMOTE_ERROR, "%s: %s", dbus_error, debug_message);
}

GError *
_tp_proxy_take_and_remap_error (TpProxy *self,
                                GError *error)
{
  if (error == NULL ||
      error->domain != DBUS_GERROR ||
      error->code != DBUS_GERROR_REMOTE_EXCEPTION)
    {
      return error;
    }
  else
    {
      GError *replacement = NULL;
      const gchar *dbus = dbus_g_error_get_name (error);

      tp_proxy_dbus_error_to_gerror (self, dbus, error->message, &replacement);
      g_error_free (error);
      return replacement;
    }
}

static void
dup_quark_into_ptr_array (GQuark q,
                          gpointer unused,
                          gpointer user_data)
{
  GPtrArray *strings = user_data;

  g_ptr_array_add (strings, g_strdup (g_quark_to_string (q)));
}

static void
tp_proxy_get_property (GObject *object,
                       guint property_id,
                       GValue *value,
                       GParamSpec *pspec)
{
  TpProxy *self = TP_PROXY (object);

  switch (property_id)
    {
    case PROP_DBUS_DAEMON:
      if (TP_IS_DBUS_DAEMON (self))
        {
          g_value_set_object (value, self);
        }
      else
        {
          g_value_set_object (value, self->dbus_daemon);
        }
      break;
    case PROP_DBUS_CONNECTION:
      g_value_set_boxed (value, self->dbus_connection);
      break;
    case PROP_BUS_NAME:
      g_value_set_string (value, self->bus_name);
      break;
    case PROP_OBJECT_PATH:
      g_value_set_string (value, self->object_path);
      break;
    case PROP_INTERFACES:
        {
          GPtrArray *strings = g_ptr_array_new ();

          g_datalist_foreach (&self->priv->interfaces,
              dup_quark_into_ptr_array, strings);
          g_ptr_array_add (strings, NULL);
          g_value_take_boxed (value, g_ptr_array_free (strings, FALSE));
        }
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
  }
}

static void
tp_proxy_set_property (GObject *object,
                       guint property_id,
                       const GValue *value,
                       GParamSpec *pspec)
{
  TpProxy *self = TP_PROXY (object);

  switch (property_id)
    {
    case PROP_DBUS_DAEMON:
      if (TP_IS_DBUS_DAEMON (self))
        {
          g_assert (g_value_get_object (value) == NULL);
        }
      else
        {
          TpProxy *daemon_as_proxy = TP_PROXY (g_value_get_object (value));

          g_assert (self->dbus_daemon == NULL);

          if (daemon_as_proxy != NULL)
            self->dbus_daemon = TP_DBUS_DAEMON (g_object_ref
                (daemon_as_proxy));

          if (daemon_as_proxy != NULL)
            {
              g_assert (self->dbus_connection == NULL ||
                  self->dbus_connection == daemon_as_proxy->dbus_connection);

              if (self->dbus_connection == NULL)
                self->dbus_connection =
                    dbus_g_connection_ref (daemon_as_proxy->dbus_connection);
            }
        }
      break;
    case PROP_DBUS_CONNECTION:
        {
          DBusGConnection *conn = g_value_get_boxed (value);

          /* if we're given a NULL dbus-connection, but we've got a
           * DBusGConnection from the dbus-daemon, we want to keep it */
          if (conn == NULL)
            return;

          if (self->dbus_connection == NULL)
            self->dbus_connection = g_value_dup_boxed (value);

          g_assert (self->dbus_connection == g_value_get_boxed (value));
        }
      break;
    case PROP_BUS_NAME:
      g_assert (self->bus_name == NULL);
      self->bus_name = g_value_dup_string (value);
      break;
    case PROP_OBJECT_PATH:
      g_assert (self->object_path == NULL);
      self->object_path = g_value_dup_string (value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
  }
}

static void
tp_proxy_init (TpProxy *self)
{
  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, TP_TYPE_PROXY,
      TpProxyPrivate);
}

static GQuark
interface_added_cb_quark (void)
{
  static GQuark q = 0;

  if (G_UNLIKELY (q == 0))
    {
      q = g_quark_from_static_string ("TpProxyInterfaceAddedCb_0.7.1");
    }

  return q;
}

static GObject *
tp_proxy_constructor (GType type,
                      guint n_params,
                      GObjectConstructParam *params)
{
  GObjectClass *object_class = (GObjectClass *) tp_proxy_parent_class;
  TpProxy *self = TP_PROXY (object_class->constructor (type,
        n_params, params));
  TpProxyClass *klass = TP_PROXY_GET_CLASS (self);
  TpProxyInterfaceAddLink *iter;
  GType ancestor_type = type;
  GType proxy_parent_type = G_TYPE_FROM_CLASS (tp_proxy_parent_class);

  _tp_register_dbus_glib_marshallers ();

  for (ancestor_type = type;
       ancestor_type != proxy_parent_type && ancestor_type != 0;
       ancestor_type = g_type_parent (ancestor_type))
    {
      for (iter = g_type_get_qdata (ancestor_type,
              interface_added_cb_quark ());
           iter != NULL;
           iter = iter->next)
        g_signal_connect (self, "interface-added", G_CALLBACK (iter->callback),
            NULL);
    }

  g_return_val_if_fail (self->dbus_connection != NULL, NULL);
  g_return_val_if_fail (self->object_path != NULL, NULL);
  g_return_val_if_fail (self->bus_name != NULL, NULL);

  g_return_val_if_fail (tp_dbus_check_valid_object_path (self->object_path,
        NULL), NULL);
  g_return_val_if_fail (tp_dbus_check_valid_bus_name (self->bus_name,
        TP_DBUS_NAME_TYPE_ANY, NULL), NULL);

  tp_proxy_add_interface_by_id (self, TP_IFACE_QUARK_DBUS_INTROSPECTABLE);
  tp_proxy_add_interface_by_id (self, TP_IFACE_QUARK_DBUS_PEER);
  tp_proxy_add_interface_by_id (self, TP_IFACE_QUARK_DBUS_PROPERTIES);

  if (klass->interface != 0)
    {
      tp_proxy_add_interface_by_id (self, klass->interface);
    }

  /* Some interfaces are stateful, so we only allow binding to a unique
   * name, like in dbus_g_proxy_new_for_name_owner() */
  if (klass->must_have_unique_name)
    {
      g_return_val_if_fail (self->bus_name[0] == ':', NULL);
    }

  return (GObject *) self;
}

static void
tp_proxy_dispose (GObject *object)
{
  TpProxy *self = TP_PROXY (object);
  GError e = { TP_DBUS_ERRORS, TP_DBUS_ERROR_PROXY_UNREFERENCED,
      "Proxy unreferenced" };

  if (self->priv->dispose_has_run)
    return;
  self->priv->dispose_has_run = TRUE;

  DEBUG ("%p", self);

  tp_proxy_invalidate (self, &e);

  if (self->dbus_daemon != NULL)
    {
      g_object_unref (self->dbus_daemon);
      self->dbus_daemon = NULL;
    }

  G_OBJECT_CLASS (tp_proxy_parent_class)->dispose (object);
}

static void
tp_proxy_finalize (GObject *object)
{
  TpProxy *self = TP_PROXY (object);

  DEBUG ("%p", self);

  g_assert (self->invalidated != NULL);
  g_error_free (self->invalidated);

  g_free (self->bus_name);
  g_free (self->object_path);

  G_OBJECT_CLASS (tp_proxy_parent_class)->finalize (object);
}

/**
 * tp_proxy_or_subclass_hook_on_interface_add:
 * @proxy_or_subclass: The #GType of #TpProxy or a subclass
 * @callback: A signal handler for #TpProxy::interface-added
 *
 * Arrange for @callback to be connected to #TpProxy::interface-added
 * during the #TpProxy constructor. This is done sufficiently early that
 * it will see the signal for the default interface (@interface member of
 * #TpProxyClass), if any, being added. The intended use is for the callback
 * to call dbus_g_proxy_add_signal() on the new #DBusGProxy.
 *
 * Since 0.7.6, to ensure correct overriding of interfaces that might be
 * added to telepathy-glib, before calling this function you should
 * call tp_proxy_init_known_interfaces, tp_connection_init_known_interfaces,
 * tp_channel_init_known_interfaces etc. as appropriate for the subclass.
 *
 * Since: 0.7.1
 */
void
tp_proxy_or_subclass_hook_on_interface_add (GType proxy_or_subclass,
    TpProxyInterfaceAddedCb callback)
{
  GQuark q = interface_added_cb_quark ();
  TpProxyInterfaceAddLink *old_link = g_type_get_qdata (proxy_or_subclass, q);
  TpProxyInterfaceAddLink *new_link;

  g_return_if_fail (g_type_is_a (proxy_or_subclass, TP_TYPE_PROXY));
  g_return_if_fail (callback != NULL);

  /* never freed, suppressed in telepathy-glib.supp */
  new_link = g_slice_new0 (TpProxyInterfaceAddLink);
  new_link->callback = callback;
  new_link->next = old_link;    /* may be NULL */
  g_type_set_qdata (proxy_or_subclass, q, new_link);
}

/**
 * tp_proxy_subclass_add_error_mapping:
 * @proxy_subclass: The #GType of a subclass of #TpProxy (which must not be
 *  #TpProxy itself)
 * @static_prefix: A prefix for D-Bus error names, not including the trailing
 *  dot (which must remain valid forever, and should usually be in static
 *  storage)
 * @domain: A quark representing the corresponding #GError domain
 * @code_enum_type: The type of a subclass of #GEnumClass
 *
 * Register a mapping from D-Bus errors received from the given proxy
 * subclass to #GError instances.
 *
 * When a D-Bus error is received, the #TpProxy code checks for error
 * mappings registered for the class of the proxy receiving the error,
 * then for all of its parent classes.
 *
 * If there is an error mapping for which the D-Bus error name
 * starts with the mapping's @static_prefix, the proxy will check the
 * corresponding @code_enum_type for a value whose @value_nick is
 * the rest of the D-Bus error name (with the leading dot removed). If there
 * isn't such a value, it will continue to try other error mappings.
 *
 * If a suitable error mapping and code are found, the #GError that is raised
 * will have its error domain set to the @domain from the error mapping,
 * and its error code taken from the enum represented by the @code_enum_type.
 *
 * If no suitable error mapping or code is found, the #GError will have
 * error domain %TP_DBUS_ERRORS and error code
 * %TP_DBUS_ERROR_UNKNOWN_REMOTE_ERROR.
 *
 * Since: 0.7.1
 */
void
tp_proxy_subclass_add_error_mapping (GType proxy_subclass,
                                     const gchar *static_prefix,
                                     GQuark domain,
                                     GType code_enum_type)
{
  GQuark q = error_mapping_quark ();
  TpProxyErrorMappingLink *old_link = g_type_get_qdata (proxy_subclass, q);
  TpProxyErrorMappingLink *new_link;
  GType tp_type_proxy = TP_TYPE_PROXY;

  g_return_if_fail (proxy_subclass != tp_type_proxy);
  g_return_if_fail (g_type_is_a (proxy_subclass, tp_type_proxy));
  g_return_if_fail (static_prefix != NULL);
  g_return_if_fail (domain != 0);
  g_return_if_fail (code_enum_type != G_TYPE_INVALID);

  new_link = g_slice_new0 (TpProxyErrorMappingLink);
  new_link->prefix = static_prefix;
  new_link->domain = domain;
  /* We never unref the enum type - intentional one-per-process leak.
   * See "tp_proxy_subclass_add_error_mapping refs the enum" in our valgrind
   * suppressions file */
  new_link->code_enum_class = g_type_class_ref (code_enum_type);
  new_link->next = old_link;    /* may be NULL */
  g_type_set_qdata (proxy_subclass, q, new_link);
}

static void
tp_proxy_class_init (TpProxyClass *klass)
{
  GParamSpec *param_spec;
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  tp_proxy_init_known_interfaces ();

  g_type_class_add_private (klass, sizeof (TpProxyPrivate));

  object_class->constructor = tp_proxy_constructor;
  object_class->get_property = tp_proxy_get_property;
  object_class->set_property = tp_proxy_set_property;
  object_class->dispose = tp_proxy_dispose;
  object_class->finalize = tp_proxy_finalize;

  /**
   * TpProxy:dbus-daemon:
   *
   * The D-Bus daemon for this object (this object itself, if it is a
   * TpDBusDaemon). Read-only except during construction.
   */
  param_spec = g_param_spec_object ("dbus-daemon", "D-Bus daemon",
      "The D-Bus daemon used by this object, or this object itself if it's "
      "a TpDBusDaemon", TP_TYPE_DBUS_DAEMON,
      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
      G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK);
  g_object_class_install_property (object_class, PROP_DBUS_DAEMON,
      param_spec);

  /**
   * TpProxy:dbus-connection:
   *
   * The D-Bus connection for this object. Read-only except during
   * construction.
   */
  param_spec = g_param_spec_boxed ("dbus-connection", "D-Bus connection",
      "The D-Bus connection used by this object", DBUS_TYPE_G_CONNECTION,
      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
      G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK);
  g_object_class_install_property (object_class, PROP_DBUS_CONNECTION,
      param_spec);

  /**
   * TpProxy:bus-name:
   *
   * The D-Bus bus name for this object. Read-only except during construction.
   */
  param_spec = g_param_spec_string ("bus-name", "D-Bus bus name",
      "The D-Bus bus name for this object", NULL,
      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
      G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK);
  g_object_class_install_property (object_class, PROP_BUS_NAME,
      param_spec);

  /**
   * TpProxy:object-path:
   *
   * The D-Bus object path for this object. Read-only except during
   * construction.
   */
  param_spec = g_param_spec_string ("object-path", "D-Bus object path",
      "The D-Bus object path for this object", NULL,
      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
      G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK);
  g_object_class_install_property (object_class, PROP_OBJECT_PATH,
      param_spec);

  /**
   * TpProxy:interfaces:
   *
   * Known D-Bus interface names for this object.
   */
  param_spec = g_param_spec_boxed ("interfaces", "D-Bus interfaces",
      "Known D-Bus interface names for this object", G_TYPE_STRV,
      G_PARAM_READABLE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK
      | G_PARAM_STATIC_BLURB);
  g_object_class_install_property (object_class, PROP_INTERFACES,
      param_spec);

  /**
   * TpProxy::interface-added:
   * @self: the proxy object
   * @id: the GQuark representing the interface
   * @proxy: the dbus-glib proxy representing the interface
   *
   * Emitted when this proxy has gained an interface. It is not guaranteed
   * to be emitted immediately, but will be emitted before the interface is
   * first used (at the latest: before it's returned from
   * tp_proxy_borrow_interface_by_id(), any signal is connected, or any
   * method is called).
   *
   * The intended use is to call dbus_g_proxy_add_signals(). This signal
   * should only be used by TpProy implementations
   */
  signals[SIGNAL_INTERFACE_ADDED] = g_signal_new ("interface-added",
      G_OBJECT_CLASS_TYPE (klass),
      G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
      0,
      NULL, NULL,
      _tp_marshal_VOID__UINT_OBJECT,
      G_TYPE_NONE, 2, G_TYPE_UINT, DBUS_TYPE_G_PROXY);

  /**
   * TpProxy::invalidated:
   * @self: the proxy object
   * @domain: domain of a GError indicating why this proxy was invalidated
   * @code: error code of a GError indicating why this proxy was invalidated
   * @message: a message associated with the error
   *
   * Emitted when this proxy has been become invalid for
   * whatever reason. Any more specific signal should be emitted first.
   */
  signals[SIGNAL_INVALIDATED] = g_signal_new ("invalidated",
      G_OBJECT_CLASS_TYPE (klass),
      G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
      0,
      NULL, NULL,
      _tp_marshal_VOID__UINT_INT_STRING,
      G_TYPE_NONE, 3, G_TYPE_UINT, G_TYPE_INT, G_TYPE_STRING);
}

/**
 * tp_proxy_get_dbus_daemon:
 * @self: a #TpProxy or subclass
 *
 * <!-- -->
 *
 * Returns: a borrowed reference to the #TpDBusDaemon for this object, if any;
 *  always %NULL if this object is itself a #TpDBusDaemon. The caller must
 *  reference the returned object with g_object_ref() if it will be kept.
 *
 * Since: 0.7.17
 */
TpDBusDaemon *
tp_proxy_get_dbus_daemon (gpointer self)
{
  TpProxy *proxy = TP_PROXY (self);

  return proxy->dbus_daemon;
}

/**
 * tp_proxy_get_dbus_connection:
 * @self: a #TpProxy or subclass
 *
 * <!-- -->
 *
 * Returns: a borrowed reference to the D-Bus connection used by this object.
 *  The caller must reference the returned pointer with
 *  dbus_g_connection_ref() if it will be kept.
 *
 * Since: 0.7.17
 */
DBusGConnection *
tp_proxy_get_dbus_connection (gpointer self)
{
  TpProxy *proxy = TP_PROXY (self);

  return proxy->dbus_connection;
}

/**
 * tp_proxy_get_bus_name:
 * @self: a #TpProxy or subclass
 *
 * <!-- -->
 *
 * Returns: the bus name of the application exporting the object. The caller
 *  must copy the string with g_strdup() if it will be kept.
 *
 * Since: 0.7.17
 */
const gchar *
tp_proxy_get_bus_name (gpointer self)
{
  TpProxy *proxy = TP_PROXY (self);

  return proxy->bus_name;
}

/**
 * tp_proxy_get_object_path:
 * @self: a #TpProxy or subclass
 *
 * <!-- -->
 *
 * Returns: the object path of the remote object. The caller must copy the
 *  string with g_strdup() if it will be kept.
 *
 * Since: 0.7.17
 */
const gchar *
tp_proxy_get_object_path (gpointer self)
{
  TpProxy *proxy = TP_PROXY (self);

  return proxy->object_path;
}

/**
 * tp_proxy_get_invalidated:
 * @self: a #TpProxy or subclass
 *
 * <!-- -->
 *
 * Returns: the reason this proxy was invalidated, or %NULL if has not been
 *  invalidated. The caller must copy the error, for instance with
 *  g_error_copy(), if it will be kept.
 *
 * Since: 0.7.17
 */
const GError *
tp_proxy_get_invalidated (gpointer self)
{
  TpProxy *proxy = TP_PROXY (self);

  return proxy->invalidated;
}

/**
 * tp_proxy_dbus_g_proxy_claim_for_signal_adding:
 * @proxy: a #DBusGProxy
 *
 * Attempt to "claim" a #DBusGProxy for addition of signal signatures.
 * If this function has not been called on @proxy before, %TRUE is
 * returned, and the caller may safely call dbus_g_proxy_add_signal()
 * on @proxy. If this function has already been caled, %FALSE is
 * returned, and the caller may not safely call dbus_g_proxy_add_signal().
 *
 * This is intended for use by auto-generated signal-adding functions,
 * to allow interfaces provided as local extensions to override those in
 * telepathy-glib without causing assertion failures.
 *
 * Returns: %TRUE if it is safe to call dbus_g_proxy_add_signal()
 * Since: 0.7.6
 */
gboolean
tp_proxy_dbus_g_proxy_claim_for_signal_adding (DBusGProxy *proxy)
{
  static GQuark q = 0;

  g_return_val_if_fail (proxy != NULL, FALSE);

  if (G_UNLIKELY (q == 0))
    {
      q = g_quark_from_static_string (
          "tp_proxy_dbus_g_proxy_claim_for_signal_adding@0.7.6");
    }

  if (g_object_get_qdata ((GObject *) proxy, q) != NULL)
    {
      /* Someone else has already added signal signatures for this interface.
       * We can't do it again or it'll cause an assertion */
      return FALSE;
    }

  /* the proxy is just used as qdata here because it's a convenient
   * non-NULL pointer */
  g_object_set_qdata ((GObject *) proxy, q, proxy);
  return TRUE;
}

static gpointer
tp_proxy_once (gpointer data G_GNUC_UNUSED)
{
  GType type = TP_TYPE_PROXY;

  tp_proxy_or_subclass_hook_on_interface_add (type,
      tp_cli_generic_add_signals);

  return NULL;
}

/**
 * tp_proxy_init_known_interfaces:
 *
 * Ensure that the known interfaces for TpProxy have been set up.
 * This is done automatically when necessary, but for correct
 * overriding of library interfaces by local extensions, you should
 * call this function before calling
 * tp_proxy_or_subclass_hook_on_interface_add().
 *
 * Functions like tp_connection_init_known_interfaces and
 * tp_channel_init_known_interfaces do this automatically.
 *
 * Since: 0.7.6
 */
void
tp_proxy_init_known_interfaces (void)
{
  static GOnce once = G_ONCE_INIT;

  g_once (&once, tp_proxy_once, NULL);
}
