diff --git a/introspection/nm-settings-connection.xml b/introspection/nm-settings-connection.xml index 9089b009a1..d7e5dbf033 100644 --- a/introspection/nm-settings-connection.xml +++ b/introspection/nm-settings-connection.xml @@ -88,6 +88,16 @@ + + + If set, indicates that the in-memory state of the + connection does not match the on-disk state. This flag + will be set when UpdateUnsaved() is called or when any + connection details change, and cleared when the connection + is saved to disk via Save() or from internal operations. + + + diff --git a/libnm-util/nm-connection.c b/libnm-util/nm-connection.c index c95794b878..3101546d90 100644 --- a/libnm-util/nm-connection.c +++ b/libnm-util/nm-connection.c @@ -414,9 +414,12 @@ nm_connection_replace_settings_from_connection (NMConnection *connection, */ g_hash_table_remove_all (NM_CONNECTION_GET_PRIVATE (connection)->settings); - g_hash_table_iter_init (&iter, NM_CONNECTION_GET_PRIVATE (new_connection)->settings); - while (g_hash_table_iter_next (&iter, NULL, (gpointer) &setting)) - nm_connection_add_setting (connection, nm_setting_duplicate (setting)); + if (g_hash_table_size (NM_CONNECTION_GET_PRIVATE (new_connection)->settings)) { + g_hash_table_iter_init (&iter, NM_CONNECTION_GET_PRIVATE (new_connection)->settings); + while (g_hash_table_iter_next (&iter, NULL, (gpointer) &setting)) + nm_connection_add_setting (connection, nm_setting_duplicate (setting)); + } else + g_signal_emit (connection, signals[CHANGED], 0); return nm_connection_verify (connection, error); } diff --git a/src/settings/nm-settings-connection.c b/src/settings/nm-settings-connection.c index 087ba1bcc2..093bc38fa1 100644 --- a/src/settings/nm-settings-connection.c +++ b/src/settings/nm-settings-connection.c @@ -16,7 +16,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * (C) Copyright 2008 Novell, Inc. - * (C) Copyright 2008 - 2012 Red Hat, Inc. + * (C) Copyright 2008 - 2013 Red Hat, Inc. */ #include "config.h" @@ -40,6 +40,7 @@ #include "nm-manager-auth.h" #include "nm-agent-manager.h" #include "NetworkManagerUtils.h" +#include "nm-properties-changed-signal.h" #define SETTINGS_TIMESTAMPS_FILE NMSTATEDIR "/timestamps" #define SETTINGS_SEEN_BSSIDS_FILE NMSTATEDIR "/seen-bssids" @@ -69,6 +70,7 @@ G_DEFINE_TYPE (NMSettingsConnection, nm_settings_connection, NM_TYPE_CONNECTION) enum { PROP_0 = 0, PROP_VISIBLE, + PROP_UNSAVED, }; enum { @@ -86,6 +88,13 @@ typedef struct { NMSessionMonitor *session_monitor; guint session_changed_id; + /* TRUE if the connection has not yet been saved to disk, + * or it it contains changes that have not been saved to disk. + */ + gboolean unsaved; + + guint updated_idle_id; + GSList *pending_auths; /* List of pending authentication requests */ gboolean visible; /* Is this connection is visible by some session? */ GSList *reqs; /* in-progress secrets requests */ @@ -366,12 +375,45 @@ secrets_cleared_cb (NMSettingsConnection *self) priv->agent_secrets = NULL; } +static gboolean +emit_updated (NMSettingsConnection *self) +{ + NM_SETTINGS_CONNECTION_GET_PRIVATE (self)->updated_idle_id = 0; + g_signal_emit (self, signals[UPDATED], 0); + return FALSE; +} + +static void +set_unsaved (NMSettingsConnection *self, gboolean now_unsaved) +{ + NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self); + + if (priv->unsaved != now_unsaved) { + priv->unsaved = now_unsaved; + g_object_notify (G_OBJECT (self), NM_SETTINGS_CONNECTION_UNSAVED); + } +} + +static void +changed_cb (NMSettingsConnection *self, gpointer user_data) +{ + gboolean update_unsaved = !!user_data; + + NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self); + + if (update_unsaved) + set_unsaved (self, TRUE); + if (priv->updated_idle_id == 0) + priv->updated_idle_id = g_idle_add ((GSourceFunc) emit_updated, self); +} + /* Update the settings of this connection to match that of 'new_connection', * taking care to make a private copy of secrets. */ gboolean nm_settings_connection_replace_settings (NMSettingsConnection *self, NMConnection *new_connection, + gboolean update_unsaved, GError **error) { NMSettingsConnectionPrivate *priv; @@ -390,6 +432,11 @@ nm_settings_connection_replace_settings (NMSettingsConnection *self, return TRUE; } + /* Disconnect the changed signal to ensure we don't set Unsaved when + * it's not required. + */ + g_signal_handlers_block_by_func (self, G_CALLBACK (changed_cb), GUINT_TO_POINTER (TRUE)); + if (nm_connection_replace_settings_from_connection (NM_CONNECTION (self), new_connection, error)) { @@ -411,7 +458,15 @@ nm_settings_connection_replace_settings (NMSettingsConnection *self, } nm_settings_connection_recheck_visibility (self); + + /* Manually emit changed signal since we disconnected the handler, but + * only update Unsaved if the caller wanted us to. + */ + changed_cb (self, GUINT_TO_POINTER (update_unsaved)); } + + g_signal_handlers_unblock_by_func (self, G_CALLBACK (changed_cb), GUINT_TO_POINTER (TRUE)); + return success; } @@ -438,7 +493,7 @@ nm_settings_connection_replace_and_commit (NMSettingsConnection *self, g_return_if_fail (NM_IS_SETTINGS_CONNECTION (self)); g_return_if_fail (NM_IS_CONNECTION (new_connection)); - if (nm_settings_connection_replace_settings (self, new_connection, &error)) { + if (nm_settings_connection_replace_settings (self, new_connection, TRUE, &error)) { nm_settings_connection_commit_changes (self, callback ? callback : ignore_cb, user_data); } else { if (callback) @@ -447,6 +502,21 @@ nm_settings_connection_replace_and_commit (NMSettingsConnection *self, } } +static void +commit_changes (NMSettingsConnection *self, + NMSettingsConnectionCommitFunc callback, + gpointer user_data) +{ + /* Subclasses only call this function if the save was successful, so at + * this point the connection is synced to disk and no longer unsaved. + */ + set_unsaved (self, FALSE); + + g_object_ref (self); + callback (self, NULL, user_data); + g_object_unref (self); +} + void nm_settings_connection_commit_changes (NMSettingsConnection *connection, NMSettingsConnectionCommitFunc callback, @@ -489,17 +559,6 @@ nm_settings_connection_delete (NMSettingsConnection *connection, } } -static void -commit_changes (NMSettingsConnection *connection, - NMSettingsConnectionCommitFunc callback, - gpointer user_data) -{ - g_object_ref (connection); - g_signal_emit (connection, signals[UPDATED], 0); - callback (connection, NULL, user_data); - g_object_unref (connection); -} - static void remove_entry_from_db (NMSettingsConnection *connection, const char* db_name) { @@ -1397,6 +1456,14 @@ nm_settings_connection_signal_remove (NMSettingsConnection *self) g_signal_emit_by_name (self, "unregister"); } +gboolean +nm_settings_connection_get_unsaved (NMSettingsConnection *self) +{ + return NM_SETTINGS_CONNECTION_GET_PRIVATE (self)->unsaved; +} + +/**************************************************************/ + /** * nm_settings_connection_get_timestamp: * @connection: the #NMSettingsConnection @@ -1729,7 +1796,8 @@ nm_settings_connection_init (NMSettingsConnection *self) priv->seen_bssids = g_hash_table_new_full (mac_hash, mac_equal, g_free, g_free); - g_signal_connect (self, "secrets-cleared", G_CALLBACK (secrets_cleared_cb), NULL); + g_signal_connect (self, NM_CONNECTION_SECRETS_CLEARED, G_CALLBACK (secrets_cleared_cb), NULL); + g_signal_connect (self, NM_CONNECTION_CHANGED, G_CALLBACK (changed_cb), GUINT_TO_POINTER (TRUE)); } static void @@ -1743,6 +1811,11 @@ dispose (GObject *object) goto out; priv->disposed = TRUE; + if (priv->updated_idle_id) { + g_source_remove (priv->updated_idle_id); + priv->updated_idle_id = 0; + } + if (priv->system_secrets) g_object_unref (priv->system_secrets); if (priv->agent_secrets) @@ -1776,9 +1849,14 @@ static void get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { + NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (object); + switch (prop_id) { case PROP_VISIBLE: - g_value_set_boolean (value, NM_SETTINGS_CONNECTION_GET_PRIVATE (object)->visible); + g_value_set_boolean (value, priv->visible); + break; + case PROP_UNSAVED: + g_value_set_boolean (value, priv->unsaved); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -1818,6 +1896,16 @@ nm_settings_connection_class_init (NMSettingsConnectionClass *class) FALSE, G_PARAM_READABLE)); + g_object_class_install_property + (object_class, PROP_UNSAVED, + g_param_spec_boolean (NM_SETTINGS_CONNECTION_UNSAVED, + "Unsaved", + "TRUE when the connection has not yet been saved " + "to permanent storage (eg disk) or when it " + "has been changed but not yet saved.", + FALSE, + G_PARAM_READABLE)); + /* Signals */ signals[UPDATED] = g_signal_new (NM_SETTINGS_CONNECTION_UPDATED, @@ -1847,6 +1935,7 @@ nm_settings_connection_class_init (NMSettingsConnectionClass *class) g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); - dbus_g_object_type_install_info (G_TYPE_FROM_CLASS (class), - &dbus_glib_nm_settings_connection_object_info); + nm_dbus_manager_register_exported_type (nm_dbus_manager_get (), + G_TYPE_FROM_CLASS (class), + &dbus_glib_nm_settings_connection_object_info); } diff --git a/src/settings/nm-settings-connection.h b/src/settings/nm-settings-connection.h index d8c54e0549..0c93614156 100644 --- a/src/settings/nm-settings-connection.h +++ b/src/settings/nm-settings-connection.h @@ -16,7 +16,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * (C) Copyright 2008 Novell, Inc. - * (C) Copyright 2008 - 2011 Red Hat, Inc. + * (C) Copyright 2008 - 2013 Red Hat, Inc. */ #ifndef NM_SETTINGS_CONNECTION_H @@ -35,15 +35,17 @@ G_BEGIN_DECLS #define NM_IS_SETTINGS_CONNECTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_SETTINGS_CONNECTION)) #define NM_SETTINGS_CONNECTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_SETTINGS_CONNECTION, NMSettingsConnectionClass)) +/* Signals */ #define NM_SETTINGS_CONNECTION_UPDATED "updated" #define NM_SETTINGS_CONNECTION_REMOVED "removed" #define NM_SETTINGS_CONNECTION_GET_SECRETS "get-secrets" #define NM_SETTINGS_CONNECTION_CANCEL_SECRETS "cancel-secrets" +/* Properties */ #define NM_SETTINGS_CONNECTION_VISIBLE "visible" +#define NM_SETTINGS_CONNECTION_UNSAVED "unsaved" typedef struct _NMSettingsConnection NMSettingsConnection; - typedef struct _NMSettingsConnectionClass NMSettingsConnectionClass; typedef void (*NMSettingsConnectionCommitFunc) (NMSettingsConnection *connection, @@ -82,6 +84,7 @@ void nm_settings_connection_commit_changes (NMSettingsConnection *connection, gboolean nm_settings_connection_replace_settings (NMSettingsConnection *self, NMConnection *new_connection, + gboolean update_unsaved, GError **error); void nm_settings_connection_replace_and_commit (NMSettingsConnection *self, @@ -122,6 +125,8 @@ gboolean nm_settings_connection_check_permission (NMSettingsConnection *self, void nm_settings_connection_signal_remove (NMSettingsConnection *self); +gboolean nm_settings_connection_get_unsaved (NMSettingsConnection *self); + gboolean nm_settings_connection_get_timestamp (NMSettingsConnection *connection, guint64 *out_timestamp); diff --git a/src/settings/plugins/example/nm-example-connection.c b/src/settings/plugins/example/nm-example-connection.c index 17fd27f0f0..e22268297e 100644 --- a/src/settings/plugins/example/nm-example-connection.c +++ b/src/settings/plugins/example/nm-example-connection.c @@ -77,7 +77,10 @@ nm_example_connection_new (const char *full_path, /* Update our settings with what was read from the file or what got passed * in as a source NMConnection. */ - if (!nm_settings_connection_replace_settings (NM_SETTINGS_CONNECTION (object), tmp, error)) { + if (!nm_settings_connection_replace_settings (NM_SETTINGS_CONNECTION (object), + tmp, + TRUE, + error)) { g_object_unref (object); object = NULL; goto out; diff --git a/src/settings/plugins/ifcfg-rh/nm-ifcfg-connection.c b/src/settings/plugins/ifcfg-rh/nm-ifcfg-connection.c index 04d3d3e453..43b6fc6d2b 100644 --- a/src/settings/plugins/ifcfg-rh/nm-ifcfg-connection.c +++ b/src/settings/plugins/ifcfg-rh/nm-ifcfg-connection.c @@ -109,6 +109,7 @@ nm_ifcfg_connection_new (const char *full_path, char *routefile = NULL; char *route6file = NULL; NMInotifyHelper *ih; + gboolean update_unsaved = TRUE; g_return_val_if_fail (full_path != NULL, NULL); @@ -125,6 +126,9 @@ nm_ifcfg_connection_new (const char *full_path, ignore_error); if (!tmp) return NULL; + + /* If we just read the connection from disk, it's clearly not Unsaved */ + update_unsaved = FALSE; } object = (GObject *) g_object_new (NM_TYPE_IFCFG_CONNECTION, @@ -134,7 +138,10 @@ nm_ifcfg_connection_new (const char *full_path, goto out; /* Update our settings with what was read from the file */ - if (!nm_settings_connection_replace_settings (NM_SETTINGS_CONNECTION (object), tmp, error)) { + if (nm_settings_connection_replace_settings (NM_SETTINGS_CONNECTION (object), + tmp, + update_unsaved, + error)) { g_object_unref (object); object = NULL; goto out; diff --git a/src/settings/plugins/ifnet/nm-ifnet-connection.c b/src/settings/plugins/ifnet/nm-ifnet-connection.c index 9042ddf8bb..fa5491487f 100644 --- a/src/settings/plugins/ifnet/nm-ifnet-connection.c +++ b/src/settings/plugins/ifnet/nm-ifnet-connection.c @@ -62,6 +62,7 @@ nm_ifnet_connection_new (const char *conn_name, NMConnection *source) NMConnection *tmp; GObject *object; GError *error = NULL; + gboolean update_unsaved = TRUE; g_return_val_if_fail (conn_name != NULL, NULL); @@ -73,11 +74,17 @@ nm_ifnet_connection_new (const char *conn_name, NMConnection *source) g_error_free (error); return NULL; } + + /* If we just read the connection from disk, it's clearly not Unsaved */ + update_unsaved = FALSE; } object = (GObject *) g_object_new (NM_TYPE_IFNET_CONNECTION, NULL); NM_IFNET_CONNECTION_GET_PRIVATE (object)->conn_name = g_strdup (conn_name); - nm_settings_connection_replace_settings (NM_SETTINGS_CONNECTION (object), tmp, NULL); + nm_settings_connection_replace_settings (NM_SETTINGS_CONNECTION (object), + tmp, + update_unsaved, + NULL); g_object_unref (tmp); return NM_IFNET_CONNECTION (object); diff --git a/src/settings/plugins/keyfile/nm-keyfile-connection.c b/src/settings/plugins/keyfile/nm-keyfile-connection.c index c128e1ac58..99bd0bce4b 100644 --- a/src/settings/plugins/keyfile/nm-keyfile-connection.c +++ b/src/settings/plugins/keyfile/nm-keyfile-connection.c @@ -49,6 +49,7 @@ nm_keyfile_connection_new (const char *full_path, NMKeyfileConnectionPrivate *priv; NMConnection *tmp; const char *uuid; + gboolean update_unsaved = TRUE; g_return_val_if_fail (full_path != NULL, NULL); @@ -59,6 +60,9 @@ nm_keyfile_connection_new (const char *full_path, tmp = nm_keyfile_plugin_connection_from_file (full_path, error); if (!tmp) return NULL; + + /* If we just read the connection from disk, it's clearly not Unsaved */ + update_unsaved = FALSE; } object = (GObject *) g_object_new (NM_TYPE_KEYFILE_CONNECTION, NULL); @@ -67,7 +71,10 @@ nm_keyfile_connection_new (const char *full_path, priv->path = g_strdup (full_path); /* Update our settings with what was read from the file */ - if (!nm_settings_connection_replace_settings (NM_SETTINGS_CONNECTION (object), tmp, error)) { + if (!nm_settings_connection_replace_settings (NM_SETTINGS_CONNECTION (object), + tmp, + update_unsaved, + error)) { g_object_unref (object); object = NULL; goto out;