summary refs log tree commit diff
path: root/libnm-glib/nm-remote-settings.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2011-04-08 07:30:59 +0200
committerMichael Biebl <biebl@debian.org>2011-04-08 07:30:59 +0200
commitf75dd6fd1975146623052b843b182dc32c3fbe46 (patch)
tree05ba60b772670f038c9a1fbff940ec5d3e28d870 /libnm-glib/nm-remote-settings.c
parentc980bdf58dc973dd5617aaa6f9466f9e44fcbf58 (diff)
Imported Upstream version 0.8.998 upstream/0.8.998
Diffstat (limited to 'libnm-glib/nm-remote-settings.c')
-rw-r--r--libnm-glib/nm-remote-settings.c647
1 files changed, 528 insertions, 119 deletions
diff --git a/libnm-glib/nm-remote-settings.c b/libnm-glib/nm-remote-settings.c
index 6a9a1d71..e8239d78 100644
--- a/libnm-glib/nm-remote-settings.c
+++ b/libnm-glib/nm-remote-settings.c
@@ -18,7 +18,7 @@
  * Boston, MA 02110-1301 USA.
  *
  * Copyright (C) 2008 Novell, Inc.
- * Copyright (C) 2009 - 2010 Red Hat, Inc.
+ * Copyright (C) 2009 - 2011 Red Hat, Inc.
  */
 
 #include <string.h>
@@ -26,26 +26,30 @@
 #include <nm-connection.h>
 
 #include "nm-marshal.h"
+#include "nm-dbus-glib-types.h"
 #include "nm-remote-settings.h"
 #include "nm-settings-bindings.h"
-#include "nm-settings-interface.h"
 #include "nm-remote-connection-private.h"
 
