summary refs log tree commit diff
path: root/libnm-glib/nm-remote-connection.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2014-07-06 02:16:10 +0200
committerMichael Biebl <biebl@debian.org>2014-07-06 02:16:10 +0200
commit33491bc4279481db8ae47213e34a6d695a0e8830 (patch)
tree097d2b0fdff3fae6885381ae5e57a182cd8cbbba /libnm-glib/nm-remote-connection.c
parent59c3714a494c3b3765657c0551ad82842d98a7d2 (diff)
Imported Upstream version 0.9.10.0 upstream/0.9.10.0
Diffstat (limited to 'libnm-glib/nm-remote-connection.c')
-rw-r--r--libnm-glib/nm-remote-connection.c509
1 files changed, 412 insertions, 97 deletions
diff --git a/libnm-glib/nm-remote-connection.c b/libnm-glib/nm-remote-connection.c
index 9cadf2f7..38911a43 100644
--- a/libnm-glib/nm-remote-connection.c
+++ b/libnm-glib/nm-remote-connection.c
@@ -23,14 +23,17 @@
 
 #include <string.h>
 #include <gio/gio.h>
+#include <glib/gi18n.h>
 
 #include <NetworkManager.h>
 #include <nm-utils.h>
 #include <nm-setting-connection.h>
 #include "nm-remote-connection.h"
 #include "nm-remote-connection-private.h"
+#include "nm-object-private.h"
 #include "nm-dbus-glib-types.h"
 #include "nm-glib-compat.h"
+#include "nm-dbus-helpers-private.h"
 
 #define NM_REMOTE_CONNECTION_BUS "bus"
 #define NM_REMOTE_CONNECTION_DBUS_CONNECTION "dbus-connection"
@@ -49,6 +52,7 @@ enum {
 	PROP_BUS,
 	PROP_DBUS_CONNECTION,
 	PROP_DBUS_PATH,
+	PROP_UNSAVED,
 
 	LAST_PROP
 };
@@ -62,10 +66,14 @@ enum {
 };
 static guint signals[LAST_SIGNAL] = { 0 };
 
+typedef struct RemoteCall RemoteCall;
+typedef void (*RemoteCallFetchResultCb) (RemoteCall *call, DBusGProxyCall *proxy_call, GError *error);
 
