[glib: 2/24] gfile: Implement interface API to make symbolic links asynchronously




commit 04718a96920d953658cf81350b648dabf2bcb104
Author: Marco Trevisan (TreviƱo) <mail 3v1n0 net>
Date:   Wed Jun 1 18:07:35 2022 +0200

    gfile: Implement interface API to make symbolic links asynchronously
    
    The interface was ready for this API but it was not provided.
    
    So implement this, using a thread that calls the sync API for now.
    
    Add tests.
    
    Helps with: GNOME/glib#157

 docs/reference/gio/gio-sections-common.txt |   2 +
 gio/gfile.c                                | 128 +++++++++++++++++++++++++++
 gio/gfile.h                                |  26 +++++-
 gio/tests/file.c                           | 134 +++++++++++++++++++++++++++++
 4 files changed, 286 insertions(+), 4 deletions(-)
---
diff --git a/docs/reference/gio/gio-sections-common.txt b/docs/reference/gio/gio-sections-common.txt
index 603338ffbe..a1537f0df6 100644
--- a/docs/reference/gio/gio-sections-common.txt
+++ b/docs/reference/gio/gio-sections-common.txt
@@ -156,6 +156,8 @@ g_file_make_directory_async
 g_file_make_directory_finish
 g_file_make_directory_with_parents
 g_file_make_symbolic_link
+g_file_make_symbolic_link_async
+g_file_make_symbolic_link_finish
 g_file_query_settable_attributes
 g_file_query_writable_namespaces
 g_file_set_attribute
