/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
 * Copyright (C) 2007 - 2008 Novell, Inc.
 * Copyright (C) 2007 - 2011 Red Hat, Inc.
 */

#include "libnm-client-impl/nm-default-libnm.h"

#include "nm-remote-connection.h"

#include "libnm-glib-aux/nm-dbus-aux.h"
#include "nm-dbus-interface.h"
#include "nm-utils.h"
#include "nm-setting-connection.h"
#include "libnm-core-intern/nm-core-internal.h"
#include "nm-remote-connection-private.h"
#include "nm-object-private.h"
#include "nm-dbus-helpers.h"

/**
 * SECTION:nm-remote-connection
 * @short_description: A connection managed by NetworkManager server
 *
 * A #NMRemoteConnection represents a connection that is exported via
 * NetworkManager D-Bus interface.
 **/

/*****************************************************************************/

NM_GOBJECT_PROPERTIES_DEFINE(NMRemoteConnection,
                             PROP_UNSAVED,
                             PROP_FLAGS,
                             PROP_FILENAME,
                             PROP_VERSION_ID,
                             PROP_VISIBLE, );

typedef struct {
    GCancellable *get_settings_cancellable;

    char   *filename;
    guint64 version_id;
    guint32 flags;
    bool    unsaved;

    bool visible : 1;
    bool is_initialized : 1;
} NMRemoteConnectionPrivate;

struct _NMRemoteConnection {
    NMObject                  parent;
    NMRemoteConnectionPrivate _priv;
};

struct _NMRemoteConnectionClass {
    NMObjectClass parent_class;
};

static void nm_remote_connection_connection_iface_init(NMConnectionInterface *iface);

G_DEFINE_TYPE_WITH_CODE(NMRemoteConnection,
                        nm_remote_connection,
                        NM_TYPE_OBJECT,
                        G_IMPLEMENT_INTERFACE(NM_TYPE_CONNECTION,
                                              nm_remote_connection_connection_iface_init);)

#define NM_REMOTE_CONNECTION_GET_PRIVATE(self) \
    _NM_GET_PRIVATE(self, NMRemoteConnection, NM_IS_REMOTE_CONNECTION, NMObject)

/*****************************************************************************/

/**
 * nm_remote_connection_update2:
 * @connection: the #NMRemoteConnection
 * @settings: (nullable): optional connection to update the settings.
 * @flags: update-flags
 * @args: (nullable): optional arguments.
 * @cancellable: a #GCancellable, or %NULL
 * @callback: callback to be called when the commit operation completes
 * @user_data: caller-specific data passed to @callback
 *
 * Asynchronously calls the Update2() D-Bus method.
 *
 * Since: 1.12
 **/
void
nm_remote_connection_update2(NMRemoteConnection    *connection,
                             GVariant              *settings,
                             NMSettingsUpdate2Flags flags,
                             GVariant              *args,
                             GCancellable          *cancellable,
                             GAsyncReadyCallback    callback,
                             gpointer               user_data)
{
    g_return_if_fail(NM_IS_REMOTE_CONNECTION(connection));
    g_return_if_fail(!settings || g_variant_is_of_type(settings, NM_VARIANT_TYPE_CONNECTION));
    g_return_if_fail(!args || g_variant_is_of_type(args, G_VARIANT_TYPE("a{sv}")));
    g_return_if_fail(!cancellable || G_IS_CANCELLABLE(cancellable));

    if (!settings)
        settings = nm_g_variant_singleton_aLsaLsvII();
    if (!args)
        args = nm_g_variant_singleton_aLsvI();

    _nm_client_dbus_call(_nm_object_get_client(connection),
                         connection,
                         nm_remote_connection_update2,
                         cancellable,
                         callback,
                         user_data,
                         _nm_object_get_path(connection),
                         NM_DBUS_INTERFACE_SETTINGS_CONNECTION,
                         "Update2",
                         g_variant_new("(@a{sa{sv}}u@a{sv})", settings, (guint32) flags, args),
                         G_VARIANT_TYPE("(a{sv})"),
                         G_DBUS_CALL_FLAGS_NONE,
                         NM_DBUS_DEFAULT_TIMEOUT_MSEC,
                         nm_dbus_connection_call_finish_variant_strip_dbus_error_cb);
}

