/* GDBus - GLib D-Bus Library
 *
 * Copyright (C) 2008-2010 Red Hat, Inc.
 *
 * 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 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., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 * Author: David Zeuthen <davidz@redhat.com>
 */

#include "config.h"

#include "gdbusobjectmanager.h"
#include "gdbusobjectmanagerclient.h"
#include "gdbusobject.h"
#include "gdbusprivate.h"
#include "gioenumtypes.h"
#include "ginitable.h"
#include "gasyncresult.h"
#include "gsimpleasyncresult.h"
#include "gasyncinitable.h"
#include "gdbusconnection.h"
#include "gdbusutils.h"
#include "gdbusobject.h"
#include "gdbusobjectproxy.h"
#include "gdbusproxy.h"
#include "gdbusinterface.h"

#include "glibintl.h"

/**
 * SECTION:gdbusobjectmanagerclient
 * @short_description: Client-side object manager
 * @include: gio/gio.h
 *
 * #GDBusObjectManagerClient is used to create, monitor and delete object
 * proxies for remote objects exported by a #GDBusObjectManagerServer (or any
 * code implementing the <ulink
 * url="http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-objectmanager">org.freedesktop.DBus.ObjectManager</ulink>
 * interface).
 *
 * Once an instance of this type has been created, you can connect to
 * the #GDBusObjectManager::object-added and
 * #GDBusObjectManager::object-removed signals and inspect the
 * #GDBusObjectProxy objects returned by
 * g_dbus_object_manager_get_objects().
 *
 * If the name for a #GDBusObjectManagerClient is not owned by anyone at
 * object construction time, the default behavior is to request the
 * message bus to launch an owner for the name. This behavior can be
 * disabled using the %G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_DO_NOT_AUTO_START
 * flag. It's also worth noting that this only works if the name of
 * interest is activatable in the first place. E.g. in some cases it
 * is not possible to launch an owner for the requested name. In this
 * case, #GDBusObjectManagerClient object construction still succeeds but
 * there will be no object proxies
 * (e.g. g_dbus_object_manager_get_objects() returns the empty list) and
 * the #GDBusObjectManagerClient:name-owner property is %NULL.
 *
 * The owner of the requested name can come and go (for example
 * consider a system service being restarted) – #GDBusObjectManagerClient
 * handles this case too; simply connect to the #GObject::notify
 * signal to watch for changes on the #GDBusObjectManagerClient:name-owner
 * property. When the name owner vanishes, the behavior is that
 * #GDBusObjectManagerClient:name-owner is set to %NULL (this includes
 * emission of the #GObject::notify signal) and then
 * #GDBusObjectManager::object-removed signals are synthesized
 * for all currently existing object proxies. Since
 * #GDBusObjectManagerClient:name-owner is %NULL when this happens, you can
 * use this information to disambiguate a synthesized signal from a
 * genuine signal caused by object removal on the remote
 * #GDBusObjectManager. Similarly, when a new name owner appears,
 * #GDBusObjectManager::object-added signals are synthesized
 * while #GDBusObjectManagerClient:name-owner is still %NULL. Only when all
 * object proxies have been added, the #GDBusObjectManagerClient:name-owner
 * is set to the new name owner (this includes emission of the
 * #GObject::notify signal).  Furthermore, you are guaranteed that
 * #GDBusObjectManagerClient:name-owner will alternate between a name owner
 * (e.g. <literal>:1.42</literal>) and %NULL even in the case where
 * the name of interest is atomically replaced
 *
 * Ultimately, #GDBusObjectManagerClient is used to obtain #GDBusProxy
 * instances. All signals (including the
 * <literal>org.freedesktop.DBus.Properties::PropertiesChanged</literal>
 * signal) delivered to #GDBusProxy instances are guaranteed to
 * originate from the name owner. This guarantee along with the
 * behavior described above, means that certain race conditions
 * including the <emphasis><quote>half the proxy is from the old owner
 * and the other half is from the new owner</quote></emphasis> problem
 * cannot happen.
 *
 * To avoid having the application connect to signals on the returned
 * #GDBusObjectProxy and #GDBusProxy objects, the
 * #GDBusObject::interface-added,
 * #GDBusObject::interface-removed,
 * #GDBusProxy::g-properties-changed and
 * #GDBusProxy::g-signal signals
 * are also emitted on the #GDBusObjectManagerClient instance managing these
 * objects. The signals emitted are
 * #GDBusObjectManager::interface-added,
 * #GDBusObjectManager::interface-removed,
 * #GDBusObjectManagerClient::interface-proxy-properties-changed and
 * #GDBusObjectManagerClient::interface-proxy-signal.
 *
 * Note that all callbacks and signals are emitted in the
 * <link linkend="g-main-context-push-thread-default">thread-default main loop</link>
 * that the #GDBusObjectManagerClient object was constructed
 * in. Additionally, the #GDBusObjectProxy and #GDBusProxy objects
 * originating from the #GDBusObjectManagerClient object will be created in
 * the same context and, consequently, will deliver signals in the
 * same main loop.
 */

struct _GDBusObjectManagerClientPrivate
{
  GMutex *lock;

  GBusType bus_type;
  GDBusConnection *connection;
  gchar *object_path;
  gchar *name;
  gchar *name_owner;
  GDBusObjectManagerClientFlags flags;

  GDBusProxy *control_proxy;

  GHashTable *map_object_path_to_object_proxy;

  guint signal_subscription_id;
  gchar *match_rule;

  GDBusProxyTypeFunc get_proxy_type_func;
  gpointer get_proxy_type_user_data;
  GDestroyNotify get_proxy_type_destroy_notify;
};

enum
{
  PROP_0,
  PROP_BUS_TYPE,
  PROP_CONNECTION,
  PROP_FLAGS,
  PROP_OBJECT_PATH,
  PROP_NAME,
  PROP_NAME_OWNER,
  PROP_GET_PROXY_TYPE_FUNC,
  PROP_GET_PROXY_TYPE_USER_DATA,
  PROP_GET_PROXY_TYPE_DESTROY_NOTIFY
};

enum
{
  INTERFACE_PROXY_SIGNAL_SIGNAL,
  INTERFACE_PROXY_PROPERTIES_CHANGED_SIGNAL,
  LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

static void initable_iface_init       (GInitableIface *initable_iface);
static void async_initable_iface_init (GAsyncInitableIface *async_initable_iface);
static void dbus_object_manager_interface_init (GDBusObjectManagerIface *iface);

G_DEFINE_TYPE_WITH_CODE (GDBusObjectManagerClient, g_dbus_object_manager_client, G_TYPE_OBJECT,
                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init)
                         G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init)
                         G_IMPLEMENT_INTERFACE (G_TYPE_DBUS_OBJECT_MANAGER, dbus_object_manager_interface_init));

static void maybe_unsubscribe_signals (GDBusObjectManagerClient *manager);

static void on_control_proxy_g_signal (GDBusProxy   *proxy,
                                       const gchar  *sender_name,
                                       const gchar  *signal_name,
                                       GVariant     *parameters,
                                       gpointer      user_data);

static void process_get_all_result (GDBusObjectManagerClient *manager,
                                    GVariant          *value,
                                    const gchar       *name_owner);

static void
g_dbus_object_manager_client_finalize (GObject *object)
{
  GDBusObjectManagerClient *manager = G_DBUS_OBJECT_MANAGER_CLIENT (object);

  maybe_unsubscribe_signals (manager);

  g_hash_table_unref (manager->priv->map_object_path_to_object_proxy);

  if (manager->priv->control_proxy != NULL)
    {
      g_signal_handlers_disconnect_by_func (manager->priv->control_proxy,
                                            on_control_proxy_g_signal,
                                            manager);
      g_object_unref (manager->priv->control_proxy);
    }
  g_object_unref (manager->priv->connection);
  g_free (manager->priv->object_path);
  g_free (manager->priv->name);
  g_free (manager->priv->name_owner);

  if (manager->priv->get_proxy_type_destroy_notify != NULL)
    manager->priv->get_proxy_type_destroy_notify (manager->priv->get_proxy_type_user_data);

  g_mutex_free (manager->priv->lock);

  if (G_OBJECT_CLASS (g_dbus_object_manager_client_parent_class)->finalize != NULL)
    G_OBJECT_CLASS (g_dbus_object_manager_client_parent_class)->finalize (object);
}

static void
g_dbus_object_manager_client_get_property (GObject    *_object,
                                           guint       prop_id,
                                           GValue     *value,
                                           GParamSpec *pspec)
{
  GDBusObjectManagerClient *manager = G_DBUS_OBJECT_MANAGER_CLIENT (_object);

  switch (prop_id)
    {
    case PROP_CONNECTION:
      g_value_set_object (value, g_dbus_object_manager_client_get_connection (manager));
      break;

    case PROP_OBJECT_PATH:
      g_value_set_string (value, g_dbus_object_manager_get_object_path (G_DBUS_OBJECT_MANAGER (manager)));
      break;

    case PROP_NAME:
      g_value_set_string (value, g_dbus_object_manager_client_get_name (manager));
      break;

    case PROP_FLAGS:
      g_value_set_flags (value, g_dbus_object_manager_client_get_flags (manager));
      break;

    case PROP_NAME_OWNER:
      g_value_take_string (value, g_dbus_object_manager_client_get_name_owner (manager));
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (_object, prop_id, pspec);
      break;
    }
}

static void
g_dbus_object_manager_client_set_property (GObject       *_object,
                                           guint          prop_id,
                                           const GValue  *value,
                                           GParamSpec    *pspec)
{
  GDBusObjectManagerClient *manager = G_DBUS_OBJECT_MANAGER_CLIENT (_object);

  switch (prop_id)
    {
    case PROP_BUS_TYPE:
      manager->priv->bus_type = g_value_get_enum (value);
      break;

    case PROP_CONNECTION:
      if (g_value_get_object (value) != NULL)
        {
          g_assert (manager->priv->connection == NULL);
          g_assert (G_IS_DBUS_CONNECTION (g_value_get_object (value)));
          manager->priv->connection = g_value_dup_object (value);
        }
      break;

    case PROP_OBJECT_PATH:
      g_assert (manager->priv->object_path == NULL);
      g_assert (g_variant_is_object_path (g_value_get_string (value)));
      manager->priv->object_path = g_value_dup_string (value);
      break;

    case PROP_NAME:
      g_assert (manager->priv->name == NULL);
      g_assert (g_dbus_is_name (g_value_get_string (value)));
      manager->priv->name = g_value_dup_string (value);
      break;

    case PROP_FLAGS:
      manager->priv->flags = g_value_get_flags (value);
      break;

    case PROP_GET_PROXY_TYPE_FUNC:
      manager->priv->get_proxy_type_func = g_value_get_pointer (value);
      break;

    case PROP_GET_PROXY_TYPE_USER_DATA:
      manager->priv->get_proxy_type_user_data = g_value_get_pointer (value);
      break;

    case PROP_GET_PROXY_TYPE_DESTROY_NOTIFY:
      manager->priv->get_proxy_type_destroy_notify = g_value_get_pointer (value);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (_object, prop_id, pspec);
      break;
    }
}