-typedef struct {
+
+typedef struct RemoteCall {
 	NMRemoteConnection *self;
 	DBusGProxyCall *call;
+	RemoteCallFetchResultCb fetch_result_cb;
 	GFunc callback;
 	gpointer user_data;
 } RemoteCall;
@@ -73,44 +81,156 @@ typedef struct {
 typedef struct {
 	DBusGConnection *bus;
 	DBusGProxy *proxy;
+	DBusGProxy *props_proxy;
+	gboolean proxy_is_destroyed;
 	GSList *calls;
 
+	gboolean inited;
+	gboolean unsaved;
+
 	gboolean visible;
 } NMRemoteConnectionPrivate;
 
 #define NM_REMOTE_CONNECTION_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_REMOTE_CONNECTION, NMRemoteConnectionPrivate))
 
+/**
+ * nm_remote_connection_error_quark:
+ *
+ * Registers an error quark for #NMRemoteConnection if necessary.
+ *
+ * Returns: the error quark used for #NMRemoteConnection errors.
+ **/
+GQuark
+nm_remote_connection_error_quark (void)
+{
+	static GQuark quark = 0;
+
+	if (G_UNLIKELY (quark == 0))
+		quark = g_quark_from_static_string ("nm-remote-connection-error-quark");
+	return quark;
+}
+
 /****************************************************************/
 
 static void
-remote_call_complete (NMRemoteConnection *self, RemoteCall *call)
+_nm_remote_connection_ensure_inited (NMRemoteConnection *self)
 {
 	NMRemoteConnectionPrivate *priv = NM_REMOTE_CONNECTION_GET_PRIVATE (self);
+	GError *error = NULL;
 
-	priv->calls = g_slist_remove (priv->calls, call);
-	/* Don't need to cancel it since this function should only be called from
-	 * the dispose handler (where the proxy will be destroyed immediately after)
-	 * or from the call's completion callback.
-	 */
-	memset (call, 0, sizeof (RemoteCall));
-	g_free (call);
+	if (!priv->inited) {
+		if (!g_initable_init (G_INITABLE (self), NULL, &error)) {
+			/* Don't warn when the call times out because the settings service can't
+			 * be activated or whatever.
+			 */
+			if (!g_error_matches (error, DBUS_GERROR, DBUS_GERROR_NO_REPLY)) {
+				g_warning ("%s: (NMRemoteConnection) error initializing: %s\n",
+					       __func__, error->message);
+			}
+			g_error_free (error);
+		}
+		priv->inited = TRUE;
+	}
 }
 
+/****************************************************************/
+
 static void
-update_cb (DBusGProxy *proxy, DBusGProxyCall *proxy_call, gpointer user_data)
+remote_call_dbus_cb (DBusGProxy *proxy, DBusGProxyCall *proxy_call, gpointer user_data)
 {
 	RemoteCall *call = user_data;
-	NMRemoteConnectionCommitFunc func = (NMRemoteConnectionCommitFunc) call->callback;
+	NMRemoteConnectionPrivate *priv = NM_REMOTE_CONNECTION_GET_PRIVATE (call->self);
 	GError *error = NULL;
-	NMRemoteConnection *self = g_object_ref (call->self);
 
-	dbus_g_proxy_end_call (proxy, proxy_call, &error,
-	                       G_TYPE_INVALID);
-	if (func != NULL)
-		(*func)(call->self, error, call->user_data);
+	g_assert ( (!proxy && !proxy_call &&  priv->proxy_is_destroyed) ||
+	           ( proxy &&  proxy_call && !priv->proxy_is_destroyed && proxy == priv->proxy) );
+
+	if (priv->proxy_is_destroyed) {
+		error = g_error_new_literal (NM_REMOTE_CONNECTION_ERROR,
+		                             NM_REMOTE_CONNECTION_ERROR_DISCONNECTED,
+		                             _("Disconnected by D-Bus"));
+	}
+	call->fetch_result_cb (call, proxy_call, error);
 	g_clear_error (&error);
-	remote_call_complete (call->self, call);
-	g_object_unref (self);
+
+	priv->calls = g_slist_remove (priv->calls, call);
+	g_object_unref (call->self);
+	g_free (call);
+}
+
+static gboolean
+remote_call_cleanup_cb (void *user_data)
+{
+	remote_call_dbus_cb (NULL, NULL, user_data);
+	return G_SOURCE_REMOVE;
+}
+
+static RemoteCall *
+remote_call_new (NMRemoteConnection *self,
+                 RemoteCallFetchResultCb fetch_result_cb,
+                 GFunc callback,
+                 gpointer user_data)
+{
+	RemoteCall *call;
+	NMRemoteConnectionPrivate *priv = NM_REMOTE_CONNECTION_GET_PRIVATE (self);
+
+	g_assert (fetch_result_cb);
+
+	if (priv->proxy_is_destroyed && !callback)
+		return NULL;
+
+	call = g_malloc0 (sizeof (RemoteCall));
+	call->self = g_object_ref (self);
+	call->fetch_result_cb = fetch_result_cb;
+	call->user_data = user_data;
+	call->callback = callback;
+
+	if (priv->proxy_is_destroyed) {
+		g_idle_add (remote_call_cleanup_cb, call);
+		return NULL;
+	}
+	priv->calls = g_slist_prepend (priv->calls, call);
+	return call;
+}
+
+static void
+proxy_set_destroyed (NMRemoteConnection *self)
+{
+	NMRemoteConnectionPrivate *priv = NM_REMOTE_CONNECTION_GET_PRIVATE (self);
+
+	if (priv->proxy_is_destroyed) {
+		g_assert (!priv->calls);
+		return;
+	}
+
+	priv->proxy_is_destroyed = TRUE;
+
+	priv->calls = g_slist_reverse (priv->calls);
+	while (priv->calls)
+		remote_call_dbus_cb (NULL, NULL, priv->calls->data);
+}
+
+static void
+proxy_destroy_cb (DBusGProxy* proxy, gpointer user_data) {
+	proxy_set_destroyed (user_data);
+}
+
+/****************************************************************/
+
+static void
+result_cb (RemoteCall *call, DBusGProxyCall *proxy_call, GError *error)
+{
+	NMRemoteConnectionResultFunc func = (NMRemoteConnectionResultFunc) call->callback;
+	GError *local_error = NULL;
+
+	if (!error) {
+		dbus_g_proxy_end_call (NM_REMOTE_CONNECTION_GET_PRIVATE (call->self)->proxy,
+		                       proxy_call, &local_error, G_TYPE_INVALID);
+		error = local_error;
+	}
+	if (func)
+		(*func) (call->self, error, call->user_data);
+	g_clear_error (&local_error);
 }
 
 /**
@@ -120,54 +240,105 @@ update_cb (DBusGProxy *proxy, DBusGProxyCall *proxy_call, gpointer user_data)
  * commit completes
  * @user_data: (closure): caller-specific data to be passed to @callback
  *
- * Save any local changes to the settings and properties of this connection and
- * save them in the settings service.
+ * Send any local changes to the settings and properties of this connection to
+ * NetworkManager, which will immediately save them to disk.
  **/
 void
 nm_remote_connection_commit_changes (NMRemoteConnection *self,
-                                     NMRemoteConnectionCommitFunc callback,
+                                     NMRemoteConnectionResultFunc callback,
                                      gpointer user_data)
 {
 	NMRemoteConnectionPrivate *priv;
-	GHashTable *settings = NULL;
 	RemoteCall *call;
+	GHashTable *settings;
 
-	g_return_if_fail (self != NULL);
 	g_return_if_fail (NM_IS_REMOTE_CONNECTION (self));
-	g_return_if_fail (callback != NULL);
 
 	priv = NM_REMOTE_CONNECTION_GET_PRIVATE (self);
 
-	call = g_malloc0 (sizeof (RemoteCall));
-	call->self = self;
-	call->callback = (GFunc) callback;
-	call->user_data = user_data;
+	call = remote_call_new (self, result_cb, (GFunc) callback, user_data);
+	if (!call)
+		return;
 
 	settings = nm_connection_to_hash (NM_CONNECTION (self), NM_SETTING_HASH_FLAG_ALL);
-
 	call->call = dbus_g_proxy_begin_call (priv->proxy, "Update",
-	                                      update_cb, call, NULL,
+	                                      remote_call_dbus_cb, call, NULL,
 	                                      DBUS_TYPE_G_MAP_OF_MAP_OF_VARIANT, settings,
 	                                      G_TYPE_INVALID);
 	g_assert (call->call);
-	priv->calls = g_slist_append (priv->calls, call);
+	g_hash_table_destroy (settings);
+}
 
+/**
+ * nm_remote_connection_commit_changes_unsaved:
+ * @connection: the #NMRemoteConnection
+ * @callback: (scope async) (allow-none): a function to be called when the
+ * commit completes
+ * @user_data: (closure): caller-specific data to be passed to @callback
+ *
+ * Send any local changes to the settings and properties of this connection to
+ * NetworkManager.  The changes are not saved to disk until either
+ * nm_remote_connection_save() or nm_remote_connection_commit_changes() is
+ * called.
+ *
+ * Since: 0.9.10
+ **/
+void
+nm_remote_connection_commit_changes_unsaved (NMRemoteConnection *connection,
+                                             NMRemoteConnectionResultFunc callback,
+                                             gpointer user_data)
+{
+	NMRemoteConnectionPrivate *priv;
+	GHashTable *settings = NULL;
+	RemoteCall *call;
+
+	g_return_if_fail (NM_IS_REMOTE_CONNECTION (connection));
+
+	priv = NM_REMOTE_CONNECTION_GET_PRIVATE (connection);
+
+	call = remote_call_new (connection, result_cb, (GFunc) callback, user_data);
+	if (!call)
+		return;
+
+	settings = nm_connection_to_hash (NM_CONNECTION (connection), NM_SETTING_HASH_FLAG_ALL);
+	call->call = dbus_g_proxy_begin_call (priv->proxy, "UpdateUnsaved",
+	                                      remote_call_dbus_cb, call, NULL,
+	                                      DBUS_TYPE_G_MAP_OF_MAP_OF_VARIANT, settings,
+	                                      G_TYPE_INVALID);
+	g_assert (call->call);
 	g_hash_table_destroy (settings);
 }
 
-static void
-delete_cb (DBusGProxy *proxy, DBusGProxyCall *proxy_call, gpointer user_data)
+/**
+ * nm_remote_connection_save:
+ * @connection: the #NMRemoteConnection
+ * @callback: (scope async) (allow-none): a function to be called when the
+ * save completes
+ * @user_data: (closure): caller-specific data to be 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.
+ *
+ * Since: 0.9.10
+ **/
+void
+nm_remote_connection_save (NMRemoteConnection *connection,
+                           NMRemoteConnectionResultFunc callback,
+                           gpointer user_data)
 {
-	RemoteCall *call = user_data;
-	NMRemoteConnectionDeleteFunc func = (NMRemoteConnectionDeleteFunc) call->callback;
-	GError *error = NULL;
+	NMRemoteConnectionPrivate *priv;
+	RemoteCall *call;
 
-	dbus_g_proxy_end_call (proxy, proxy_call, &error,
-	                       G_TYPE_INVALID);
-	if (func != NULL)
-		(*func)(call->self, error, call->user_data);
-	g_clear_error (&error);
-	remote_call_complete (call->self, call);
+	g_return_if_fail (NM_IS_REMOTE_CONNECTION (connection));
+
+	priv = NM_REMOTE_CONNECTION_GET_PRIVATE (connection);
+
+	call = remote_call_new (connection, result_cb, (GFunc) callback, user_data);
+	if (!call)
+		return;
+
+	call->call = dbus_g_proxy_begin_call (priv->proxy, "Save", remote_call_dbus_cb, call, NULL, G_TYPE_INVALID);
+	g_assert (call->call);
 }
 
 /**
@@ -180,47 +351,48 @@ delete_cb (DBusGProxy *proxy, DBusGProxyCall *proxy_call, gpointer user_data)
  **/
 void
 nm_remote_connection_delete (NMRemoteConnection *self,
-                             NMRemoteConnectionDeleteFunc callback,
+                             NMRemoteConnectionResultFunc callback,
                              gpointer user_data)
 {
 	NMRemoteConnectionPrivate *priv;
 	RemoteCall *call;
 
-	g_return_if_fail (self != NULL);
 	g_return_if_fail (NM_IS_REMOTE_CONNECTION (self));
 
 	priv = NM_REMOTE_CONNECTION_GET_PRIVATE (self);
 
-	call = g_malloc0 (sizeof (RemoteCall));
-	call->self = self;
-	call->callback = (GFunc) callback;
-	call->user_data = user_data;
+	call = remote_call_new (self, result_cb, (GFunc) callback, user_data);
+	if (!call)
+		return;
 
 	call->call = dbus_g_proxy_begin_call (priv->proxy, "Delete",
-	                                      delete_cb, call, NULL,
+	                                      remote_call_dbus_cb, call, NULL,
 	                                      G_TYPE_INVALID);
 	g_assert (call->call);
-	priv->calls = g_slist_append (priv->calls, call);
 }
 
 static void
-get_secrets_cb (DBusGProxy *proxy, DBusGProxyCall *proxy_call, gpointer user_data)
+get_secrets_cb (RemoteCall *call, DBusGProxyCall *proxy_call, GError *error)
 {
-	RemoteCall *call = user_data;
 	NMRemoteConnectionGetSecretsFunc func = (NMRemoteConnectionGetSecretsFunc) call->callback;
 	GHashTable *secrets = NULL;
-	GError *error = NULL;
-
-	dbus_g_proxy_end_call (proxy, proxy_call, &error,
-	                       DBUS_TYPE_G_MAP_OF_MAP_OF_VARIANT, &secrets,
-	                       G_TYPE_INVALID);
-	(*func)(call->self, error ? NULL : secrets, error, call->user_data);
+	GError *local_error = NULL;
+
+	if (!error) {
+		dbus_g_proxy_end_call (NM_REMOTE_CONNECTION_GET_PRIVATE (call->self)->proxy,
+		                       proxy_call, &local_error,
+		                       DBUS_TYPE_G_MAP_OF_MAP_OF_VARIANT, &secrets,
+		                       G_TYPE_INVALID);
+		error = local_error;
+	}
+	if (func)
+		(*func) (call->self, error ? NULL : secrets, error, call->user_data);
+	g_clear_error (&local_error);
 	if (secrets)
 		g_hash_table_destroy (secrets);
-	g_clear_error (&error);
-	remote_call_complete (call->self, call);
 }
 
+
 /**
  * nm_remote_connection_get_secrets:
  * @connection: the #NMRemoteConnection
@@ -240,23 +412,39 @@ nm_remote_connection_get_secrets (NMRemoteConnection *self,
 	NMRemoteConnectionPrivate *priv;
 	RemoteCall *call;
 
-	g_return_if_fail (self != NULL);
 	g_return_if_fail (NM_IS_REMOTE_CONNECTION (self));
 	g_return_if_fail (callback != NULL);
 
 	priv = NM_REMOTE_CONNECTION_GET_PRIVATE (self);
 
-	call = g_malloc0 (sizeof (RemoteCall));
-	call->self = self;
-	call->callback = (GFunc) callback;
-	call->user_data = user_data;
+	call = remote_call_new (self, get_secrets_cb, (GFunc) callback, user_data);
+	if (!call)
+		return;
 
 	call->call = dbus_g_proxy_begin_call (priv->proxy, "GetSecrets",
-	                                      get_secrets_cb, call, NULL,
+	                                      remote_call_dbus_cb, call, NULL,
 	                                      G_TYPE_STRING, setting_name,
 	                                      G_TYPE_INVALID);
 	g_assert (call->call);
-	priv->calls = g_slist_append (priv->calls, call);
+}
+
+/**
+ * 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.
+ *
+ * Since: 0.9.10
+ **/
+gboolean
+nm_remote_connection_get_unsaved (NMRemoteConnection *connection)
+{
+	g_return_val_if_fail (NM_IS_REMOTE_CONNECTION (connection), FALSE);
+
+	_nm_remote_connection_ensure_inited (connection);
+	return NM_REMOTE_CONNECTION_GET_PRIVATE (connection)->unsaved;
 }
 
 /****************************************************************/
@@ -328,9 +516,11 @@ updated_cb (DBusGProxy *proxy, gpointer user_data)
 	NMRemoteConnectionPrivate *priv = NM_REMOTE_CONNECTION_GET_PRIVATE (self);
 
 	/* The connection got updated; request the replacement settings */
-	dbus_g_proxy_begin_call (priv->proxy, "GetSettings",
-	                         updated_get_settings_cb, self, NULL,
-	                         G_TYPE_INVALID);
+	if (!priv->proxy_is_destroyed) {
+		dbus_g_proxy_begin_call (priv->proxy, "GetSettings",
+		                         updated_get_settings_cb, self, NULL,
+		                         G_TYPE_INVALID);
+	}
 }
 
 static void
@@ -339,6 +529,25 @@ removed_cb (DBusGProxy *proxy, gpointer user_data)
 	g_signal_emit (G_OBJECT (user_data), signals[REMOVED], 0);
 }
 