/**
 * nm_remote_connection_update2_finish:
 * @connection: the #NMRemoteConnection
 * @result: the result passed to the #GAsyncReadyCallback
 * @error: location for a #GError, or %NULL
 *
 * Gets the result of a call to nm_remote_connection_commit_changes_async().
 *
 * Returns: (transfer full): on success, a #GVariant of type "a{sv}" with the result. On failure,
 *   %NULL.
 *
 * Since: 1.12
 **/
GVariant *
nm_remote_connection_update2_finish(NMRemoteConnection *connection,
                                    GAsyncResult       *result,
                                    GError            **error)
{
    gs_unref_variant GVariant *ret = NULL;
    GVariant                  *v_result;

    g_return_val_if_fail(NM_IS_REMOTE_CONNECTION(connection), NULL);
    g_return_val_if_fail(nm_g_task_is_valid(result, connection, nm_remote_connection_update2),
                         NULL);

    ret = g_task_propagate_pointer(G_TASK(result), error);
    if (!ret)
        return NULL;

    g_variant_get(ret, "(@a{sv})", &v_result);

    return v_result;
}

/*****************************************************************************/

/**
 * nm_remote_connection_commit_changes:
 * @connection: the #NMRemoteConnection
 * @save_to_disk: whether to persist the changes to disk
 * @cancellable: a #GCancellable, or %NULL
 * @error: location for a #GError, or %NULL
 *
 * Send any local changes to the settings and properties of @connection to
 * NetworkManager. If @save_to_disk is %TRUE, the updated connection will be saved to
 * disk; if %FALSE, then only the in-memory representation will be changed.
 *
 * Returns: %TRUE on success, %FALSE on error, in which case @error will be set.
 *
 * Deprecated: 1.22: Use nm_remote_connection_commit_changes_async() or GDBusConnection.
 **/
gboolean
nm_remote_connection_commit_changes(NMRemoteConnection *connection,
                                    gboolean            save_to_disk,
                                    GCancellable       *cancellable,
                                    GError            **error)
{
    gs_unref_variant GVariant *ret = NULL;

    g_return_val_if_fail(NM_IS_REMOTE_CONNECTION(connection), FALSE);
    g_return_val_if_fail(!cancellable || G_IS_CANCELLABLE(cancellable), FALSE);

    ret = _nm_client_dbus_call_sync(
        _nm_object_get_client(connection),
        cancellable,
        _nm_object_get_path(connection),
        NM_DBUS_INTERFACE_SETTINGS_CONNECTION,
        "Update2",
        g_variant_new("(@a{sa{sv}}u@a{sv})",
                      nm_connection_to_dbus(NM_CONNECTION(connection), NM_CONNECTION_SERIALIZE_ALL),
                      (guint32) (save_to_disk ? NM_SETTINGS_UPDATE2_FLAG_TO_DISK
                                              : NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY),
                      nm_g_variant_singleton_aLsvI()),
        G_VARIANT_TYPE("(a{sv})"),
        G_DBUS_CALL_FLAGS_NONE,
        NM_DBUS_DEFAULT_TIMEOUT_MSEC,
        TRUE,
        error);
    if (!ret)
        return FALSE;

    return TRUE;
}

/**
 * nm_remote_connection_commit_changes_async:
 * @connection: the #NMRemoteConnection
 * @save_to_disk: whether to save the changes to persistent storage
 * @cancellable: a #GCancellable, or %NULL
 * @callback: callback to be called when the commit operation completes
 * @user_data: caller-specific data passed to @callback
 *
 * Asynchronously sends any local changes to the settings and properties of
 * @connection to NetworkManager. If @save is %TRUE, the updated connection will
 * be saved to disk; if %FALSE, then only the in-memory representation will be
 * changed.
 **/