static void
g_dbus_object_manager_client_class_init (GDBusObjectManagerClientClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->finalize     = g_dbus_object_manager_client_finalize;
  gobject_class->set_property = g_dbus_object_manager_client_set_property;
  gobject_class->get_property = g_dbus_object_manager_client_get_property;

  /**
   * GDBusObjectManagerClient:connection:
   *
   * The #GDBusConnection to use.
   *
   * Since: 2.30
   */
  g_object_class_install_property (gobject_class,
                                   PROP_CONNECTION,
                                   g_param_spec_object ("connection",
                                                        "Connection",
                                                        "The connection to use",
                                                        G_TYPE_DBUS_CONNECTION,
                                                        G_PARAM_READABLE |
                                                        G_PARAM_WRITABLE |
                                                        G_PARAM_CONSTRUCT_ONLY |
                                                        G_PARAM_STATIC_STRINGS));

  /**
   * GDBusObjectManagerClient:bus-type:
   *
   * If this property is not %G_BUS_TYPE_NONE, then
   * #GDBusObjectManagerClient:connection must be %NULL and will be set to the
   * #GDBusConnection obtained by calling g_bus_get() with the value
   * of this property.
   *
   * Since: 2.30
   */
  g_object_class_install_property (gobject_class,
                                   PROP_BUS_TYPE,
                                   g_param_spec_enum ("bus-type",
                                                      "Bus Type",
                                                      "The bus to connect to, if any",
                                                      G_TYPE_BUS_TYPE,
                                                      G_BUS_TYPE_NONE,
                                                      G_PARAM_WRITABLE |
                                                      G_PARAM_CONSTRUCT_ONLY |
                                                      G_PARAM_STATIC_NAME |
                                                      G_PARAM_STATIC_BLURB |
                                                      G_PARAM_STATIC_NICK));

  /**
   * GDBusObjectManagerClient:flags:
   *
   * Flags from the #GDBusObjectManagerClientFlags enumeration.
   *
   * Since: 2.30
   */
  g_object_class_install_property (gobject_class,
                                   PROP_FLAGS,
                                   g_param_spec_flags ("flags",
                                                       "Flags",
                                                       "Flags for the proxy manager",
                                                       G_TYPE_DBUS_OBJECT_MANAGER_CLIENT_FLAGS,
                                                       G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
                                                       G_PARAM_READABLE |
                                                       G_PARAM_WRITABLE |
                                                       G_PARAM_CONSTRUCT_ONLY |
                                                       G_PARAM_STATIC_NAME |
                                                       G_PARAM_STATIC_BLURB |
                                                       G_PARAM_STATIC_NICK));

  /**
   * GDBusObjectManagerClient:object-path:
   *
   * The object path the manager is for.
   *
   * Since: 2.30
   */
  g_object_class_install_property (gobject_class,
                                   PROP_OBJECT_PATH,
                                   g_param_spec_string ("object-path",
                                                        "Object Path",
                                                        "The object path of the control object",
                                                        NULL,
                                                        G_PARAM_READABLE |
                                                        G_PARAM_WRITABLE |
                                                        G_PARAM_CONSTRUCT_ONLY |
                                                        G_PARAM_STATIC_STRINGS));

  /**
   * GDBusObjectManagerClient:name:
   *
   * The well-known name or unique name that the manager is for.
   *
   * Since: 2.30
   */
  g_object_class_install_property (gobject_class,
                                   PROP_NAME,
                                   g_param_spec_string ("name",
                                                        "Name",
                                                        "Name that the manager is for",
                                                        NULL,
                                                        G_PARAM_READABLE |
                                                        G_PARAM_WRITABLE |
                                                        G_PARAM_CONSTRUCT_ONLY |
                                                        G_PARAM_STATIC_STRINGS));

  /**
   * GDBusObjectManagerClient:name-owner:
   *
   * The unique name that owns #GDBusObjectManagerClient:name or %NULL if
   * no-one is currently owning the name. Connect to the
   * #GObject::notify signal to track changes to this property.
   *
   * Since: 2.30
   */
  g_object_class_install_property (gobject_class,
                                   PROP_NAME_OWNER,
                                   g_param_spec_string ("name-owner",
                                                        "Name Owner",
                                                        "The owner of the name we are watching",
                                                        NULL,
                                                        G_PARAM_READABLE |
                                                        G_PARAM_STATIC_STRINGS));

  /**
   * GDBusObjectManagerClient:get-proxy-type-func:
   *
   * The #GDBusProxyTypeFunc to use when determining what #GType to
   * use for interface proxies or %NULL.
   *
   * Since: 2.30
   */
  g_object_class_install_property (gobject_class,
                                   PROP_GET_PROXY_TYPE_FUNC,
                                   g_param_spec_pointer ("get-proxy-type-func",
                                                         "GDBusProxyTypeFunc Function Pointer",
                                                         "The GDBusProxyTypeFunc pointer to use",
                                                         G_PARAM_READABLE |
                                                         G_PARAM_WRITABLE |
                                                         G_PARAM_CONSTRUCT_ONLY |
                                                         G_PARAM_STATIC_STRINGS));

  /**
   * GDBusObjectManagerClient:get-proxy-type-user-data:
   *
   * The #gpointer user_data to pass to #GDBusObjectManagerClient:get-proxy-type-func.
   *
   * Since: 2.30
   */
  g_object_class_install_property (gobject_class,
                                   PROP_GET_PROXY_TYPE_USER_DATA,
                                   g_param_spec_pointer ("get-proxy-type-user-data",
                                                         "GDBusProxyTypeFunc User Data",
                                                         "The GDBusProxyTypeFunc user_data",
                                                         G_PARAM_READABLE |
                                                         G_PARAM_WRITABLE |
                                                         G_PARAM_CONSTRUCT_ONLY |
                                                         G_PARAM_STATIC_STRINGS));

  /**
   * GDBusObjectManagerClient:get-proxy-type-destroy-notify:
   *
   * A #GDestroyNotify for the #gpointer user_data in #GDBusObjectManagerClient:get-proxy-type-user-data.
   *
   * Since: 2.30
   */
  g_object_class_install_property (gobject_class,
                                   PROP_GET_PROXY_TYPE_DESTROY_NOTIFY,
                                   g_param_spec_pointer ("get-proxy-type-destroy-notify",
                                                         "GDBusProxyTypeFunc user data free function",
                                                         "The GDBusProxyTypeFunc user data free function",
                                                         G_PARAM_READABLE |
                                                         G_PARAM_WRITABLE |
                                                         G_PARAM_CONSTRUCT_ONLY |
                                                         G_PARAM_STATIC_STRINGS));

  /**
   * GDBusObjectManagerClient::interface-proxy-signal:
   * @manager: The #GDBusObjectManagerClient emitting the signal.
   * @object_proxy: The #GDBusObjectProxy on which an interface is emitting a D-Bus signal.
   * @interface_proxy: The #GDBusProxy that is emitting a D-Bus signal.
   * @sender_name: The sender of the signal or NULL if the connection is not a bus connection.
   * @signal_name: The signal name.
   * @parameters: A #GVariant tuple with parameters for the signal.
   *
   * Emitted when a D-Bus signal is received on @interface_proxy.
   *
   * This signal exists purely as a convenience to avoid having to
   * connect signals to all interface proxies managed by @manager.
   *
   * This signal is emitted in the
   * <link linkend="g-main-context-push-thread-default">thread-default main loop</link>
   * that @manager was constructed in.
   *
   * Since: 2.30
   */
  signals[INTERFACE_PROXY_SIGNAL_SIGNAL] =
    g_signal_new ("interface-proxy-signal",
                  G_TYPE_DBUS_OBJECT_MANAGER_CLIENT,
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GDBusObjectManagerClientClass, interface_proxy_signal),
                  NULL,
                  NULL,
                  NULL,
                  G_TYPE_NONE,
                  5,
                  G_TYPE_DBUS_OBJECT_PROXY,
                  G_TYPE_DBUS_PROXY,
                  G_TYPE_STRING,
                  G_TYPE_STRING,
                  G_TYPE_VARIANT);

  /**
   * GDBusObjectManagerClient::interface-proxy-properties-changed:
   * @manager: The #GDBusObjectManagerClient emitting the signal.
   * @object_proxy: The #GDBusObjectProxy on which an interface has properties that are changing.
   * @interface_proxy: The #GDBusProxy that has properties that are changing.
   * @changed_properties: A #GVariant containing the properties that changed.
   * @invalidated_properties: A %NULL terminated array of properties that was invalidated.
   *
   * Emitted when one or more D-Bus properties on proxy changes. The
   * local cache has already been updated when this signal fires. Note
   * that both @changed_properties and @invalidated_properties are
   * guaranteed to never be %NULL (either may be empty though).
   *
   * This signal exists purely as a convenience to avoid having to
   * connect signals to all interface proxies managed by @manager.
   *
   * This signal is emitted in the
   * <link linkend="g-main-context-push-thread-default">thread-default main loop</link>
   * that @manager was constructed in.
   *
   * Since: 2.30
   */
  signals[INTERFACE_PROXY_PROPERTIES_CHANGED_SIGNAL] =
    g_signal_new ("interface-proxy-properties-changed",
                  G_TYPE_DBUS_OBJECT_MANAGER_CLIENT,
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GDBusObjectManagerClientClass, interface_proxy_properties_changed),
                  NULL,
                  NULL,
                  NULL,
                  G_TYPE_NONE,
                  4,
                  G_TYPE_DBUS_OBJECT_PROXY,
                  G_TYPE_DBUS_PROXY,
                  G_TYPE_VARIANT,
                  G_TYPE_STRV);

  g_type_class_add_private (klass, sizeof (GDBusObjectManagerClientPrivate));
}

static void
g_dbus_object_manager_client_init (GDBusObjectManagerClient *manager)
{
  manager->priv = G_TYPE_INSTANCE_GET_PRIVATE (manager,
                                               G_TYPE_DBUS_OBJECT_MANAGER_CLIENT,
                                               GDBusObjectManagerClientPrivate);
  manager->priv->lock = g_mutex_new ();
  manager->priv->map_object_path_to_object_proxy = g_hash_table_new_full (g_str_hash,
                                                                          g_str_equal,
                                                                          g_free,
                                                                          (GDestroyNotify) g_object_unref);
}

/* ---------------------------------------------------------------------------------------------------- */

/**
 * g_dbus_object_manager_client_new_sync:
 * @connection: A #GDBusConnection.
 * @flags: Zero or more flags from the #GDBusObjectManagerClientFlags enumeration.
 * @name: The owner of the control object (unique or well-known name).
 * @object_path: The object path of the control object.
 * @get_proxy_type_func: A #GDBusProxyTypeFunc function or %NULL to always construct #GDBusProxy proxies.
 * @get_proxy_type_user_data: User data to pass to @get_proxy_type_func.
 * @get_proxy_type_destroy_notify: (allow-none): Free function for @get_proxy_type_user_data or %NULL.
 * @cancellable: A #GCancellable or %NULL
 * @error: Return location for error or %NULL.
 *
 * Creates a new #GDBusObjectManagerClient object.
 *
 * This is a synchronous failable constructor - the calling thread is
 * blocked until a reply is received. See g_dbus_object_manager_client_new()
 * for the asynchronous version.
 *
 * Returns: (transfer full) (type GDBusObjectManagerClient): A
 *   #GDBusObjectManagerClient object or %NULL if @error is set. Free
 *   with g_object_unref().
 *
 * Since: 2.30
 */