+static void
+properties_changed_cb (DBusGProxy *proxy,
+                       GHashTable *properties,
+                       gpointer user_data)
+{
+	NMRemoteConnection *self = NM_REMOTE_CONNECTION (user_data);
+	GHashTableIter iter;
+	const char *key;
+	GValue *value;
+
+	g_hash_table_iter_init (&iter, properties);
+	while (g_hash_table_iter_next (&iter, (gpointer) &key, (gpointer) &value)) {
+		if (!strcmp (key, "Unsaved")) {
+			NM_REMOTE_CONNECTION_GET_PRIVATE (self)->unsaved = g_value_get_boolean (value);
+			g_object_notify (G_OBJECT (self), NM_REMOTE_CONNECTION_UNSAVED);
+		}
+	}
+}
+
 /****************************************************************/
 
 /**
@@ -366,16 +575,16 @@ nm_remote_connection_new (DBusGConnection *bus,
 static void
 constructed (GObject *object)
 {
-	NMRemoteConnectionPrivate *priv;
+	NMRemoteConnectionPrivate *priv = NM_REMOTE_CONNECTION_GET_PRIVATE (object);
+
+	G_OBJECT_CLASS (nm_remote_connection_parent_class)->constructed (object);
 
-	priv = NM_REMOTE_CONNECTION_GET_PRIVATE (object);
 	g_assert (priv->bus);
 	g_assert (nm_connection_get_path (NM_CONNECTION (object)));
 
-	priv->proxy = dbus_g_proxy_new_for_name (priv->bus,
-	                                         NM_DBUS_SERVICE,
-	                                         nm_connection_get_path (NM_CONNECTION (object)),
-	                                         NM_DBUS_IFACE_SETTINGS_CONNECTION);
+	priv->proxy = _nm_dbus_new_proxy_for_connection (priv->bus,
+	                                                 nm_connection_get_path (NM_CONNECTION (object)),
+	                                                 NM_DBUS_IFACE_SETTINGS_CONNECTION);
 	g_assert (priv->proxy);
 	dbus_g_proxy_set_default_timeout (priv->proxy, G_MAXINT);
 
@@ -384,23 +593,54 @@ constructed (GObject *object)
 
 	dbus_g_proxy_add_signal (priv->proxy, "Removed", G_TYPE_INVALID);
 	dbus_g_proxy_connect_signal (priv->proxy, "Removed", G_CALLBACK (removed_cb), object, NULL);
+
+	g_signal_connect (priv->proxy, "destroy", G_CALLBACK (proxy_destroy_cb), object);
+
+	/* 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);
+
+	priv->props_proxy = _nm_dbus_new_proxy_for_connection (priv->bus,
+	                                                       nm_connection_get_path (NM_CONNECTION (object)),
+	                                                       DBUS_INTERFACE_PROPERTIES);
+	g_assert (priv->props_proxy);
 }
 
 static gboolean
 init_sync (GInitable *initable, GCancellable *cancellable, GError **error)
 {
 	NMRemoteConnectionPrivate *priv = NM_REMOTE_CONNECTION_GET_PRIVATE (initable);
-	GHashTable *settings;
+	GHashTable *hash;
 
 	if (!dbus_g_proxy_call (priv->proxy, "GetSettings", error,
 	                        G_TYPE_INVALID,
-	                        DBUS_TYPE_G_MAP_OF_MAP_OF_VARIANT, &settings,
+	                        DBUS_TYPE_G_MAP_OF_MAP_OF_VARIANT, &hash,
 	                        G_TYPE_INVALID))
 		return FALSE;
-
 	priv->visible = TRUE;
-	replace_settings (NM_REMOTE_CONNECTION (initable), settings);
-	g_hash_table_destroy (settings);
+	replace_settings (NM_REMOTE_CONNECTION (initable), hash);
+	g_hash_table_destroy (hash);
+
+	/* Get properties */
+	hash = NULL;
+	if (!dbus_g_proxy_call (priv->props_proxy, "GetAll", error,
+	                        G_TYPE_STRING, NM_DBUS_IFACE_SETTINGS_CONNECTION,
+	                        G_TYPE_INVALID,
+	                        DBUS_TYPE_G_MAP_OF_VARIANT, &hash,
+	                        G_TYPE_INVALID))
+		return FALSE;
+	properties_changed_cb (priv->props_proxy, hash, NM_REMOTE_CONNECTION (initable));
+	g_hash_table_destroy (hash);
+
 	return TRUE;
 }
 
