gcr: Cancel the prompt when prompter goes away
authorStef Walter <stefw@gnome.org>
Mon, 20 Aug 2012 14:03:48 +0000 (16:03 +0200)
committerStef Walter <stefw@gnome.org>
Mon, 22 Oct 2012 14:08:12 +0000 (16:08 +0200)
 * If the prompter quits, cancel any prompting that's going on.

https://bugzilla.gnome.org/show_bug.cgi?id=684478

gcr/gcr-base.symbols
gcr/gcr-mock-prompter.c
gcr/gcr-mock-prompter.h
gcr/gcr-system-prompt.c
gcr/tests/test-system-prompt.c

index c794829..35a74a1 100644 (file)
@@ -111,6 +111,7 @@ gcr_mock_prompter_is_prompting
 gcr_mock_prompter_set_delay_msec
 gcr_mock_prompter_start
 gcr_mock_prompter_stop
+gcr_mock_prompter_disconnect
 gcr_parsed_get_attributes
 gcr_parsed_get_data
 gcr_parsed_get_description
index 564ed2d..31cbba2 100644 (file)
@@ -105,7 +105,7 @@ typedef struct {
 
        /* Owned by the prompter thread*/
        GcrSystemPrompter *prompter;
-       const gchar *bus_name;
+       GDBusConnection *connection;
        GMainLoop *loop;
 } ThreadData;
 