diff --git a/gio/gfile.c b/gio/gfile.c
index 4aae0ed0d1..52113a91c3 100644
--- a/gio/gfile.c
+++ b/gio/gfile.c
@@ -269,6 +269,15 @@ static void               g_file_real_make_directory_async        (GFile
 static gboolean           g_file_real_make_directory_finish       (GFile                  *file,
                                                                    GAsyncResult           *res,
                                                                    GError                **error);
+static void               g_file_real_make_symbolic_link_async    (GFile                  *file,
+                                                                   const char             *symlink_value,
+                                                                   int                     io_priority,
+                                                                   GCancellable           *cancellable,
+                                                                   GAsyncReadyCallback     callback,
+                                                                   gpointer                user_data);
+static gboolean           g_file_real_make_symbolic_link_finish   (GFile                  *file,
+                                                                   GAsyncResult           *result,
+                                                                   GError                **error);
 static void               g_file_real_open_readwrite_async        (GFile                  *file,
                                                                    int                  io_priority,
                                                                    GCancellable           *cancellable,
@@ -399,6 +408,8 @@ g_file_default_init (GFileIface *iface)
   iface->move_finish = g_file_real_move_finish;
   iface->make_directory_async = g_file_real_make_directory_async;
   iface->make_directory_finish = g_file_real_make_directory_finish;
+  iface->make_symbolic_link_async = g_file_real_make_symbolic_link_async;
+  iface->make_symbolic_link_finish = g_file_real_make_symbolic_link_finish;
   iface->open_readwrite_async = g_file_real_open_readwrite_async;
   iface->open_readwrite_finish = g_file_real_open_readwrite_finish;
   iface->create_readwrite_async = g_file_real_create_readwrite_async;
@@ -4154,6 +4165,123 @@ g_file_make_symbolic_link (GFile         *file,
   return (* iface->make_symbolic_link) (file, symlink_value, cancellable, error);
 }
 
+static void
+make_symbolic_link_async_thread (GTask         *task,
+                                 gpointer       object,
+                                 gpointer       task_data,
+                                 GCancellable  *cancellable)
+{
+  const char *symlink_value = task_data;
+  GError *error = NULL;
+
+  if (g_file_make_symbolic_link (G_FILE (object), symlink_value, cancellable, &error))
+    g_task_return_boolean (task, TRUE);
+  else
+    g_task_return_error (task, g_steal_pointer (&error));
+}
+
+static void
+g_file_real_make_symbolic_link_async (GFile               *file,
+                                      const char          *symlink_value,
+                                      int                  io_priority,
+                                      GCancellable        *cancellable,
+                                      GAsyncReadyCallback  callback,
+                                      gpointer             user_data)
+{
+  GTask *task;
+
+  g_return_if_fail (G_IS_FILE (file));
+  g_return_if_fail (symlink_value != NULL);
+  g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (file, cancellable, callback, user_data);
+  g_task_set_source_tag (task, g_file_real_make_symbolic_link_async);
+  g_task_set_task_data (task, g_strdup (symlink_value), g_free);
+  g_task_set_priority (task, io_priority);
+
+  g_task_run_in_thread (task, make_symbolic_link_async_thread);
+  g_object_unref (task);
+}
+
+/**
+ * g_file_make_symbolic_link_async:
+ * @file: a #GFile with the name of the symlink to create
+ * @symlink_value: (type filename): a string with the path for the target
+ *   of the new symlink
+ * @io_priority: the [I/O priority][io-priority] of the request
+ * @cancellable: (nullable): optional #GCancellable object,
+ *   %NULL to ignore
+ * @callback: a #GAsyncReadyCallback to call
+ *   when the request is satisfied
+ * @user_data: the data to pass to callback function
+ *
+ * Asynchronously creates a symbolic link named @file which contains the
+ * string @symlink_value.
+ *
+ * Virtual: make_symbolic_link_async
+ * Since: 2.74
+ */
+void
+g_file_make_symbolic_link_async (GFile               *file,
+                                 const char          *symlink_value,
+                                 int                  io_priority,
+                                 GCancellable        *cancellable,
+                                 GAsyncReadyCallback  callback,
+                                 gpointer             user_data)
+{
+  GFileIface *iface;
+
+  g_return_if_fail (G_IS_FILE (file));
+
+  iface = G_FILE_GET_IFACE (file);
+
+  /* Default implementation should always be provided by GFileIface */
+  g_assert (iface->make_symbolic_link_async != NULL);
+
+  (* iface->make_symbolic_link_async) (file, symlink_value, io_priority,
+                                       cancellable, callback, user_data);
+}
+
+static gboolean
+g_file_real_make_symbolic_link_finish (GFile         *file,
+                                       GAsyncResult  *result,
+                                       GError       **error)
+{
+  g_return_val_if_fail (g_task_is_valid (result, file), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+/**
+ * g_file_make_symbolic_link_finish:
+ * @file: input #GFile
+ * @result: a #GAsyncResult
+ * @error: a #GError, or %NULL
+ *
+ * Finishes an asynchronous symbolic link creation, started with
+ * g_file_make_symbolic_link_async().
+ *
+ * Virtual: make_symbolic_link_finish
+ * Returns: %TRUE on successful directory creation, %FALSE otherwise.
+ * Since: 2.74
+ */
+gboolean
+g_file_make_symbolic_link_finish (GFile         *file,
+                                  GAsyncResult  *result,
+                                  GError       **error)
+{
+  GFileIface *iface;
+
+  g_return_val_if_fail (G_IS_FILE (file), FALSE);
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+  iface = G_FILE_GET_IFACE (file);
+  /* Default implementation should always be provided by GFileIface */
+  g_assert (iface->make_symbolic_link_finish != NULL);
+
+  return (* iface->make_symbolic_link_finish) (file, result, error);
+}
+
 /**
  * g_file_delete:
  * @file: input #GFile
diff --git a/gio/gfile.h b/gio/gfile.h
index f2bffc114a..4e95c16e47 100644
--- a/gio/gfile.h
+++ b/gio/gfile.h
@@ -115,8 +115,8 @@ typedef struct _GFileIface                  GFileIface;
  * @make_directory_finish: Finishes making a directory asynchronously.
  * @make_symbolic_link: (nullable): Makes a symbolic link. %NULL if symbolic
  *    links are unsupported.
- * @_make_symbolic_link_async: Asynchronously makes a symbolic link
- * @_make_symbolic_link_finish: Finishes making a symbolic link asynchronously.
+ * @make_symbolic_link_async: Asynchronously makes a symbolic link
+ * @make_symbolic_link_finish: Finishes making a symbolic link asynchronously.
  * @copy: (nullable): Copies a file. %NULL if copying is unsupported, which will
  *     cause `GFile` to use a fallback copy method where it reads from the
  *     source and writes to the destination.
@@ -396,8 +396,15 @@ struct _GFileIface
                                                        const char           *symlink_value,
                                                        GCancellable         *cancellable,
                                                        GError              **error);
-  void                (* _make_symbolic_link_async)   (void);
-  void                (* _make_symbolic_link_finish)  (void);
+  void                (* make_symbolic_link_async)    (GFile                *file,
+                                                       const char           *symlink_value,
+                                                       int                   io_priority,
+                                                       GCancellable         *cancellable,
+                                                       GAsyncReadyCallback   callback,
+                                                       gpointer              user_data);
+  gboolean            (* make_symbolic_link_finish)   (GFile                 *file,
+                                                       GAsyncResult         *result,
+                                                       GError              **error);
 
   gboolean            (* copy)                        (GFile                *source,
                                                        GFile                *destination,
@@ -976,6 +983,17 @@ gboolean                g_file_make_symbolic_link         (GFile
                                                           const char                 *symlink_value,
                                                           GCancellable               *cancellable,
                                                           GError                    **error);
+GLIB_AVAILABLE_IN_2_74
+void                    g_file_make_symbolic_link_async   (GFile                      *file,
+                                                           const char                 *symlink_value,
+                                                           int                         io_priority,
+                                                           GCancellable               *cancellable,
+                                                           GAsyncReadyCallback         callback,
+                                                           gpointer                    user_data);
+GLIB_AVAILABLE_IN_2_74
+gboolean                g_file_make_symbolic_link_finish  (GFile                      *file,
+                                                           GAsyncResult               *result,
+                                                           GError                    **error);
 GLIB_AVAILABLE_IN_ALL
 GFileAttributeInfoList *g_file_query_settable_attributes  (GFile                      *file,
                                                           GCancellable               *cancellable,
diff --git a/gio/tests/file.c b/gio/tests/file.c
index 28643648da..ab3bde2a80 100644
--- a/gio/tests/file.c
+++ b/gio/tests/file.c
@@ -8,6 +8,12 @@
 #include <sys/stat.h>
 #endif
 
+typedef struct
+{
+  GMainLoop *loop;
+  GError **error;
+} AsyncErrorData;
+
 static void
 test_basic_for_file (GFile       *file,
                      const gchar *suffix)
@@ -2012,6 +2018,133 @@ test_async_delete (void)
   g_object_unref (file);
 }
 
+static void
+on_symlink_done (GObject      *object,
+                 GAsyncResult *result,
+                 gpointer      user_data)
+{
+  GFile *file = (GFile *) object;
+  GError *error = NULL;
+  GMainLoop *loop = user_data;
+
+  g_assert_true (g_file_make_symbolic_link_finish (file, result, &error));
+  g_assert_no_error (error);
+
+  g_main_loop_quit (loop);
+}
+
+static void
+on_symlink_error (GObject      *object,
+                  GAsyncResult *result,
+                  gpointer      user_data)
+{
+  GFile *file = (GFile *) object;
+  GError *error = NULL;
+  AsyncErrorData *data = user_data;
+
+  g_assert_false (g_file_make_symbolic_link_finish (file, result, &error));
+  g_assert_nonnull (error);
+  g_propagate_error (data->error, g_steal_pointer (&error));
+
+  g_main_loop_quit (data->loop);
+}
+
+static void
+test_async_make_symlink (void)
+{
+  GFile *link;
+  GFile *parent_dir;
+  GFile *target;
+  GFileInfo *link_info;
+  GFileIOStream *iostream;
+  GError *error = NULL;
+  GCancellable *cancellable;
+  GMainLoop *loop;
+  AsyncErrorData error_data = {0};
+  gchar *tmpdir_path;
+  gchar *target_path;
+
+  target = g_file_new_tmp ("g_file_symlink_target_XXXXXX", &iostream, &error);
+  g_assert_no_error (error);
+
+  g_io_stream_close ((GIOStream *) iostream, NULL, &error);
+  g_assert_no_error (error);
+  g_object_unref (iostream);
+
+  g_assert_true (g_file_query_exists (target, NULL));
+
+  loop = g_main_loop_new (NULL, TRUE);
+  error_data.loop = loop;
+  error_data.error = &error;
+
+  tmpdir_path = g_dir_make_tmp ("g_file_symlink_XXXXXX", &error);
+  g_assert_no_error (error);
+
+  parent_dir = g_file_new_for_path (tmpdir_path);
+  g_assert_true (g_file_query_exists (parent_dir, NULL));
+
+  link = g_file_get_child (parent_dir, "symlink");
+  g_assert_false (g_file_query_exists (link, NULL));
+
+  g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
+                         "*assertion*symlink_value*failed*");
+  g_file_make_symbolic_link_async (link, NULL,
+                                   G_PRIORITY_DEFAULT, NULL,
+                                   on_symlink_done, loop);
+  g_test_assert_expected_messages ();
+
+  g_file_make_symbolic_link_async (link, "",
+                                   G_PRIORITY_DEFAULT, NULL,
+                                   on_symlink_error, &error_data);
+  g_main_loop_run (loop);
+  g_assert_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT);
+  g_clear_error (&error);
+
+  target_path = g_file_get_path (target);
+  g_file_make_symbolic_link_async (link, target_path,
+                                   G_PRIORITY_DEFAULT, NULL,
+                                   on_symlink_done, loop);
+  g_main_loop_run (loop);
+
+  g_assert_true (g_file_query_exists (link, NULL));
+  link_info = g_file_query_info (link,
+                                 G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK ","
+                                 G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET,
+                                 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                 NULL,
+                                 &error);
+  g_assert_no_error (error);
+
+  g_assert_true (g_file_info_get_is_symlink (link_info));
+  g_assert_cmpstr (target_path, ==, g_file_info_get_symlink_target (link_info));
+
+  /* Try creating it again, it fails */
+  g_file_make_symbolic_link_async (link, target_path,
+                                   G_PRIORITY_DEFAULT, NULL,
+                                   on_symlink_error, &error_data);
+  g_main_loop_run (loop);
+  g_assert_error (error, G_IO_ERROR, G_IO_ERROR_EXISTS);
+  g_clear_error (&error);
+
+  cancellable = g_cancellable_new ();
+  g_file_make_symbolic_link_async (link, target_path,
+                                   G_PRIORITY_DEFAULT, cancellable,
+                                   on_symlink_error, &error_data);
+  g_cancellable_cancel (cancellable);
+  g_main_loop_run (loop);
+  g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+  g_clear_error (&error);
+  g_clear_object (&cancellable);
+
+  g_main_loop_unref (loop);
+  g_object_unref (target);
+  g_object_unref (parent_dir);
+  g_object_unref (link);
+  g_object_unref (link_info);
+  g_free (tmpdir_path);
+  g_free (target_path);
+}
+
 static void
 test_copy_preserve_mode (void)
 {
@@ -3163,6 +3296,7 @@ main (int argc, char *argv[])
   g_test_add_data_func ("/file/replace/write-only", GUINT_TO_POINTER (FALSE), test_replace);
   g_test_add_data_func ("/file/replace/read-write", GUINT_TO_POINTER (TRUE), test_replace);
   g_test_add_func ("/file/async-delete", test_async_delete);
+  g_test_add_func ("/file/async-make-symlink", test_async_make_symlink);
   g_test_add_func ("/file/copy-preserve-mode", test_copy_preserve_mode);
   g_test_add_func ("/file/measure", test_measure);
   g_test_add_func ("/file/measure-async", test_measure_async);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]