@@ -410,6 +650,38 @@ typedef struct {
 } NMRemoteConnectionInitData;
 
 static void
+init_async_complete (NMRemoteConnectionInitData *init_data, GError *error)
+{
+	if (error)
+		g_simple_async_result_take_error (init_data->result, error);
+	else {
+		g_simple_async_result_set_op_res_gboolean (init_data->result, TRUE);
+		NM_REMOTE_CONNECTION_GET_PRIVATE (init_data->connection)->inited = TRUE;
+	}
+
+	g_simple_async_result_complete (init_data->result);
+	g_object_unref (init_data->result);
+	g_slice_free (NMRemoteConnectionInitData, init_data);
+}
+
+static void
+init_async_got_properties (DBusGProxy *proxy, DBusGProxyCall *call, gpointer user_data)
+{
+	NMRemoteConnectionInitData *init_data = user_data;
+	NMRemoteConnectionPrivate *priv = NM_REMOTE_CONNECTION_GET_PRIVATE (init_data->connection);
+	GHashTable *props;
+	GError *error = NULL;
+
+	if (dbus_g_proxy_end_call (proxy, call, &error,
+	                           DBUS_TYPE_G_MAP_OF_VARIANT, &props,
+	                           G_TYPE_INVALID)) {
+		properties_changed_cb (priv->props_proxy, props, init_data->connection);
+		g_hash_table_destroy (props);
+	}
+	init_async_complete (init_data, error);
+}
+
+static void
 init_get_settings_cb (DBusGProxy *proxy,
                       DBusGProxyCall *call,
                       gpointer user_data)
