/*
 * dbus-daemon.c - Source for TpDBusDaemon
 *
 * Copyright (C) 2005-2009 Collabora Ltd. <http://www.collabora.co.uk/>
 * Copyright (C) 2005-2009 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/dbus.h>
#include <telepathy-glib/dbus-internal.h>

#include <dbus/dbus.h>
#include <dbus/dbus-glib-lowlevel.h>

#include <telepathy-glib/errors.h>
#include <telepathy-glib/interfaces.h>
#include <telepathy-glib/proxy-subclass.h>
#include <telepathy-glib/util.h>

#include "telepathy-glib/_gen/signals-marshal.h"

#include "telepathy-glib/_gen/tp-cli-dbus-daemon-body.h"

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

/**
 * TpDBusDaemonClass:
 *
 * The class of #TpDBusDaemon.
 *
 * Since: 0.7.1
 */
struct _TpDBusDaemonClass
{
  /*<private>*/
  TpProxyClass parent_class;
  gpointer priv;
};

/**
 * TpDBusDaemon:
 *
 * A subclass of #TpProxy that represents the D-Bus daemon. It mainly provides
 * functionality to manage well-known names on the bus.
 *
 * Since: 0.7.1
 */
struct _TpDBusDaemon
{
  /*<private>*/
  TpProxy parent;

  TpDBusDaemonPrivate *priv;
};

struct _TpDBusDaemonPrivate
{
  /* dup'd name => _NameOwnerWatch */
  GHashTable *name_owner_watches;
  /* reffed */
  DBusConnection *libdbus;
};

G_DEFINE_TYPE (TpDBusDaemon, tp_dbus_daemon, TP_TYPE_PROXY);

static gpointer starter_bus_daemon = NULL;

/**
 * tp_dbus_daemon_dup:
 * @error: Used to indicate error if %NULL is returned
 *
 * Returns a proxy for signals and method calls on the D-Bus daemon on which
 * this process was activated (if it was launched by D-Bus service
 * activation), or the session bus (otherwise).
 *
 * If it is not possible to connect to the appropriate bus, raise an error
 * and return %NULL.
 *
 * The returned #TpDBusDaemon is cached; the same #TpDBusDaemon object will
 * be returned by this function repeatedly, as long as at least one reference
 * exists.
 *
 * Returns: a reference to a proxy for signals and method calls on the bus
 *  daemon, or %NULL
 *
 * Since: 0.7.26
 */
TpDBusDaemon *
tp_dbus_daemon_dup (GError **error)
{
  DBusGConnection *conn;

  if (starter_bus_daemon != NULL)
    return g_object_ref (starter_bus_daemon);

  conn = _tp_dbus_starter_bus_conn (error);

  if (conn == NULL)
    return NULL;

  starter_bus_daemon = tp_dbus_daemon_new (conn);
  g_assert (starter_bus_daemon != NULL);
  g_object_add_weak_pointer (starter_bus_daemon, &starter_bus_daemon);

  return starter_bus_daemon;
}

/**
 * tp_dbus_daemon_new:
 * @connection: a connection to D-Bus
 *
 * Returns a proxy for signals and method calls on a particular bus
 * connection.
 *
 * Use tp_dbus_daemon_dup() instead if you just want a connection to the
 * starter or session bus (which is almost always the right thing for
 * Telepathy).
 *
 * Returns: a new proxy for signals and method calls on the bus daemon
 *  to which @connection is connected
 *
 * Since: 0.7.1
 */
TpDBusDaemon *
tp_dbus_daemon_new (DBusGConnection *connection)
{
  g_return_val_if_fail (connection != NULL, NULL);

  return TP_DBUS_DAEMON (g_object_new (TP_TYPE_DBUS_DAEMON,
        "dbus-connection", connection,
        "bus-name", DBUS_SERVICE_DBUS,
        "object-path", DBUS_PATH_DBUS,
        NULL));
}

typedef struct
{
  TpDBusDaemonNameOwnerChangedCb callback;
  gpointer user_data;
  GDestroyNotify destroy;
  gchar *last_owner;
} _NameOwnerWatch;

typedef struct
{
  TpDBusDaemonNameOwnerChangedCb callback;
  gpointer user_data;
  GDestroyNotify destroy;
} _NameOwnerSubWatch;

static void
_tp_dbus_daemon_name_owner_changed_multiple (TpDBusDaemon *self,
                                             const gchar *name,
                                             const gchar *new_owner,
                                             gpointer user_data)
{
  GArray *array = user_data;
  guint i;

  for (i = 0; i < array->len; i++)
    {
      _NameOwnerSubWatch *watch = &g_array_index (array, _NameOwnerSubWatch,
          i);

      watch->callback (self, name, new_owner, watch->user_data);
    }
}

static void
_tp_dbus_daemon_name_owner_changed_multiple_free (gpointer data)
{
  GArray *array = data;
  guint i;

  for (i = 0; i < array->len; i++)
    {
      _NameOwnerSubWatch *watch = &g_array_index (array, _NameOwnerSubWatch,
          i);

      if (watch->destroy)
        watch->destroy (watch->user_data);
    }

  g_array_free (array, TRUE);
}

static void
_tp_dbus_daemon_name_owner_changed (TpDBusDaemon *self,
                                    const gchar *name,
                                    const gchar *new_owner)
{
  _NameOwnerWatch *watch = g_hash_table_lookup (self->priv->name_owner_watches,
      name);

  if (watch == NULL)
    return;

  /* This is partly to handle the case where an owner change happens
   * while GetNameOwner is in flight, partly to be able to optimize by only
   * calling GetNameOwner if we didn't already know, and partly because of a
   * dbus-glib bug that means we get every signal twice
   * (it thinks org.freedesktop.DBus is both a well-known name and a unique
   * name). */
  if (!tp_strdiff (watch->last_owner, new_owner))
    return;

  g_free (watch->last_owner);
  watch->last_owner = g_strdup (new_owner);

  watch->callback (self, name, new_owner, watch->user_data);
}

static dbus_int32_t daemons_slot = -1;

typedef struct {
    DBusConnection *libdbus;
    DBusMessage *message;
} NOCIdleContext;

static NOCIdleContext *
noc_idle_context_new (DBusConnection *libdbus,
                      DBusMessage *message)
{
  NOCIdleContext *context = g_slice_new (NOCIdleContext);

  context->libdbus = dbus_connection_ref (libdbus);
  context->message = dbus_message_ref (message);
  return context;
}

static void
noc_idle_context_free (gpointer data)
{
  NOCIdleContext *context = data;

  dbus_connection_unref (context->libdbus);
  dbus_message_unref (context->message);
  g_slice_free (NOCIdleContext, context);
}

static gboolean
noc_idle_context_invoke (gpointer data)
{
  NOCIdleContext *context = data;
  const gchar *name;
  const gchar *old_owner;
  const gchar *new_owner;
  DBusError dbus_error = DBUS_ERROR_INIT;
  GSList **daemons;

  if (daemons_slot == -1)
    return FALSE;

  if (!dbus_message_get_args (context->message, &dbus_error,
        DBUS_TYPE_STRING, &name,
        DBUS_TYPE_STRING, &old_owner,
        DBUS_TYPE_STRING, &new_owner,
        DBUS_TYPE_INVALID))
    {
      DEBUG ("Couldn't unpack NameOwnerChanged(s, s, s): %s: %s",
          dbus_error.name, dbus_error.message);
      dbus_error_free (&dbus_error);
      return FALSE;
    }

  daemons = dbus_connection_get_data (context->libdbus, daemons_slot);

  DEBUG ("NameOwnerChanged(%s, %s -> %s)", name, old_owner, new_owner);

  /* should always be non-NULL, barring bugs */
  if (G_LIKELY (daemons != NULL))
    {
      GSList *iter;

      for (iter = *daemons; iter != NULL; iter = iter->next)
        _tp_dbus_daemon_name_owner_changed (iter->data, name, new_owner);
    }

  return FALSE;
}

static DBusHandlerResult
_tp_dbus_daemon_name_owner_changed_filter (DBusConnection *libdbus,
                                           DBusMessage *message,
                                           void *unused G_GNUC_UNUSED)
{
  /* We have to do the real work in an idle, so we don't break re-entrant
   * calls (the dbus-glib event source isn't re-entrant) */
  if (dbus_message_is_signal (message, DBUS_INTERFACE_DBUS,
        "NameOwnerChanged") &&
      dbus_message_has_sender (message, DBUS_SERVICE_DBUS))
    g_idle_add_full (G_PRIORITY_HIGH, noc_idle_context_invoke,
        noc_idle_context_new (libdbus, message),
        noc_idle_context_free);

  return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}

typedef struct {
    TpDBusDaemon *self;
    gchar *name;
    DBusMessage *reply;
    gsize refs;
} GetNameOwnerContext;

static GetNameOwnerContext *
get_name_owner_context_new (TpDBusDaemon *self,
                            const gchar *name)
{
  GetNameOwnerContext *context = g_slice_new (GetNameOwnerContext);

  context->self = g_object_ref (self);
  context->name = g_strdup (name);
  context->reply = NULL;
  context->refs = 1;
  return context;
}

static void
get_name_owner_context_unref (gpointer data)
{
  GetNameOwnerContext *context = data;

  if (--context->refs == 0)
    {
      g_object_unref (context->self);
      g_free (context->name);

      if (context->reply != NULL)
        dbus_message_unref (context->reply);

      g_slice_free (GetNameOwnerContext, context);
    }
}

static gboolean
_tp_dbus_daemon_get_name_owner_idle (gpointer data)
{
  GetNameOwnerContext *context = data;
  const gchar *owner = "";

  if (context->reply == NULL)
    {
      DEBUG ("Connection disconnected or no reply to GetNameOwner(%s)",
          context->name);
    }
  else if (dbus_message_get_type (context->reply) ==
      DBUS_MESSAGE_TYPE_METHOD_RETURN)
    {
      if (dbus_message_get_args (context->reply, NULL,
            DBUS_TYPE_STRING, &owner,
            DBUS_TYPE_INVALID))
        {
          DEBUG ("GetNameOwner(%s) -> %s", context->name, owner);
        }
      else
        {
          DEBUG ("Malformed reply from GetNameOwner(%s), assuming no owner",
              context->name);
        }
    }
  else
    {
      if (DEBUGGING)
        {
          DBusError error = DBUS_ERROR_INIT;

          if (dbus_set_error_from_message (&error, context->reply))
            {
              DEBUG ("GetNameOwner(%s) raised %s: %s", context->name,
                  error.name, error.message);
              dbus_error_free (&error);
            }
          else
            {
              DEBUG ("Unexpected message type from GetNameOwner(%s)",
                  context->name);
            }
        }
    }

  _tp_dbus_daemon_name_owner_changed (context->self, context->name, owner);

  return FALSE;
}

/**
 * TpDBusDaemonNameOwnerChangedCb:
 * @bus_daemon: The D-Bus daemon
 * @name: The name whose ownership has changed or been discovered
 * @new_owner: The unique name that now owns @name
 * @user_data: Arbitrary user-supplied data as passed to
 *  tp_dbus_daemon_watch_name_owner()
 *
 * The signature of the callback called by tp_dbus_daemon_watch_name_owner().
 *
 * Since: 0.7.1
 */

static inline gchar *
_tp_dbus_daemon_get_noc_rule (const gchar *name)
{
  return g_strdup_printf ("type='signal',"
      "sender='" DBUS_SERVICE_DBUS "',"
      "path='" DBUS_PATH_DBUS "',"
      "interface='"DBUS_INTERFACE_DBUS "',"
      "member='NameOwnerChanged',"
      "arg0='%s'", name);
}

static void
_tp_dbus_daemon_get_name_owner_notify (DBusPendingCall *pc,
                                       gpointer data)
{
  GetNameOwnerContext *context = data;

  /* we recycle this function for the case where the connection is already
   * disconnected: in that case we use pc = NULL */
  if (pc != NULL)
    context->reply = dbus_pending_call_steal_reply (pc);

  /* We have to do the real work in an idle, so we don't break re-entrant
   * calls (the dbus-glib event source isn't re-entrant) */
  context->refs++;
  g_idle_add_full (G_PRIORITY_HIGH, _tp_dbus_daemon_get_name_owner_idle,
      context, get_name_owner_context_unref);

  if (pc != NULL)
    dbus_pending_call_unref (pc);
}

/**
 * tp_dbus_daemon_watch_name_owner:
 * @self: The D-Bus daemon
 * @name: The name whose ownership is to be watched
 * @callback: Callback to call when the ownership is discovered or changes
 * @user_data: Arbitrary data to pass to @callback
 * @destroy: Called to destroy @user_data when the name owner watch is
 *  cancelled due to tp_dbus_daemon_cancel_name_owner_watch()
 *
 * Arrange for @callback to be called with the owner of @name as soon as
 * possible (which might even be before this function returns!), then
 * again every time the ownership of @name changes.
 *
 * If multiple watches are registered for the same @name, they will be called
 * in the order they were registered.
 *
 * Since: 0.7.1
 */
void
tp_dbus_daemon_watch_name_owner (TpDBusDaemon *self,
                                 const gchar *name,
                                 TpDBusDaemonNameOwnerChangedCb callback,
                                 gpointer user_data,
                                 GDestroyNotify destroy)
{
  _NameOwnerWatch *watch = g_hash_table_lookup (self->priv->name_owner_watches,
      name);

  g_return_if_fail (TP_IS_DBUS_DAEMON (self));
  g_return_if_fail (tp_dbus_check_valid_bus_name (name,
        TP_DBUS_NAME_TYPE_ANY, NULL));
  g_return_if_fail (callback != NULL);

  if (watch == NULL)
    {
      gchar *match_rule;
      DBusMessage *message;
      DBusPendingCall *pc = NULL;
      GetNameOwnerContext *context = get_name_owner_context_new (self, name);

      /* Allocate a single watch (common case) */
      watch = g_slice_new (_NameOwnerWatch);
      watch->callback = callback;
      watch->user_data = user_data;
      watch->destroy = destroy;
      watch->last_owner = NULL;

      g_hash_table_insert (self->priv->name_owner_watches, g_strdup (name),
          watch);

      /* We want to be notified about name owner changes for this one.
       * Assume the match addition will succeed; there's no good way to cope
       * with failure here... */
      match_rule = _tp_dbus_daemon_get_noc_rule (name);
      DEBUG ("Adding match rule %s", match_rule);
      dbus_bus_add_match (self->priv->libdbus, match_rule, NULL);
      g_free (match_rule);

      message = dbus_message_new_method_call (DBUS_SERVICE_DBUS,
          DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "GetNameOwner");

      if (message == NULL)
        g_error ("Out of memory");

      /* We already checked that @name was in (a small subset of) UTF-8,
       * so OOM is the only thing that can go wrong. The use of &name here
       * is because libdbus is strange. */
      if (!dbus_message_append_args (message,
            DBUS_TYPE_STRING, &name,
            DBUS_TYPE_INVALID))
        g_error ("Out of memory");

      if (!dbus_connection_send_with_reply (self->priv->libdbus,
          message, &pc, -1))
        g_error ("Out of memory");
      /* pc is unreffed by _tp_dbus_daemon_get_name_owner_notify */

      if (pc == NULL || dbus_pending_call_get_completed (pc))
        {
          /* pc can be NULL when the connection is already disconnected */
          _tp_dbus_daemon_get_name_owner_notify (pc, context);
          get_name_owner_context_unref (context);
        }
      else if (!dbus_pending_call_set_notify (pc,
            _tp_dbus_daemon_get_name_owner_notify,
            context, get_name_owner_context_unref))
        {
          g_error ("Out of memory");
        }
    }
  else
    {
      _NameOwnerSubWatch tmp = { callback, user_data, destroy };

      if (watch->callback == _tp_dbus_daemon_name_owner_changed_multiple)
        {
          /* The watch is already a "multiplexer", just append to it */
          GArray *array = watch->user_data;

          g_array_append_val (array, tmp);
        }
      else
        {
          /* Replace the old contents of the watch with one that dispatches
           * the signal to (potentially) more than one watcher */
          GArray *array = g_array_sized_new (FALSE, FALSE,
              sizeof (_NameOwnerSubWatch), 2);

          /* The new watcher */
          g_array_append_val (array, tmp);
          /* The old watcher */
          tmp.callback = watch->callback;
          tmp.user_data = watch->user_data;
          tmp.destroy = watch->destroy;
          g_array_prepend_val (array, tmp);

          watch->callback = _tp_dbus_daemon_name_owner_changed_multiple;
          watch->user_data = array;
          watch->destroy = _tp_dbus_daemon_name_owner_changed_multiple_free;
        }

      if (watch->last_owner != NULL)
        {
          /* FIXME: should avoid reentrancy? */
          callback (self, name, watch->last_owner, user_data);
        }
    }
}

static void
_tp_dbus_daemon_stop_watching (TpDBusDaemon *self,
                               const gchar *name,
                               _NameOwnerWatch *watch)
{
  gchar *match_rule;

  if (watch->destroy)
    watch->destroy (watch->user_data);

  g_free (watch->last_owner);
  g_slice_free (_NameOwnerWatch, watch);

  match_rule = _tp_dbus_daemon_get_noc_rule (name);
  DEBUG ("Removing match rule %s", match_rule);
  dbus_bus_remove_match (self->priv->libdbus, match_rule, NULL);
  g_free (match_rule);
}

/**
 * tp_dbus_daemon_cancel_name_owner_watch:
 * @self: the D-Bus daemon
 * @name: the name that was being watched
 * @callback: the callback that was called
 * @user_data: the user data that was provided
 *
 * If there was a previous call to tp_dbus_daemon_watch_name_owner()
 * with exactly the given @name, @callback and @user_data, remove it.
 *
 * If more than one watch matching the details provided was active, remove
 * only the most recently added one.
 *
 * Returns: %TRUE if there was such a watch, %FALSE otherwise
 *
 * Since: 0.7.1
 */
gboolean
tp_dbus_daemon_cancel_name_owner_watch (TpDBusDaemon *self,
                                        const gchar *name,
                                        TpDBusDaemonNameOwnerChangedCb callback,
                                        gconstpointer user_data)
{
  _NameOwnerWatch *watch = g_hash_table_lookup (self->priv->name_owner_watches,
      name);

  g_return_val_if_fail (TP_IS_DBUS_DAEMON (self), FALSE);
  g_return_val_if_fail (name != NULL, FALSE);
  g_return_val_if_fail (callback != NULL, FALSE);

  if (watch == NULL)
    {
      /* No watch at all */
      return FALSE;
    }
  else if (watch->callback == callback && watch->user_data == user_data)
    {
      /* Simple case: there is one name-owner watch and it's what we wanted */
      _tp_dbus_daemon_stop_watching (self, name, watch);
      g_hash_table_remove (self->priv->name_owner_watches, name);
      return TRUE;
    }
  else if (watch->callback == _tp_dbus_daemon_name_owner_changed_multiple)
    {
      /* Complicated case: this watch is a "multiplexer", we need to check
       * its contents */
      GArray *array = watch->user_data;
      guint i;

      for (i = 1; i <= array->len; i++)
        {
          _NameOwnerSubWatch *entry = &g_array_index (array,
              _NameOwnerSubWatch, array->len - i);

          if (entry->callback == callback && entry->user_data == user_data)
            {
              if (entry->destroy != NULL)
                entry->destroy (entry->user_data);

              g_array_remove_index (array, array->len - i);

              if (array->len == 0)
                {
                  _tp_dbus_daemon_stop_watching (self, name, watch);
                  g_hash_table_remove (self->priv->name_owner_watches, name);
                }

              return TRUE;
            }
        }
    }

  /* We haven't found it */
  return FALSE;
}

/* for internal use (TpChannel, TpConnection _new convenience functions) */
gboolean
_tp_dbus_daemon_get_name_owner (TpDBusDaemon *self,
                                gint timeout_ms,
                                const gchar *well_known_name,
                                gchar **unique_name,
                                GError **error)
{
  DBusGConnection *gconn;
  DBusConnection *dbc;
  DBusMessage *message;
  DBusMessage *reply;
  DBusError dbus_error;
  const char *name_in_reply;
  const GError *invalidated;

  g_return_val_if_fail (TP_IS_DBUS_DAEMON (self), FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  invalidated = tp_proxy_get_invalidated (self);

  if (invalidated != NULL)
    {
      if (error != NULL)
        *error = g_error_copy (invalidated);

      return FALSE;
    }

  gconn = tp_proxy_get_dbus_connection (self);
  dbc = dbus_g_connection_get_connection (gconn);

  message = dbus_message_new_method_call (DBUS_SERVICE_DBUS, DBUS_PATH_DBUS,
      DBUS_INTERFACE_DBUS, "GetNameOwner");

  if (message == NULL)
    g_error ("Out of memory");

  if (!dbus_message_append_args (message,
        DBUS_TYPE_STRING, &well_known_name,
        DBUS_TYPE_INVALID))
    g_error ("Out of memory");

  dbus_error_init (&dbus_error);
  reply = dbus_connection_send_with_reply_and_block (dbc, message,
      timeout_ms, &dbus_error);

  dbus_message_unref (message);

  if (reply == NULL)
    {
      if (!tp_strdiff (dbus_error.name, DBUS_ERROR_NO_MEMORY))
        g_error ("Out of memory");

      /* FIXME: ideally we'd use dbus-glib's error mapping for this */
      g_set_error (error, TP_DBUS_ERRORS, TP_DBUS_ERROR_NAME_OWNER_LOST,
          "%s: %s", dbus_error.name, dbus_error.message);

      dbus_error_free (&dbus_error);
      return FALSE;
    }

  if (!dbus_message_get_args (reply, &dbus_error,
        DBUS_TYPE_STRING, &name_in_reply,
        DBUS_TYPE_INVALID))
    {
      g_set_error (error, TP_DBUS_ERRORS, TP_DBUS_ERROR_NAME_OWNER_LOST,
          "%s: %s", dbus_error.name, dbus_error.message);

      dbus_error_free (&dbus_error);
      dbus_message_unref (reply);
      return FALSE;
    }

  if (unique_name != NULL)
    *unique_name = g_strdup (name_in_reply);

  dbus_message_unref (reply);

  return TRUE;
}

/**
 * tp_dbus_daemon_request_name:
 * @self: a TpDBusDaemon
 * @well_known_name: a well-known name to acquire
 * @idempotent: whether to consider it to be a success if this process
 *              already owns the name
 * @error: used to raise an error if %FALSE is returned
 *
 * Claim the given well-known name without queueing, allowing replacement
 * or replacing an existing name-owner. This makes a synchronous call to the
 * bus daemon.
 *
 * Returns: %TRUE if @well_known_name was claimed, or %FALSE and sets @error if
 *          an error occurred.
 *
 * Since: 0.7.30
 */
gboolean
tp_dbus_daemon_request_name (TpDBusDaemon *self,
                             const gchar *well_known_name,
                             gboolean idempotent,
                             GError **error)
{
  TpProxy *as_proxy = (TpProxy *) self;
  DBusGConnection *gconn;
  DBusConnection *dbc;
  DBusError dbus_error;
  int result;
  const GError *invalidated;

  g_return_val_if_fail (TP_IS_DBUS_DAEMON (self), FALSE);
  g_return_val_if_fail (tp_dbus_check_valid_bus_name (well_known_name,
        TP_DBUS_NAME_TYPE_WELL_KNOWN, error), FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  invalidated = tp_proxy_get_invalidated (self);

  if (invalidated != NULL)
    {
      if (error != NULL)
        *error = g_error_copy (invalidated);

      return FALSE;
    }

  gconn = as_proxy->dbus_connection;
  dbc = dbus_g_connection_get_connection (gconn);

  dbus_error_init (&dbus_error);
  result = dbus_bus_request_name (dbc, well_known_name,
      DBUS_NAME_FLAG_DO_NOT_QUEUE, &dbus_error);

  switch (result)
    {
    case DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER:
      return TRUE;

    case DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER:
      if (idempotent)
        {
          return TRUE;
        }
      else
        {
          g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
              "Name '%s' already in use by this process", well_known_name);
          return FALSE;
        }

    case DBUS_REQUEST_NAME_REPLY_EXISTS:
    case DBUS_REQUEST_NAME_REPLY_IN_QUEUE:
      /* the latter shouldn't actually happen since we said DO_NOT_QUEUE */
      g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
          "Name '%s' already in use by another process", well_known_name);
      return FALSE;

    case -1:
      g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
          "%s: %s", dbus_error.name, dbus_error.message);
      dbus_error_free (&dbus_error);
      return FALSE;

    default:
      g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
          "RequestName('%s') returned %d and I don't know what that means",
          well_known_name, result);
      return FALSE;
    }
}

/**
 * tp_dbus_daemon_release_name:
 * @self: a TpDBusDaemon
 * @well_known_name: a well-known name owned by this process to release
 * @error: used to raise an error if %FALSE is returned
 *
 * Release the given well-known name. This makes a synchronous call to the bus
 * daemon.
 *
 * Returns: %TRUE if @well_known_name was released, or %FALSE and sets @error
 *          if an error occurred.
 *
 * Since: 0.7.30
 */
gboolean
tp_dbus_daemon_release_name (TpDBusDaemon *self,
                             const gchar *well_known_name,
                             GError **error)
{
  TpProxy *as_proxy = (TpProxy *) self;
  DBusGConnection *gconn;
  DBusConnection *dbc;
  DBusError dbus_error;
  int result;
  const GError *invalidated;

  g_return_val_if_fail (TP_IS_DBUS_DAEMON (self), FALSE);
  g_return_val_if_fail (tp_dbus_check_valid_bus_name (well_known_name,
        TP_DBUS_NAME_TYPE_WELL_KNOWN, error), FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  invalidated = tp_proxy_get_invalidated (self);

  if (invalidated != NULL)
    {
      if (error != NULL)
        *error = g_error_copy (invalidated);

      return FALSE;
    }

  gconn = as_proxy->dbus_connection;
  dbc = dbus_g_connection_get_connection (gconn);
  dbus_error_init (&dbus_error);
  result = dbus_bus_release_name (dbc, well_known_name, &dbus_error);

  switch (result)
    {
    case DBUS_RELEASE_NAME_REPLY_RELEASED:
      return TRUE;

    case DBUS_RELEASE_NAME_REPLY_NOT_OWNER:
      g_set_error (error, TP_ERRORS, TP_ERROR_NOT_YOURS,
          "Name '%s' owned by another process", well_known_name);
      return FALSE;

    case DBUS_RELEASE_NAME_REPLY_NON_EXISTENT:
      g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
          "Name '%s' not owned", well_known_name);
      return FALSE;

    case -1:
      g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
          "%s: %s", dbus_error.name, dbus_error.message);
      dbus_error_free (&dbus_error);
      return FALSE;

    default:
      g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
          "ReleaseName('%s') returned %d and I don't know what that means",
          well_known_name, result);
      return FALSE;
    }
}

/**
 * tp_dbus_daemon_get_unique_name:
 * @self: object representing a connection to a bus
 *
 * <!-- Returns: is enough -->
 *
 * Returns: the unique name of this connection to the bus, which is valid for
 *  as long as this #TpDBusDaemon is
 * Since: 0.7.35
 */
const gchar *
tp_dbus_daemon_get_unique_name (TpDBusDaemon *self)
{
  g_return_val_if_fail (TP_IS_DBUS_DAEMON (self), NULL);

  return dbus_bus_get_unique_name (self->priv->libdbus);
}

typedef struct {
    TpDBusDaemon *self;
    DBusMessage *reply;
    TpDBusDaemonListNamesCb callback;
    gpointer user_data;
    GDestroyNotify destroy;
    gpointer weak_object;
    gsize refs;
} ListNamesContext;

static ListNamesContext *
list_names_context_new (TpDBusDaemon *self,
    TpDBusDaemonListNamesCb callback,
    gpointer user_data,
    GDestroyNotify destroy,
    GObject *weak_object)
{
  ListNamesContext *context = g_slice_new (ListNamesContext);

  context->self = g_object_ref (self);
  context->reply = NULL;
  context->callback = callback;
  context->user_data = user_data;
  context->destroy = destroy;
  context->weak_object = weak_object;

  if (context->weak_object != NULL)
    g_object_add_weak_pointer (weak_object, &context->weak_object);

  context->refs = 1;
  return context;
}

static void
list_names_context_unref (gpointer data)
{
  ListNamesContext *context = data;

  if (--context->refs == 0)
    {
      g_object_unref (context->self);

      if (context->reply != NULL)
        dbus_message_unref (context->reply);

      if (context->destroy != NULL)
        context->destroy (context->user_data);

      context->destroy = NULL;

      if (context->weak_object != NULL)
        g_object_remove_weak_pointer (context->weak_object,
            &context->weak_object);

      g_slice_free (ListNamesContext, context);
    }
}

static gboolean
_tp_dbus_daemon_list_names_idle (gpointer data)
{
  ListNamesContext *context = data;
  char **array = NULL;
  const gchar * const *result = NULL;
  GError *error = NULL;

  if (context->callback == NULL)
    {
      DEBUG ("Caller no longer cares (weak object vanished), ignoring");
      return FALSE;
    }

  if (context->reply == NULL)
    {
      g_set_error_literal (&error, DBUS_GERROR, DBUS_GERROR_DISCONNECTED,
          "DBusConnection disconnected");
    }
  else if (dbus_message_get_type (context->reply) ==
      DBUS_MESSAGE_TYPE_METHOD_RETURN)
    {
      int n_elements;

      if (dbus_message_get_args (context->reply, NULL,
            DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &array, &n_elements,
            DBUS_TYPE_INVALID))
        {
          result = (const gchar * const *) array;
          g_assert (result[n_elements] == NULL);
        }
      else
        {
          g_set_error_literal (&error, DBUS_GERROR, DBUS_GERROR_INVALID_ARGS,
              "Malformed reply from List*Names()");
        }
    }
  else
    {
      DBusError dbus_error = DBUS_ERROR_INIT;

      if (dbus_set_error_from_message (&dbus_error, context->reply))
        {
          /* FIXME: ideally we'd use dbus-glib's error mapping here, but we
           * don't have access to it */
          g_set_error (&error, DBUS_GERROR, DBUS_GERROR_FAILED,
              "List*Names() raised %s: %s", dbus_error.name,
              dbus_error.message);
          dbus_error_free (&dbus_error);
        }
      else
        {
          g_set_error_literal (&error, DBUS_GERROR, DBUS_GERROR_INVALID_ARGS,
              "Unexpected message type from List*Names()");
        }
    }

  if (error != NULL)
    DEBUG ("%s", error->message);

  context->callback (context->self, result, error, context->user_data,
      context->weak_object);
  dbus_free_string_array (array);   /* NULL-safe */
  return FALSE;
}

static void
_tp_dbus_daemon_list_names_notify (DBusPendingCall *pc,
                                   gpointer data)
{
  ListNamesContext *context = data;

  /* we recycle this function for the case where the connection is already
   * disconnected: in that case we use pc = NULL */
  if (pc != NULL)
    context->reply = dbus_pending_call_steal_reply (pc);

  /* We have to do the real work in an idle, so we don't break re-entrant
   * calls (the dbus-glib event source isn't re-entrant) */
  context->refs++;
  g_idle_add_full (G_PRIORITY_HIGH, _tp_dbus_daemon_list_names_idle,
      context, list_names_context_unref);

  if (pc != NULL)
    dbus_pending_call_unref (pc);
}