void
nm_remote_connection_commit_changes_async(NMRemoteConnection *connection,
                                          gboolean            save_to_disk,
                                          GCancellable       *cancellable,
                                          GAsyncReadyCallback callback,
                                          gpointer            user_data)
{
    g_return_if_fail(NM_IS_REMOTE_CONNECTION(connection));
    g_return_if_fail(!cancellable || G_IS_CANCELLABLE(cancellable));

    nm_remote_connection_update2(
        connection,
        nm_connection_to_dbus(NM_CONNECTION(connection), NM_CONNECTION_SERIALIZE_ALL),
        save_to_disk ? NM_SETTINGS_UPDATE2_FLAG_TO_DISK : NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY,
        NULL,
        cancellable,
        callback,
        user_data);
}

/**
 * nm_remote_connection_commit_changes_finish:
 * @connection: the #NMRemoteConnection
 * @result: the result passed to the #GAsyncReadyCallback
 * @error: location for a #GError, or %NULL
 *
 * Gets the result of a call to nm_remote_connection_commit_changes_async().
 *
 * Returns: %TRUE on success, %FALSE on error, in which case @error will be set.
 **/
gboolean
nm_remote_connection_commit_changes_finish(NMRemoteConnection *connection,
                                           GAsyncResult       *result,
                                           GError            **error)
{
    gs_unref_variant GVariant *v_result = NULL;

    v_result = nm_remote_connection_update2_finish(connection, result, error);
    return !!v_result;
}

/*****************************************************************************/

/**
 * nm_remote_connection_save:
 * @connection: the #NMRemoteConnection
 * @cancellable: a #GCancellable, or %NULL
 * @error: location for a #GError, or %NULL
 *
 * Saves the connection to disk if the connection has changes that have not yet
 * been written to disk, or if the connection has never been saved.
 *
 * Returns: %TRUE on success, %FALSE on error, in which case @error will be set.
 *
 * Deprecated: 1.22: Use nm_remote_connection_save_async() or GDBusConnection.
 **/
gboolean
nm_remote_connection_save(NMRemoteConnection *connection, GCancellable *cancellable, GError **error)
{
    g_return_val_if_fail(NM_IS_REMOTE_CONNECTION(connection), FALSE);
    g_return_val_if_fail(!cancellable || G_IS_CANCELLABLE(cancellable), FALSE);

    return _nm_client_dbus_call_sync_void(_nm_object_get_client(connection),
                                          cancellable,
                                          _nm_object_get_path(connection),
                                          NM_DBUS_INTERFACE_SETTINGS_CONNECTION,
                                          "Save",
                                          g_variant_new("()"),
                                          G_DBUS_CALL_FLAGS_NONE,
                                          NM_DBUS_DEFAULT_TIMEOUT_MSEC,
                                          TRUE,
                                          error);
}

/**
 * nm_remote_connection_save_async:
 * @connection: the #NMRemoteConnection
 * @cancellable: a #GCancellable, or %NULL
 * @callback: callback to be called when the save operation completes
 * @user_data: caller-specific data passed to @callback
 *
 * Saves the connection to disk if the connection has changes that have not yet
 * been written to disk, or if the connection has never been saved.
 **/
void
nm_remote_connection_save_async(NMRemoteConnection *connection,
                                GCancellable       *cancellable,
                                GAsyncReadyCallback callback,
                                gpointer            user_data)
{
    g_return_if_fail(NM_IS_REMOTE_CONNECTION(connection));
    g_return_if_fail(!cancellable || G_IS_CANCELLABLE(cancellable));

    _nm_client_dbus_call(_nm_object_get_client(connection),
                         connection,
                         nm_remote_connection_save_async,
                         cancellable,
                         callback,
                         user_data,
                         _nm_object_get_path(connection),
                         NM_DBUS_INTERFACE_SETTINGS_CONNECTION,
                         "Save",
                         g_variant_new("()"),
                         G_VARIANT_TYPE("()"),
                         G_DBUS_CALL_FLAGS_NONE,
                         NM_DBUS_DEFAULT_TIMEOUT_MSEC,
                         nm_dbus_connection_call_finish_void_strip_dbus_error_cb);
}

/**
 * nm_remote_connection_save_finish:
 * @connection: the #NMRemoteConnection
 * @result: the result passed to the #GAsyncReadyCallback
 * @error: location for a #GError, or %NULL
 *
 * Gets the result of a call to nm_remote_connection_save_async().
 *
 * Returns: %TRUE on success, %FALSE on error, in which case @error will be set.
 **/
gboolean
nm_remote_connection_save_finish(NMRemoteConnection *connection,
                                 GAsyncResult       *result,
                                 GError            **error)
{
    g_return_val_if_fail(NM_IS_REMOTE_CONNECTION(connection), FALSE);
    g_return_val_if_fail(nm_g_task_is_valid(result, connection, nm_remote_connection_save_async),
                         FALSE);

    return g_task_propagate_boolean(G_TASK(result), error);
}

/*****************************************************************************/

/**
 * nm_remote_connection_delete:
 * @connection: the #NMRemoteConnection
 * @cancellable: a #GCancellable, or %NULL
 * @error: location for a #GError, or %NULL
 *
 * Deletes the connection.
 *
 * Returns: %TRUE on success, %FALSE on error, in which case @error will be set.
 *
 * Deprecated: 1.22: Use nm_remote_connection_delete_async() or GDBusConnection.
 **/
gboolean
nm_remote_connection_delete(NMRemoteConnection *connection,
                            GCancellable       *cancellable,
                            GError            **error)
{
    g_return_val_if_fail(NM_IS_REMOTE_CONNECTION(connection), FALSE);

    return _nm_client_dbus_call_sync_void(_nm_object_get_client(connection),
                                          cancellable,
                                          _nm_object_get_path(connection),
                                          NM_DBUS_INTERFACE_SETTINGS_CONNECTION,
                                          "Delete",
                                          g_variant_new("()"),
                                          G_DBUS_CALL_FLAGS_NONE,
                                          NM_DBUS_DEFAULT_TIMEOUT_MSEC,
                                          TRUE,
                                          error);
}

/**
 * nm_remote_connection_delete_async:
 * @connection: the #NMRemoteConnection
 * @cancellable: a #GCancellable, or %NULL
 * @callback: callback to be called when the delete operation completes
 * @user_data: caller-specific data passed to @callback
 *
 * Asynchronously deletes the connection.
 **/
void
nm_remote_connection_delete_async(NMRemoteConnection *connection,
                                  GCancellable       *cancellable,
                                  GAsyncReadyCallback callback,
                                  gpointer            user_data)
{
    g_return_if_fail(NM_IS_REMOTE_CONNECTION(connection));
    g_return_if_fail(!cancellable || G_IS_CANCELLABLE(cancellable));

    _nm_client_dbus_call(_nm_object_get_client(connection),
                         connection,
                         nm_remote_connection_delete_async,
                         cancellable,
                         callback,
                         user_data,
                         _nm_object_get_path(connection),
                         NM_DBUS_INTERFACE_SETTINGS_CONNECTION,
                         "Delete",
                         g_variant_new("()"),
                         G_VARIANT_TYPE("()"),
                         G_DBUS_CALL_FLAGS_NONE,
                         NM_DBUS_DEFAULT_TIMEOUT_MSEC,
                         nm_dbus_connection_call_finish_void_strip_dbus_error_cb);
}

/**
 * nm_remote_connection_delete_finish:
 * @connection: the #NMRemoteConnection
 * @result: the result passed to the #GAsyncReadyCallback
 * @error: location for a #GError, or %NULL
 *
 * Gets the result of a call to nm_remote_connection_delete_async().
 *
 * Returns: %TRUE on success, %FALSE on error, in which case @error will be set.
 **/
gboolean
nm_remote_connection_delete_finish(NMRemoteConnection *connection,
                                   GAsyncResult       *result,
                                   GError            **error)
{
    g_return_val_if_fail(NM_IS_REMOTE_CONNECTION(connection), FALSE);
    g_return_val_if_fail(nm_g_task_is_valid(result, connection, nm_remote_connection_delete_async),
                         FALSE);

    return g_task_propagate_boolean(G_TASK(result), error);
}

/**
 * nm_remote_connection_get_secrets:
 * @connection: the #NMRemoteConnection
 * @setting_name: the #NMSetting object name to get secrets for
 * @cancellable: a #GCancellable, or %NULL
 * @error: location for a #GError, or %NULL
 *
 * Request the connection's secrets. Note that this is a blocking D-Bus call,
 * not a simple property accessor.
 *
 * Returns: (transfer full): a #GVariant of type %NM_VARIANT_TYPE_CONNECTION containing
 * @connection's secrets, or %NULL on error.
 *
 * Warning: NMClient contains a cache of objects on D-Bus. This cache gets updated
 *   with D-Bus signals when iterating the GMainContext. This function performs a
 *   (pseudo) blocking D-Bus call. Aside blocking, the result will not be in sync
 *   and not be ordered with the content of the NMClient cache.
 *   This function used to be deprecated between 1.22 and 1.38 releases.
 **/
GVariant *
nm_remote_connection_get_secrets(NMRemoteConnection *connection,
                                 const char         *setting_name,
                                 GCancellable       *cancellable,
                                 GError            **error)
{
    gs_unref_variant GVariant *ret = NULL;
    GVariant                  *secrets;

    g_return_val_if_fail(NM_IS_REMOTE_CONNECTION(connection), NULL);
    g_return_val_if_fail(setting_name, NULL);
    g_return_val_if_fail(!cancellable || G_IS_CANCELLABLE(cancellable), NULL);

    ret = _nm_client_dbus_call_sync(_nm_object_get_client(connection),
                                    cancellable,
                                    _nm_object_get_path(connection),
                                    NM_DBUS_INTERFACE_SETTINGS_CONNECTION,
                                    "GetSecrets",
                                    g_variant_new("(s)", setting_name),
                                    G_VARIANT_TYPE("(a{sa{sv}})"),
                                    G_DBUS_CALL_FLAGS_NONE,
                                    NM_DBUS_DEFAULT_TIMEOUT_MSEC,
                                    TRUE,
                                    error);
    if (!ret)
        return NULL;

    g_variant_get(ret, "(@a{sa{sv}})", &secrets);

    return secrets;
}

/**
 * nm_remote_connection_get_secrets_async:
 * @connection: the #NMRemoteConnection
 * @setting_name: the #NMSetting object name to get secrets for
 * @cancellable: a #GCancellable, or %NULL
 * @callback: callback to be called when the secret request completes
 * @user_data: caller-specific data passed to @callback
 *
 * Asynchronously requests the connection's secrets.
 **/
void
nm_remote_connection_get_secrets_async(NMRemoteConnection *connection,
                                       const char         *setting_name,
                                       GCancellable       *cancellable,
                                       GAsyncReadyCallback callback,
                                       gpointer            user_data)
{
    g_return_if_fail(NM_IS_REMOTE_CONNECTION(connection));
    g_return_if_fail(setting_name);
    g_return_if_fail(!cancellable || G_IS_CANCELLABLE(cancellable));

    _nm_client_dbus_call(_nm_object_get_client(connection),
                         connection,
                         nm_remote_connection_get_secrets_async,
                         cancellable,
                         callback,
                         user_data,
                         _nm_object_get_path(connection),
                         NM_DBUS_INTERFACE_SETTINGS_CONNECTION,
                         "GetSecrets",
                         g_variant_new("(s)", setting_name),
                         G_VARIANT_TYPE("(a{sa{sv}})"),
                         G_DBUS_CALL_FLAGS_NONE,
                         NM_DBUS_DEFAULT_TIMEOUT_MSEC,
                         nm_dbus_connection_call_finish_variant_strip_dbus_error_cb);
}

/**
 * nm_remote_connection_get_secrets_finish:
 * @connection: the #NMRemoteConnection
 * @result: the result passed to the #GAsyncReadyCallback
 * @error: location for a #GError, or %NULL
 *
 * Gets the result of a call to nm_remote_connection_get_secrets_async().
 *
 * Returns: (transfer full): a #GVariant of type %NM_VARIANT_TYPE_CONNECTION
 *   containing @connection's secrets, or %NULL on error.
 **/
GVariant *
nm_remote_connection_get_secrets_finish(NMRemoteConnection *connection,
                                        GAsyncResult       *result,
                                        GError            **error)
{
    gs_unref_variant GVariant *ret = NULL;
    GVariant                  *secrets;

    g_return_val_if_fail(NM_IS_REMOTE_CONNECTION(connection), NULL);
    g_return_val_if_fail(
        nm_g_task_is_valid(result, connection, nm_remote_connection_get_secrets_async),
        FALSE);

    ret = g_task_propagate_pointer(G_TASK(result), error);
    if (!ret)
        return NULL;

    g_variant_get(ret, "(@a{sa{sv}})", &secrets);

    return secrets;
}

/**
 * nm_remote_connection_get_unsaved:
 * @connection: the #NMRemoteConnection
 *
 * Returns: %TRUE if the remote connection contains changes that have not
 * been saved to disk, %FALSE if the connection is the same as its on-disk
 * representation.
 **/
gboolean
nm_remote_connection_get_unsaved(NMRemoteConnection *connection)
{
    g_return_val_if_fail(NM_IS_REMOTE_CONNECTION(connection), FALSE);

    return NM_REMOTE_CONNECTION_GET_PRIVATE(connection)->unsaved;
}

/**
 * nm_remote_connection_get_flags:
 * @connection: the #NMRemoteConnection
 *
 * Returns: the flags of the connection of type #NMSettingsConnectionFlags.
 *
 * Since: 1.12
 **/
NMSettingsConnectionFlags
nm_remote_connection_get_flags(NMRemoteConnection *connection)
{
    g_return_val_if_fail(NM_IS_REMOTE_CONNECTION(connection), FALSE);

    return (NMSettingsConnectionFlags) NM_REMOTE_CONNECTION_GET_PRIVATE(connection)->flags;
}

/**
 * nm_remote_connection_get_filename:
 * @connection: the #NMRemoteConnection
 *
 * Returns: file that stores the connection in case the connection is file-backed.
 *
 * Since: 1.12
 **/
const char *
nm_remote_connection_get_filename(NMRemoteConnection *connection)
{
    g_return_val_if_fail(NM_IS_REMOTE_CONNECTION(connection), NULL);

    return NM_REMOTE_CONNECTION_GET_PRIVATE(connection)->filename;
}

/**
 * nm_remote_connection_get_version_id:
 * @connection: the #NMRemoteConnection
 *
 * Returns: the version-id of the profile. This ID is incremented
 *   whenever the profile is modified.
 *
 * Since: 1.44
 */
guint64
nm_remote_connection_get_version_id(NMRemoteConnection *connection)
{
    g_return_val_if_fail(NM_IS_REMOTE_CONNECTION(connection), 0);

    return NM_REMOTE_CONNECTION_GET_PRIVATE(connection)->version_id;
}

/**
 * nm_remote_connection_get_visible:
 * @connection: the #NMRemoteConnection
 *
 * Checks if the connection is visible to the current user.  If the
 * connection is not visible then it is essentially useless; it will
 * not contain any settings, and operations such as
 * nm_remote_connection_save() and nm_remote_connection_delete() will
 * always fail. (#NMRemoteSettings will not normally return
 * non-visible connections to callers, but it is possible for a
 * connection's visibility to change after you already have a
 * reference to it.)
 *
 * Returns: %TRUE if the remote connection is visible to the current
 * user, %FALSE if not.
 **/
gboolean
nm_remote_connection_get_visible(NMRemoteConnection *connection)
{
    g_return_val_if_fail(NM_IS_REMOTE_CONNECTION(connection), FALSE);

    return NM_REMOTE_CONNECTION_GET_PRIVATE(connection)->visible;
}

/*****************************************************************************/

GCancellable *
_nm_remote_settings_get_settings_prepare(NMRemoteConnection *self)
{
    NMRemoteConnectionPrivate *priv = NM_REMOTE_CONNECTION_GET_PRIVATE(self);

    nm_clear_g_cancellable(&priv->get_settings_cancellable);
    priv->get_settings_cancellable = g_cancellable_new();
    return priv->get_settings_cancellable;
}