@@ -423,17 +695,19 @@ init_get_settings_cb (DBusGProxy *proxy,
 	                       DBUS_TYPE_G_MAP_OF_MAP_OF_VARIANT, &settings,
 	                       G_TYPE_INVALID);
 	if (error) {
-		g_simple_async_result_take_error (init_data->result, error);
-	} else {
-		priv->visible = TRUE;
-		replace_settings (init_data->connection, settings);
-		g_hash_table_destroy (settings);
-		g_simple_async_result_set_op_res_gboolean (init_data->result, TRUE);
+		init_async_complete (init_data, error);
+		return;
 	}
 
-	g_simple_async_result_complete (init_data->result);
-	g_object_unref (init_data->result);
-	g_slice_free (NMRemoteConnectionInitData, init_data);
+	priv->visible = TRUE;
+	replace_settings (init_data->connection, settings);
+	g_hash_table_destroy (settings);
+
+	/* Grab properties */
+	dbus_g_proxy_begin_call (priv->props_proxy, "GetAll",
+	                         init_async_got_properties, init_data, NULL,
+	                         G_TYPE_STRING, NM_DBUS_IFACE_SETTINGS_CONNECTION,
+	                         G_TYPE_INVALID);
 }
 
 static void
@@ -453,7 +727,6 @@ init_async (GAsyncInitable *initable, int io_priority,
 	dbus_g_proxy_begin_call (priv->proxy, "GetSettings",
 	                         init_get_settings_cb, init_data, NULL,
 	                         G_TYPE_INVALID);
-
 }
 
 static gboolean