@@ -943,9 +943,9 @@ mock_prompter_thread (gpointer data)
                                                                     G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
                                                                     NULL, NULL, &error);
                if (error == NULL) {
+                       thread_data->connection = connection;
                        gcr_system_prompter_register (GCR_SYSTEM_PROMPTER (thread_data->prompter),
                                                      connection);
-                       thread_data->bus_name = g_dbus_connection_get_unique_name (connection);
                } else {
                        g_critical ("couldn't create connection: %s", error->message);
                        g_error_free (error);
@@ -978,10 +978,19 @@ mock_prompter_thread (gpointer data)
        thread_data->prompter = NULL;
 
        if (connection) {
-               if (!g_dbus_connection_flush_sync (connection, NULL, &error)) {
-                       g_critical ("connection flush failed: %s", error->message);
-                       g_error_free (error);
+               thread_data->connection = NULL;
+
+               if (!g_dbus_connection_is_closed (connection)) {
+                       if (!g_dbus_connection_flush_sync (connection, NULL, &error)) {
+                               g_critical ("connection flush failed: %s", error->message);
+                               g_error_free (error);
+                       }
+                       if (!g_dbus_connection_close_sync (connection, NULL, &error)) {
+                               g_critical ("connection close failed: %s", error->message);
+                               g_error_free (error);
+                       }
                }
+
                g_object_unref (connection);
        }
 
@@ -1030,7 +1039,22 @@ gcr_mock_prompter_start (void)
        g_assert (running->prompter);
        g_mutex_unlock (running->mutex);
 
-       return running->bus_name;
+       return g_dbus_connection_get_unique_name (running->connection);
+}
+
+void
+gcr_mock_prompter_disconnect (void)
+{
+       GError *error = NULL;
+
+       g_assert (running != NULL);
+       g_assert (running->connection);
+
+       g_dbus_connection_close_sync (running->connection, NULL, &error);
+       if (error != NULL) {
+               g_critical ("disconnect connection close failed: %s", error->message);
+               g_error_free (error);
+       }
 }
 
 /**
index 3e2cf26..f89f762 100644 (file)
@@ -36,6 +36,8 @@ G_BEGIN_DECLS
 
 const gchar *        gcr_mock_prompter_start                     (void);
 
+void                 gcr_mock_prompter_disconnect                (void);
+
 void                 gcr_mock_prompter_stop                      (void);
 
 gboolean             gcr_mock_prompter_is_prompting              (void);
index 640aa77..b370b1c 100644 (file)
@@ -143,8 +143,10 @@ static gint unique_prompt_id = 0;
 
 typedef struct {
        GSource *timeout;
+       GSource *waiting;
        GMainContext *context;
        GCancellable *cancellable;
+       guint watch_id;
 } CallClosure;
 
 static void
@@ -153,11 +155,45 @@ call_closure_free (gpointer data)
        CallClosure *closure = data;
        if (closure->timeout)
                g_source_destroy (closure->timeout);
-       g_clear_object (&closure->cancellable);
+       if (closure->waiting)
+               g_source_destroy (closure->waiting);
+       if (closure->watch_id)
+               g_bus_unwatch_name (closure->watch_id);
+       g_object_unref (closure->cancellable);
        g_free (data);
 }
 
 static void
+on_propagate_cancelled (GCancellable *cancellable,
+                        gpointer user_data)
+{
+       /* Propagate the cancelled signal */
+       GCancellable *cancel = G_CANCELLABLE (user_data);
+       g_cancellable_cancel (cancel);
+}
+
+static CallClosure *
+call_closure_new (GCancellable *cancellable)
+{
+       CallClosure *call;
+
+       /*
+        * We use our own cancellable object, since we cancel it it in
+        * situations other than when the caller cancels.
+        */
+
+       call = g_new0 (CallClosure, 1);
+       call->cancellable = g_cancellable_new ();
+
+       if (cancellable) {
+               g_cancellable_connect (cancellable, G_CALLBACK (on_propagate_cancelled),
+                                      g_object_ref (call->cancellable), g_object_unref);
+       }
+
+       return call;
+}
+
+static void
 gcr_system_prompt_init (GcrSystemPrompt *self)
 {
        self->pv = G_TYPE_INSTANCE_GET_PRIVATE (self, GCR_TYPE_SYSTEM_PROMPT,
@@ -742,6 +778,16 @@ register_prompt_object (GcrSystemPrompt *self,
 }
 
 static void
+on_prompter_vanished (GDBusConnection *connection,
+                      const gchar *name,
+                      gpointer user_data)
+{
+       GSimpleAsyncResult *async = G_SIMPLE_ASYNC_RESULT (user_data);
+       CallClosure *call = g_simple_async_result_get_op_res_gpointer (async);
+       g_cancellable_cancel (call->cancellable);
+}
+
+static void
 on_bus_connected (GObject *source,
                   GAsyncResult *result,
                   gpointer user_data)
@@ -763,6 +809,12 @@ on_bus_connected (GObject *source,
 
                g_main_context_push_thread_default (closure->context);
 
+               closure->watch_id = g_bus_watch_name_on_connection (self->pv->connection,
+                                                                   self->pv->prompter_bus_name,
+                                                                   G_BUS_NAME_WATCHER_FLAGS_NONE,
+                                                                   NULL, on_prompter_vanished,
+                                                                   res, NULL);
+
                register_prompt_object (self, &error);
 
                g_main_context_pop_thread_default (closure->context);
@@ -835,6 +887,27 @@ on_call_timeout (gpointer user_data)
        return FALSE; /* Don't call this function again */
 }
 
+static gboolean
+on_call_cancelled (GCancellable *cancellable,
+                   gpointer user_data)
+{
+       GSimpleAsyncResult *async = G_SIMPLE_ASYNC_RESULT (user_data);
+       CallClosure *call = g_simple_async_result_get_op_res_gpointer (async);
+       GcrSystemPrompt *self = GCR_SYSTEM_PROMPT (g_async_result_get_source_object (user_data));
+
+       g_source_destroy (call->waiting);
+       call->waiting = NULL;
+
+       g_simple_async_result_set_error (async, G_IO_ERROR, G_IO_ERROR_CANCELLED,
+                                        _("The operation was cancelled"));
+
+       /* Tell the prompter we're no longer interested */
+       gcr_system_prompt_close_async (self, NULL, NULL, NULL);
+
+       g_object_unref (self);
+       return FALSE; /* Don't call this function again */
+}
+
 void
 perform_init_async (GcrSystemPrompt *self,
                     GSimpleAsyncResult *res)
@@ -896,7 +969,7 @@ gcr_system_prompt_real_init_async (GAsyncInitable *initable,
 
        res = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
                                         gcr_system_prompt_real_init_async);
-       closure = g_new0 (CallClosure, 1);
+       closure = call_closure_new (cancellable);
        closure->context = g_main_context_get_thread_default ();
        if (closure->context)
                g_main_context_ref (closure->context);
@@ -1029,6 +1102,7 @@ on_perform_prompt_complete (GObject *source,
 {
        GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
        GcrSystemPrompt *self = GCR_SYSTEM_PROMPT (g_async_result_get_source_object (user_data));
+       CallClosure *call = g_simple_async_result_get_op_res_gpointer (res);
        GError *error = NULL;
        GVariant *retval;
 
@@ -1037,6 +1111,11 @@ on_perform_prompt_complete (GObject *source,
                self->pv->pending = NULL;
                g_simple_async_result_take_error (res, error);
                g_simple_async_result_complete (res);
+       } else {
+               g_assert (call->waiting == NULL);
+               call->waiting = g_cancellable_source_new (call->cancellable);
+               g_source_set_callback (call->waiting, (GSourceFunc)on_call_cancelled, res, NULL);
+               g_source_attach (call->waiting, call->context);
        }
 
        if (retval)
@@ -1069,8 +1148,7 @@ perform_prompt_async (GcrSystemPrompt *self,
        }
 
        res = g_simple_async_result_new (G_OBJECT (self), callback, user_data, source_tag);
-       closure = g_new0 (CallClosure, 1);
-       closure->cancellable = cancellable ? g_object_ref (cancellable) : cancellable;
+       closure = call_closure_new (cancellable);
        g_simple_async_result_set_op_res_gpointer (res, closure, call_closure_free);
 
        if (self->pv->closed) {
@@ -1088,6 +1166,12 @@ perform_prompt_async (GcrSystemPrompt *self,
        else
                sent = gcr_secret_exchange_begin (exchange);
 
+       closure->watch_id = g_bus_watch_name_on_connection (self->pv->connection,
+                                                           self->pv->prompter_bus_name,
+                                                           G_BUS_NAME_WATCHER_FLAGS_NONE,
+                                                           NULL, on_prompter_vanished,
+                                                           res, NULL);
+
        builder = build_dirty_properties (self);
 
        /* Reregister the prompt object in the current GMainContext */
@@ -1470,8 +1554,7 @@ gcr_system_prompt_close_async (GcrSystemPrompt *self,
 
        res = g_simple_async_result_new (NULL, callback, user_data,
                                         gcr_system_prompt_close_async);
-       closure = g_new0 (CallClosure, 1);
-       closure->cancellable = cancellable ? g_object_ref (cancellable) : g_cancellable_new ();
+       closure = call_closure_new (cancellable);
        closure->context = g_main_context_get_thread_default ();
        if (closure->context != NULL)
                g_main_context_ref (closure->context);
index 0d48124..b5cae2c 100644 (file)
@@ -641,6 +641,89 @@ test_after_close_dismisses (Test *test,
        egg_assert_not_object (prompt);
 }
 
+typedef struct {
+       GAsyncResult *result1;
+       GAsyncResult *result2;
+} ResultPair;
+
+static void
+on_result_pair_one (GObject *source,
+                    GAsyncResult *result,
+                    gpointer user_data)
+{
+       ResultPair *pair = user_data;
+       g_assert (pair->result1 == NULL);
+       pair->result1 = g_object_ref (result);
+       if (pair->result1 && pair->result2)
+               egg_test_wait_stop ();
+}
+
+static void
+on_result_pair_two (GObject *source,
+                    GAsyncResult *result,
+                    gpointer user_data)
+{
+       ResultPair *pair = user_data;
+       g_assert (pair->result2 == NULL);
+       pair->result2 = g_object_ref (result);
+       if (pair->result1 && pair->result2)
+               egg_test_wait_stop ();
+}
+
+static void
+test_watch_cancels (Test *test,
+                    gconstpointer unused)
+{
+       GDBusConnection *connection;
+       GcrPrompt *prompt;
+       GcrPrompt *prompt2;
+       GError *error = NULL;
+       const gchar *password;
+       ResultPair pair = { NULL, NULL };
+
+       gcr_mock_prompter_set_delay_msec (3000);
+       gcr_mock_prompter_expect_password_ok ("booo", NULL);
+
+       connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+       g_assert_no_error (error);
+
+       /* This should happen immediately */
+       prompt = gcr_system_prompt_open_for_prompter (test->prompter_name, 0, NULL, &error);
+       g_assert_no_error (error);
+       g_assert (GCR_IS_SYSTEM_PROMPT (prompt));
+
+       /* Show a password prompt */
+       gcr_prompt_password_async (prompt, NULL, on_result_pair_one, &pair);
+
+       /* This prompt should wait, block */
+       gcr_system_prompt_open_for_prompter_async (test->prompter_name, 0, NULL,
+                                                  on_result_pair_two, &pair);
+
+       /* Wait a bit before stopping, so outgoing request is done */
+       g_usleep (G_TIME_SPAN_SECOND / 4);
+
+       /* Kill the mock prompter */
+       gcr_mock_prompter_disconnect ();
+
+       /* Both the above operations should cancel */
+       egg_test_wait ();
+
+       prompt2 = gcr_system_prompt_open_finish (pair.result2, &error);
+       g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+       g_clear_error (&error);
+       g_assert (prompt2 == NULL);
+
+       password = gcr_prompt_password_finish (prompt, pair.result1, &error);
+       g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+       g_clear_error (&error);
+       g_assert (password == NULL);
+
+       g_object_unref (prompt);
+       g_object_unref (pair.result1);
+       g_object_unref (pair.result2);
+       g_object_unref (connection);
+}
+
 int
 main (int argc, char **argv)
 {
@@ -667,6 +750,7 @@ main (int argc, char **argv)
        g_test_add ("/gcr/system-prompt/close-cancels", Test, NULL, setup, test_close_cancels, teardown);
        g_test_add ("/gcr/system-prompt/after-close-dismisses", Test, NULL, setup, test_after_close_dismisses, teardown);
        g_test_add ("/gcr/system-prompt/close-from-prompter", Test, NULL, setup, test_close_from_prompter, teardown);
+       g_test_add ("/gcr/system-prompt/watch-cancels", Test, NULL, setup, test_watch_cancels, teardown);
 
        return egg_tests_run_with_loop ();
 }