about summary refs log tree commit diff
path: root/libnm-glib/nm-object.c
diff options
context:
space:
mode:
Diffstat (limited to 'libnm-glib/nm-object.c')
-rw-r--r--libnm-glib/nm-object.c1096
1 files changed, 880 insertions, 216 deletions
diff --git a/libnm-glib/nm-object.c b/libnm-glib/nm-object.c
index 9a8a60f7..fc9d7f86 100644
--- a/libnm-glib/nm-object.c
+++ b/libnm-glib/nm-object.c
@@ -22,6 +22,7 @@
  */
 
 #include <string.h>
+#include <gio/gio.h>
 #include <nm-utils.h>
 #include "NetworkManager.h"
 #include "nm-object.h"
@@ -29,28 +30,59 @@
 #include "nm-object-private.h"
 #include "nm-dbus-glib-types.h"
 #include "nm-glib-compat.h"
+#include "nm-types.h"
 
 #define DEBUG 0
 
-G_DEFINE_ABSTRACT_TYPE (NMObject, nm_object, G_TYPE_OBJECT)
+static void nm_object_initable_iface_init (GInitableIface *iface);
+static void nm_object_async_initable_iface_init (GAsyncInitableIface *iface);
+
+G_DEFINE_ABSTRACT_TYPE_WITH_CODE (NMObject, nm_object, G_TYPE_OBJECT,
+                                  G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, nm_object_initable_iface_init);
+                                  G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, nm_object_async_initable_iface_init);
+                                  )
 
 #define NM_OBJECT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_OBJECT, NMObjectPrivate))
 