-static void settings_interface_init (NMSettingsInterface *class);
-
-G_DEFINE_TYPE_EXTENDED (NMRemoteSettings, nm_remote_settings, G_TYPE_OBJECT, 0,
-                        G_IMPLEMENT_INTERFACE (NM_TYPE_SETTINGS_INTERFACE, settings_interface_init))
+G_DEFINE_TYPE (NMRemoteSettings, nm_remote_settings, G_TYPE_OBJECT)
 
 #define NM_REMOTE_SETTINGS_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_REMOTE_SETTINGS, NMRemoteSettingsPrivate))
 
 typedef struct {
 	DBusGConnection *bus;
-	NMConnectionScope scope;
 
 	DBusGProxy *proxy;
 	GHashTable *connections;
 	GHashTable *pending;  /* Connections we don't have settings for yet */
 	gboolean service_running;
+	guint32 init_left;
+
+	/* AddConnectionInfo objects that are waiting for the connection to become initialized */
+	GSList *add_list;
+	
+	DBusGProxy *props_proxy;
+	char *hostname;
+	gboolean can_modify;
 
 	DBusGProxy *dbus_proxy;
 
@@ -57,15 +61,124 @@ typedef struct {
 enum {
 	PROP_0,
 	PROP_BUS,
-	PROP_SCOPE,
 	PROP_SERVICE_RUNNING,
+	PROP_HOSTNAME,
+	PROP_CAN_MODIFY,
 
 	LAST_PROP
 };
 
-static NMSettingsConnectionInterface *
-get_connection_by_path (NMSettingsInterface *settings, const char *path)
+/* Signals */
+enum {
+	NEW_CONNECTION,
+	CONNECTIONS_READ,
+
+	LAST_SIGNAL
+};
+static guint signals[LAST_SIGNAL] = { 0 };
+
+/**********************************************************************/
+
+/**
+ * nm_remote_settings_error_quark:
+ *
+ * Registers an error quark for #NMRemoteSettings if necessary.
+ *
+ * Returns: the error quark used for #NMRemoteSettings errors.
+ **/
+GQuark
+nm_remote_settings_error_quark (void)
+{
+	static GQuark quark;
+
+	if (G_UNLIKELY (!quark))
+		quark = g_quark_from_static_string ("nm-remote-settings-error-quark");
+	return quark;
+}
+
+/* This should really be standard. */
+#define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
+
+GType
+nm_remote_settings_error_get_type (void)
+{
+	static GType etype = 0;
+
+	if (etype == 0) {
+		static const GEnumValue values[] = {
+			ENUM_ENTRY (NM_REMOTE_SETTINGS_ERROR_UNKNOWN, "UnknownError"),
+			ENUM_ENTRY (NM_REMOTE_SETTINGS_ERROR_CONNECTION_REMOVED, "ConnectionRemoved"),
+			ENUM_ENTRY (NM_REMOTE_SETTINGS_ERROR_CONNECTION_UNAVAILABLE, "ConnectionUnavailable"),
+			{ 0, 0, 0 }
+		};
+		etype = g_enum_register_static ("NMRemoteSettingsError", values);
+	}
+	return etype;
+}
+
+/**********************************************************************/
+
+typedef struct {
+	NMRemoteSettings *self;
+	NMRemoteSettingsAddConnectionFunc callback;
+	gpointer callback_data;
+	NMRemoteConnection *connection;
+} AddConnectionInfo;
+
+static AddConnectionInfo *
+add_connection_info_find (NMRemoteSettings *self, NMRemoteConnection *connection)
+{
+	NMRemoteSettingsPrivate *priv = NM_REMOTE_SETTINGS_GET_PRIVATE (self);
+	GSList *iter;
+
+	for (iter = priv->add_list; iter; iter = g_slist_next (iter)) {
+		AddConnectionInfo *info = iter->data;
+
+		if (info->connection == connection)
+			return info;
+	}
+
+	return NULL;
+}
+
+static void
+add_connection_info_dispose (NMRemoteSettings *self, AddConnectionInfo *info)
+{
+	NMRemoteSettingsPrivate *priv = NM_REMOTE_SETTINGS_GET_PRIVATE (self);
+
+	priv->add_list = g_slist_remove (priv->add_list, info);
+
+	g_free (info);
+}
+
+static void
+add_connection_info_complete (NMRemoteSettings *self,
+                              AddConnectionInfo *info,
+                              GError *error)
+{
+	g_return_if_fail (info != NULL);
+
+	info->callback (info->self, error ? NULL : info->connection, error, info->callback_data);
+	add_connection_info_dispose (self, info);
+}
+
+/**
+ * nm_remote_settings_get_connection_by_path:
+ * @settings: the %NMRemoteSettings
+ * @path: the D-Bus object path of the remote connection
+ *
+ * Returns the %NMRemoteConnection representing the connection at @path.
+ *
+ * Returns: (transfer none): the remote connection object on success, or NULL if the object was
+ *  not known
+ **/
+NMRemoteConnection *
+nm_remote_settings_get_connection_by_path (NMRemoteSettings *settings, const char *path)
 {
+	g_return_val_if_fail (settings != NULL, NULL);
+	g_return_val_if_fail (NM_IS_REMOTE_SETTINGS (settings), NULL);
+	g_return_val_if_fail (path != NULL, NULL);
+
 	return g_hash_table_lookup (NM_REMOTE_SETTINGS_GET_PRIVATE (settings)->connections, path);
 }
 
@@ -74,14 +187,68 @@ connection_removed_cb (NMRemoteConnection *remote, gpointer user_data)
 {
 	NMRemoteSettings *self = NM_REMOTE_SETTINGS (user_data);
 	NMRemoteSettingsPrivate *priv = NM_REMOTE_SETTINGS_GET_PRIVATE (self);
+	AddConnectionInfo *addinfo;
+	GError *add_error;
 	const char *path;
 
+	/* Might have been removed while it was waiting to be initialized */
+	addinfo = add_connection_info_find (self, remote);
+	if (addinfo) {
+		add_error = g_error_new_literal (NM_REMOTE_SETTINGS_ERROR,
+		                                 NM_REMOTE_SETTINGS_ERROR_CONNECTION_REMOVED,
+		                                 "Connection removed before it was initialized");
+		add_connection_info_complete (self, addinfo, add_error);
+		g_error_free (add_error);
+	}
+
 	path = nm_connection_get_path (NM_CONNECTION (remote));
 	g_hash_table_remove (priv->connections, path);
 	g_hash_table_remove (priv->pending, path);
 }
 
 static void
+connection_visible_cb (NMRemoteConnection *remote,
+                       gboolean visible,
+                       gpointer user_data)
+{
+	NMRemoteSettings *self = NM_REMOTE_SETTINGS (user_data);
+	NMRemoteSettingsPrivate *priv = NM_REMOTE_SETTINGS_GET_PRIVATE (self);
+	const char *path;
+
+	path = nm_connection_get_path (NM_CONNECTION (remote));
+	g_assert (path);
+
+	/* When a connection becomes invisible, we put it back in the pending
+	 * hash until it becomes visible again.  When it does, we move it back to
+	 * the normal connections hash.
+	 */
+	if (visible) {
+		/* Connection visible to this user again */
+		if (g_hash_table_lookup (priv->pending, path)) {
+			/* Move connection from pending to visible hash; emit for clients */
+			g_hash_table_insert (priv->connections, g_strdup (path), g_object_ref (remote));
+			g_hash_table_remove (priv->pending, path);
+			g_signal_emit (self, signals[NEW_CONNECTION], 0, remote);
+		}
+	} else {
+		/* Connection now invisible to this user */
+		if (g_hash_table_lookup (priv->connections, path)) {
+			/* Move connection to pending hash and wait for it to become visible again */
+			g_hash_table_insert (priv->pending, g_strdup (path), g_object_ref (remote));
+			g_hash_table_remove (priv->connections, path);
+
+			/* Signal to clients that the connection is gone; but we have to
+			 * block our connection removed handler so we don't destroy
+			 * the connection when the signal is emitted.
+			 */
+			g_signal_handlers_block_by_func (remote, connection_removed_cb, self);
+			g_signal_emit_by_name (remote, NM_REMOTE_CONNECTION_REMOVED);
+			g_signal_handlers_unblock_by_func (remote, connection_removed_cb, self);
+		}
+	}
+}
+
+static void
 connection_init_result_cb (NMRemoteConnection *remote,
                            GParamSpec *pspec,
                            gpointer user_data)
@@ -89,7 +256,10 @@ connection_init_result_cb (NMRemoteConnection *remote,
 	NMRemoteSettings *self = NM_REMOTE_SETTINGS (user_data);
 	NMRemoteSettingsPrivate *priv = NM_REMOTE_SETTINGS_GET_PRIVATE (self);
 	guint32 init_result = NM_REMOTE_CONNECTION_INIT_RESULT_UNKNOWN;
+	AddConnectionInfo *addinfo;
 	const char *path;
+	GError *add_error = NULL;
+	gboolean remove_from_pending = TRUE;
 
 	/* Disconnect from the init-result signal just to be safe */
 	g_signal_handlers_disconnect_matched (remote,
@@ -106,6 +276,8 @@ connection_init_result_cb (NMRemoteConnection *remote,
 	              NM_REMOTE_CONNECTION_INIT_RESULT, &init_result,
 	              NULL);
 
+	addinfo = add_connection_info_find (self, remote);
+
 	switch (init_result) {
 	case NM_REMOTE_CONNECTION_INIT_RESULT_SUCCESS:
 		/* ref it when adding to ->connections, since removing it from ->pending
@@ -113,36 +285,69 @@ connection_init_result_cb (NMRemoteConnection *remote,
 		 */
 		g_hash_table_insert (priv->connections, g_strdup (path), g_object_ref (remote));
 
+		/* If there's a pending AddConnection request, complete that here before
+		 * signaling new-connection.
+		 */
+		if (addinfo)
+			add_connection_info_complete (self, addinfo, NULL);
+
 		/* Finally, let users know of the new connection now that it has all
 		 * its settings and is valid.
 		 */
-		g_signal_emit_by_name (self, "new-connection", remote);
+		g_signal_emit (self, signals[NEW_CONNECTION], 0, remote);
 		break;
+	case NM_REMOTE_CONNECTION_INIT_RESULT_INVISIBLE:
+		remove_from_pending = FALSE;
+		/* fall through */
 	case NM_REMOTE_CONNECTION_INIT_RESULT_ERROR:
+		/* Complete pending AddConnection callbacks */
+		if (addinfo) {
+			add_error = g_error_new_literal (NM_REMOTE_SETTINGS_ERROR,
+			                                 NM_REMOTE_SETTINGS_ERROR_CONNECTION_UNAVAILABLE,
+			                                 "Connection not visible or not available");
+			add_connection_info_complete (self, addinfo, add_error);
+			g_error_free (add_error);
+		}
+		break;
 	default:
 		break;
 	}
 
-	g_hash_table_remove (priv->pending, path);
+	if (remove_from_pending)
+		g_hash_table_remove (priv->pending, path);
 
 	/* Let listeners know that all connections have been found */
-	if (!g_hash_table_size (priv->pending))
-		g_signal_emit_by_name (self, NM_SETTINGS_INTERFACE_CONNECTIONS_READ);
+	priv->init_left--;
+	if (priv->init_left == 0)
+		g_signal_emit (self, signals[CONNECTIONS_READ], 0);
 }
 
-static void
+static NMRemoteConnection *
 new_connection_cb (DBusGProxy *proxy, const char *path, gpointer user_data)
 {
 	NMRemoteSettings *self = NM_REMOTE_SETTINGS (user_data);
 	NMRemoteSettingsPrivate *priv = NM_REMOTE_SETTINGS_GET_PRIVATE (self);
-	NMRemoteConnection *connection;
-
-	connection = nm_remote_connection_new (priv->bus, priv->scope, path);
+	NMRemoteConnection *connection = NULL;
+
+	/* Make double-sure we don't already have it */
+	connection = g_hash_table_lookup (priv->pending, path);
+	if (connection)
+		return connection;
+	connection = g_hash_table_lookup (priv->connections, path);
+	if (connection)
+		return connection;
+
+	/* Create a new connection object for it */
+	connection = nm_remote_connection_new (priv->bus, path);
 	if (connection) {
-		g_signal_connect (connection, "removed",
+		g_signal_connect (connection, NM_REMOTE_CONNECTION_REMOVED,
 		                  G_CALLBACK (connection_removed_cb),
 		                  self);
 
+		g_signal_connect (connection, "visible",
+		                  G_CALLBACK (connection_visible_cb),
+		                  self);
+
 		g_signal_connect (connection, "notify::" NM_REMOTE_CONNECTION_INIT_RESULT,
 		                  G_CALLBACK (connection_init_result_cb),
 		                  self);
@@ -153,6 +358,7 @@ new_connection_cb (DBusGProxy *proxy, const char *path, gpointer user_data)
 		 */
 		g_hash_table_insert (priv->pending, g_strdup (path), connection);
 	}
+	return connection;
 }
 
 static void
@@ -162,45 +368,38 @@ fetch_connections_done (DBusGProxy *proxy,
                         gpointer user_data)
 {
 	NMRemoteSettings *self = NM_REMOTE_SETTINGS (user_data);
-	NMRemoteSettingsPrivate *priv = NM_REMOTE_SETTINGS_GET_PRIVATE (self);	
+	NMRemoteSettingsPrivate *priv = NM_REMOTE_SETTINGS_GET_PRIVATE (self);
 	int i;
 
 	if (error) {
-		gboolean is_spawn_error = FALSE;
-
-		/* Don't warn if the user settings service wasn't running since that's
-		 * just annoying when running headless.
-		 */
-		if (   g_error_matches (error, DBUS_GERROR, DBUS_GERROR_SERVICE_UNKNOWN)
-		    || g_error_matches (error, DBUS_GERROR, DBUS_GERROR_NAME_HAS_NO_OWNER))
-			is_spawn_error = TRUE;
-
-		if (!is_spawn_error || priv->scope == NM_CONNECTION_SCOPE_SYSTEM) {
-			g_warning ("%s: error fetching %s connections: (%d) %s.",
-				       __func__,
-				       priv->scope == NM_CONNECTION_SCOPE_USER ? "user" : "system",
+		/* Ignore settings service spawn errors */
+		if (   !g_error_matches (error, DBUS_GERROR, DBUS_GERROR_SERVICE_UNKNOWN)
+		    && !g_error_matches (error, DBUS_GERROR, DBUS_GERROR_NAME_HAS_NO_OWNER)) {
+			g_warning ("%s: error fetching connections: (%d) %s.",
+			           __func__,
 				       error->code,
 				       error->message ? error->message : "(unknown)");
 		}
 		g_clear_error (&error);
 
 		/* We tried to read connections and failed */
-		g_signal_emit_by_name (self, NM_SETTINGS_INTERFACE_CONNECTIONS_READ);
+		g_signal_emit (self, signals[CONNECTIONS_READ], 0);
 		return;
 	}
 
 	/* Let listeners know we are done getting connections */
-	if (connections->len == 0) {
-		g_signal_emit_by_name (self, NM_SETTINGS_INTERFACE_CONNECTIONS_READ);
-		return;
+	if (connections->len == 0)
+		g_signal_emit (self, signals[CONNECTIONS_READ], 0);
+	else {
+		priv->init_left = connections->len;
+		for (i = 0; i < connections->len; i++) {
+			char *path = g_ptr_array_index (connections, i);
+
+			new_connection_cb (proxy, path, user_data);
+			g_free (path);
+		}
 	}
 
-	for (i = 0; connections && (i < connections->len); i++) {
-		char *path = g_ptr_array_index (connections, i);
-
-		new_connection_cb (proxy, path, user_data);
-		g_free (path);
-	}
 	g_ptr_array_free (connections, TRUE);
 }
 
@@ -212,20 +411,36 @@ fetch_connections (gpointer user_data)
 
 	priv->fetch_id = 0;
 
-	org_freedesktop_NetworkManagerSettings_list_connections_async (priv->proxy,
-	                                                               fetch_connections_done,
-	                                                               self);
+	org_freedesktop_NetworkManager_Settings_list_connections_async (priv->proxy,
+	                                                                fetch_connections_done,
+	                                                                self);
 	return FALSE;
 }
 
-static GSList *
-list_connections (NMSettingsInterface *settings)
+/**
+ * nm_remote_settings_list_connections:
+ * @settings: the %NMRemoteSettings
+ *
+ * Returns: (transfer container) (element-type NMClient.RemoteConnection): a
+ * list containing all connections provided by the remote settings service.
+ * Each element of the returned list is a %NMRemoteConnection instance, which is
+ * owned by the %NMRemoteSettings object and should not be freed by the caller.
+ * The returned list is, however, owned by the caller and should be freed
+ * using g_slist_free() when no longer required.
+ **/
+GSList *
+nm_remote_settings_list_connections (NMRemoteSettings *settings)
 {
-	NMRemoteSettingsPrivate *priv = NM_REMOTE_SETTINGS_GET_PRIVATE (settings);
+	NMRemoteSettingsPrivate *priv;
 	GSList *list = NULL;
 	GHashTableIter iter;
 	gpointer value;
 
+	g_return_val_if_fail (settings != NULL, NULL);
+	g_return_val_if_fail (NM_IS_REMOTE_SETTINGS (settings), NULL);
+
+	priv = NM_REMOTE_SETTINGS_GET_PRIVATE (settings);
+
 	g_hash_table_iter_init (&iter, priv->connections);
 	while (g_hash_table_iter_next (&iter, NULL, &value))
 		list = g_slist_prepend (list, NM_REMOTE_CONNECTION (value));
@@ -233,53 +448,77 @@ list_connections (NMSettingsInterface *settings)
 	return list;
 }
 
-typedef struct {
-	NMSettingsInterface *self;
-	NMSettingsAddConnectionFunc callback;
-	gpointer callback_data;
-} AddConnectionInfo;
-
 static void
-add_connection_done (DBusGProxy *proxy,
-                     GError *error,
-                     gpointer user_data)
+add_connection_done (DBusGProxy *proxy, DBusGProxyCall *call, gpointer user_data)
 {
 	AddConnectionInfo *info = user_data;
+	GError *error = NULL;
+	char *path = NULL;
 
-	info->callback (info->self, error, info->callback_data);
-	g_free (info);
+	if (dbus_g_proxy_end_call (proxy, call, &error, DBUS_TYPE_G_OBJECT_PATH, &path, G_TYPE_INVALID)) {
+		info->connection = new_connection_cb (proxy, path, info->self);
+		g_assert (info->connection);
+		/* Wait until this connection is fully initialized before calling the callback */
+		g_free (path);
+	} else
+		add_connection_info_complete (info->self, info, error);
+
+	g_clear_error (&error);
 }
 
-static gboolean
-add_connection (NMSettingsInterface *settings,
-	            NMConnection *connection,
-	            NMSettingsAddConnectionFunc callback,
-	            gpointer user_data)
+/**
+ * nm_remote_settings_add_connection:
+ * @settings: the %NMRemoteSettings
+ * @connection: the connection to add. Note that this object's settings will be
+ *   added, not the object itself
+ * @callback: (scope async): callback to be called when the add operation completes
+ * @user_data: caller-specific data passed to @callback
+ *
+ * Requests that the remote settings service add the given settings to a new
+ * connection.
+ *
+ * Returns: TRUE if the request was successful, FALSE if it failed
+ **/
+gboolean
+nm_remote_settings_add_connection (NMRemoteSettings *settings,
+                                   NMConnection *connection,
+                                   NMRemoteSettingsAddConnectionFunc callback,
+                                   gpointer user_data)
 {
-	NMRemoteSettings *self = NM_REMOTE_SETTINGS (settings);
-	NMRemoteSettingsPrivate *priv = NM_REMOTE_SETTINGS_GET_PRIVATE (self);
+	NMRemoteSettingsPrivate *priv;
 	AddConnectionInfo *info;
 	GHashTable *new_settings;
 
+	g_return_val_if_fail (settings != NULL, FALSE);
+	g_return_val_if_fail (NM_IS_REMOTE_SETTINGS (settings), FALSE);
+	g_return_val_if_fail (connection != NULL, FALSE);
+	g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE);
+	g_return_val_if_fail (callback != NULL, FALSE);
+
+	priv = NM_REMOTE_SETTINGS_GET_PRIVATE (settings);
+	
 	info = g_malloc0 (sizeof (AddConnectionInfo));
 	info->self = settings;
 	info->callback = callback;
 	info->callback_data = user_data;
 
-	new_settings = nm_connection_to_hash (connection);
-	org_freedesktop_NetworkManagerSettings_add_connection_async (priv->proxy,
-	                                                             new_settings,
-	                                                             add_connection_done,
-	                                                             info);
+	new_settings = nm_connection_to_hash (connection, NM_SETTING_HASH_FLAG_ALL);
+	dbus_g_proxy_begin_call (priv->proxy, "AddConnection",
+	                         add_connection_done,
+	                         info,
+	                         NULL,
+	                         DBUS_TYPE_G_MAP_OF_MAP_OF_VARIANT, new_settings,
+	                         G_TYPE_INVALID);
 	g_hash_table_destroy (new_settings);
+
+	priv->add_list = g_slist_append (priv->add_list, info);
+
 	return TRUE;
 }
 
-static gboolean
-remove_connections (gpointer user_data)
+static void
+clear_one_hash (GHashTable *table)
 {
-	NMRemoteSettings *self = NM_REMOTE_SETTINGS (user_data);
-	NMRemoteSettingsPrivate *priv = NM_REMOTE_SETTINGS_GET_PRIVATE (self);
 	GHashTableIter iter;
 	gpointer value;
 	GSList *list = NULL, *list_iter;
@@ -289,18 +528,90 @@ remove_connections (gpointer user_data)
 	 * that explicitly removes the the connection from the hash table somewhere
 	 * else.
 	 */
-	g_hash_table_iter_init (&iter, priv->connections);
+	g_hash_table_iter_init (&iter, table);
 	while (g_hash_table_iter_next (&iter, NULL, &value))
 		list = g_slist_prepend (list, NM_REMOTE_CONNECTION (value));
 
 	for (list_iter = list; list_iter; list_iter = g_slist_next (list_iter))
-		g_signal_emit_by_name (NM_REMOTE_CONNECTION (list_iter->data), "removed");
+		g_signal_emit_by_name (NM_REMOTE_CONNECTION (list_iter->data), NM_REMOTE_CONNECTION_REMOVED);
 	g_slist_free (list);
 
-	g_hash_table_remove_all (priv->connections);
+	g_hash_table_remove_all (table);
+}
+
+static gboolean
+remove_connections (gpointer user_data)
+{
+	NMRemoteSettings *self = NM_REMOTE_SETTINGS (user_data);
+	NMRemoteSettingsPrivate *priv = NM_REMOTE_SETTINGS_GET_PRIVATE (self);
+
+	clear_one_hash (priv->pending);
+	clear_one_hash (priv->connections);
 	return FALSE;
 }
 
+typedef struct {
+	NMRemoteSettings *settings;
+	NMRemoteSettingsSaveHostnameFunc callback;
+	gpointer callback_data;
+} SaveHostnameInfo;
+
+static void
+save_hostname_cb (DBusGProxy *proxy,
+                  DBusGProxyCall *call,
+                  gpointer user_data)
+{
+	SaveHostnameInfo *info = user_data;
+	GError *error = NULL;
+
+	dbus_g_proxy_end_call (proxy, call, &error, G_TYPE_INVALID);
+	info->callback (info->settings, error, info->callback_data);
+	g_clear_error (&error);
+}
+
+/**
+ * nm_remote_settings_save_hostname:
+ * @settings: the %NMRemoteSettings
+ * @hostname: the new persistent hostname to set, or NULL to clear any existing
+ *  persistent hostname
+ * @callback: (scope async): callback to be called when the hostname operation completes
+ * @user_data: caller-specific data passed to @callback
+ *
+ * Requests that the machine's persistent hostname be set to the specified value
+ * or cleared.
+ *
+ * Returns: TRUE if the request was successful, FALSE if it failed
+ **/
+gboolean
+nm_remote_settings_save_hostname (NMRemoteSettings *settings,
+                                  const char *hostname,
+                                  NMRemoteSettingsSaveHostnameFunc callback,
+                                  gpointer user_data)
+{
+	NMRemoteSettingsPrivate *priv;
+	SaveHostnameInfo *info;
+
+	g_return_val_if_fail (settings != NULL, FALSE);
+	g_return_val_if_fail (NM_IS_REMOTE_SETTINGS (settings), FALSE);
+	g_return_val_if_fail (hostname != NULL, FALSE);
+	g_return_val_if_fail (callback != NULL, FALSE);
+	
+	priv = NM_REMOTE_SETTINGS_GET_PRIVATE (settings);
+
+	info = g_malloc0 (sizeof (SaveHostnameInfo));
+	info->settings = settings;
+	info->callback = callback;
+	info->callback_data = user_data;
+
+	dbus_g_proxy_begin_call (priv->proxy, "SaveHostname",
+	                         save_hostname_cb,
+	                         info,
+	                         g_free,
+	                         G_TYPE_STRING, hostname ? hostname : "",
+	                         G_TYPE_INVALID);
+	return TRUE;
+}
+
 static void
 name_owner_changed (DBusGProxy *proxy,
                     const char *name,
@@ -310,10 +621,7 @@ name_owner_changed (DBusGProxy *proxy,
 {
 	NMRemoteSettings *self = NM_REMOTE_SETTINGS (user_data);
 	NMRemoteSettingsPrivate *priv = NM_REMOTE_SETTINGS_GET_PRIVATE (self);
-	const char *sname = NM_DBUS_SERVICE_USER_SETTINGS;
-
-	if (priv->scope == NM_CONNECTION_SCOPE_SYSTEM)
-		sname = NM_DBUS_SERVICE_SYSTEM_SETTINGS;
+	const char *sname = NM_DBUS_SERVICE;
 
 	if (!strcmp (name, sname)) {
 		if (priv->fetch_id)
@@ -330,35 +638,80 @@ name_owner_changed (DBusGProxy *proxy,
 	}
 }
 
-/****************************************************************/
+static void
+properties_changed_cb (DBusGProxy *proxy,
+                       GHashTable *properties,
+                       gpointer user_data)
+{
+	NMRemoteSettings *self = NM_REMOTE_SETTINGS (user_data);
+	NMRemoteSettingsPrivate *priv = NM_REMOTE_SETTINGS_GET_PRIVATE (self);
+	GHashTableIter iter;
+	gpointer key, tmp;
+
+	g_hash_table_iter_init (&iter, properties);
+	while (g_hash_table_iter_next (&iter, &key, &tmp)) {
+		GValue *value = tmp;
+
+		if (!strcmp ((const char *) key, "Hostname")) {
+			g_free (priv->hostname);
+			priv->hostname = g_value_dup_string (value);
+			g_object_notify (G_OBJECT (self), NM_REMOTE_SETTINGS_HOSTNAME);
+		}
+
+		if (!strcmp ((const char *) key, "CanModify")) {
+			priv->can_modify = g_value_get_boolean (value);
+			g_object_notify (G_OBJECT (self), NM_REMOTE_SETTINGS_CAN_MODIFY);
+		}
+	}
+}
 
 static void
-settings_interface_init (NMSettingsInterface *iface)
+get_all_cb  (DBusGProxy *proxy,
+             DBusGProxyCall *call,
+             gpointer user_data)
 {
-	/* interface implementation */
-	iface->list_connections = list_connections;
-	iface->get_connection_by_path = get_connection_by_path;
-	iface->add_connection = add_connection;
+	NMRemoteSettings *self = NM_REMOTE_SETTINGS (user_data);
+	GHashTable *props = NULL;
+	GError *error = NULL;
+
+	if (!dbus_g_proxy_end_call (proxy, call, &error,
+	                            DBUS_TYPE_G_MAP_OF_VARIANT, &props,
+	                            G_TYPE_INVALID)) {
+		/* Don't warn when the call times out because the settings service can't
+		 * be activated or whatever.
+		 */
+		if (!(error->domain == DBUS_GERROR && error->code == DBUS_GERROR_NO_REPLY)) {
+			g_warning ("%s: couldn't retrieve system settings properties: (%d) %s.",
+			           __func__,
+			           error ? error->code : -1,
+			           (error && error->message) ? error->message : "(unknown)");
+		}
+		g_clear_error (&error);
+		return;
+	}
+
+	properties_changed_cb (NULL, props, self);
+	g_hash_table_destroy (props);
 }
 
+/****************************************************************/
+
 /**
  * nm_remote_settings_new:
- * @bus: a valid and connected D-Bus connection
- * @scope: the settings service scope (either user or system)
+ * @bus: (allow-none): a valid and connected D-Bus connection
  *
  * Creates a new object representing the remote settings service.
  *
  * Returns: the new remote settings object on success, or %NULL on failure
  **/
 NMRemoteSettings *
-nm_remote_settings_new (DBusGConnection *bus, NMConnectionScope scope)
+nm_remote_settings_new (DBusGConnection *bus)
 {
-	g_return_val_if_fail (bus != NULL, NULL);
-	g_return_val_if_fail (scope != NM_CONNECTION_SCOPE_UNKNOWN, NULL);
+	if (bus == NULL)
+		bus = dbus_g_bus_get (DBUS_BUS_SYSTEM, NULL);
 
 	return (NMRemoteSettings *) g_object_new (NM_TYPE_REMOTE_SETTINGS,
 	                                          NM_REMOTE_SETTINGS_BUS, bus,
-	                                          NM_REMOTE_SETTINGS_SCOPE, scope,
 	                                          NULL);
 }
 
@@ -378,7 +731,6 @@ constructor (GType type,
 {
 	GObject *object;
 	NMRemoteSettingsPrivate *priv;
-	const char *service = NM_DBUS_SERVICE_USER_SETTINGS;
 	GError *error = NULL;
 
 	object = G_OBJECT_CLASS (nm_remote_settings_parent_class)->constructor (type, n_construct_params, construct_params);
@@ -406,12 +758,8 @@ constructor (GType type,
 	                             G_CALLBACK (name_owner_changed),
 	                             object, NULL);
 
-	/* Settings service proxy */
-	if (priv->scope == NM_CONNECTION_SCOPE_SYSTEM)
-		service = NM_DBUS_SERVICE_SYSTEM_SETTINGS;
-
 	if (!dbus_g_proxy_call (priv->dbus_proxy, "NameHasOwner", &error,
-	                        G_TYPE_STRING, service,
+	                        G_TYPE_STRING, NM_DBUS_SERVICE,
 	                        G_TYPE_INVALID,
 	                        G_TYPE_BOOLEAN, &priv->service_running,
 	                        G_TYPE_INVALID)) {
@@ -424,7 +772,7 @@ constructor (GType type,
 	}
 
 	priv->proxy = dbus_g_proxy_new_for_name (priv->bus,
-	                                         service,
+	                                         NM_DBUS_SERVICE,
 	                                         NM_DBUS_PATH_SETTINGS,
 	                                         NM_DBUS_IFACE_SETTINGS);
 	g_assert (priv->proxy);
@@ -440,13 +788,43 @@ constructor (GType type,
 
 	priv->fetch_id = g_idle_add (fetch_connections, object);
 
+
+	/* D-Bus properties proxy */
+	priv->props_proxy = dbus_g_proxy_new_for_name (priv->bus,
+	                                               NM_DBUS_SERVICE,
+	                                               NM_DBUS_PATH_SETTINGS,
+	                                               "org.freedesktop.DBus.Properties");
+	g_assert (priv->props_proxy);
+
+	/* Monitor properties */
+	dbus_g_object_register_marshaller (g_cclosure_marshal_VOID__BOXED,
+	                                   G_TYPE_NONE,
+	                                   DBUS_TYPE_G_MAP_OF_VARIANT,
+	                                   G_TYPE_INVALID);
+	dbus_g_proxy_add_signal (priv->proxy, "PropertiesChanged",
+	                         DBUS_TYPE_G_MAP_OF_VARIANT,
+	                         G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal (priv->proxy, "PropertiesChanged",
+	                             G_CALLBACK (properties_changed_cb),
+	                             object,
+	                             NULL);
+
+	/* Get properties */
+	dbus_g_proxy_begin_call (priv->props_proxy, "GetAll",
+	                         get_all_cb,
+	                         object,
+	                         NULL,
+	                         G_TYPE_STRING, NM_DBUS_IFACE_SETTINGS,
+	                         G_TYPE_INVALID);	
+
 	return object;
 }
 
 static void
 dispose (GObject *object)
 {
-	NMRemoteSettingsPrivate *priv = NM_REMOTE_SETTINGS_GET_PRIVATE (object);
+	NMRemoteSettings *self = NM_REMOTE_SETTINGS (object);
+	NMRemoteSettingsPrivate *priv = NM_REMOTE_SETTINGS_GET_PRIVATE (self);
 
 	if (priv->disposed)
 		return;
@@ -456,14 +834,20 @@ dispose (GObject *object)
 	if (priv->fetch_id)
 		g_source_remove (priv->fetch_id);
 
+	while (g_slist_length (priv->add_list))
+		add_connection_info_dispose (self, (AddConnectionInfo *) priv->add_list->data);
+
 	if (priv->connections)
 		g_hash_table_destroy (priv->connections);
 
 	if (priv->pending)
 		g_hash_table_destroy (priv->pending);
 
+	g_free (priv->hostname);
+
 	g_object_unref (priv->dbus_proxy);
 	g_object_unref (priv->proxy);
+	g_object_unref (priv->props_proxy);
 	dbus_g_connection_unref (priv->bus);
 
 	G_OBJECT_CLASS (nm_remote_settings_parent_class)->dispose (object);
@@ -480,9 +864,6 @@ set_property (GObject *object, guint prop_id,
 		/* Construct only */
 		priv->bus = dbus_g_connection_ref ((DBusGConnection *) g_value_get_boxed (value));
 		break;
-	case PROP_SCOPE:
-		priv->scope = (NMConnectionScope) g_value_get_uint (value);
-		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -499,12 +880,15 @@ get_property (GObject *object, guint prop_id,
 	case PROP_BUS:
 		g_value_set_boxed (value, priv->bus);
 		break;
-	case PROP_SCOPE:
-		g_value_set_uint (value, priv->scope);
-		break;
 	case PROP_SERVICE_RUNNING:
 		g_value_set_boolean (value, priv->service_running);
 		break;
+	case PROP_HOSTNAME:
+		g_value_set_string (value, priv->hostname);
+		break;
+	case PROP_CAN_MODIFY:
+		g_value_set_boolean (value, priv->can_modify);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -534,21 +918,46 @@ nm_remote_settings_class_init (NMRemoteSettingsClass *class)
 		                     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 
 	g_object_class_install_property
-		(object_class, PROP_SCOPE,
-		 g_param_spec_uint (NM_REMOTE_SETTINGS_SCOPE,
-		                    "Scope",
-		                    "NMConnection scope",
-		                    NM_CONNECTION_SCOPE_UNKNOWN,
-		                    NM_CONNECTION_SCOPE_USER,
-		                    NM_CONNECTION_SCOPE_USER,
-		                    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
-
-	g_object_class_install_property
 		(object_class, PROP_SERVICE_RUNNING,
 		 g_param_spec_boolean (NM_REMOTE_SETTINGS_SERVICE_RUNNING,
 		                       "Service running",
 		                       "Is service running",
 		                       FALSE,
 		                       G_PARAM_READABLE));
+
+	g_object_class_install_property
+		(object_class, PROP_HOSTNAME,
+		 g_param_spec_string (NM_REMOTE_SETTINGS_HOSTNAME,
+		                      "Hostname",
+		                      "Persistent hostname",
+		                      NULL,
+		                      G_PARAM_READABLE));
+
+	g_object_class_install_property
+		(object_class, PROP_CAN_MODIFY,
+		 g_param_spec_boolean (NM_REMOTE_SETTINGS_CAN_MODIFY,
+		                       "CanModify",
+		                       "Can modify anything (hostname, connections, etc)",
+		                       FALSE,
+		                       G_PARAM_READABLE));
+
+	/* Signals */
+	signals[NEW_CONNECTION] = 
+	                g_signal_new (NM_REMOTE_SETTINGS_NEW_CONNECTION,
+	                              G_OBJECT_CLASS_TYPE (object_class),
+	                              G_SIGNAL_RUN_FIRST,
+	                              G_STRUCT_OFFSET (NMRemoteSettingsClass, new_connection),
+	                              NULL, NULL,
+	                              g_cclosure_marshal_VOID__OBJECT,
+	                              G_TYPE_NONE, 1, G_TYPE_OBJECT);
+
+	signals[CONNECTIONS_READ] = 
+	                g_signal_new (NM_REMOTE_SETTINGS_CONNECTIONS_READ,
+	                              G_OBJECT_CLASS_TYPE (object_class),
+	                              G_SIGNAL_RUN_FIRST,
+	                              G_STRUCT_OFFSET (NMRemoteSettingsClass, connections_read),
+	                              NULL, NULL,
+	                              g_cclosure_marshal_VOID__VOID,
+	                              G_TYPE_NONE, 0);
 }