From cd5d92705dcd9dc508a58b088d174984409b73ed Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 11 Apr 2013 18:53:54 -0500 Subject: [PATCH] settings: track whether connection is saved to disk or not Use the new NMConnection 'changed' signal to mark connections as dirty/unsaved, and reset that when they get flushed to disk. Previously, the 'Updated' signal was emitted only when the connection was changed and flushed to disk, but now we have more granular needs, and the signal is emitted whenever the connection actually *is* changed, regardless of whether its flushed to disk or not. --- introspection/nm-settings-connection.xml | 10 ++ libnm-util/nm-connection.c | 9 +- src/settings/nm-settings-connection.c | 123 +++++++++++++++--- src/settings/nm-settings-connection.h | 9 +- .../plugins/example/nm-example-connection.c | 5 +- .../plugins/ifcfg-rh/nm-ifcfg-connection.c | 9 +- .../plugins/ifnet/nm-ifnet-connection.c | 9 +- .../plugins/keyfile/nm-keyfile-connection.c | 9 +- 8 files changed, 157 insertions(+), 26 deletions(-) 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;