+static GHashTable *type_funcs, *type_async_funcs;
+
 typedef struct {
-	PropChangedMarshalFunc func;
+	PropertyMarshalFunc func;
+	GType object_type;
+
 	gpointer field;
-} PropChangedInfo;
+} PropertyInfo;
+
+static void reload_complete (NMObject *object);
+
+typedef struct {
+	PropertyInfo pi;
+
+	NMObject *self;
+	DBusGProxy *proxy;
+
+	char *get_method;
+	NMPseudoPropertyChangedFunc added_func;
+	NMPseudoPropertyChangedFunc removed_func;
+} PseudoPropertyInfo;
 
 typedef struct {
 	DBusGConnection *connection;
 	char *path;
 	DBusGProxy *properties_proxy;
-	GSList *pcs;
+	GSList *property_interfaces;
+	GSList *property_tables;
+	GHashTable *pseudo_properties;
 	NMObject *parent;
+	gboolean suppress_property_updates;
 
 	GSList *notify_props;
 	guint32 notify_id;
-	gboolean disposed;
+	gboolean inited, disposed;
+
+	GSList *reload_results;
+	guint reload_remaining;
+	GError *reload_error;
 } NMObjectPrivate;
 
 enum {
@@ -77,8 +109,6 @@ constructor (GType type,
 	object = G_OBJECT_CLASS (nm_object_parent_class)->constructor (type,
 																   n_construct_params,
 																   construct_params);
-	if (!object)
-		return NULL;
 
 	priv = NM_OBJECT_GET_PRIVATE (object);
 
@@ -88,17 +118,78 @@ constructor (GType type,
 		return NULL;
 	}
 
-	priv->properties_proxy = dbus_g_proxy_new_for_name (priv->connection,
-														NM_DBUS_SERVICE,
-														priv->path,
-														"org.freedesktop.DBus.Properties");
-
 	_nm_object_cache_add (NM_OBJECT (object));
 
 	return object;
 }
 
 static void
+constructed (GObject *object)
+{
+	NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (object);
+
+	if (G_OBJECT_CLASS (nm_object_parent_class)->constructed)
+		G_OBJECT_CLASS (nm_object_parent_class)->constructed (object);
+
+	priv->properties_proxy = dbus_g_proxy_new_for_name (priv->connection,
+	                                                    NM_DBUS_SERVICE,
+	                                                    priv->path,
+	                                                    "org.freedesktop.DBus.Properties");
+}
+
+static gboolean
+init_sync (GInitable *initable, GCancellable *cancellable, GError **error)
+{
+	NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (initable);
+
+	priv->inited = TRUE;
+	return _nm_object_reload_properties (NM_OBJECT (initable), error);
+}
+
+static void
+init_async_got_properties (GObject *object, GAsyncResult *result, gpointer user_data)
+{
+	NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (object);
+	GSimpleAsyncResult *simple = user_data;
+	GError *error = NULL;
+
+	priv->inited = TRUE;
+	if (_nm_object_reload_properties_finish (NM_OBJECT (object), result, &error))
+		g_simple_async_result_set_op_res_gboolean (simple, TRUE);
+	else
+		g_simple_async_result_take_error (simple, error);
+
+	g_simple_async_result_complete (simple);
+	g_object_unref (simple);
+}
+
+static void
+init_async (GAsyncInitable *initable, int io_priority,
+            GCancellable *cancellable, GAsyncReadyCallback callback,
+            gpointer user_data)
+{
+	GSimpleAsyncResult *simple;
+
+	simple = g_simple_async_result_new (G_OBJECT (initable), callback, user_data, init_async);
+	_nm_object_reload_properties_async (NM_OBJECT (initable), init_async_got_properties, simple);
+}
+
+static gboolean
+init_finish (GAsyncInitable *initable, GAsyncResult *result, GError **error)
+{
+	GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
+
+	/* This is needed for now because of bug 667375; it can go away
+	 * when we depend on glib >= 2.38
+	 */
+
+	if (g_simple_async_result_propagate_error (simple, error))
+		return FALSE;
+	else
+		return g_simple_async_result_get_op_res_gboolean (simple);
+}
+
+static void
 dispose (GObject *object)
 {
 	NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (object);
@@ -118,6 +209,9 @@ dispose (GObject *object)
 	g_slist_foreach (priv->notify_props, (GFunc) g_free, NULL);
 	g_slist_free (priv->notify_props);
 
+	g_slist_foreach (priv->property_interfaces, (GFunc) g_free, NULL);
+	g_slist_free (priv->property_interfaces);
+
 	g_object_unref (priv->properties_proxy);
 	dbus_g_connection_unref (priv->connection);
 
@@ -129,10 +223,13 @@ finalize (GObject *object)
 {
 	NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (object);
 
-	g_slist_foreach (priv->pcs, (GFunc) g_hash_table_destroy, NULL);
-	g_slist_free (priv->pcs);
+	g_slist_foreach (priv->property_tables, (GFunc) g_hash_table_destroy, NULL);
+	g_slist_free (priv->property_tables);
 	g_free (priv->path);
 
+	if (priv->pseudo_properties)
+		g_hash_table_destroy (priv->pseudo_properties);
+
 	G_OBJECT_CLASS (nm_object_parent_class)->finalize (object);
 }
 
@@ -187,8 +284,14 @@ nm_object_class_init (NMObjectClass *nm_object_class)
 
 	g_type_class_add_private (nm_object_class, sizeof (NMObjectPrivate));
 
+	if (!type_funcs) {
+		type_funcs = g_hash_table_new (NULL, NULL);
+		type_async_funcs = g_hash_table_new (NULL, NULL);
+	}
+
 	/* virtual methods */
 	object_class->constructor = constructor;
+	object_class->constructed = constructed;
 	object_class->set_property = set_property;
 	object_class->get_property = get_property;
 	object_class->dispose = dispose;
@@ -223,6 +326,19 @@ nm_object_class_init (NMObjectClass *nm_object_class)
 							  G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 }
 
+static void
+nm_object_initable_iface_init (GInitableIface *iface)
+{
+	iface->init = init_sync;
+}
+
+static void
+nm_object_async_initable_iface_init (GAsyncInitableIface *iface)
+{
+	iface->init_async = init_async;
+	iface->init_finish = init_finish;
+}
+
 /**
  * nm_object_get_connection:
  * @object: a #NMObject
@@ -306,6 +422,142 @@ _nm_object_queue_notify (NMObject *object, const char *property)
 		priv->notify_props = g_slist_prepend (priv->notify_props, g_strdup (property));
 }
 
+void
+_nm_object_register_type_func (GType base_type, NMObjectTypeFunc type_func,
+                               NMObjectTypeAsyncFunc type_async_func)
+{
+	g_hash_table_insert (type_funcs,
+	                     GSIZE_TO_POINTER (base_type),
+	                     type_func);
+	g_hash_table_insert (type_async_funcs,
+	                     GSIZE_TO_POINTER (base_type),
+	                     type_async_func);
+}
+
+static GObject *
+_nm_object_create (GType type, DBusGConnection *connection, const char *path)
+{
+	NMObjectTypeFunc type_func;
+	GObject *object;
+	GError *error = NULL;
+
+	type_func = g_hash_table_lookup (type_funcs, GSIZE_TO_POINTER (type));
+	if (type_func)
+		type = type_func (connection, path);
+
+	if (type == G_TYPE_INVALID) {
+		g_warning ("Could not create object for %s: unknown object type", path);
+		return NULL;
+	}
+
+	object = g_object_new (type,
+	                       NM_OBJECT_DBUS_CONNECTION, connection,
+	                       NM_OBJECT_DBUS_PATH, path,
+	                       NULL);
+	if (!g_initable_init (G_INITABLE (object), NULL, &error)) {
+		g_object_unref (object);
+		object = NULL;
+		g_warning ("Could not create object for %s: %s", path, error->message);
+		g_error_free (error);
+	}
+
+	return object;
+}
+
+typedef void (*NMObjectCreateCallbackFunc) (GObject *, gpointer);
+typedef struct {
+	DBusGConnection *connection;
+	char *path;
+	NMObjectCreateCallbackFunc callback;
+	gpointer user_data;
+} NMObjectTypeAsyncData;
+
+static void
+create_async_complete (GObject *object, NMObjectTypeAsyncData *async_data)
+{
+	async_data->callback (object, async_data->user_data);
+
+	g_free (async_data->path);
+	g_slice_free (NMObjectTypeAsyncData, async_data);
+}
+
+static void
+async_inited (GObject *source, GAsyncResult *result, gpointer user_data)
+{
+	NMObjectTypeAsyncData *async_data = user_data;
+	GObject *object = G_OBJECT (source);
+	GError *error = NULL;
+
+	if (!g_async_initable_init_finish (G_ASYNC_INITABLE (object), result, &error)) {
+		g_warning ("Could not create object for %s: %s",
+		           nm_object_get_path (NM_OBJECT (object)), error->message);
+		g_error_free (error);
+		g_object_unref (object);
+		object = NULL;
+	}
+
+	create_async_complete (object, async_data);
+}
+
+static void
+async_got_type (GType type, gpointer user_data)
+{
+	NMObjectTypeAsyncData *async_data = user_data;
+	GObject *object;
+
+	/* Ensure we don't have the object already; we may get multiple type
+	 * requests for the same object if there are multiple properties on
+	 * other objects that refer to the object at this path.  One of those
+	 * other requests may have already completed.
+	 */
+	object = (GObject *) _nm_object_cache_get (async_data->path);
+	if (object) {
+		create_async_complete (object, async_data);
+		return;
+	}
+
+	if (type == G_TYPE_INVALID) {
+		/* Don't know how to create this object */
+		create_async_complete (NULL, async_data);
+		return;
+	}
+
+	object = g_object_new (type,
+	                       NM_OBJECT_DBUS_CONNECTION, async_data->connection,
+	                       NM_OBJECT_DBUS_PATH, async_data->path,
+	                       NULL);
+	g_warn_if_fail (object != NULL);
+	g_async_initable_init_async (G_ASYNC_INITABLE (object), G_PRIORITY_DEFAULT,
+	                             NULL, async_inited, async_data);
+}
+
+static void
+_nm_object_create_async (GType type, DBusGConnection *connection, const char *path,
+                         NMObjectCreateCallbackFunc callback, gpointer user_data)
+{
+	NMObjectTypeAsyncFunc type_async_func;
+	NMObjectTypeFunc type_func;
+	NMObjectTypeAsyncData *async_data;
+
+	async_data = g_slice_new (NMObjectTypeAsyncData);
+	async_data->connection = connection;
+	async_data->path = g_strdup (path);
+	async_data->callback = callback;
+	async_data->user_data = user_data;
+
+	type_async_func = g_hash_table_lookup (type_async_funcs, GSIZE_TO_POINTER (type));
+	if (type_async_func) {
+		type_async_func (connection, path, async_got_type, async_data);
+		return;
+	}
+
+	type_func = g_hash_table_lookup (type_funcs, GSIZE_TO_POINTER (type));
+	if (type_func)
+		type = type_func (connection, path);
+
+	async_got_type (type, async_data);
+}
+
 /* Stolen from dbus-glib */
 static char*
 wincaps_to_dash (const char *caps)
@@ -328,19 +580,213 @@ wincaps_to_dash (const char *caps)
 	return g_string_free (str, FALSE);
 }
 
+/* Adds object to array if it's not already there */
+static void
+add_to_object_array_unique (GPtrArray *array, GObject *obj)
+{
+	guint i;
+
+	g_return_if_fail (array != NULL);
+
+	if (obj != NULL) {
+		for (i = 0; i < array->len; i++) {
+			if (g_ptr_array_index (array, i) == obj)
+				return;
+		}
+		g_ptr_array_add (array, obj);
+	}
+}
+
+typedef struct {
+	NMObject *self;
+	PropertyInfo *pi;
+
+	GObject **objects;
+	int length, remaining;
+
+	gboolean array;
+	const char *property_name;
+} ObjectCreatedData;
+
 static void
-handle_property_changed (gpointer key, gpointer data, gpointer user_data)
+object_property_complete (ObjectCreatedData *odata)
+{
+	NMObject *self = odata->self;
+	NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (self);
+	PropertyInfo *pi = odata->pi;
+
+	if (odata->array) {
+		GPtrArray **array = pi->field;
+		int i;
+
+		if (*array)
+			g_boxed_free (NM_TYPE_OBJECT_ARRAY, *array);
+		*array = g_ptr_array_sized_new (odata->length);
+		for (i = 0; i < odata->length; i++)
+			add_to_object_array_unique (*array, odata->objects[i]);
+	} else {
+		GObject **obj_p = pi->field;
+
+		if (*obj_p)
+			g_object_unref (*obj_p);
+		*obj_p = odata->objects[0];
+	}
+
+	if (odata->property_name)
+		_nm_object_queue_notify (self, odata->property_name);
+
+	if (priv->reload_results && --priv->reload_remaining == 0)
+		reload_complete (self);
+
+	g_object_unref (self);
+	g_free (odata->objects);
+	g_slice_free (ObjectCreatedData, odata);
+}
+
+static void
+object_created (GObject *obj, gpointer user_data)
+{
+	ObjectCreatedData *odata = user_data;
+
+	/* We assume that on error, the creator_func printed something */
+
+	odata->objects[--odata->remaining] = obj;
+	if (!odata->remaining)
+		object_property_complete (odata);
+}
+
+static gboolean
+handle_object_property (NMObject *self, const char *property_name, GValue *value,
+                        PropertyInfo *pi, gboolean synchronously)
+{
+	NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (self);
+	GObject *obj;
+	const char *path;
+	ObjectCreatedData *odata;
+
+	odata = g_slice_new (ObjectCreatedData);
+	odata->self = g_object_ref (self);
+	odata->pi = pi;
+	odata->objects = g_new (GObject *, 1);
+	odata->length = odata->remaining = 1;
+	odata->array = FALSE;
+	odata->property_name = property_name;
+
+	if (priv->reload_results)
+		priv->reload_remaining++;
+
+	path = g_value_get_boxed (value);
+	if (!strcmp (path, "/")) {
+		object_created (NULL, odata);
+		return TRUE;
+	}
+
+	obj = G_OBJECT (_nm_object_cache_get (path));
+	if (obj) {
+		object_created (obj, odata);
+		return TRUE;
+	} else if (synchronously) {
+		obj = _nm_object_create (pi->object_type, priv->connection, path);
+		object_created (obj, odata);
+		return obj != NULL;
+	} else {
+		_nm_object_create_async (pi->object_type, priv->connection, path,
+		                         object_created, odata);
+		/* Assume success */
+		return TRUE;
+	}
+}
+
+static gboolean
+handle_object_array_property (NMObject *self, const char *property_name, GValue *value,
+                              PropertyInfo *pi, gboolean synchronously)
+{
+	NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (self);
+	GObject *obj;
+	GPtrArray *paths;
+	GPtrArray **array = pi->field;
+	const char *path;
+	ObjectCreatedData *odata;
+	int i;
+
+	paths = g_value_get_boxed (value);
+
+	odata = g_slice_new (ObjectCreatedData);
+	odata->self = g_object_ref (self);
+	odata->pi = pi;
+	odata->objects = g_new0 (GObject *, paths->len);
+	odata->length = odata->remaining = paths->len;
+	odata->array = TRUE;
+	odata->property_name = property_name;
+
+	if (priv->reload_results)
+		priv->reload_remaining++;
+
+	if (paths->len == 0) {
+		object_property_complete (odata);
+		return TRUE;
+	}
+
+	for (i = 0; i < paths->len; i++) {
+		path = paths->pdata[i];
+		if (!strcmp (path, "/")) {
+			/* FIXME: can't happen? */
+			continue;
+		}
+
+		obj = G_OBJECT (_nm_object_cache_get (path));
+		if (obj) {
+			object_created (obj, odata);
+		} else if (synchronously) {
+			obj = _nm_object_create (pi->object_type, priv->connection, path);
+			object_created (obj, odata);
+		} else {
+			_nm_object_create_async (pi->object_type, priv->connection, path,
+			                         object_created, odata);
+		}
+	}
+
+	if (!synchronously) {
+		/* Assume success */
+		return TRUE;
+	}
+
+	return *array && ((*array)->len == paths->len);
+}
+
+static void
+handle_property_changed (NMObject *self, const char *dbus_name, GValue *value, gboolean synchronously)
 {
-	NMObject *self = NM_OBJECT (user_data);
 	NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (self);
 	char *prop_name;
-	PropChangedInfo *pci;
+	PropertyInfo *pi;
 	GParamSpec *pspec;
 	gboolean success = FALSE, found = FALSE;
 	GSList *iter;
-	GValue *value = data;
 
-	prop_name = wincaps_to_dash ((char *) key);
+	prop_name = wincaps_to_dash (dbus_name);
+
+	/* Iterate through the object and its parents to find the property */
+	for (iter = priv->property_tables; iter; iter = g_slist_next (iter)) {
+		pi = g_hash_table_lookup ((GHashTable *) iter->data, prop_name);
+		if (pi) {
+			if (!pi->field) {
+				/* We know about this property but aren't tracking changes on it. */
+				goto out;
+			}
+
+			found = TRUE;
+			break;
+		}
+	}
+
+	if (!found) {
+#if DEBUG
+		g_warning ("Property '%s' unhandled.", prop_name);
+#endif
+		goto out;
+	}
+
 	pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (G_OBJECT (self)), prop_name);
 	if (!pspec) {
 		g_warning ("%s: property '%s' changed but wasn't defined by object type %s.",
@@ -350,29 +796,33 @@ handle_property_changed (gpointer key, gpointer data, gpointer user_data)
 		goto out;
 	}
 
-	/* Iterate through the object and its parents to find the property */
-	for (iter = priv->pcs; iter; iter = g_slist_next (iter)) {
-		pci = g_hash_table_lookup ((GHashTable *) iter->data, prop_name);
-		if (pci) {
-			found = TRUE;
-
-			/* Handle NULL object paths */
-			if (G_VALUE_HOLDS (value, DBUS_TYPE_G_OBJECT_PATH)) {
-				if (g_strcmp0 (g_value_get_boxed (value), "/") == 0)
-					value = NULL;
-			}
-
-			success = (*(pci->func)) (self, pspec, value, pci->field);
-			if (success)
-				break;
-		}
+#if DEBUG
+	{
+		char *s;
+		s = g_strdup_value_contents (value);
+		g_message ("PC: %p (%s) prop (%s) '%s' value (%s) %s",
+		           self, G_OBJECT_TYPE_NAME (self),
+		           g_type_name (pspec->value_type), prop_name,
+		           G_VALUE_TYPE_NAME (value), s);
+		g_free (s);
 	}
-
-	if (!found) {
+#endif
+	if (pi->object_type) {
 #if DEBUG
-		g_warning ("Property '%s' unhandled.", prop_name);
+		g_message ("   Value is object type %s", g_type_name (pi->object_type));
 #endif
-	} else if (!success) {
+		if (G_VALUE_HOLDS (value, DBUS_TYPE_G_OBJECT_PATH))
+			success = handle_object_property (self, pspec->name, value, pi, synchronously);
+		else if (G_VALUE_HOLDS (value, DBUS_TYPE_G_ARRAY_OF_OBJECT_PATH))
+			success = handle_object_array_property (self, pspec->name, value, pi, synchronously);
+		else {
+			g_warn_if_reached ();
+			goto out;
+		}
+	} else
+		success = (*(pi->func)) (self, pspec, value, pi->field);
+
+	if (!success) {
 		g_warning ("%s: failed to update property '%s' of object type %s.",
 		           __func__,
 		           prop_name,
@@ -383,10 +833,19 @@ out:
 	g_free (prop_name);
 }
 
-void
-_nm_object_process_properties_changed (NMObject *self, GHashTable *properties)
+static void
+process_properties_changed (NMObject *self, GHashTable *properties, gboolean synchronously)
 {
-	g_hash_table_foreach (properties, handle_property_changed, self);
+	NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (self);
+	GHashTableIter iter;
+	gpointer name, value;
+
+	if (priv->suppress_property_updates)
+		return;
+
+	g_hash_table_iter_init (&iter, properties);
+	while (g_hash_table_iter_next (&iter, &name, &value))
+		handle_property_changed (self, name, value, synchronously);
 }
 
 static void
@@ -394,49 +853,7 @@ properties_changed_proxy (DBusGProxy *proxy,
                           GHashTable *properties,
                           gpointer user_data)
 {
-	_nm_object_process_properties_changed (NM_OBJECT (user_data), properties);
-}
-
-void
-_nm_object_handle_properties_changed (NMObject *object,
-                                     DBusGProxy *proxy,
-                                     const NMPropertiesChangedInfo *info)
-{
-	NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (object);
-	NMPropertiesChangedInfo *tmp;
-	GHashTable *instance;
-
-	g_return_if_fail (NM_IS_OBJECT (object));
-	g_return_if_fail (proxy != NULL);
-	g_return_if_fail (info != NULL);
-
-	dbus_g_proxy_add_signal (proxy, "PropertiesChanged", DBUS_TYPE_G_MAP_OF_VARIANT, G_TYPE_INVALID);
-	dbus_g_proxy_connect_signal (proxy,
-						    "PropertiesChanged",
-						    G_CALLBACK (properties_changed_proxy),
-						    object,
-						    NULL);
-
-	instance = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
-	priv->pcs = g_slist_prepend (priv->pcs, instance);
-
-	for (tmp = (NMPropertiesChangedInfo *) info; tmp->name; tmp++) {
-		PropChangedInfo *pci;
-
-		if (!tmp->name || !tmp->func || !tmp->field) {
-			g_warning ("%s: missing field in NMPropertiesChangedInfo", __func__);
-			continue;
-		}
-
-		pci = g_malloc0 (sizeof (PropChangedInfo));
-		if (!pci) {
-			g_warning ("%s: not enough memory for PropChangedInfo", __func__);
-			continue;
-		}
-		pci->func = tmp->func;
-		pci->field = tmp->field;
-		g_hash_table_insert (instance, g_strdup (tmp->name), pci);
-	}
+	process_properties_changed (NM_OBJECT (user_data), properties, FALSE);
 }
 
 #define HANDLE_TYPE(ucase, lcase, getter) \
@@ -449,11 +866,11 @@ _nm_object_handle_properties_changed (NMObject *object,
 			goto done; \
 		}
 
-gboolean
-_nm_object_demarshal_generic (NMObject *object,
-                             GParamSpec *pspec,
-                             GValue *value,
-                             gpointer field)
+static gboolean
+demarshal_generic (NMObject *object,
+                   GParamSpec *pspec,
+                   GValue *value,
+                   gpointer field)
 {
 	gboolean success = TRUE;
 
@@ -503,34 +920,132 @@ done:
 	return success;
 }
 
+void
+_nm_object_register_properties (NMObject *object,
+                                DBusGProxy *proxy,
+                                const NMPropertiesInfo *info)
+{
+	NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (object);
+	NMPropertiesInfo *tmp;
+	GHashTable *instance;
+
+	g_return_if_fail (NM_IS_OBJECT (object));
+	g_return_if_fail (proxy != NULL);
+	g_return_if_fail (info != NULL);
+
+	priv->property_interfaces = g_slist_prepend (priv->property_interfaces,
+	                                             g_strdup (dbus_g_proxy_get_interface (proxy)));
+
+	dbus_g_proxy_add_signal (proxy, "PropertiesChanged", DBUS_TYPE_G_MAP_OF_VARIANT, G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal (proxy,
+						    "PropertiesChanged",
+						    G_CALLBACK (properties_changed_proxy),
+						    object,
+						    NULL);
+
+	instance = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+	priv->property_tables = g_slist_prepend (priv->property_tables, instance);
+
+	for (tmp = (NMPropertiesInfo *) info; tmp->name; tmp++) {
+		PropertyInfo *pi;
+
+		if (!tmp->name || (tmp->func && !tmp->field)) {
+			g_warning ("%s: missing field in NMPropertiesInfo", __func__);
+			continue;
+		}
+
+		pi = g_malloc0 (sizeof (PropertyInfo));
+		pi->func = tmp->func ? tmp->func : demarshal_generic;
+		pi->object_type = tmp->object_type;
+		pi->field = tmp->field;
+		g_hash_table_insert (instance, g_strdup (tmp->name), pi);
+	}
+}
+
 gboolean
-_nm_object_get_property (NMObject *object,
-                         const char *interface,
-                         const char *prop_name,
-                         GValue *value,
-                         GError **error)
+_nm_object_reload_properties (NMObject *object, GError **error)
 {
+	NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (object);
+	GHashTable *props = NULL;
+	GSList *p;
+	GHashTableIter pp;
+	gpointer name, info;
+
+	if (!priv->property_interfaces)
+		return TRUE;
+
+	for (p = priv->property_interfaces; p; p = p->next) {
+		if (!dbus_g_proxy_call (priv->properties_proxy, "GetAll", error,
+		                        G_TYPE_STRING, p->data,
+		                        G_TYPE_INVALID,
+		                        DBUS_TYPE_G_MAP_OF_VARIANT, &props,
+		                        G_TYPE_INVALID))
+			return FALSE;
+
+		process_properties_changed (object, props, TRUE);
+		g_hash_table_destroy (props);
+	}
+
+	if (priv->pseudo_properties) {
+		g_hash_table_iter_init (&pp, priv->pseudo_properties);
+		while (g_hash_table_iter_next (&pp, &name, &info))
+			_nm_object_reload_pseudo_property (object, name);
+	}
+
+	return TRUE;
+}
+
+void
+_nm_object_suppress_property_updates (NMObject *object, gboolean suppress)
+{
+	NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (object);
+
+	priv->suppress_property_updates = suppress;
+}
+
+
+void
+_nm_object_ensure_inited (NMObject *object)
+{
+	NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (object);
+	GError *error = NULL;
+
+	if (!priv->inited) {
+		if (!g_initable_init (G_INITABLE (object), NULL, &error)) {
+			g_warning ("Could not initialize %s %s: %s",
+			           G_OBJECT_TYPE_NAME (object), priv->path,
+			           error->message);
+			g_error_free (error);
+
+			/* Only warn once */
+			priv->inited = TRUE;
+		}
+	}
+}
+
+void
+_nm_object_reload_property (NMObject *object,
+                            const char *interface,
+                            const char *prop_name)
+{
+	GValue value = { 0, };
 	GError *err = NULL;
 
-	g_return_val_if_fail (NM_IS_OBJECT (object), FALSE);
-	g_return_val_if_fail (interface != NULL, FALSE);
-	g_return_val_if_fail (prop_name != NULL, FALSE);
-	g_return_val_if_fail (value != NULL, FALSE);
-	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+	g_return_if_fail (NM_IS_OBJECT (object));
+	g_return_if_fail (interface != NULL);
+	g_return_if_fail (prop_name != NULL);
 
 	if (!dbus_g_proxy_call_with_timeout (NM_OBJECT_GET_PRIVATE (object)->properties_proxy,
 							"Get", 15000, &err,
 							G_TYPE_STRING, interface,
 							G_TYPE_STRING, prop_name,
 							G_TYPE_INVALID,
-							G_TYPE_VALUE, value,
+							G_TYPE_VALUE, &value,
 							G_TYPE_INVALID)) {
 		/* Don't warn about D-Bus no reply/timeout errors; it's mostly noise and
 		 * happens for example when NM quits and the applet is still running.
-		 * And don't warn when 'error' is not NULL, rather propagate 'err' so the caller
-		 * can do something with it. */
-		if (   !error
-		    && !(err->domain == DBUS_GERROR && err->code == DBUS_GERROR_NO_REPLY)) {
+		 */
+		if (!g_error_matches (err, DBUS_GERROR, DBUS_GERROR_NO_REPLY)) {
 			g_warning ("%s: Error getting '%s' for %s: (%d) %s\n",
 			           __func__,
 			           prop_name,
@@ -538,11 +1053,12 @@ _nm_object_get_property (NMObject *object,
 			           err->code,
 			           err->message);
 		}
-		g_propagate_error (error, err);
-		return FALSE;
+		g_clear_error (&err);
+		return;
 	}
 
-	return TRUE;
+	handle_property_changed (object, prop_name, &value, TRUE);
+	g_value_unset (&value);
 }
 
 void
@@ -569,157 +1085,305 @@ _nm_object_set_property (NMObject *object,
 	}
 }
 
-char *
-_nm_object_get_string_property (NMObject *object,
-                                const char *interface,
-                                const char *prop_name,
-                                GError **error)
+static void
+pseudo_property_object_created (GObject *obj, gpointer user_data)
 {
-	char *str = NULL;
-	const char *tmp;
-	GValue value = {0,};
+	PseudoPropertyInfo *ppi = user_data;
 
-	if (_nm_object_get_property (object, interface, prop_name, &value, error)) {
-		if (G_VALUE_HOLDS_STRING (&value))
-			str = g_strdup (g_value_get_string (&value));
-		else if (G_VALUE_HOLDS (&value, DBUS_TYPE_G_OBJECT_PATH)) {
-			tmp = g_value_get_boxed (&value);
-			/* Handle "NULL" object paths */
-			if (g_strcmp0 (tmp, "/") != 0)
-				str = g_strdup (tmp);
-		}
-		g_value_unset (&value);
-	}
+	if (obj) {
+		GPtrArray **list_p = (GPtrArray **)ppi->pi.field;
 
-	return str;
+		if (!*list_p)
+			*list_p = g_ptr_array_new ();
+		add_to_object_array_unique (*list_p, obj);
+		ppi->added_func (ppi->self, NM_OBJECT (obj));
+	}
 }
 
-char *
-_nm_object_get_object_path_property (NMObject *object,
-                                     const char *interface,
-                                     const char *prop_name,
-                                     GError **error)
+static void
+pseudo_property_added (DBusGProxy *proxy, const char *path, gpointer user_data)
 {
-	char *path = NULL;
-	const char *tmp;
-	GValue value = {0,};
+	PseudoPropertyInfo *ppi = user_data;
+	NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (ppi->self);
+	NMObject *obj;
 
-	if (_nm_object_get_property (object, interface, prop_name, &value, error)) {
-		tmp = g_value_get_boxed (&value);
-		if (g_strcmp0 (tmp, "/") != 0)
-			path = g_strdup (tmp);
-		g_value_unset (&value);
-	}
+	if (priv->suppress_property_updates)
+		return;
 
-	return path;
+	obj = _nm_object_cache_get (path);
+	if (obj)
+		pseudo_property_object_created (G_OBJECT (obj), ppi);
+	else {
+		_nm_object_create_async (ppi->pi.object_type, priv->connection, path,
+		                         pseudo_property_object_created, ppi);
+	}
 }
 
-gint32
-_nm_object_get_int_property (NMObject *object,
-                             const char *interface,
-                             const char *prop_name,
-                             GError **error)
+static void
+pseudo_property_removed (DBusGProxy *proxy, const char *path, gpointer user_data)
 {
-	gint32 i = 0;
-	GValue value = {0,};
+	PseudoPropertyInfo *ppi = user_data;
+	NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (ppi->self);
+	GPtrArray *list = *(GPtrArray **)ppi->pi.field;
+	NMObject *obj = NULL;
+	int i;
 
-	if (_nm_object_get_property (object, interface, prop_name, &value, error)) {
-		i = g_value_get_int (&value);
-		g_value_unset (&value);
+	if (!list || priv->suppress_property_updates)
+		return;
+
+	for (i = 0; i < list->len; i++) {
+		obj = list->pdata[i];
+		if (!strcmp (path, nm_object_get_path (obj))) {
+			g_ptr_array_remove_index (list, i);
+			ppi->removed_func (ppi->self, obj);
+			g_object_unref (obj);
+			return;
+		}
 	}
+}
 
-	return i;
+static void
+free_pseudo_property (PseudoPropertyInfo *ppi)
+{
+	g_object_unref (ppi->proxy);
+	g_free (ppi->get_method);
+	g_slice_free (PseudoPropertyInfo, ppi);
 }
 
-guint32
-_nm_object_get_uint_property (NMObject *object,
-                              const char *interface,
-                              const char *prop_name,
-                              GError **error)
+void
+_nm_object_register_pseudo_property (NMObject *object,
+                                     DBusGProxy *proxy,
+                                     const char *name,
+                                     gpointer field,
+                                     GType object_type,
+                                     NMPseudoPropertyChangedFunc added_func,
+                                     NMPseudoPropertyChangedFunc removed_func)
 {
-	guint32 i = 0;
-	GValue value = {0,};
+	NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (object);
+	PseudoPropertyInfo *ppi;
+	int basename_len;
+	char *added_signal, *removed_signal;
 
-	if (_nm_object_get_property (object, interface, prop_name, &value, error)) {
-		i = g_value_get_uint (&value);
-		g_value_unset (&value);
-	}
+	g_return_if_fail (NM_IS_OBJECT (object));
+	g_return_if_fail (proxy != NULL);
 
-	return i;
+	ppi = g_slice_new0 (PseudoPropertyInfo);
+	ppi->pi.field = field;
+	ppi->pi.object_type = object_type;
+	ppi->self = object;
+	ppi->proxy = g_object_ref (proxy);
+	ppi->added_func = added_func;
+	ppi->removed_func = removed_func;
+
+	basename_len = strlen (name);
+	if (basename_len > 4 && !strcmp (name + basename_len - 4, "List"))
+		basename_len -= 4;
+	else if (basename_len > 1 && name[basename_len - 1] == 's')
+		basename_len--;
+	else
+		g_assert_not_reached ();
+
+	ppi->get_method = g_strdup_printf ("Get%s", name);
+	added_signal = g_strdup_printf ("%.*sAdded", basename_len, name);
+	removed_signal = g_strdup_printf ("%.*sRemoved", basename_len, name);
+
+	if (!priv->pseudo_properties) {
+		priv->pseudo_properties = g_hash_table_new_full (g_str_hash, g_str_equal,
+		                                                 g_free, (GDestroyNotify) free_pseudo_property);
+	}
+	g_hash_table_insert (priv->pseudo_properties, g_strdup (name), ppi);
+
+	dbus_g_proxy_add_signal (proxy, added_signal,
+	                         DBUS_TYPE_G_OBJECT_PATH,
+	                         G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal (proxy, added_signal,
+	                             G_CALLBACK (pseudo_property_added),
+	                             ppi, NULL);
+
+	dbus_g_proxy_add_signal (proxy, removed_signal,
+	                         DBUS_TYPE_G_OBJECT_PATH,
+	                         G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal (proxy, removed_signal,
+	                             G_CALLBACK (pseudo_property_removed),
+	                             ppi, NULL);
+
+	g_free (added_signal);
+	g_free (removed_signal);
 }
 
-gboolean
-_nm_object_get_boolean_property (NMObject *object,
-                                 const char *interface,
-                                 const char *prop_name,
-                                 GError **error)
+void
+_nm_object_reload_pseudo_property (NMObject *object,
+                                   const char *name)
 {
-	gboolean b = FALSE;
-	GValue value = {0,};
+	NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (object);
+	PseudoPropertyInfo *ppi;
+	GPtrArray *temp;
+	GError *error = NULL;
+	GValue value = { 0, };
 
-	if (_nm_object_get_property (object, interface, prop_name, &value, error)) {
-		b = g_value_get_boolean (&value);
-		g_value_unset (&value);
+	g_return_if_fail (NM_IS_OBJECT (object));
+	g_return_if_fail (name != NULL);
+
+	ppi = g_hash_table_lookup (priv->pseudo_properties, name);
+	g_return_if_fail (ppi != NULL);
+
+	if (!dbus_g_proxy_call (ppi->proxy, ppi->get_method, &error,
+	                        G_TYPE_INVALID,
+	                        DBUS_TYPE_G_ARRAY_OF_OBJECT_PATH, &temp,
+	                        G_TYPE_INVALID)) {
+		g_warning ("%s: error calling %s: %s", __func__, ppi->get_method, error->message);
+		g_error_free (error);
+		return;
 	}
 
-	return b;
+	g_value_init (&value, DBUS_TYPE_G_ARRAY_OF_OBJECT_PATH);
+	g_value_take_boxed (&value, temp);
+	handle_object_array_property (object, NULL, &value, &ppi->pi, TRUE);
+	g_value_unset (&value);
 }
 
-gint8
-_nm_object_get_byte_property (NMObject *object,
-                              const char *interface,
-                              const char *prop_name,
-                              GError **error)
+static void
+reload_complete (NMObject *object)
 {
-	gint8 b = G_MAXINT8;
-	GValue value = {0,};
+	NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (object);
+	GSimpleAsyncResult *simple;
+	GSList *results, *iter;
+	GError *error;
 
-	if (_nm_object_get_property (object, interface, prop_name, &value, error)) {
-		b = g_value_get_uchar (&value);
-		g_value_unset (&value);
-	}
+	results = priv->reload_results;
+	priv->reload_results = NULL;
+	error = priv->reload_error;
+	priv->reload_error = NULL;
+
+	for (iter = results; iter; iter = iter->next) {
+		simple = results->data;
 
-	return b;
+		if (error)
+			g_simple_async_result_set_from_error (simple, error);
+		else
+			g_simple_async_result_set_op_res_gboolean (simple, TRUE);
+
+		g_simple_async_result_complete (simple);
+		g_object_unref (simple);
+	}
+	g_slist_free (results);
+	g_clear_error (&error);
 }
 
-gdouble
-_nm_object_get_double_property (NMObject *object,
-                                const char *interface,
-                                const char *prop_name,
-                                GError **error)
+static void
+reload_got_properties (DBusGProxy *proxy, DBusGProxyCall *call,
+                       gpointer user_data)
 {
-	gdouble d = G_MAXDOUBLE;
-	GValue value = {0,};
+	NMObject *object = user_data;
+	NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (object);
+	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)) {
+		process_properties_changed (object, props, FALSE);
+		g_hash_table_destroy (props);
+	} else {
+		if (priv->reload_error)
+			g_error_free (error);
+		else
+			priv->reload_error = error;
+	}
 
-	if (_nm_object_get_property (object, interface, prop_name, &value, error)) {
-		d = g_value_get_double (&value);
+	if (--priv->reload_remaining == 0)
+		reload_complete (object);
+}
+
+static void
+reload_got_pseudo_property (DBusGProxy *proxy, DBusGProxyCall *call,
+                            gpointer user_data)
+{
+	PseudoPropertyInfo *ppi = user_data;
+	NMObject *object = ppi->self;
+	NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (object);
+	GPtrArray *temp;
+	GValue value = { 0, };
+	GError *error = NULL;
+
+	if (dbus_g_proxy_end_call (proxy, call, &error,
+	                           DBUS_TYPE_G_ARRAY_OF_OBJECT_PATH, &temp,
+	                           G_TYPE_INVALID)) {
+		g_value_init (&value, DBUS_TYPE_G_ARRAY_OF_OBJECT_PATH);
+		g_value_take_boxed (&value, temp);
+		if (!priv->suppress_property_updates)
+			handle_object_array_property (object, NULL, &value, &ppi->pi, FALSE);
 		g_value_unset (&value);
+	} else {
+		if (priv->reload_error)
+			g_error_free (error);
+		else
+			priv->reload_error = error;
 	}
 
-	return d;
+	if (--priv->reload_remaining == 0)
+		reload_complete (object);
 }
 
-GByteArray *
-_nm_object_get_byte_array_property (NMObject *object,
-                                    const char *interface,
-                                    const char *prop_name,
-                                    GError **error)
+void
+_nm_object_reload_properties_async (NMObject *object, GAsyncReadyCallback callback, gpointer user_data)
 {
-	GByteArray *array = NULL;
-	GValue value = {0,};
+	NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (object);
+	GSimpleAsyncResult *simple;
+	GSList *p;
 
-	if (_nm_object_get_property (object, interface, prop_name, &value, error)) {
-		GArray * tmp = g_value_get_boxed (&value);
-		int i;
-		unsigned char byte;
+	simple = g_simple_async_result_new (G_OBJECT (object), callback,
+	                                    user_data, _nm_object_reload_properties_async);
+
+	if (!priv->property_interfaces && !priv->pseudo_properties) {
+		g_simple_async_result_complete_in_idle (simple);
+		return;
+	}
+
+	priv->reload_results = g_slist_prepend (priv->reload_results, simple);
+
+	/* If there was already a reload happening, we don't need to
+	 * re-read the properties again, we just need to wait for the
+	 * existing reload to finish.
+	 */
+	if (priv->reload_results->next)
+		return;
 
-		array = g_byte_array_sized_new (tmp->len);
-		for (i = 0; i < tmp->len; i++) {
-			byte = g_array_index (tmp, unsigned char, i);
-			g_byte_array_append (array, &byte, 1);
+	for (p = priv->property_interfaces; p; p = p->next) {
+		priv->reload_remaining++;
+		dbus_g_proxy_begin_call (priv->properties_proxy, "GetAll",
+		                         reload_got_properties, object, NULL,
+		                         G_TYPE_STRING, p->data,
+		                         G_TYPE_INVALID);
+	}
+
+	if (priv->pseudo_properties) {
+		GHashTableIter iter;
+		gpointer key, value;
+		PseudoPropertyInfo *ppi;
+
+		g_hash_table_iter_init (&iter, priv->pseudo_properties);
+		while (g_hash_table_iter_next (&iter, &key, &value)) {
+			ppi = value;
+			priv->reload_remaining++;
+			dbus_g_proxy_begin_call (ppi->proxy, ppi->get_method,
+			                         reload_got_pseudo_property, ppi, NULL,
+			                         G_TYPE_INVALID);
 		}
-		g_value_unset (&value);
 	}
+}
+
+gboolean
+_nm_object_reload_properties_finish (NMObject *object, GAsyncResult *result, GError **error)
+{
+	GSimpleAsyncResult *simple;
+
+	g_return_val_if_fail (NM_IS_OBJECT (object), FALSE);
+	g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (object), _nm_object_reload_properties_async), FALSE);
+
+	simple = G_SIMPLE_ASYNC_RESULT (result);
+	if (g_simple_async_result_propagate_error (simple, error))
+		return FALSE;
 
-	return array;
+	return g_simple_async_result_get_op_res_gboolean (simple);
 }