GDBusObjectManager *
g_dbus_object_manager_client_new_sync (GDBusConnection               *connection,
                                       GDBusObjectManagerClientFlags  flags,
                                       const gchar                   *name,
                                       const gchar                   *object_path,
                                       GDBusProxyTypeFunc             get_proxy_type_func,
                                       gpointer                       get_proxy_type_user_data,
                                       GDestroyNotify                 get_proxy_type_destroy_notify,
                                       GCancellable                  *cancellable,
                                       GError                       **error)
{
  GInitable *initable;

  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
  g_return_val_if_fail ((name == NULL && g_dbus_connection_get_unique_name (connection) == NULL) ||
                        g_dbus_is_name (name), NULL);
  g_return_val_if_fail (g_variant_is_object_path (object_path), NULL);
  g_return_val_if_fail (error == NULL || *error == NULL, NULL);

  initable = g_initable_new (G_TYPE_DBUS_OBJECT_MANAGER_CLIENT,
                             cancellable,
                             error,
                             "connection", connection,
                             "flags", flags,
                             "name", name,
                             "object-path", object_path,
                             "get-proxy-type-func", get_proxy_type_func,
                             "get-proxy-type-user-data", get_proxy_type_user_data,
                             "get-proxy-type-destroy-notify", get_proxy_type_destroy_notify,
                             NULL);
  if (initable != NULL)
    return G_DBUS_OBJECT_MANAGER (initable);
  else
    return NULL;
}

/**
 * g_dbus_object_manager_client_new:
 * @connection: A #GDBusConnection.
 * @flags: Zero or more flags from the #GDBusObjectManagerClientFlags enumeration.
 * @name: The owner of the control object (unique or well-known name).
 * @object_path: The object path of the control object.
 * @get_proxy_type_func: A #GDBusProxyTypeFunc function or %NULL to always construct #GDBusProxy proxies.
 * @get_proxy_type_user_data: User data to pass to @get_proxy_type_func.
 * @get_proxy_type_destroy_notify: (allow-none): Free function for @get_proxy_type_user_data or %NULL.
 * @cancellable: A #GCancellable or %NULL
 * @callback: A #GAsyncReadyCallback to call when the request is satisfied.
 * @user_data: The data to pass to @callback.
 *
 * Asynchronously creates a new #GDBusObjectManagerClient object.
 *
 * This is an asynchronous failable constructor. When the result is
 * ready, @callback will be invoked in the
 * <link linkend="g-main-context-push-thread-default">thread-default main loop</link>
 * of the thread you are calling this method from. You can
 * then call g_dbus_object_manager_client_new_finish() to get the result. See
 * g_dbus_object_manager_client_new_sync() for the synchronous version.
 *
 * Since: 2.30
 */
void
g_dbus_object_manager_client_new (GDBusConnection               *connection,
                                  GDBusObjectManagerClientFlags  flags,
                                  const gchar                   *name,
                                  const gchar                   *object_path,
                                  GDBusProxyTypeFunc             get_proxy_type_func,
                                  gpointer                       get_proxy_type_user_data,
                                  GDestroyNotify                 get_proxy_type_destroy_notify,
                                  GCancellable                  *cancellable,
                                  GAsyncReadyCallback            callback,
                                  gpointer                       user_data)
{
  g_return_if_fail (G_IS_DBUS_CONNECTION (connection));
  g_return_if_fail ((name == NULL && g_dbus_connection_get_unique_name (connection) == NULL) ||
                        g_dbus_is_name (name));
  g_return_if_fail (g_variant_is_object_path (object_path));

  g_async_initable_new_async (G_TYPE_DBUS_OBJECT_MANAGER_CLIENT,
                              G_PRIORITY_DEFAULT,
                              cancellable,
                              callback,
                              user_data,
                              "connection", connection,
                              "flags", flags,
                              "name", name,
                              "object-path", object_path,
                              "get-proxy-type-func", get_proxy_type_func,
                              "get-proxy-type-user-data", get_proxy_type_user_data,
                              "get-proxy-type-destroy-notify", get_proxy_type_destroy_notify,
                              NULL);
}

/**
 * g_dbus_object_manager_client_new_finish:
 * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to g_dbus_object_manager_client_new().
 * @error: Return location for error or %NULL.
 *
 * Finishes an operation started with g_dbus_object_manager_client_new().
 *
 * Returns: (transfer full) (type GDBusObjectManagerClient): A
 *   #GDBusObjectManagerClient object or %NULL if @error is set. Free
 *   with g_object_unref().
 *
 * Since: 2.30
 */
GDBusObjectManager *
g_dbus_object_manager_client_new_finish (GAsyncResult   *res,
                                         GError        **error)
{
  GObject *object;
  GObject *source_object;

  source_object = g_async_result_get_source_object (res);
  g_assert (source_object != NULL);

  object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object),
                                        res,
                                        error);
  g_object_unref (source_object);

  if (object != NULL)
    return G_DBUS_OBJECT_MANAGER (object);
  else
    return NULL;
}

/* ---------------------------------------------------------------------------------------------------- */

/**
 * g_dbus_object_manager_client_new_for_bus_sync:
 * @bus_type: A #GBusType.
 * @flags: Zero or more flags from the #GDBusObjectManagerClientFlags enumeration.
 * @name: The owner of the control object (unique or well-known name).
 * @object_path: The object path of the control object.
 * @get_proxy_type_func: A #GDBusProxyTypeFunc function or %NULL to always construct #GDBusProxy proxies.
 * @get_proxy_type_user_data: User data to pass to @get_proxy_type_func.
 * @get_proxy_type_destroy_notify: (allow-none): Free function for @get_proxy_type_user_data or %NULL.
 * @cancellable: A #GCancellable or %NULL
 * @error: Return location for error or %NULL.
 *
 * Like g_dbus_object_manager_client_new_sync() but takes a #GBusType instead
 * of a #GDBusConnection.
 *
 * This is a synchronous failable constructor - the calling thread is
 * blocked until a reply is received. See g_dbus_object_manager_client_new_for_bus()
 * for the asynchronous version.
 *
 * Returns: (transfer full) (type GDBusObjectManagerClient): A
 *   #GDBusObjectManagerClient object or %NULL if @error is set. Free
 *   with g_object_unref().
 *
 * Since: 2.30
 */
GDBusObjectManager *
g_dbus_object_manager_client_new_for_bus_sync (GBusType                       bus_type,
                                               GDBusObjectManagerClientFlags  flags,
                                               const gchar                   *name,
                                               const gchar                   *object_path,
                                               GDBusProxyTypeFunc             get_proxy_type_func,
                                               gpointer                       get_proxy_type_user_data,
                                               GDestroyNotify                 get_proxy_type_destroy_notify,
                                               GCancellable                  *cancellable,
                                               GError                       **error)
{
  GInitable *initable;

  g_return_val_if_fail (bus_type != G_BUS_TYPE_NONE, NULL);
  g_return_val_if_fail (g_dbus_is_name (name), NULL);
  g_return_val_if_fail (g_variant_is_object_path (object_path), NULL);
  g_return_val_if_fail (error == NULL || *error == NULL, NULL);

  initable = g_initable_new (G_TYPE_DBUS_OBJECT_MANAGER_CLIENT,
                             cancellable,
                             error,
                             "bus-type", bus_type,
                             "flags", flags,
                             "name", name,
                             "object-path", object_path,
                             "get-proxy-type-func", get_proxy_type_func,
                             "get-proxy-type-user-data", get_proxy_type_user_data,
                             "get-proxy-type-destroy-notify", get_proxy_type_destroy_notify,
                             NULL);
  if (initable != NULL)
    return G_DBUS_OBJECT_MANAGER (initable);
  else
    return NULL;
}