void
_nm_remote_settings_get_settings_commit(NMRemoteConnection *self, GVariant *settings)
{
    NMRemoteConnectionPrivate *priv    = NM_REMOTE_CONNECTION_GET_PRIVATE(self);
    GError                    *error   = NULL;
    gboolean                   visible = FALSE;
    gboolean                   changed = FALSE;

    g_clear_object(&priv->get_settings_cancellable);

    if (!priv->is_initialized) {
        changed              = TRUE;
        priv->is_initialized = TRUE;
    }

    if (settings) {
        if (!_nm_connection_replace_settings((NMConnection *) self,
                                             settings,
                                             NM_SETTING_PARSE_FLAGS_BEST_EFFORT,
                                             &error)) {
            NML_NMCLIENT_LOG_E(_nm_object_get_client(self),
                               "[%s] failure to update settings: %s",
                               _nm_object_get_path(self),
                               error->message);
            g_clear_error(&error);
        } else
            visible = TRUE;
    } else
        nm_connection_clear_settings(NM_CONNECTION(self));

    if (priv->visible != visible) {
        priv->visible = visible;
        _nm_client_queue_notify_object(_nm_object_get_client(self),
                                       self,
                                       obj_properties[PROP_VISIBLE]);
        changed = TRUE;
    }

    if (changed)
        _nm_client_notify_object_changed(_nm_object_get_client(self), _nm_object_get_dbobj(self));
}

/*****************************************************************************/

static gboolean
is_ready(NMObject *nmobj)
{
    NMRemoteConnectionPrivate *priv = NM_REMOTE_CONNECTION_GET_PRIVATE(nmobj);

    if (!priv->is_initialized)
        return FALSE;

    return NM_OBJECT_CLASS(nm_remote_connection_parent_class)->is_ready(nmobj);
}

/*****************************************************************************/

static void
register_client(NMObject *nmobj, NMClient *client, NMLDBusObject *dbobj)
{
    NM_OBJECT_CLASS(nm_remote_connection_parent_class)->register_client(nmobj, client, dbobj);
    _nm_connection_set_path_rstr(NM_CONNECTION(nmobj), dbobj->dbus_path);
    _nm_client_get_settings_call(client, dbobj);
}

static void
unregister_client(NMObject *nmobj, NMClient *client, NMLDBusObject *dbobj)
{
    nm_clear_g_cancellable(&NM_REMOTE_CONNECTION_GET_PRIVATE(nmobj)->get_settings_cancellable);
    NM_OBJECT_CLASS(nm_remote_connection_parent_class)->unregister_client(nmobj, client, dbobj);
}

/*****************************************************************************/

static void
get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
{
    switch (prop_id) {
    case PROP_UNSAVED:
        g_value_set_boolean(value, NM_REMOTE_CONNECTION_GET_PRIVATE(object)->unsaved);
        break;
    case PROP_FLAGS:
        g_value_set_uint(value, NM_REMOTE_CONNECTION_GET_PRIVATE(object)->flags);
        break;
    case PROP_FILENAME:
        g_value_set_string(value, NM_REMOTE_CONNECTION_GET_PRIVATE(object)->filename);
        break;
    case PROP_VERSION_ID:
        g_value_set_uint64(value, NM_REMOTE_CONNECTION_GET_PRIVATE(object)->version_id);
        break;
    case PROP_VISIBLE:
        g_value_set_boolean(value, NM_REMOTE_CONNECTION_GET_PRIVATE(object)->visible);
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
        break;
    }
}

/*****************************************************************************/

static void
nm_remote_connection_init(NMRemoteConnection *self)
{}

