mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-02-02 17:26:17 +01:00
gfile: Add g_file_{copy,move}_async_with_closures
g_file_copy_async() and g_file_move_async() are written in a way that is not bindable with gobject-introspection. The progress callback data can be freed once the async callback has been called, which is convenient for C, but in language bindings the progress callback closure is currently just leaked. There is no scope annotation that fits how the progress callback should be treated: - (scope call) is correct for the sync versions of the functions, but incorrect for the async functions; the progress callback is called after the async functions return. - (scope notified) is incorrect because there is no GDestroyNotify parameter, meaning the callback will always leak. - (scope async) is incorrect because the callback is called more than once. - (scope forever) is incorrect because the callback closure could be freed after the async callback runs. This adds g_file_copy_async_with_closures() and g_file_move_async_with_closures() for the benefit of language bindings. See: GNOME/gjs#590
This commit is contained in:
parent
afcb839121
commit
64b06c633a
153
gio/gfile.c
153
gio/gfile.c
@ -3818,6 +3818,123 @@ g_file_copy_async (GFile *source,
|
||||
user_data);
|
||||
}
|
||||
|
||||
typedef struct _CopyAsyncClosuresData
|
||||
{
|
||||
GClosure *progress_callback_closure;
|
||||
GClosure *ready_callback_closure;
|
||||
} CopyAsyncClosuresData;
|
||||
|
||||
static CopyAsyncClosuresData *
|
||||
copy_async_closures_data_new (GClosure *progress_callback_closure,
|
||||
GClosure *ready_callback_closure)
|
||||
{
|
||||
CopyAsyncClosuresData *data;
|
||||
|
||||
data = g_new0 (CopyAsyncClosuresData, 1);
|
||||
|
||||
if (progress_callback_closure != NULL)
|
||||
{
|
||||
data->progress_callback_closure = g_closure_ref (progress_callback_closure);
|
||||
g_closure_sink (progress_callback_closure);
|
||||
if (G_CLOSURE_NEEDS_MARSHAL (progress_callback_closure))
|
||||
g_closure_set_marshal (progress_callback_closure, g_cclosure_marshal_generic);
|
||||
}
|
||||
|
||||
data->ready_callback_closure = g_closure_ref (ready_callback_closure);
|
||||
g_closure_sink (ready_callback_closure);
|
||||
if (G_CLOSURE_NEEDS_MARSHAL (ready_callback_closure))
|
||||
g_closure_set_marshal (ready_callback_closure, g_cclosure_marshal_generic);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static void
|
||||
copy_async_closures_data_free (CopyAsyncClosuresData *data)
|
||||
{
|
||||
if (data->progress_callback_closure != NULL)
|
||||
g_closure_unref (data->progress_callback_closure);
|
||||
|
||||
g_closure_unref (data->ready_callback_closure);
|
||||
|
||||
g_free (data);
|
||||
}
|
||||
|
||||
static void
|
||||
copy_async_invoke_progress (goffset current_num_bytes,
|
||||
goffset total_num_bytes,
|
||||
void *user_data)
|
||||
{
|
||||
CopyAsyncClosuresData *data = (CopyAsyncClosuresData *) user_data;
|
||||
GValue params[2] = { G_VALUE_INIT, G_VALUE_INIT };
|
||||
|
||||
/* goffset is 64-bits even on 32-bits platforms */
|
||||
g_value_init (¶ms[0], G_TYPE_INT64);
|
||||
g_value_set_int64 (¶ms[0], current_num_bytes);
|
||||
g_value_init (¶ms[1], G_TYPE_INT64);
|
||||
g_value_set_int64 (¶ms[1], total_num_bytes);
|
||||
|
||||
g_closure_invoke (data->progress_callback_closure, /* result = */ NULL, 2, params, /* hint = */ NULL);
|
||||
|
||||
g_value_unset (¶ms[0]);
|
||||
g_value_unset (¶ms[1]);
|
||||
}
|
||||
|
||||
static void
|
||||
copy_async_invoke_ready (GObject *file,
|
||||
GAsyncResult *result,
|
||||
void *user_data)
|
||||
{
|
||||
CopyAsyncClosuresData *data = (CopyAsyncClosuresData *) user_data;
|
||||
GValue params[2] = { G_VALUE_INIT, G_VALUE_INIT };
|
||||
|
||||
g_value_init (¶ms[0], G_TYPE_FILE);
|
||||
g_value_set_object (¶ms[0], file);
|
||||
g_value_init (¶ms[1], G_TYPE_ASYNC_RESULT);
|
||||
g_value_set_object (¶ms[1], result);
|
||||
|
||||
g_closure_invoke (data->ready_callback_closure, /* result = */ NULL, 2, params, /* hint = */ NULL);
|
||||
|
||||
copy_async_closures_data_free (data);
|
||||
g_value_unset (¶ms[0]);
|
||||
g_value_unset (¶ms[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* g_file_copy_async_with_closures: (rename-to g_file_copy_async) (finish-func g_file_copy_finish):
|
||||
* @source: input [type@Gio.File]
|
||||
* @destination: destination [type@Gio.File]
|
||||
* @flags: set of [flags@Gio.FileCopyFlags]
|
||||
* @io_priority: the [I/O priority](iface.AsyncResult.html#io-priority) of the request
|
||||
* @cancellable: (nullable): optional [class@Gio.Cancellable] object,
|
||||
* `NULL` to ignore
|
||||
* @progress_callback_closure: (nullable): [type@GObject.Closure] to invoke with progress
|
||||
* information, or `NULL` if progress information is not needed
|
||||
* @ready_callback_closure: (not nullable): [type@GObject.Closure] to invoke when the request is satisfied
|
||||
*
|
||||
* Version of [method@Gio.File.copy_async] using closures instead of callbacks for
|
||||
* easier binding in other languages.
|
||||
*
|
||||
* Since: 2.82
|
||||
*/
|
||||
void
|
||||
g_file_copy_async_with_closures (GFile *source,
|
||||
GFile *destination,
|
||||
GFileCopyFlags flags,
|
||||
int io_priority,
|
||||
GCancellable *cancellable,
|
||||
GClosure *progress_callback_closure,
|
||||
GClosure *ready_callback_closure)
|
||||
{
|
||||
CopyAsyncClosuresData *data;
|
||||
|
||||
/* freed in copy_async_invoke_ready */
|
||||
data = copy_async_closures_data_new (progress_callback_closure, ready_callback_closure);
|
||||
|
||||
g_file_copy_async (source, destination, flags, io_priority, cancellable,
|
||||
progress_callback_closure == NULL ? NULL : copy_async_invoke_progress, data,
|
||||
copy_async_invoke_ready, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* g_file_copy_finish:
|
||||
* @file: input #GFile
|
||||
@ -4035,6 +4152,42 @@ g_file_move_async (GFile *source,
|
||||
user_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* g_file_move_async_with_closures: (rename-to g_file_move_async) (finish-func g_file_move_finish):
|
||||
* @source: input [type@Gio.File]
|
||||
* @destination: destination [type@Gio.File]
|
||||
* @flags: set of [flags@Gio.FileCopyFlags]
|
||||
* @io_priority: the [I/O priority](iface.AsyncResult.html#io-priority) of the request
|
||||
* @cancellable: (nullable): optional [class@Gio.Cancellable] object,
|
||||
* `NULL` to ignore
|
||||
* @progress_callback_closure: (nullable): [type@GObject.Closure] to invoke with progress
|
||||
* information, or `NULL` if progress information is not needed
|
||||
* @ready_callback_closure: (not nullable): [type@GObject.Closure] to invoke when the request is satisfied
|
||||
*
|
||||
* Version of [method@Gio.File.move_async] using closures instead of callbacks for
|
||||
* easier binding in other languages.
|
||||
*
|
||||
* Since: 2.82
|
||||
*/
|
||||
void
|
||||
g_file_move_async_with_closures (GFile *source,
|
||||
GFile *destination,
|
||||
GFileCopyFlags flags,
|
||||
int io_priority,
|
||||
GCancellable *cancellable,
|
||||
GClosure *progress_callback_closure,
|
||||
GClosure *ready_callback_closure)
|
||||
{
|
||||
CopyAsyncClosuresData *data;
|
||||
|
||||
/* freed in copy_async_invoke_ready */
|
||||
data = copy_async_closures_data_new (progress_callback_closure, ready_callback_closure);
|
||||
|
||||
g_file_move_async (source, destination, flags, io_priority, cancellable,
|
||||
progress_callback_closure == NULL ? NULL : copy_async_invoke_progress, data,
|
||||
copy_async_invoke_ready, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* g_file_move_finish:
|
||||
* @file: input source #GFile
|
||||
|
16
gio/gfile.h
16
gio/gfile.h
@ -944,6 +944,14 @@ void g_file_copy_async (GFile
|
||||
gpointer progress_callback_data,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
GIO_AVAILABLE_IN_2_82
|
||||
void g_file_copy_async_with_closures (GFile *source,
|
||||
GFile *destination,
|
||||
GFileCopyFlags flags,
|
||||
int io_priority,
|
||||
GCancellable *cancellable,
|
||||
GClosure *progress_callback_closure,
|
||||
GClosure *ready_callback_closure);
|
||||
GIO_AVAILABLE_IN_ALL
|
||||
gboolean g_file_copy_finish (GFile *file,
|
||||
GAsyncResult *res,
|
||||
@ -966,6 +974,14 @@ void g_file_move_async (GFile
|
||||
gpointer progress_callback_data,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
GIO_AVAILABLE_IN_2_82
|
||||
void g_file_move_async_with_closures (GFile *source,
|
||||
GFile *destination,
|
||||
GFileCopyFlags flags,
|
||||
int io_priority,
|
||||
GCancellable *cancellable,
|
||||
GClosure *progress_callback_closure,
|
||||
GClosure *ready_callback_closure);
|
||||
GIO_AVAILABLE_IN_2_72
|
||||
gboolean g_file_move_finish (GFile *file,
|
||||
GAsyncResult *result,
|
||||
|
189
gio/tests/file.c
189
gio/tests/file.c
@ -2606,6 +2606,119 @@ test_copy_progress (void)
|
||||
g_clear_object (&dest_tmpfile);
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GError *error;
|
||||
gboolean done;
|
||||
gboolean res;
|
||||
} CopyAsyncData;
|
||||
|
||||
static void
|
||||
test_copy_async_cb (GObject *object,
|
||||
GAsyncResult *result,
|
||||
void *user_data)
|
||||
{
|
||||
GFile *file = G_FILE (object);
|
||||
CopyAsyncData *data = user_data;
|
||||
GError *error = NULL;
|
||||
|
||||
data->res = g_file_move_finish (file, result, &error);
|
||||
data->error = g_steal_pointer (&error);
|
||||
data->done = TRUE;
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
goffset total_num_bytes;
|
||||
} CopyAsyncProgressData;
|
||||
|
||||
static void
|
||||
test_copy_async_progress_cb (goffset current_num_bytes,
|
||||
goffset total_num_bytes,
|
||||
void *user_data)
|
||||
{
|
||||
CopyAsyncProgressData *data = user_data;
|
||||
data->total_num_bytes = total_num_bytes;
|
||||
}
|
||||
|
||||
/* Exercise copy_async_with_closures() */
|
||||
static void
|
||||
test_copy_async_with_closures (void)
|
||||
{
|
||||
CopyAsyncData data = { 0 };
|
||||
CopyAsyncProgressData progress_data = { 0 };
|
||||
GFile *source;
|
||||
GFileIOStream *iostream;
|
||||
GOutputStream *ostream;
|
||||
GFile *destination;
|
||||
gchar *destination_path;
|
||||
GError *error = NULL;
|
||||
gboolean res;
|
||||
const guint8 buffer[] = { 1, 2, 3, 4, 5 };
|
||||
GClosure *progress_closure;
|
||||
GClosure *ready_closure;
|
||||
|
||||
source = g_file_new_tmp ("g_file_copy_async_with_closures_XXXXXX", &iostream, NULL);
|
||||
|
||||
destination_path = g_build_path (G_DIR_SEPARATOR_S, g_get_tmp_dir (), "g_file_copy_async_with_closures_target", NULL);
|
||||
destination = g_file_new_for_path (destination_path);
|
||||
|
||||
g_assert_nonnull (source);
|
||||
g_assert_nonnull (iostream);
|
||||
|
||||
res = g_file_query_exists (source, NULL);
|
||||
g_assert_true (res);
|
||||
res = g_file_query_exists (destination, NULL);
|
||||
g_assert_false (res);
|
||||
|
||||
/* Write a known number of bytes to the file, so we can test the progress
|
||||
* callback against it */
|
||||
ostream = g_io_stream_get_output_stream (G_IO_STREAM (iostream));
|
||||
g_output_stream_write (ostream, buffer, sizeof (buffer), NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
progress_closure = g_cclosure_new (G_CALLBACK (test_copy_async_progress_cb), &progress_data, NULL);
|
||||
ready_closure = g_cclosure_new (G_CALLBACK (test_copy_async_cb), &data, NULL);
|
||||
|
||||
g_file_copy_async_with_closures (source,
|
||||
destination,
|
||||
G_FILE_COPY_NONE,
|
||||
0,
|
||||
NULL,
|
||||
progress_closure,
|
||||
ready_closure);
|
||||
|
||||
while (!data.done)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
g_assert_no_error (data.error);
|
||||
g_assert_true (data.res);
|
||||
g_assert_cmpuint (progress_data.total_num_bytes, ==, sizeof (buffer));
|
||||
|
||||
res = g_file_query_exists (source, NULL);
|
||||
g_assert_true (res);
|
||||
res = g_file_query_exists (destination, NULL);
|
||||
g_assert_true (res);
|
||||
|
||||
res = g_io_stream_close (G_IO_STREAM (iostream), NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_true (res);
|
||||
g_object_unref (iostream);
|
||||
|
||||
res = g_file_delete (source, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_true (res);
|
||||
|
||||
res = g_file_delete (destination, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_true (res);
|
||||
|
||||
g_object_unref (source);
|
||||
g_object_unref (destination);
|
||||
|
||||
g_free (destination_path);
|
||||
}
|
||||
|
||||
static void
|
||||
test_measure (void)
|
||||
{
|
||||
@ -3577,6 +3690,80 @@ test_move_async (void)
|
||||
g_free (destination_path);
|
||||
}
|
||||
|
||||
/* Same test as for move_async(), but for move_async_with_closures() */
|
||||
static void
|
||||
test_move_async_with_closures (void)
|
||||
{
|
||||
MoveAsyncData data = { 0 };
|
||||
MoveAsyncProgressData progress_data = { 0 };
|
||||
GFile *source;
|
||||
GFileIOStream *iostream;
|
||||
GOutputStream *ostream;
|
||||
GFile *destination;
|
||||
gchar *destination_path;
|
||||
GError *error = NULL;
|
||||
gboolean res;
|
||||
const guint8 buffer[] = { 1, 2, 3, 4, 5 };
|
||||
GClosure *progress_closure;
|
||||
GClosure *ready_closure;
|
||||
|
||||
source = g_file_new_tmp ("g_file_move_async_with_closures_XXXXXX", &iostream, NULL);
|
||||
|
||||
destination_path = g_build_path (G_DIR_SEPARATOR_S, g_get_tmp_dir (), "g_file_move_async_with_closures_target", NULL);
|
||||
destination = g_file_new_for_path (destination_path);
|
||||
|
||||
g_assert_nonnull (source);
|
||||
g_assert_nonnull (iostream);
|
||||
|
||||
res = g_file_query_exists (source, NULL);
|
||||
g_assert_true (res);
|
||||
res = g_file_query_exists (destination, NULL);
|
||||
g_assert_false (res);
|
||||
|
||||
/* Write a known number of bytes to the file, so we can test the progress
|
||||
* callback against it */
|
||||
ostream = g_io_stream_get_output_stream (G_IO_STREAM (iostream));
|
||||
g_output_stream_write (ostream, buffer, sizeof (buffer), NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
progress_closure = g_cclosure_new (G_CALLBACK (test_move_async_progress_cb), &progress_data, NULL);
|
||||
ready_closure = g_cclosure_new (G_CALLBACK (test_move_async_cb), &data, NULL);
|
||||
|
||||
g_file_move_async_with_closures (source,
|
||||
destination,
|
||||
G_FILE_COPY_NONE,
|
||||
0,
|
||||
NULL,
|
||||
progress_closure,
|
||||
ready_closure);
|
||||
|
||||
while (!data.done)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
g_assert_no_error (data.error);
|
||||
g_assert_true (data.res);
|
||||
g_assert_cmpuint (progress_data.total_num_bytes, ==, sizeof (buffer));
|
||||
|
||||
res = g_file_query_exists (source, NULL);
|
||||
g_assert_false (res);
|
||||
res = g_file_query_exists (destination, NULL);
|
||||
g_assert_true (res);
|
||||
|
||||
res = g_io_stream_close (G_IO_STREAM (iostream), NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_true (res);
|
||||
g_object_unref (iostream);
|
||||
|
||||
res = g_file_delete (destination, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_true (res);
|
||||
|
||||
g_object_unref (source);
|
||||
g_object_unref (destination);
|
||||
|
||||
g_free (destination_path);
|
||||
}
|
||||
|
||||
static GAppInfo *
|
||||
create_command_line_app_info (const char *name,
|
||||
const char *command_line,
|
||||
@ -4028,6 +4215,7 @@ main (int argc, char *argv[])
|
||||
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/copy/progress", test_copy_progress);
|
||||
g_test_add_func ("/file/copy-async-with-closurse", test_copy_async_with_closures);
|
||||
g_test_add_func ("/file/measure", test_measure);
|
||||
g_test_add_func ("/file/measure-async", test_measure_async);
|
||||
g_test_add_func ("/file/load-bytes", test_load_bytes);
|
||||
@ -4045,6 +4233,7 @@ main (int argc, char *argv[])
|
||||
g_test_add_func ("/file/writev/async_all-cancellation", test_writev_async_all_cancellation);
|
||||
g_test_add_func ("/file/build-attribute-list-for-copy", test_build_attribute_list_for_copy);
|
||||
g_test_add_func ("/file/move_async", test_move_async);
|
||||
g_test_add_func ("/file/move-async-with-closures", test_move_async_with_closures);
|
||||
g_test_add_func ("/file/query-zero-length-content-type", test_query_zero_length_content_type);
|
||||
g_test_add_func ("/file/query-default-handler-file", test_query_default_handler_file);
|
||||
g_test_add_func ("/file/query-default-handler-file-async", test_query_default_handler_file_async);
|
||||
|
Loading…
Reference in New Issue
Block a user