/**
 * g_dbus_object_manager_client_new_for_bus:
 * @bus_type: A #GBusType.
 * @flags: Zero or more flags from the #GDBusObjectManagerClientFlags enumeration.
 * @name: The owner of the control object (unique or well-known name).
 * @object_path: The object path of the control object.
 * @get_proxy_type_func: A #GDBusProxyTypeFunc function or %NULL to always construct #GDBusProxy proxies.
 * @get_proxy_type_user_data: User data to pass to @get_proxy_type_func.
 * @get_proxy_type_destroy_notify: (allow-none): Free function for @get_proxy_type_user_data or %NULL.
 * @cancellable: A #GCancellable or %NULL
 * @callback: A #GAsyncReadyCallback to call when the request is satisfied.
 * @user_data: The data to pass to @callback.
 *
 * Like g_dbus_object_manager_client_new() but takes a #GBusType instead of a
 * #GDBusConnection.
 *
 * This is an asynchronous failable constructor. When the result is
 * ready, @callback will be invoked in the
 * <link linkend="g-main-context-push-thread-default">thread-default main loop</link>
 * of the thread you are calling this method from. You can
 * then call g_dbus_object_manager_client_new_for_bus_finish() to get the result. See
 * g_dbus_object_manager_client_new_for_bus_sync() for the synchronous version.
 *
 * Since: 2.30
 */
void
g_dbus_object_manager_client_new_for_bus (GBusType                       bus_type,
                                          GDBusObjectManagerClientFlags  flags,
                                          const gchar                   *name,
                                          const gchar                   *object_path,
                                          GDBusProxyTypeFunc             get_proxy_type_func,
                                          gpointer                       get_proxy_type_user_data,
                                          GDestroyNotify                 get_proxy_type_destroy_notify,
                                          GCancellable                  *cancellable,
                                          GAsyncReadyCallback            callback,
                                          gpointer                       user_data)
{
  g_return_if_fail (bus_type != G_BUS_TYPE_NONE);
  g_return_if_fail (g_dbus_is_name (name));
  g_return_if_fail (g_variant_is_object_path (object_path));

  g_async_initable_new_async (G_TYPE_DBUS_OBJECT_MANAGER_CLIENT,
                              G_PRIORITY_DEFAULT,
                              cancellable,
                              callback,
                              user_data,
                              "bus-type", bus_type,
                              "flags", flags,
                              "name", name,
                              "object-path", object_path,
                              "get-proxy-type-func", get_proxy_type_func,
                              "get-proxy-type-user-data", get_proxy_type_user_data,
                              "get-proxy-type-destroy-notify", get_proxy_type_destroy_notify,
                              NULL);
}

/**
 * g_dbus_object_manager_client_new_for_bus_finish:
 * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to g_dbus_object_manager_client_new_for_bus().
 * @error: Return location for error or %NULL.
 *
 * Finishes an operation started with g_dbus_object_manager_client_new_for_bus().
 *
 * Returns: (transfer full) (type GDBusObjectManagerClient): A
 *   #GDBusObjectManagerClient object or %NULL if @error is set. Free
 *   with g_object_unref().
 *
 * Since: 2.30
 */
GDBusObjectManager *
g_dbus_object_manager_client_new_for_bus_finish (GAsyncResult   *res,
                                                 GError        **error)
{
  GObject *object;
  GObject *source_object;

  source_object = g_async_result_get_source_object (res);
  g_assert (source_object != NULL);

  object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object),
                                        res,
                                        error);
  g_object_unref (source_object);

  if (object != NULL)
    return G_DBUS_OBJECT_MANAGER (object);
  else
    return NULL;
}

/* ---------------------------------------------------------------------------------------------------- */

/**
 * g_dbus_object_manager_client_get_connection:
 * @manager: A #GDBusObjectManagerClient
 *
 * Gets the #GDBusConnection used by @manager.
 *
 * Returns: (transfer none): A #GDBusConnection object. Do not free,
 *   the object belongs to @manager.
 *
 * Since: 2.30
 */
GDBusConnection *
g_dbus_object_manager_client_get_connection (GDBusObjectManagerClient *manager)
{
  GDBusConnection *ret;
  g_return_val_if_fail (G_IS_DBUS_OBJECT_MANAGER_CLIENT (manager), NULL);
  g_mutex_lock (manager->priv->lock);
  ret = manager->priv->connection;
  g_mutex_unlock (manager->priv->lock);
  return ret;
}

/**
 * g_dbus_object_manager_client_get_name:
 * @manager: A #GDBusObjectManagerClient
 *
 * Gets the name that @manager is for.
 *
 * Returns: A unique or well-known name. Do not free, the string
 * belongs to @manager.
 *
 * Since: 2.30
 */
const gchar *
g_dbus_object_manager_client_get_name (GDBusObjectManagerClient *manager)
{
  const gchar *ret;
  g_return_val_if_fail (G_IS_DBUS_OBJECT_MANAGER_CLIENT (manager), NULL);
  g_mutex_lock (manager->priv->lock);
  ret = manager->priv->name;
  g_mutex_unlock (manager->priv->lock);
  return ret;
}

/**
 * g_dbus_object_manager_client_get_flags:
 * @manager: A #GDBusObjectManagerClient
 *
 * Gets the flags that @manager was constructed with.
 *
 * Returns: Zero of more flags from the #GDBusObjectManagerClientFlags
 * enumeration.
 *
 * Since: 2.30
 */
GDBusObjectManagerClientFlags
g_dbus_object_manager_client_get_flags (GDBusObjectManagerClient *manager)
{
  GDBusObjectManagerClientFlags ret;
  g_return_val_if_fail (G_IS_DBUS_OBJECT_MANAGER_CLIENT (manager), G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE);
  g_mutex_lock (manager->priv->lock);
  ret = manager->priv->flags;
  g_mutex_unlock (manager->priv->lock);
  return ret;
}

/**
 * g_dbus_object_manager_client_get_name_owner:
 * @manager: A #GDBusObjectManagerClient.
 *
 * The unique name that owns the name that @manager is for or %NULL if
 * no-one currently owns that name. You can connect to the
 * #GObject::notify signal to track changes to the
 * #GDBusObjectManagerClient:name-owner property.
 *
 * Returns: The name owner or %NULL if no name owner exists. Free with
 * g_free().
 *
 * Since: 2.30
 */
gchar *
g_dbus_object_manager_client_get_name_owner (GDBusObjectManagerClient *manager)
{
  gchar *ret;
  g_return_val_if_fail (G_IS_DBUS_OBJECT_MANAGER_CLIENT (manager), NULL);
  g_mutex_lock (manager->priv->lock);
  ret = g_strdup (manager->priv->name_owner);
  g_mutex_unlock (manager->priv->lock);
  return ret;
}

/* ---------------------------------------------------------------------------------------------------- */

/* signal handler for all objects we manage - we dispatch signals
 * from here to the objects
 */