@@ -510,6 +783,22 @@ constructor (GType type, guint n_construct_properties,
 }
 
 static void
+get_property (GObject *object, guint prop_id,
+              GValue *value, GParamSpec *pspec)
+{
+	_nm_remote_connection_ensure_inited (NM_REMOTE_CONNECTION (object));
+
+	switch (prop_id) {
+	case PROP_UNSAVED:
+		g_value_set_boolean (value, NM_REMOTE_CONNECTION_GET_PRIVATE (object)->unsaved);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
 set_property (GObject *object, guint prop_id,
               const GValue *value, GParamSpec *pspec)
 {
@@ -519,8 +808,15 @@ set_property (GObject *object, guint prop_id,
 	case PROP_BUS:
 	case PROP_DBUS_CONNECTION:
 		/* Construct only */
-		if (g_value_get_boxed (value))
-			priv->bus = dbus_g_connection_ref ((DBusGConnection *) g_value_get_boxed (value));
+		/* priv->bus is set from either of two properties so that it (a) remains
+		 * backwards compatible with the previous "bus" property, and that (b)
+		 * it can be created just like an NMObject using the "dbus-connection",
+		 * even though it's not a subclass of NMObject.  So don't overwrite the
+		 * a valid value that the other property set with NULL, if one of the
+		 * properties isn't specified at construction time.
+		 */
+		if (!priv->bus)
+			priv->bus = g_value_dup_boxed (value);
 		break;
 	case PROP_DBUS_PATH:
 		/* Don't need to do anything; see constructor(). */
@@ -535,12 +831,15 @@ static void
 dispose (GObject *object)
 {
 	NMRemoteConnection *self = NM_REMOTE_CONNECTION (object);
-	NMRemoteConnectionPrivate *priv = NM_REMOTE_CONNECTION_GET_PRIVATE (object);
+	NMRemoteConnectionPrivate *priv = NM_REMOTE_CONNECTION_GET_PRIVATE (self);
 
-	while (g_slist_length (priv->calls))
-		remote_call_complete (self, priv->calls->data);
+	proxy_set_destroyed (self);
 
-	g_clear_object (&priv->proxy);
+	if (priv->proxy) {
+		g_signal_handlers_disconnect_by_func (priv->proxy, proxy_destroy_cb, object);
+		g_clear_object (&priv->proxy);
+	}
+	g_clear_object (&priv->props_proxy);
 
 	if (priv->bus) {
 		dbus_g_connection_unref (priv->bus);
@@ -559,6 +858,7 @@ nm_remote_connection_class_init (NMRemoteConnectionClass *remote_class)
 
 	/* virtual methods */
 	object_class->constructor = constructor;
+	object_class->get_property = get_property;
 	object_class->set_property = set_property;
 	object_class->dispose = dispose;
 	object_class->constructed = constructed;
@@ -588,6 +888,21 @@ nm_remote_connection_class_init (NMRemoteConnectionClass *remote_class)
 		                      NULL,
 		                      G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
 
+	/**
+	 * 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.
+	 *
+	 * Since: 0.9.10
+	 **/
+	g_object_class_install_property (object_class, PROP_UNSAVED,
+		 g_param_spec_boolean (NM_REMOTE_CONNECTION_UNSAVED,
+		                       "Unsaved",
+		                       "Unsaved",
+		                       FALSE,
+		                       G_PARAM_READABLE));
+
 	/* Signals */
 	/**
 	 * NMRemoteConnection::updated: