diff --git a/docs/reference/gio/gio-sections-common.txt b/docs/reference/gio/gio-sections-common.txt index 603338ffb..a1537f0df 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 4aae0ed0d..52113a91c 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 f2bffc114..4e95c16e4 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 28643648d..ab3bde2a8 100644 --- a/gio/tests/file.c +++ b/gio/tests/file.c @@ -8,6 +8,12 @@ #include #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);