static void
signal_cb (GDBusConnection *connection,
           const gchar     *sender_name,
           const gchar     *object_path,
           const gchar     *interface_name,
           const gchar     *signal_name,
           GVariant        *parameters,
           gpointer         user_data)
{
  GDBusObjectManagerClient *manager = G_DBUS_OBJECT_MANAGER_CLIENT (user_data);
  GDBusObjectProxy *object_proxy;
  GDBusInterface *interface;

  g_mutex_lock (manager->priv->lock);
  object_proxy = g_hash_table_lookup (manager->priv->map_object_path_to_object_proxy, object_path);
  if (object_proxy == NULL)
    {
      g_mutex_unlock (manager->priv->lock);
      goto out;
    }
  g_object_ref (object_proxy);
  g_mutex_unlock (manager->priv->lock);

  //g_debug ("yay, signal_cb %s %s: %s\n", signal_name, object_path, g_variant_print (parameters, TRUE));

  if (g_strcmp0 (interface_name, "org.freedesktop.DBus.Properties") == 0)
    {
      if (g_strcmp0 (signal_name, "PropertiesChanged") == 0)
        {
          const gchar *interface_name;
          GVariant *changed_properties;
          const gchar **invalidated_properties;

          g_variant_get (parameters,
                         "(&s@a{sv}^a&s)",
                         &interface_name,
                         &changed_properties,
                         &invalidated_properties);

          interface = g_dbus_object_get_interface (G_DBUS_OBJECT (object_proxy), interface_name);
          if (interface != NULL)
            {
              GVariantIter property_iter;
              const gchar *property_name;
              GVariant *property_value;
              guint n;

              /* update caches... */
              g_variant_iter_init (&property_iter, changed_properties);
              while (g_variant_iter_next (&property_iter,
                                          "{&sv}",
                                          &property_name,
                                          &property_value))
                {
                  g_dbus_proxy_set_cached_property (G_DBUS_PROXY (interface),
                                                    property_name,
                                                    property_value);
                  g_variant_unref (property_value);
                }

              for (n = 0; invalidated_properties[n] != NULL; n++)
                {
                  g_dbus_proxy_set_cached_property (G_DBUS_PROXY (interface),
                                                    invalidated_properties[n],
                                                    NULL);
                }
              /* ... and then synthesize the signal */
              g_signal_emit_by_name (interface,
                                     "g-properties-changed",
                                     changed_properties,
                                     invalidated_properties);
              g_signal_emit (manager,
                             signals[INTERFACE_PROXY_PROPERTIES_CHANGED_SIGNAL],
                             0,
                             object_proxy,
                             interface,
                             changed_properties,
                             invalidated_properties);
              g_object_unref (interface);
            }
          g_variant_unref (changed_properties);
          g_free (invalidated_properties);
        }
    }
  else
    {
      /* regular signal - just dispatch it */
      interface = g_dbus_object_get_interface (G_DBUS_OBJECT (object_proxy), interface_name);
      if (interface != NULL)
        {
          g_signal_emit_by_name (interface,
                                 "g-signal",
                                 sender_name,
                                 signal_name,
                                 parameters);
          g_signal_emit (manager,
                         signals[INTERFACE_PROXY_SIGNAL_SIGNAL],
                         0,
                         object_proxy,
                         interface,
                         sender_name,
                         signal_name,
                         parameters);
          g_object_unref (interface);
        }
    }

 out:
  if (object_proxy != NULL)
    g_object_ref (object_proxy);
}

static void
subscribe_signals (GDBusObjectManagerClient *manager,
                   const gchar *name_owner)
{
  GError *error;
  GVariant *ret;

  g_return_if_fail (G_IS_DBUS_OBJECT_MANAGER_CLIENT (manager));
  g_return_if_fail (manager->priv->signal_subscription_id == 0);
  g_return_if_fail (g_dbus_is_unique_name (name_owner));

  /* the bus daemon may not implement path_prefix so gracefully
   * handle this by using a fallback
   */
  manager->priv->match_rule = g_strdup_printf ("type='signal',sender='%s',path_namespace='%s'",
                                               name_owner,
                                               manager->priv->object_path);

  error = NULL;
  ret = g_dbus_connection_call_sync (manager->priv->connection,
                                     "org.freedesktop.DBus",
                                     "/org/freedeskop/DBus",
                                     "org.freedesktop.DBus",
                                     "AddMatch",
                                     g_variant_new ("(s)",
                                                    manager->priv->match_rule),
                                     NULL, /* reply_type */
                                     G_DBUS_CALL_FLAGS_NONE,
                                     -1, /* default timeout */
                                     NULL, /* TODO: Cancellable */
                                     &error);
  if (ret != NULL)
    {
      /* yay, bus daemon supports path_namespace */
      g_variant_unref (ret);

      /* still need to ask GDBusConnection for the callbacks */
      manager->priv->signal_subscription_id =
        g_dbus_connection_signal_subscribe (manager->priv->connection,
                                            name_owner,
                                            NULL, /* interface */
                                            NULL, /* member */
                                            NULL, /* path - TODO: really want wilcard support here */
                                            NULL, /* arg0 */
                                            G_DBUS_SIGNAL_FLAGS_NONE |
                                            G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
                                            signal_cb,
                                            manager,
                                            NULL); /* user_data_free_func */

    }
  else
    {
      /* TODO: we could report this to the user
      g_warning ("Message bus daemon does not support path_namespace: %s (%s %d)",
                 error->message,
                 g_quark_to_string (error->domain),
                 error->code);
      */

      g_error_free (error);

      /* no need to call RemoveMatch when done since it didn't work */
      g_free (manager->priv->match_rule);
      manager->priv->match_rule = NULL;

      /* Fallback is to subscribe to *all* signals from the name owner which
       * is rather wasteful. It's probably not a big practical problem because
       * users typically want all objects that the name owner supplies.
       */
      manager->priv->signal_subscription_id =
        g_dbus_connection_signal_subscribe (manager->priv->connection,
                                            name_owner,
                                            NULL, /* interface */
                                            NULL, /* member */
                                            NULL, /* path - TODO: really want wilcard support here */
                                            NULL, /* arg0 */
                                            G_DBUS_SIGNAL_FLAGS_NONE,
                                            signal_cb,
                                            manager,
                                            NULL); /* user_data_free_func */
    }
}

static void
maybe_unsubscribe_signals (GDBusObjectManagerClient *manager)
{
  g_return_if_fail (G_IS_DBUS_OBJECT_MANAGER_CLIENT (manager));

  if (manager->priv->signal_subscription_id > 0)
    {
      g_dbus_connection_signal_unsubscribe (manager->priv->connection,
                                            manager->priv->signal_subscription_id);
      manager->priv->signal_subscription_id = 0;
    }

  if (manager->priv->match_rule != NULL)
    {
      /* Since the AddMatch call succeeded this is guaranteed to not
       * fail - therefore, don't bother checking the return value
       */
      g_dbus_connection_call (manager->priv->connection,
                              "org.freedesktop.DBus",
                              "/org/freedeskop/DBus",
                              "org.freedesktop.DBus",
                              "RemoveMatch",
                              g_variant_new ("(s)",
                                             manager->priv->match_rule),
                              NULL, /* reply_type */
                              G_DBUS_CALL_FLAGS_NONE,
                              -1, /* default timeout */
                              NULL, /* GCancellable */
                              NULL, /* GAsyncReadyCallback */
                              NULL); /* user data */
      g_free (manager->priv->match_rule);
      manager->priv->match_rule = NULL;
    }

}

/* ---------------------------------------------------------------------------------------------------- */

static void
on_notify_g_name_owner (GObject    *object,
                        GParamSpec *pspec,
                        gpointer    user_data)
{
  GDBusObjectManagerClient *manager = G_DBUS_OBJECT_MANAGER_CLIENT (user_data);
  gchar *old_name_owner;
  gchar *new_name_owner;

  g_mutex_lock (manager->priv->lock);
  old_name_owner = manager->priv->name_owner;
  new_name_owner = g_dbus_proxy_get_name_owner (manager->priv->control_proxy);
  manager->priv->name_owner = NULL;

  if (g_strcmp0 (old_name_owner, new_name_owner) != 0)
    {
      GList *l;
      GList *proxies;

      /* remote manager changed; nuke all local proxies  */
      proxies = g_hash_table_get_values (manager->priv->map_object_path_to_object_proxy);
      g_list_foreach (proxies, (GFunc) g_object_ref, NULL);
      g_hash_table_remove_all (manager->priv->map_object_path_to_object_proxy);

      g_mutex_unlock (manager->priv->lock);

      /* do the :name-owner notify with a NULL name - this way the user knows
       * the ::object-proxy-removed following is because the name owner went
       * away
       */
      g_object_notify (G_OBJECT (manager), "name-owner");

      for (l = proxies; l != NULL; l = l->next)
        {
          GDBusObjectProxy *object_proxy = G_DBUS_OBJECT_PROXY (l->data);
          g_signal_emit_by_name (manager, "object-removed", object_proxy);
        }
      g_list_foreach (proxies, (GFunc) g_object_unref, NULL);
      g_list_free (proxies);

      /* nuke local filter */
      maybe_unsubscribe_signals (manager);
    }
  else
    {
      g_mutex_unlock (manager->priv->lock);
    }

  if (new_name_owner != NULL)
    {
      GError *error;
      GVariant *value;

      //g_debug ("repopulating for %s", new_name_owner);

      /* TODO: do this async! */
      subscribe_signals (manager,
                         new_name_owner);
      error = NULL;
      value = g_dbus_proxy_call_sync (manager->priv->control_proxy,
                                      "GetManagedObjects",
                                      NULL, /* parameters */
                                      G_DBUS_CALL_FLAGS_NONE,
                                      -1,
                                      NULL,
                                      &error);
      if (value == NULL)
        {
          maybe_unsubscribe_signals (manager);
          g_warning ("Error calling GetManagedObjects() when name owner %s for name %s came back: %s",
                     new_name_owner,
                     manager->priv->name,
                     error->message);
          g_error_free (error);
        }
      else
        {
          process_get_all_result (manager, value, new_name_owner);
          g_variant_unref (value);
        }

      /* do the :name-owner notify *AFTER* emitting ::object-proxy-added signals - this
       * way the user knows that the signals were emitted because the name owner came back
       */
      g_mutex_lock (manager->priv->lock);
      manager->priv->name_owner = new_name_owner;
      g_mutex_unlock (manager->priv->lock);
      g_object_notify (G_OBJECT (manager), "name-owner");

    }
  g_free (old_name_owner);
}

static gboolean
initable_init (GInitable     *initable,
               GCancellable  *cancellable,
               GError       **error)
{
  GDBusObjectManagerClient *manager = G_DBUS_OBJECT_MANAGER_CLIENT (initable);
  gboolean ret;
  GVariant *value;
  GDBusProxyFlags proxy_flags;

  ret = FALSE;

  if (manager->priv->bus_type != G_BUS_TYPE_NONE)
    {
      g_assert (manager->priv->connection == NULL);
      manager->priv->connection = g_bus_get_sync (manager->priv->bus_type, cancellable, error);
      if (manager->priv->connection == NULL)
        goto out;
    }

  proxy_flags = G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES;
  if (manager->priv->flags & G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_DO_NOT_AUTO_START)
    proxy_flags |= G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START;;

  manager->priv->control_proxy = g_dbus_proxy_new_sync (manager->priv->connection,
                                                        proxy_flags,
                                                        NULL, /* GDBusInterfaceInfo* */
                                                        manager->priv->name,
                                                        manager->priv->object_path,
                                                        "org.freedesktop.DBus.ObjectManager",
                                                        cancellable,
                                                        error);
  if (manager->priv->control_proxy == NULL)
    goto out;

  g_signal_connect (G_OBJECT (manager->priv->control_proxy),
                    "notify::g-name-owner",
                    G_CALLBACK (on_notify_g_name_owner),
                    manager);

  manager->priv->name_owner = g_dbus_proxy_get_name_owner (manager->priv->control_proxy);
  if (manager->priv->name_owner == NULL)
    {
      /* it's perfectly fine if there's no name owner.. we're just going to
       * wait until one is ready
       */
    }
  else
    {
      /* yay, we have a name owner */
      g_signal_connect (manager->priv->control_proxy,
                        "g-signal",
                        G_CALLBACK (on_control_proxy_g_signal),
                        manager);
      subscribe_signals (manager,
                         manager->priv->name_owner);
      value = g_dbus_proxy_call_sync (manager->priv->control_proxy,
                                      "GetManagedObjects",
                                      NULL, /* parameters */
                                      G_DBUS_CALL_FLAGS_NONE,
                                      -1,
                                      cancellable,
                                      error);
      if (value == NULL)
        {
          maybe_unsubscribe_signals (manager);
          g_warn_if_fail (g_signal_handlers_disconnect_by_func (manager->priv->control_proxy,
                                                                on_control_proxy_g_signal,
                                                                manager) == 1);
          g_object_unref (manager->priv->control_proxy);
          manager->priv->control_proxy = NULL;
          goto out;
        }

      process_get_all_result (manager, value, manager->priv->name_owner);
      g_variant_unref (value);
    }

  ret = TRUE;

 out:
  return ret;
}

static void
initable_iface_init (GInitableIface *initable_iface)
{
  initable_iface->init = initable_init;
}

static void
async_initable_iface_init (GAsyncInitableIface *async_initable_iface)
{
  /* for now, just use default: run GInitable code in thread */
}

/* ---------------------------------------------------------------------------------------------------- */

static void
add_interfaces (GDBusObjectManagerClient *manager,
                const gchar       *object_path,
                GVariant          *ifaces_and_properties,
                const gchar       *name_owner)
{
  GDBusObjectProxy *op;
  gboolean added;
  GVariantIter iter;
  const gchar *interface_name;
  GVariant *properties;
  GList *interface_added_signals, *l;
  GDBusProxy *interface_proxy;

  g_return_if_fail (g_dbus_is_unique_name (name_owner));

  g_mutex_lock (manager->priv->lock);

  interface_added_signals = NULL;
  added = FALSE;

  op = g_hash_table_lookup (manager->priv->map_object_path_to_object_proxy, object_path);
  if (op == NULL)
    {
      GType object_proxy_type;
      if (manager->priv->get_proxy_type_func != NULL)
        {
          object_proxy_type = manager->priv->get_proxy_type_func (manager,
                                                                  object_path,
                                                                  NULL,
                                                                  manager->priv->get_proxy_type_user_data);
          g_warn_if_fail (g_type_is_a (object_proxy_type, G_TYPE_DBUS_OBJECT_PROXY));
        }
      else
        {
          object_proxy_type = G_TYPE_DBUS_OBJECT_PROXY;
        }
      op = g_object_new (object_proxy_type,
                         "g-connection", manager->priv->connection,
                         "g-object-path", object_path,
                         NULL);
      added = TRUE;
    }
  g_object_ref (op);

  g_variant_iter_init (&iter, ifaces_and_properties);
  while (g_variant_iter_next (&iter,
                              "{&s@a{sv}}",
                              &interface_name,
                              &properties))
    {
      GError *error;
      GType interface_proxy_type;

      if (manager->priv->get_proxy_type_func != NULL)
        {
          interface_proxy_type = manager->priv->get_proxy_type_func (manager,
                                                                     object_path,
                                                                     interface_name,
                                                                     manager->priv->get_proxy_type_user_data);
          g_warn_if_fail (g_type_is_a (interface_proxy_type, G_TYPE_DBUS_PROXY));
        }
      else
        {
          interface_proxy_type = G_TYPE_DBUS_PROXY;
        }

      /* this is fine - there is no blocking IO because we pass DO_NOT_LOAD_PROPERTIES and
       * DO_NOT_CONNECT_SIGNALS and use a unique name
       */
      error = NULL;
      interface_proxy = g_initable_new (interface_proxy_type,
                                        NULL, /* GCancellable */
                                        &error,
                                        "g-connection", manager->priv->connection,
                                        "g-flags", G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
                                                   G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
                                        "g-name", name_owner,
                                        "g-object-path", object_path,
                                        "g-interface-name", interface_name,
                                        NULL);
      if (interface_proxy == NULL)
        {
          g_warning ("%s: Error constructing proxy for path %s and interface %s: %s",
                     G_STRLOC,
                     object_path,
                     interface_name,
                     error->message);
          g_error_free (error);
        }
      else
        {
          GVariantIter property_iter;
          const gchar *property_name;
          GVariant *property_value;

          /* associate the interface proxy with the object */
          g_dbus_interface_set_object (G_DBUS_INTERFACE (interface_proxy),
                                       G_DBUS_OBJECT (op));

          g_variant_iter_init (&property_iter, properties);
          while (g_variant_iter_next (&property_iter,
                                      "{&sv}",
                                      &property_name,
                                      &property_value))
            {
              g_dbus_proxy_set_cached_property (interface_proxy,
                                                property_name,
                                                property_value);
              g_variant_unref (property_value);
            }

          _g_dbus_object_proxy_add_interface (op, interface_proxy);
          if (!added)
            interface_added_signals = g_list_append (interface_added_signals, g_object_ref (interface_proxy));
          g_object_unref (interface_proxy);
        }
      g_variant_unref (properties);
    }

  g_mutex_unlock (manager->priv->lock);

  /* now that we don't hold the lock any more, emit signals */
  for (l = interface_added_signals; l != NULL; l = l->next)
    {
      interface_proxy = G_DBUS_PROXY (l->data);
      g_signal_emit_by_name (manager, "interface-added", op, interface_proxy);
      g_object_unref (interface_proxy);
    }
  g_list_free (interface_added_signals);

  if (added)
    {
      g_hash_table_insert (manager->priv->map_object_path_to_object_proxy,
                           g_strdup (object_path),
                           op);
      g_signal_emit_by_name (manager, "object-added", op);
    }
  g_object_unref (op);

}

