diff --git a/libnm-glib/nm-remote-connection.c b/libnm-glib/nm-remote-connection.c index 64f0325776..cf1cc5418b 100644 --- a/libnm-glib/nm-remote-connection.c +++ b/libnm-glib/nm-remote-connection.c @@ -46,6 +46,7 @@ enum { enum { UPDATED, REMOVED, + VISIBLE, LAST_SIGNAL }; @@ -65,6 +66,7 @@ typedef struct { GSList *calls; NMRemoteConnectionInitResult init_result; + gboolean visible; gboolean disposed; } NMRemoteConnectionPrivate; @@ -263,6 +265,7 @@ init_get_settings_cb (DBusGProxy *proxy, priv->init_result = NM_REMOTE_CONNECTION_INIT_RESULT_ERROR; g_object_notify (G_OBJECT (self), NM_REMOTE_CONNECTION_INIT_RESULT); } else { + priv->visible = TRUE; replace_settings (self, new_settings); g_hash_table_destroy (new_settings); priv->init_result = NM_REMOTE_CONNECTION_INIT_RESULT_SUCCESS; @@ -277,15 +280,31 @@ updated_get_settings_cb (DBusGProxy *proxy, gpointer user_data) { NMRemoteConnection *self = user_data; + NMRemoteConnectionPrivate *priv = NM_REMOTE_CONNECTION_GET_PRIVATE (self); if (error) { - /* The connection no longer exists, or is no longer visible to this - * user; we must remove it. + GHashTable *hash; + + /* Connection is no longer visible to this user. Let the settings + * service handle this via 'visible'. The settings service will emit + * the "removed" signal for us since it handles the lifetime of this + * object. */ - g_signal_emit (self, signals[REMOVED], 0); + hash = g_hash_table_new (g_str_hash, g_str_equal); + nm_connection_replace_settings (NM_CONNECTION (self), hash, NULL); + g_hash_table_destroy (hash); + + priv->visible = FALSE; + g_signal_emit (self, signals[VISIBLE], 0, FALSE); } else { replace_settings (self, new_settings); g_hash_table_destroy (new_settings); + + /* Settings service will handle announcing the connection to clients */ + if (priv->visible == FALSE) { + priv->visible = TRUE; + g_signal_emit (self, signals[VISIBLE], 0, TRUE); + } } } @@ -474,4 +493,12 @@ nm_remote_connection_class_init (NMRemoteConnectionClass *remote_class) NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + + signals[VISIBLE] = + g_signal_new ("visible", + G_TYPE_FROM_CLASS (remote_class), + G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, + g_cclosure_marshal_VOID__BOOLEAN, + G_TYPE_NONE, 1, G_TYPE_BOOLEAN); } diff --git a/libnm-glib/nm-remote-settings.c b/libnm-glib/nm-remote-settings.c index 2c2bf69473..9469cefa6e 100644 --- a/libnm-glib/nm-remote-settings.c +++ b/libnm-glib/nm-remote-settings.c @@ -18,7 +18,7 @@ * Boston, MA 02110-1301 USA. * * Copyright (C) 2008 Novell, Inc. - * Copyright (C) 2009 - 2010 Red Hat, Inc. + * Copyright (C) 2009 - 2011 Red Hat, Inc. */ #include @@ -205,6 +205,48 @@ connection_removed_cb (NMRemoteConnection *remote, gpointer user_data) g_hash_table_remove (priv->pending, path); } +static void +connection_visible_cb (NMRemoteConnection *remote, + gboolean visible, + gpointer user_data) +{ + NMRemoteSettings *self = NM_REMOTE_SETTINGS (user_data); + NMRemoteSettingsPrivate *priv = NM_REMOTE_SETTINGS_GET_PRIVATE (self); + const char *path; + + path = nm_connection_get_path (NM_CONNECTION (remote)); + g_assert (path); + + /* When a connection becomes invisible, we put it back in the pending + * hash until it becomes visible again. When it does, we move it back to + * the normal connections hash. + */ + if (visible) { + /* Connection visible to this user again */ + if (g_hash_table_lookup (priv->pending, path)) { + /* Move connection from pending to visible hash; emit for clients */ + g_hash_table_insert (priv->connections, g_strdup (path), g_object_ref (remote)); + g_hash_table_remove (priv->pending, path); + g_signal_emit (self, signals[NEW_CONNECTION], 0, remote); + } + } else { + /* Connection now invisible to this user */ + if (g_hash_table_lookup (priv->connections, path)) { + /* Move connection to pending hash and wait for it to become visible again */ + g_hash_table_insert (priv->pending, g_strdup (path), g_object_ref (remote)); + g_hash_table_remove (priv->connections, path); + + /* Signal to clients that the connection is gone; but we have to + * block our connection removed handler so we don't destroy + * the connection when the signal is emitted. + */ + g_signal_handlers_block_by_func (remote, connection_removed_cb, self); + g_signal_emit_by_name (remote, NM_REMOTE_CONNECTION_REMOVED); + g_signal_handlers_unblock_by_func (remote, connection_removed_cb, self); + } + } +} + static void connection_init_result_cb (NMRemoteConnection *remote, GParamSpec *pspec, @@ -295,6 +337,10 @@ new_connection_cb (DBusGProxy *proxy, const char *path, gpointer user_data) G_CALLBACK (connection_removed_cb), self); + g_signal_connect (connection, "visible", + G_CALLBACK (connection_visible_cb), + self); + g_signal_connect (connection, "notify::" NM_REMOTE_CONNECTION_INIT_RESULT, G_CALLBACK (connection_init_result_cb), self); diff --git a/libnm-glib/tests/test-remote-settings-client.c b/libnm-glib/tests/test-remote-settings-client.c index 619b86d8de..ad62075d41 100644 --- a/libnm-glib/tests/test-remote-settings-client.c +++ b/libnm-glib/tests/test-remote-settings-client.c @@ -14,7 +14,7 @@ * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * - * Copyright (C) 2010 Red Hat, Inc. + * Copyright (C) 2010 - 2011 Red Hat, Inc. * */ @@ -36,6 +36,8 @@ static GPid spid = 0; static NMRemoteSettings *settings = NULL; +DBusGConnection *bus = NULL; +NMRemoteConnection *remote = NULL; /*******************************************************************/ @@ -56,26 +58,21 @@ do { \ /*******************************************************************/ -typedef struct { - gboolean done; - NMRemoteConnection *connection; -} AddInfo; - static void add_cb (NMRemoteSettings *s, NMRemoteConnection *connection, GError *error, gpointer user_data) { - AddInfo *info = user_data; - if (error) g_warning ("Add error: %s", error->message); - info->done = TRUE; - info->connection = connection; + *((gboolean *) user_data) = TRUE; + remote = connection; } +#define TEST_CON_ID "blahblahblah" + static void test_add_connection (void) { @@ -85,14 +82,14 @@ test_add_connection (void) char *uuid; gboolean success; time_t start, now; - AddInfo info = { FALSE, NULL }; + gboolean done = FALSE; connection = nm_connection_new (); s_con = (NMSettingConnection *) nm_setting_connection_new (); uuid = nm_utils_uuid_generate (); g_object_set (G_OBJECT (s_con), - NM_SETTING_CONNECTION_ID, "blahblahblah", + NM_SETTING_CONNECTION_ID, TEST_CON_ID, NM_SETTING_CONNECTION_UUID, uuid, NM_SETTING_CONNECTION_TYPE, NM_SETTING_WIRED_SETTING_NAME, NULL); @@ -105,25 +102,176 @@ test_add_connection (void) success = nm_remote_settings_add_connection (settings, connection, add_cb, - &info); + &done); test_assert (success == TRUE); start = time (NULL); do { now = time (NULL); g_main_context_iteration (NULL, FALSE); - } while ((info.done == FALSE) && (now - start < 5)); - test_assert (info.done == TRUE); - test_assert (info.connection != NULL); + } while ((done == FALSE) && (now - start < 5)); + test_assert (done == TRUE); + test_assert (remote != NULL); /* Make sure the connection is the same as what we added */ test_assert (nm_connection_compare (connection, - NM_CONNECTION (info.connection), + NM_CONNECTION (remote), NM_SETTING_COMPARE_FLAG_EXACT) == TRUE); } /*******************************************************************/ +static void +set_visible_cb (DBusGProxy *proxy, + DBusGProxyCall *call, + gpointer user_data) +{ + GError *error = NULL; + gboolean success; + + success = dbus_g_proxy_end_call (proxy, call, &error, G_TYPE_INVALID); + if (!success) + g_warning ("Failed to change connection visibility: %s", error->message); + test_assert (success == TRUE); + test_assert (error == NULL); +} + +static void +invis_removed_cb (NMRemoteConnection *connection, gboolean *done) +{ + *done = TRUE; +} + +static void +invis_has_settings_cb (NMSetting *setting, + const char *key, + const GValue *value, + GParamFlags flags, + gpointer user_data) +{ + *((gboolean *) user_data) = TRUE; +} + +static void +test_make_invisible (void) +{ + time_t start, now; + GSList *list, *iter; + DBusGProxy *proxy; + gboolean done = FALSE, has_settings = FALSE; + char *path; + + test_assert (remote != NULL); + + /* Listen for the remove event when the connection becomes invisible */ + g_signal_connect (remote, "removed", G_CALLBACK (invis_removed_cb), &done); + + path = g_strdup (nm_connection_get_path (NM_CONNECTION (remote))); + proxy = dbus_g_proxy_new_for_name (bus, + NM_DBUS_SERVICE, + path, + NM_DBUS_IFACE_SETTINGS_CONNECTION); + test_assert (proxy != NULL); + + /* Bypass the NMRemoteSettings object so we can test it independently */ + dbus_g_proxy_begin_call (proxy, "SetVisible", set_visible_cb, NULL, NULL, + G_TYPE_BOOLEAN, FALSE, G_TYPE_INVALID); + + /* Wait for the connection to be removed */ + start = time (NULL); + do { + now = time (NULL); + g_main_context_iteration (NULL, FALSE); + } while ((done == FALSE) && (now - start < 5)); + test_assert (done == TRUE); + + /* Ensure NMRemoteSettings no longer has the connection */ + list = nm_remote_settings_list_connections (settings); + for (iter = list; iter; iter = g_slist_next (iter)) { + NMConnection *candidate = NM_CONNECTION (iter->data); + + test_assert ((gpointer) remote != (gpointer) candidate); + test_assert (strcmp (path, nm_connection_get_path (candidate)) != 0); + } + + /* And ensure the invisible connection no longer has any settings */ + nm_connection_for_each_setting_value (NM_CONNECTION (remote), + invis_has_settings_cb, + &has_settings); + test_assert (has_settings == FALSE); + + g_free (path); + g_object_unref (proxy); +} + +/*******************************************************************/ + +static void +vis_new_connection_cb (NMRemoteSettings *foo, + NMRemoteConnection *connection, + NMRemoteConnection **new) +{ + *new = connection; +} + +static void +test_make_visible (void) +{ + time_t start, now; + GSList *list, *iter; + DBusGProxy *proxy; + gboolean found = FALSE; + char *path; + NMRemoteConnection *new = NULL; + + test_assert (remote != NULL); + + /* Wait for the new-connection signal when the connection is visible again */ + g_signal_connect (settings, NM_REMOTE_SETTINGS_NEW_CONNECTION, + G_CALLBACK (vis_new_connection_cb), &new); + + path = g_strdup (nm_connection_get_path (NM_CONNECTION (remote))); + proxy = dbus_g_proxy_new_for_name (bus, + NM_DBUS_SERVICE, + path, + NM_DBUS_IFACE_SETTINGS_CONNECTION); + test_assert (proxy != NULL); + + /* Bypass the NMRemoteSettings object so we can test it independently */ + dbus_g_proxy_begin_call (proxy, "SetVisible", set_visible_cb, NULL, NULL, + G_TYPE_BOOLEAN, TRUE, G_TYPE_INVALID); + + + /* Wait for the settings service to announce the connection again */ + start = time (NULL); + do { + now = time (NULL); + g_main_context_iteration (NULL, FALSE); + } while ((new == NULL) && (now - start < 5)); + + /* Ensure the new connection is the same as the one we made visible again */ + test_assert (new == remote); + + /* Ensure NMRemoteSettings has the connection */ + list = nm_remote_settings_list_connections (settings); + for (iter = list; iter; iter = g_slist_next (iter)) { + NMConnection *candidate = NM_CONNECTION (iter->data); + + if ((gpointer) remote == (gpointer) candidate) { + test_assert (strcmp (path, nm_connection_get_path (candidate)) == 0); + test_assert (strcmp (TEST_CON_ID, nm_connection_get_id (candidate)) == 0); + found = TRUE; + break; + } + } + test_assert (found == TRUE); + + g_free (path); + g_object_unref (proxy); +} + +/*******************************************************************/ + static void deleted_cb (DBusGProxy *proxy, DBusGProxyCall *call, @@ -140,15 +288,13 @@ deleted_cb (DBusGProxy *proxy, } static void -removed_cb (NMRemoteConnection *connection, gpointer user_data) +removed_cb (NMRemoteConnection *connection, gboolean *done) { - gboolean *done = user_data; - *done = TRUE; } static void -test_remove_connection (DBusGConnection *bus) +test_remove_connection (void) { NMRemoteConnection *connection; time_t start, now; @@ -210,7 +356,6 @@ int main (int argc, char **argv) char *service_argv[3] = { NULL, NULL, NULL }; int ret; GError *error = NULL; - DBusGConnection *bus; int i = 100; g_assert (argc == 3); @@ -248,7 +393,9 @@ int main (int argc, char **argv) suite = g_test_get_root (); g_test_suite_add (suite, TESTCASE (test_add_connection, NULL)); - g_test_suite_add (suite, TESTCASE (test_remove_connection, bus)); + g_test_suite_add (suite, TESTCASE (test_make_invisible, NULL)); + g_test_suite_add (suite, TESTCASE (test_make_visible, NULL)); + g_test_suite_add (suite, TESTCASE (test_remove_connection, NULL)); ret = g_test_run (); diff --git a/libnm-glib/tests/test-remote-settings-service.py b/libnm-glib/tests/test-remote-settings-service.py index 1f4f9af072..e6bd798f92 100755 --- a/libnm-glib/tests/test-remote-settings-service.py +++ b/libnm-glib/tests/test-remote-settings-service.py @@ -18,6 +18,9 @@ class UnknownInterfaceException(dbus.DBusException): class UnknownPropertyException(dbus.DBusException): _dbus_error_name = IFACE_DBUS + '.UnknownProperty' +class PermissionDeniedException(dbus.DBusException): + _dbus_error_name = IFACE_SETTINGS + '.PermissionDenied' + mainloop = gobject.MainLoop() class Connection(dbus.service.Object): @@ -26,11 +29,19 @@ class Connection(dbus.service.Object): self.path = object_path self.settings = settings self.remove_func = remove_func + self.visible = True @dbus.service.method(dbus_interface=IFACE_CONNECTION, in_signature='', out_signature='a{sa{sv}}') def GetSettings(self): + if not self.visible: + raise PermissionDeniedException() return self.settings + @dbus.service.method(dbus_interface=IFACE_CONNECTION, in_signature='b', out_signature='') + def SetVisible(self, vis): + self.visible = vis + self.Updated() + @dbus.service.method(dbus_interface=IFACE_CONNECTION, in_signature='', out_signature='') def Delete(self): self.remove_func(self) @@ -40,6 +51,10 @@ class Connection(dbus.service.Object): def Removed(self): pass + @dbus.service.signal(IFACE_CONNECTION, signature='') + def Updated(self): + pass + class Settings(dbus.service.Object): def __init__(self, bus, object_path): dbus.service.Object.__init__(self, bus, object_path)