openuriportal: Better error handling
authorMatthias Clasen <mclasen@redhat.com>
Tue, 29 Nov 2022 23:45:22 +0000 (18:45 -0500)
committerMatthias Clasen <mclasen@redhat.com>
Fri, 9 Dec 2022 16:05:48 +0000 (11:05 -0500)
Nested async calls are always a challenge.

Hopefully, things are straightened out now,
and we report GTK_DIALOG_ERROR errors for
the cases we care about.

gtk/gopenuriportal.c

index 715622ed326c1727f46de5337c3df7049915610d..16c63cbd9af1059a64c91874b4faf42709ac69a9 100644 (file)
@@ -29,6 +29,8 @@
 #include "gopenuriportal.h"
 #include "xdp-dbus.h"
 #include "gtkwindowprivate.h"
+#include "gtkprivate.h"
+#include "gtkdialogerror.h"
 
 #ifdef G_OS_UNIX
 #include <gio/gunixfdlist.h>
@@ -56,8 +58,8 @@ init_openuri_portal (void)
       if (connection != NULL)
         {
           openuri = gxdp_open_uri_proxy_new_sync (connection, 0,
-                                                  "org.freedesktop.portal.Desktop",
-                                                  "/org/freedesktop/portal/desktop",
+                                                  PORTAL_BUS_NAME,
+                                                  PORTAL_OBJECT_PATH,
                                                   NULL, &error);
           if (openuri == NULL)
             {
@@ -92,6 +94,43 @@ enum {
   XDG_DESKTOP_PORTAL_FAILED    = 2
 };
 
+enum {
+  OPEN_URI    = 0,
+  OPEN_FILE   = 1,
+  OPEN_FOLDER = 2
+};
+
+typedef struct {
+  GtkWindow *parent;
+  GFile *file;
+  gboolean open_folder;
+  GDBusConnection *connection;
+  GCancellable *cancellable;
+  GTask *task;
+  char *handle;
+  guint signal_id;
+  glong cancel_handler;
+  int call;
+} OpenUriData;
+
+static void
+open_uri_data_free (OpenUriData *data)
+{
+  if (data->signal_id)
+    g_dbus_connection_signal_unsubscribe (data->connection, data->signal_id);
+  g_clear_object (&data->connection);
+  if (data->cancel_handler)
+    g_signal_handler_disconnect (data->cancellable, data->cancel_handler);
+  if (data->parent)
+    gtk_window_unexport_handle (data->parent);
+  g_clear_object (&data->parent);
+  g_clear_object (&data->file);
+  g_clear_object (&data->cancellable);
+  g_clear_object (&data->task);
+  g_free (data->handle);
+  g_free (data);
+}
+
 static void
 response_received (GDBusConnection *connection,
                    const char      *sender_name,
@@ -103,10 +142,6 @@ response_received (GDBusConnection *connection,
 {
   GTask *task = user_data;
   guint32 response;
-  guint signal_id;
-
-  signal_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (task), "signal-id"));
-  g_dbus_connection_signal_unsubscribe (connection, signal_id);
 
   g_variant_get (parameters, "(u@a{sv})", &response, NULL);
 
@@ -116,11 +151,11 @@ response_received (GDBusConnection *connection,
       g_task_return_boolean (task, TRUE);
       break;
     case XDG_DESKTOP_PORTAL_CANCELLED:
-      g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Launch cancelled");
+      g_task_return_new_error (task, GTK_DIALOG_ERROR, GTK_DIALOG_ERROR_CANCELLED, "The portal dialog was closed");
       break;
     case XDG_DESKTOP_PORTAL_FAILED:
     default:
-      g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, "Launch failed");
+      g_task_return_new_error (task, GTK_DIALOG_ERROR, GTK_DIALOG_ERROR_FAILED, "The application launch failed");
       break;
     }
 
@@ -133,108 +168,145 @@ open_call_done (GObject      *source,
                 gpointer      user_data)
 {
   GXdpOpenURI *portal = GXDP_OPEN_URI (source);
-  GDBusConnection *connection;
   GTask *task = user_data;
+  OpenUriData *data = g_task_get_task_data (task);
   GError *error = NULL;
-  const char *call;
   gboolean res;
   char *path = NULL;
-  const char *handle;
-  guint signal_id;
-
-  connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (portal));
-  call = (const char *) g_object_get_data (G_OBJECT (task), "call");
 
-  if (g_str_equal (call, "OpenFile"))
-    res = gxdp_open_uri_call_open_file_finish (portal, &path, NULL, result, &error);
-  else if (g_str_equal (call, "OpenDirectory"))
-    res = gxdp_open_uri_call_open_directory_finish (portal, &path, NULL, result, &error);
-  else
-    res = gxdp_open_uri_call_open_uri_finish (portal, &path, result, &error);
+  switch (data->call)
+    {
+    case OPEN_FILE:
+      res = gxdp_open_uri_call_open_file_finish (portal, &path, NULL, result, &error);
+      break;
+    case OPEN_FOLDER:
+      res = gxdp_open_uri_call_open_directory_finish (portal, &path, NULL, result, &error);
+      break;
+    case OPEN_URI:
+      res = gxdp_open_uri_call_open_uri_finish (portal, &path, result, &error);
+      break;
+    default:
+      g_assert_not_reached ();
+      break;
+    }
 
   if (!res)
     {
+      g_free (path);
       g_task_return_error (task, error);
       g_object_unref (task);
-      g_free (path);
       return;
     }
 
-  handle = (const char *)g_object_get_data (G_OBJECT (task), "handle");
-  if (g_strcmp0 (handle, path) != 0)
+  if (g_strcmp0 (data->handle, path) != 0)
     {
-      signal_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (task), "signal-id"));
-      g_dbus_connection_signal_unsubscribe (connection, signal_id);
-
-      signal_id = g_dbus_connection_signal_subscribe (connection,
-                                                      "org.freedesktop.portal.Desktop",
-                                                      "org.freedesktop.portal.Request",
-                                                      "Response",
-                                                      path,
-                                                      NULL,
-                                                      G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
-                                                      response_received,
-                                                      task,
-                                                      NULL);
-      g_object_set_data (G_OBJECT (task), "signal-id", GINT_TO_POINTER (signal_id));
+      g_dbus_connection_signal_unsubscribe (data->connection, data->signal_id);
+
+      data->signal_id = g_dbus_connection_signal_subscribe (data->connection,
+                                                            PORTAL_BUS_NAME,
+                                                            PORTAL_REQUEST_INTERFACE,
+                                                            "Response",
+                                                            path,
+                                                            NULL,
+                                                            G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
+                                                            response_received,
+                                                            task,
+                                                            NULL);
+      g_free (data->handle);
+      data->handle = g_strdup (path);
     }