/**
 * TpDBusDaemonListNamesCb:
 * @bus_daemon: object representing a connection to a bus
 * @names: constant %NULL-terminated array of constant strings representing
 *  bus names, or %NULL on error
 * @error: the error that occurred, or %NULL on success
 * @user_data: the same user data that was passed to
 *  tp_dbus_daemon_list_names or tp_dbus_daemon_list_activatable_names
 * @weak_object: the same object that was passed to
 *  tp_dbus_daemon_list_names or tp_dbus_daemon_list_activatable_names
 *
 * Signature of a callback for functions that list bus names.
 *
 * Since: 0.7.35
 */

static void
_tp_dbus_daemon_list_names_common (TpDBusDaemon *self,
    const gchar *method,
    gint timeout_ms,
    TpDBusDaemonListNamesCb callback,
    gpointer user_data,
    GDestroyNotify destroy,
    GObject *weak_object)
{
  DBusMessage *message;
  DBusPendingCall *pc = NULL;
  ListNamesContext *context;

  g_return_if_fail (TP_IS_DBUS_DAEMON (self));
  g_return_if_fail (callback != NULL);
  g_return_if_fail (weak_object == NULL || G_IS_OBJECT (weak_object));

  message = dbus_message_new_method_call (DBUS_SERVICE_DBUS,
      DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, method);

  if (message == NULL)
    g_error ("Out of memory");

  if (!dbus_connection_send_with_reply (self->priv->libdbus,
      message, &pc, timeout_ms))
    g_error ("Out of memory");
  /* pc is unreffed by _tp_dbus_daemon_list_names_notify */

  context = list_names_context_new (self, callback, user_data, destroy,
    weak_object);

  if (pc == NULL || dbus_pending_call_get_completed (pc))
    {
      /* pc can be NULL when the connection is already disconnected */
      _tp_dbus_daemon_list_names_notify (pc, context);
      list_names_context_unref (context);
    }
  else if (!dbus_pending_call_set_notify (pc,
        _tp_dbus_daemon_list_names_notify, context,
        list_names_context_unref))
    {
      g_error ("Out of memory");
    }
}

/**
 * tp_dbus_daemon_list_names:
 * @self: object representing a connection to a bus
 * @timeout_ms: timeout for the call
 * @callback: callback to be called on success or failure; must not be %NULL
 * @user_data: opaque user-supplied data to pass to the callback
 * @destroy: if not %NULL, called with @user_data as argument after the call
 *  has succeeded or failed, or after @weak_object has been destroyed
 * @weak_object: if not %NULL, a GObject which will be weakly referenced; if
 *  it is destroyed, @callback will not be called at all
 *
 * Call the ListNames method on the bus daemon, asynchronously. The @callback
 * will be called from the main loop with a list of all the names (either
 * unique or well-known) that exist on the bus.
 *
 * In versions of telepathy-glib that have it, this should be preferred
 * instead of calling tp_cli_dbus_daemon_call_list_names(), since that
 * function will result in wakeups for every NameOwnerChanged signal.
 *
 * Since: 0.7.35
 */
void
tp_dbus_daemon_list_names (TpDBusDaemon *self,
    gint timeout_ms,
    TpDBusDaemonListNamesCb callback,
    gpointer user_data,
    GDestroyNotify destroy,
    GObject *weak_object)
{
  _tp_dbus_daemon_list_names_common (self, "ListNames", timeout_ms,
      callback, user_data, destroy, weak_object);
}

/**
 * tp_dbus_daemon_list_activatable_names:
 * @self: object representing a connection to a bus daemon
 * @timeout_ms: timeout for the call
 * @callback: callback to be called on success or failure; must not be %NULL
 * @user_data: opaque user-supplied data to pass to the callback
 * @destroy: if not %NULL, called with @user_data as argument after the call
 *  has succeeded or failed, or after @weak_object has been destroyed
 * @weak_object: if not %NULL, a GObject which will be weakly referenced; if
 *  it is destroyed, @callback will not be called at all
 *
 * Call the ListActivatableNames method on the bus daemon, asynchronously.
 * The @callback will be called from the main loop with a list of all the
 * well-known names that are available for service-activation on the bus.
 *
 * In versions of telepathy-glib that have it, this should be preferred
 * instead of calling tp_cli_dbus_daemon_call_list_activatable_names(), since
 * that function will result in wakeups for every NameOwnerChanged signal.
 *
 * Since: 0.7.35
 */
void
tp_dbus_daemon_list_activatable_names (TpDBusDaemon *self,
    gint timeout_ms,
    TpDBusDaemonListNamesCb callback,
    gpointer user_data,
    GDestroyNotify destroy,
    GObject *weak_object)
{
  _tp_dbus_daemon_list_names_common (self, "ListActivatableNames", timeout_ms,
      callback, user_data, destroy, weak_object);
}

static void
free_daemon_list (gpointer p)
{
  GSList **slistp = p;

  g_slist_free (*slistp);
  g_slice_free (GSList *, slistp);
}