static void
dispose(GObject *object)
{
    NMRemoteConnectionPrivate *priv = NM_REMOTE_CONNECTION_GET_PRIVATE(object);

    nm_clear_g_free(&priv->filename);

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

const NMLDBusMetaIface _nml_dbus_meta_iface_nm_settings_connection = NML_DBUS_META_IFACE_INIT_PROP(
    NM_DBUS_INTERFACE_SETTINGS_CONNECTION,
    nm_remote_connection_get_type,
    NML_DBUS_META_INTERFACE_PRIO_INSTANTIATE_30,
    NML_DBUS_META_IFACE_DBUS_PROPERTIES(
        NML_DBUS_META_PROPERTY_INIT_S("Filename",
                                      PROP_FILENAME,
                                      NMRemoteConnection,
                                      _priv.filename),
        NML_DBUS_META_PROPERTY_INIT_U("Flags", PROP_FLAGS, NMRemoteConnection, _priv.flags),
        NML_DBUS_META_PROPERTY_INIT_B("Unsaved", PROP_UNSAVED, NMRemoteConnection, _priv.unsaved),
        NML_DBUS_META_PROPERTY_INIT_T("VersionId",
                                      PROP_VERSION_ID,
                                      NMRemoteConnection,
                                      _priv.version_id), ), );

static void
nm_remote_connection_class_init(NMRemoteConnectionClass *klass)
{
    GObjectClass  *object_class    = G_OBJECT_CLASS(klass);
    NMObjectClass *nm_object_class = NM_OBJECT_CLASS(klass);

    object_class->get_property = get_property;
    object_class->dispose      = dispose;

    nm_object_class->is_ready          = is_ready;
    nm_object_class->register_client   = register_client;
    nm_object_class->unregister_client = unregister_client;

    /**
     * NMRemoteConnection:unsaved:
     *
     * %TRUE if the remote connection contains changes that have not been saved
     * to disk, %FALSE if the connection is the same as its on-disk representation.
     **/
    obj_properties[PROP_UNSAVED] = g_param_spec_boolean(NM_REMOTE_CONNECTION_UNSAVED,
                                                        "",
                                                        "",
                                                        FALSE,
                                                        G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);

    /**
     * NMRemoteConnection:flags:
     *
     * The flags of the connection as unsigned integer. The values
     * correspond to the #NMSettingsConnectionFlags enum.
     *
     * Since: 1.12
     **/
    obj_properties[PROP_FLAGS] = g_param_spec_uint(NM_REMOTE_CONNECTION_FLAGS,
                                                   "",
                                                   "",
                                                   0,
                                                   G_MAXUINT32,
                                                   0,
                                                   G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);

    /**
     * NMRemoteConnection:filename:
     *
     * File that stores the connection in case the connection is
     * file-backed.
     *
     * Since: 1.12
     **/
    obj_properties[PROP_FILENAME] = g_param_spec_string(NM_REMOTE_CONNECTION_FILENAME,
                                                        "",
                                                        "",
                                                        NULL,
                                                        G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);

    /**
     * NMRemoteConnection:version-id:
     *
     * The version ID of the profile that is incremented when the profile gets modified.
     * This can be used to track concurrent modifications of the profile.
     *
     * Since: 1.44
     **/
    obj_properties[PROP_VERSION_ID] =
        g_param_spec_uint64(NM_REMOTE_CONNECTION_VERSION_ID,
                            "",
                            "",
                            0,
                            G_MAXUINT64,
                            0,
                            G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);

    /**
     * NMRemoteConnection:visible:
     *
     * %TRUE if the remote connection is visible to the current user, %FALSE if
     * not.  If the connection is not visible then it is essentially useless; it
     * will not contain any settings, and operations such as
     * nm_remote_connection_save() and nm_remote_connection_delete() will always
     * fail. (#NMRemoteSettings will not normally return non-visible connections
     * to callers, but it is possible for a connection's visibility to change
     * after you already have a reference to it.)
     **/
    obj_properties[PROP_VISIBLE] = g_param_spec_boolean(NM_REMOTE_CONNECTION_VISIBLE,
                                                        "",
                                                        "",
                                                        FALSE,
                                                        G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);

    _nml_dbus_meta_class_init_with_properties(object_class,
                                              &_nml_dbus_meta_iface_nm_settings_connection);
}

static void
nm_remote_connection_connection_iface_init(NMConnectionInterface *iface)
{}