+
+  g_free (path);
+}
+
+static void
+send_close (OpenUriData *data)
+{
+  GDBusMessage *message;
+  GError *error = NULL;
+
+  message = g_dbus_message_new_method_call (PORTAL_BUS_NAME,
+                                            PORTAL_OBJECT_PATH,
+                                            PORTAL_REQUEST_INTERFACE,
+                                            "Close");
+  g_dbus_message_set_body (message, g_variant_new ("(o)", data->handle));
+
+  if (!g_dbus_connection_send_message (data->connection,
+                                       message,
+                                       G_DBUS_SEND_MESSAGE_FLAGS_NONE,
+                                       NULL, &error))
+    {
+      g_warning ("unable to send Close message: %s", error->message);
+      g_error_free (error);
+    }
+
+  g_object_unref (message);
+}
+
+static void
+canceled (GCancellable *cancellable,
+          GTask        *task)
+{
+  OpenUriData *data = g_task_get_task_data (task);
+
+  send_close (data);
+
+  g_task_return_new_error (task,
+                           GTK_DIALOG_ERROR, GTK_DIALOG_ERROR_ABORTED,
+                           "The OpenURI portal call was cancelled programmatically");
+  g_object_unref (task);
 }
 
 static void
 open_uri (GFile               *file,
           gboolean             open_folder,
           const char          *parent_window,
-          GCancellable        *cancellable,
           GAsyncReadyCallback  callback,
           gpointer             user_data)
 {
+  OpenUriData *data = user_data;
   GTask *task;
   GVariant *opts = NULL;
   int i;
-  guint signal_id;
-
-  if (callback)
-    {
-      GDBusConnection *connection;
-      GVariantBuilder opt_builder;
-      char *token;
-      char *sender;
-      char *handle;
-
-      connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (openuri));
-
-      task = g_task_new (NULL, cancellable, callback, user_data);
-
-      token = g_strdup_printf ("gtk%d", g_random_int_range (0, G_MAXINT));
-      sender = g_strdup (g_dbus_connection_get_unique_name (connection) + 1);
-      for (i = 0; sender[i]; i++)
-        if (sender[i] == '.')
-          sender[i] = '_';
-
-      handle = g_strdup_printf ("/org/freedesktop/portal/desktop/request/%s/%s", sender, token);
-      g_object_set_data_full (G_OBJECT (task), "handle", handle, g_free);
-      g_free (sender);
-
-      signal_id = g_dbus_connection_signal_subscribe (connection,
-                                                      "org.freedesktop.portal.Desktop",
-                                                      "org.freedesktop.portal.Request",
-                                                      "Response",
-                                                      handle,
-                                                      NULL,
-                                                      G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
-                                                      response_received,
-                                                      task,
-                                                      NULL);
-      g_object_set_data (G_OBJECT (task), "signal-id", GINT_TO_POINTER (signal_id));
-
-      g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT);
-      g_variant_builder_add (&opt_builder, "{sv}", "handle_token", g_variant_new_string (token));
-      g_free (token);
-
-      opts = g_variant_builder_end (&opt_builder);
-    }
-  else
-    task = NULL;
+  GDBusConnection *connection;
+  GVariantBuilder opt_builder;
+  char *token;
+  char *sender;
+
+  connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (openuri));
+  data->connection = g_object_ref (connection);
+
+  task = g_task_new (NULL, NULL, callback, user_data);
+  g_task_set_check_cancellable (task, FALSE);
+  g_task_set_task_data (task, user_data, NULL);
+  if (data->cancellable)
+    data->cancel_handler = g_signal_connect (data->cancellable, "cancelled", G_CALLBACK (canceled), task);
+
+  token = g_strdup_printf ("gtk%d", g_random_int_range (0, G_MAXINT));
+  sender = g_strdup (g_dbus_connection_get_unique_name (connection) + 1);
+  for (i = 0; sender[i]; i++)
+    if (sender[i] == '.')
+      sender[i] = '_';
+
+  data->handle = g_strdup_printf ("/org/freedesktop/portal/desktop/request/%s/%s", sender, token);
+  g_free (sender);
+
+  data->signal_id = g_dbus_connection_signal_subscribe (connection,
+                                                        PORTAL_BUS_NAME,
+                                                        PORTAL_REQUEST_INTERFACE,
+                                                        "Response",
+                                                        data->handle,
+                                                        NULL,
+                                                        G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
+                                                        response_received,
+                                                        task,
+                                                        NULL);
+
+  g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT);
+  g_variant_builder_add (&opt_builder, "{sv}", "handle_token", g_variant_new_string (token));
+  g_free (token);
+
+  opts = g_variant_builder_end (&opt_builder);
 
   if (g_file_is_native (file))
     {
@@ -247,9 +319,10 @@ open_uri (GFile               *file,
       errsv = errno;
       if (fd == -1)
         {
-          g_task_report_new_error (NULL, callback, user_data, NULL,
+          g_task_return_new_error (task,
                                    G_IO_ERROR, g_io_error_from_errno (errsv),
-                                   "OpenURI portal is not available");
+                                   "Failed to open file");
+          g_object_unref (task);
           return;
         }
 
@@ -262,30 +335,26 @@ open_uri (GFile               *file,
 
       if (open_folder)
         {
-          if (task)
-            g_object_set_data (G_OBJECT (task), "call", (gpointer) "OpenDirectory");
-
+          data->call = OPEN_FOLDER;
           gxdp_open_uri_call_open_directory (openuri,
                                              parent_window ? parent_window : "",
                                              g_variant_new ("h", fd_id),
                                              opts,
                                              fd_list,
-                                             cancellable,
-                                             task ? open_call_done : NULL,
+                                             NULL,
+                                             open_call_done,
                                              task);
         }
       else
         {
-          if (task)
-            g_object_set_data (G_OBJECT (task), "call", (gpointer) "OpenFile");
-
+          data->call = OPEN_FILE;
           gxdp_open_uri_call_open_file (openuri,
                                         parent_window ? parent_window : "",
                                         g_variant_new ("h", fd_id),
                                         opts,
                                         fd_list,
-                                        cancellable,
-                                        task ? open_call_done : NULL,
+                                        NULL,
+                                        open_call_done,
                                         task);
         }
 
@@ -295,39 +364,18 @@ open_uri (GFile               *file,
     {
       char *uri = g_file_get_uri (file);
 
-      if (task)
-        g_object_set_data (G_OBJECT (task), "call", (gpointer) "OpenURI");
-
+      data->call = OPEN_URI;
       gxdp_open_uri_call_open_uri (openuri,
                                    parent_window ? parent_window : "",
                                    uri,
                                    opts,
-                                   cancellable,
-                                   task ? open_call_done : NULL,
+                                   NULL,
+                                   open_call_done,
                                    task);
-
       g_free (uri);
     }
 }
 
-typedef struct {
-  GtkWindow *parent;
-  GFile *file;
-  gboolean open_folder;
-  GTask *task;
-} OpenUriData;
-
-static void
-open_uri_data_free (OpenUriData *data)
-{
-  if (data->parent)
-    gtk_window_unexport_handle (data->parent);
-  g_clear_object (&data->parent);
-  g_clear_object (&data->file);
-  g_clear_object (&data->task);
-  g_free (data);
-}
-
 static void
 open_uri_done (GObject      *source,
                GAsyncResult *result,
@@ -337,7 +385,17 @@ open_uri_done (GObject      *source,
   GError *error = NULL;
 
   if (!g_task_propagate_boolean (G_TASK (result), &error))
-    g_task_return_error (data->task, error);
+    {
+      if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+        {
+          g_error_free (error);
+          g_task_return_new_error (data->task,
+                                   GTK_DIALOG_ERROR, GTK_DIALOG_ERROR_ABORTED,
+                                   "The operation was aborted programmatically");
+        }
+      else
+        g_task_return_error (data->task, error);
+    }
   else
     g_task_return_boolean (data->task, TRUE);
 
@@ -351,7 +409,7 @@ window_handle_exported (GtkWindow  *window,
 {
   OpenUriData *data = user_data;
 
-  open_uri (data->file, data->open_folder, handle, g_task_get_cancellable (data->task), open_uri_done, data);
+  open_uri (data->file, data->open_folder, handle, open_uri_done, data);
 }
 
 void
@@ -367,8 +425,8 @@ g_openuri_portal_open_async (GFile               *file,
   if (!init_openuri_portal ())
     {
       g_task_report_new_error (NULL, callback, user_data, NULL,
-                               G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED,
-                               "OpenURI portal is not available");
+                               GTK_DIALOG_ERROR, GTK_DIALOG_ERROR_FAILED,
+                               "The OpenURI portal is not available");
       return;
     }
 
@@ -376,7 +434,9 @@ g_openuri_portal_open_async (GFile               *file,
   data->parent = parent ? g_object_ref (parent) : NULL;
   data->file = g_object_ref (file);
   data->open_folder = open_folder;
+  data->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
   data->task = g_task_new (parent, cancellable, callback, user_data);
+  g_task_set_check_cancellable (data->task, FALSE);
   g_task_set_source_tag (data->task, g_openuri_portal_open_async);
 
   if (!parent || !gtk_window_export_handle (parent, window_handle_exported, data))