/* If you add more slice-allocation in this function, make the suppression
 * "tp_dbus_daemon_constructor @daemons once per DBusConnection" in
 * telepathy-glib.supp more specific. */
static GObject *
tp_dbus_daemon_constructor (GType type,
                            guint n_params,
                            GObjectConstructParam *params)
{
  GObjectClass *object_class =
      (GObjectClass *) tp_dbus_daemon_parent_class;
  TpDBusDaemon *self = TP_DBUS_DAEMON (object_class->constructor (type,
        n_params, params));
  TpProxy *as_proxy = (TpProxy *) self;
  GSList **daemons;

  g_assert (!tp_strdiff (as_proxy->bus_name, DBUS_SERVICE_DBUS));
  g_assert (!tp_strdiff (as_proxy->object_path, DBUS_PATH_DBUS));

  self->priv->libdbus = dbus_connection_ref (
      dbus_g_connection_get_connection (
        tp_proxy_get_dbus_connection (self)));

  /* one ref per TpDBusDaemon, released in finalize */
  if (!dbus_connection_allocate_data_slot (&daemons_slot))
    g_error ("Out of memory");

  daemons = dbus_connection_get_data (self->priv->libdbus, daemons_slot);

  if (daemons == NULL)
    {
      /* This slice is never freed; it's a one-per-DBusConnection leak. */
      daemons = g_slice_new (GSList *);

      *daemons = NULL;
      dbus_connection_set_data (self->priv->libdbus, daemons_slot, daemons,
          free_daemon_list);

      /* we add this filter at most once per DBusConnection */
      if (!dbus_connection_add_filter (self->priv->libdbus,
            _tp_dbus_daemon_name_owner_changed_filter, NULL, NULL))
        g_error ("Out of memory");
    }

  *daemons = g_slist_prepend (*daemons, self);

  return (GObject *) self;
}

static void
tp_dbus_daemon_init (TpDBusDaemon *self)
{
  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, TP_TYPE_DBUS_DAEMON,
      TpDBusDaemonPrivate);

  self->priv->name_owner_watches = g_hash_table_new_full (g_str_hash,
      g_str_equal, g_free, NULL);
}

static void
tp_dbus_daemon_dispose (GObject *object)
{
  TpDBusDaemon *self = TP_DBUS_DAEMON (object);
  GSList **daemons;

  if (self->priv->name_owner_watches != NULL)
    {
      GHashTable *tmp = self->priv->name_owner_watches;
      GHashTableIter iter;
      gpointer k, v;

      self->priv->name_owner_watches = NULL;
      g_hash_table_iter_init (&iter, tmp);

      while (g_hash_table_iter_next (&iter, &k, &v))
        {
          _tp_dbus_daemon_stop_watching (self, k, v);
          g_hash_table_iter_remove (&iter);
        }

      g_hash_table_destroy (tmp);
    }

  if (self->priv->libdbus != NULL)
    {
      /* remove myself from the list to be notified on NoC */
      daemons = dbus_connection_get_data (self->priv->libdbus, daemons_slot);

      /* should always be non-NULL, barring bugs */
      if (G_LIKELY (daemons != NULL))
        {
          *daemons = g_slist_remove (*daemons, self);

          if (*daemons == NULL)
            {
              /* this results in a call to free_daemon_list (daemons) */
              dbus_connection_set_data (self->priv->libdbus, daemons_slot,
                  NULL, NULL);
            }
        }

      dbus_connection_unref (self->priv->libdbus);
      self->priv->libdbus = NULL;
    }

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

static void
tp_dbus_daemon_finalize (GObject *object)
{
  GObjectFinalizeFunc chain_up = G_OBJECT_CLASS (tp_dbus_daemon_parent_class)->finalize;

  /* one ref per TpDBusDaemon, from constructor */
  dbus_connection_free_data_slot (&daemons_slot);

  if (chain_up != NULL)
    chain_up (object);
}

/**
 * tp_dbus_daemon_init_known_interfaces:
 *
 * Ensure that the known interfaces for TpDBusDaemon 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() with first argument
 * %TP_TYPE_DBUS_DAEMON.
 *
 * Since: 0.7.32
 */
void
tp_dbus_daemon_init_known_interfaces (void)
{
  static gsize once = 0;

  if (g_once_init_enter (&once))
    {
      tp_proxy_init_known_interfaces ();
      tp_proxy_or_subclass_hook_on_interface_add (TP_TYPE_DBUS_DAEMON,
          tp_cli_dbus_daemon_add_signals);

      g_once_init_leave (&once, 1);
    }
}

static void
tp_dbus_daemon_class_init (TpDBusDaemonClass *klass)
{
  TpProxyClass *proxy_class = (TpProxyClass *) klass;
  GObjectClass *object_class = (GObjectClass *) klass;

  tp_dbus_daemon_init_known_interfaces ();

  g_type_class_add_private (klass, sizeof (TpDBusDaemonPrivate));

  object_class->constructor = tp_dbus_daemon_constructor;
  object_class->dispose = tp_dbus_daemon_dispose;
  object_class->finalize = tp_dbus_daemon_finalize;

  proxy_class->interface = TP_IFACE_QUARK_DBUS_DAEMON;
}

/* Auto-generated implementation of _tp_register_dbus_glib_marshallers */
#include "_gen/register-dbus-glib-marshallers-body.h"