static void
remove_interfaces (GDBusObjectManagerClient   *manager,
                   const gchar         *object_path,
                   const gchar *const  *interface_names)
{
  GDBusObjectProxy *op;
  GList *interfaces;
  guint n;
  guint num_interfaces;
  guint num_interfaces_to_remove;

  g_mutex_lock (manager->priv->lock);

  op = g_hash_table_lookup (manager->priv->map_object_path_to_object_proxy, object_path);
  if (op == NULL)
    {
      g_warning ("%s: Processing InterfaceRemoved signal for path %s but no object proxy exists",
                 G_STRLOC,
                 object_path);
      g_mutex_unlock (manager->priv->lock);
      goto out;
    }

  interfaces = g_dbus_object_get_interfaces (G_DBUS_OBJECT (op));
  num_interfaces = g_list_length (interfaces);
  g_list_foreach (interfaces, (GFunc) g_object_unref, NULL);
  g_list_free (interfaces);

  num_interfaces_to_remove = g_strv_length ((gchar **) interface_names);

  /* see if we are going to completety remove the object */
  if (num_interfaces_to_remove == num_interfaces)
    {
      g_object_ref (op);
      g_warn_if_fail (g_hash_table_remove (manager->priv->map_object_path_to_object_proxy, object_path));
      g_mutex_unlock (manager->priv->lock);
      g_signal_emit_by_name (manager, "object-removed", op);
      g_object_unref (op);
    }
  else
    {
      g_object_ref (op);
      g_mutex_unlock (manager->priv->lock);
      for (n = 0; interface_names != NULL && interface_names[n] != NULL; n++)
        {
          GDBusInterface *interface;
          interface = g_dbus_object_get_interface (G_DBUS_OBJECT (op), interface_names[n]);
          _g_dbus_object_proxy_remove_interface (op, interface_names[n]);
          if (interface != NULL)
            {
              g_signal_emit_by_name (manager, "interface-removed", op, interface);
              g_object_unref (interface);
            }
        }
      g_object_unref (op);
    }
 out:
  ;
}

static void
process_get_all_result (GDBusObjectManagerClient *manager,
                        GVariant          *value,
                        const gchar       *name_owner)
{
  GVariant *arg0;
  const gchar *object_path;
  GVariant *ifaces_and_properties;
  GVariantIter iter;

  g_return_if_fail (g_dbus_is_unique_name (name_owner));

  arg0 = g_variant_get_child_value (value, 0);
  g_variant_iter_init (&iter, arg0);
  while (g_variant_iter_next (&iter,
                              "{&o@a{sa{sv}}}",
                              &object_path,
                              &ifaces_and_properties))
    {
      add_interfaces (manager, object_path, ifaces_and_properties, name_owner);
      g_variant_unref (ifaces_and_properties);
    }
  g_variant_unref (arg0);
}

static void
on_control_proxy_g_signal (GDBusProxy   *proxy,
                           const gchar  *sender_name,
                           const gchar  *signal_name,
                           GVariant     *parameters,
                           gpointer      user_data)
{
  GDBusObjectManagerClient *manager = G_DBUS_OBJECT_MANAGER_CLIENT (user_data);
  const gchar *object_path;

  //g_debug ("yay, g_signal %s: %s\n", signal_name, g_variant_print (parameters, TRUE));

  if (g_strcmp0 (signal_name, "InterfacesAdded") == 0)
    {
      GVariant *ifaces_and_properties;
      g_variant_get (parameters,
                     "(&o@a{sa{sv}})",
                     &object_path,
                     &ifaces_and_properties);
      add_interfaces (manager, object_path, ifaces_and_properties, manager->priv->name_owner);
      g_variant_unref (ifaces_and_properties);
    }
  else if (g_strcmp0 (signal_name, "InterfacesRemoved") == 0)
    {
      const gchar **ifaces;
      g_variant_get (parameters,
                     "(&o^a&s)",
                     &object_path,
                     &ifaces);
      remove_interfaces (manager, object_path, ifaces);
      g_free (ifaces);
    }
}

/* ---------------------------------------------------------------------------------------------------- */

static const gchar *
g_dbus_object_manager_client_get_object_path (GDBusObjectManager *_manager)
{
  GDBusObjectManagerClient *manager = G_DBUS_OBJECT_MANAGER_CLIENT (_manager);
  return manager->priv->object_path;
}

static GDBusObject *
g_dbus_object_manager_client_get_object (GDBusObjectManager *_manager,
                                         const gchar        *object_path)
{
  GDBusObjectManagerClient *manager = G_DBUS_OBJECT_MANAGER_CLIENT (_manager);
  GDBusObject *ret;

  g_mutex_lock (manager->priv->lock);
  ret = g_hash_table_lookup (manager->priv->map_object_path_to_object_proxy, object_path);
  if (ret != NULL)
    g_object_ref (ret);
  g_mutex_unlock (manager->priv->lock);
  return ret;
}

static GDBusInterface *
g_dbus_object_manager_client_get_interface  (GDBusObjectManager  *_manager,
                                             const gchar         *object_path,
                                             const gchar         *interface_name)
{
  GDBusInterface *ret;
  GDBusObject *object;

  ret = NULL;

  object = g_dbus_object_manager_get_object (_manager, object_path);
  if (object == NULL)
    goto out;

  ret = g_dbus_object_get_interface (object, interface_name);
  g_object_unref (object);

 out:
  return ret;
}

static GList *
g_dbus_object_manager_client_get_objects (GDBusObjectManager *_manager)
{
  GDBusObjectManagerClient *manager = G_DBUS_OBJECT_MANAGER_CLIENT (_manager);
  GList *ret;

  g_return_val_if_fail (G_IS_DBUS_OBJECT_MANAGER_CLIENT (manager), NULL);

  g_mutex_lock (manager->priv->lock);
  ret = g_hash_table_get_values (manager->priv->map_object_path_to_object_proxy);
  g_list_foreach (ret, (GFunc) g_object_ref, NULL);
  g_mutex_unlock (manager->priv->lock);

  return ret;
}


static void
dbus_object_manager_interface_init (GDBusObjectManagerIface *iface)
{
  iface->get_object_path = g_dbus_object_manager_client_get_object_path;
  iface->get_objects     = g_dbus_object_manager_client_get_objects;
  iface->get_object      = g_dbus_object_manager_client_get_object;
  iface->get_interface   = g_dbus_object_manager_client_get_interface;
}

/* ---------------------------------------------------------------------------------------------------- */
