Merge branch 'feature/move_async' into 'main'

Implement async file movement

See merge request GNOME/glib!2469
This commit is contained in:
Philip Withnall 2022-02-07 14:29:52 +00:00
commit 50d23f9680
4 changed files with 358 additions and 8 deletions

View File

@ -149,6 +149,8 @@ g_file_copy
g_file_copy_async
g_file_copy_finish
g_file_move
g_file_move_async
g_file_move_finish
g_file_make_directory
g_file_make_directory_async
g_file_make_directory_finish

View File

@ -247,6 +247,18 @@ static void g_file_real_trash_async (GFile
static gboolean g_file_real_trash_finish (GFile *file,
GAsyncResult *res,
GError **error);
static void g_file_real_move_async (GFile *source,
GFile *destination,
GFileCopyFlags flags,
int io_priority,
GCancellable *cancellable,
GFileProgressCallback progress_callback,
gpointer progress_callback_data,
GAsyncReadyCallback callback,
gpointer user_data);
static gboolean g_file_real_move_finish (GFile *file,
GAsyncResult *result,
GError **error);
static void g_file_real_make_directory_async (GFile *file,
int io_priority,
GCancellable *cancellable,
@ -381,6 +393,8 @@ g_file_default_init (GFileIface *iface)
iface->delete_file_finish = g_file_real_delete_finish;
iface->trash_async = g_file_real_trash_async;
iface->trash_finish = g_file_real_trash_finish;
iface->move_async = g_file_real_move_async;
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->open_readwrite_async = g_file_real_open_readwrite_async;
@ -3768,6 +3782,91 @@ g_file_move (GFile *source,
return g_file_delete (source, cancellable, error);
}
/**
* g_file_move_async:
* @source: #GFile pointing to the source location
* @destination: #GFile pointing to the destination location
* @flags: set of #GFileCopyFlags
* @io_priority: the [I/O priority][io-priority] of the request
* @cancellable: (nullable): optional #GCancellable object,
* %NULL to ignore
* @progress_callback: (nullable) (scope call): #GFileProgressCallback
* function for updates
* @progress_callback_data: (closure): gpointer to user data for
* the callback function
* @callback: a #GAsyncReadyCallback to call
* when the request is satisfied
* @user_data: the data to pass to callback function
*
* Asynchronously moves a file @source to the location of @destination. For details of the behaviour, see g_file_move().
*
* If @progress_callback is not %NULL, then that function that will be called
* just like in g_file_move(). The callback will run in the default main context
* of the thread calling g_file_move_async() the same context as @callback is
* run in.
*
* When the operation is finished, @callback will be called. You can then call
* g_file_move_finish() to get the result of the operation.
*
* Since: 2.72
*/
void
g_file_move_async (GFile *source,
GFile *destination,
GFileCopyFlags flags,
int io_priority,
GCancellable *cancellable,
GFileProgressCallback progress_callback,
gpointer progress_callback_data,
GAsyncReadyCallback callback,
gpointer user_data)
{
GFileIface *iface;
g_return_if_fail (G_IS_FILE (source));
g_return_if_fail (G_IS_FILE (destination));
g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
iface = G_FILE_GET_IFACE (source);
(* iface->move_async) (source,
destination,
flags,
io_priority,
cancellable,
progress_callback,
progress_callback_data,
callback,
user_data);
}
/**
* g_file_move_finish:
* @file: input source #GFile
* @result: a #GAsyncResult
* @error: a #GError, or %NULL
*
* Finishes an asynchronous file movement, started with
* g_file_move_async().
*
* Returns: %TRUE on successful file move, %FALSE otherwise.
*
* Since: 2.72
*/
gboolean
g_file_move_finish (GFile *file,
GAsyncResult *result,
GError **error)
{
GFileIface *iface;
g_return_val_if_fail (G_IS_FILE (file), FALSE);
g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
iface = G_FILE_GET_IFACE (file);
return (* iface->move_finish) (file, result, error);
}
/**
* g_file_make_directory:
* @file: input #GFile
@ -6002,6 +6101,125 @@ g_file_real_trash_finish (GFile *file,
return g_task_propagate_boolean (G_TASK (res), error);
}
typedef struct {
GFile *source; /* (owned) */
GFile *destination; /* (owned) */
GFileCopyFlags flags;
GFileProgressCallback progress_cb;
gpointer progress_cb_data;
} MoveAsyncData;
static void
move_async_data_free (MoveAsyncData *data)
{
g_object_unref (data->source);
g_object_unref (data->destination);
g_slice_free (MoveAsyncData, data);
}
typedef struct {
MoveAsyncData *data; /* (unowned) */
goffset current_num_bytes;
goffset total_num_bytes;
} MoveProgressData;
static gboolean
move_async_progress_in_main (gpointer user_data)
{
MoveProgressData *progress = user_data;
MoveAsyncData *data = progress->data;
data->progress_cb (progress->current_num_bytes,
progress->total_num_bytes,
data->progress_cb_data);
return G_SOURCE_REMOVE;
}
static void
move_async_progress_callback (goffset current_num_bytes,
goffset total_num_bytes,
gpointer user_data)
{
GTask *task = user_data;
MoveAsyncData *data = g_task_get_task_data (task);
MoveProgressData *progress;
progress = g_new0 (MoveProgressData, 1);
progress->data = data;
progress->current_num_bytes = current_num_bytes;
progress->total_num_bytes = total_num_bytes;
g_main_context_invoke_full (g_task_get_context (task),
g_task_get_priority (task),
move_async_progress_in_main,
g_steal_pointer (&progress),
g_free);
}
static void
move_async_thread (GTask *task,
gpointer source,
gpointer task_data,
GCancellable *cancellable)
{
MoveAsyncData *data = task_data;
gboolean result;
GError *error = NULL;
result = g_file_move (data->source,
data->destination,
data->flags,
cancellable,
(data->progress_cb != NULL) ? move_async_progress_callback : NULL,
task,
&error);
if (result)
g_task_return_boolean (task, TRUE);
else
g_task_return_error (task, g_steal_pointer (&error));
}
static void
g_file_real_move_async (GFile *source,
GFile *destination,
GFileCopyFlags flags,
int io_priority,
GCancellable *cancellable,
GFileProgressCallback progress_callback,
gpointer progress_callback_data,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
MoveAsyncData *data;
data = g_slice_new0 (MoveAsyncData);
data->source = g_object_ref (source);
data->destination = g_object_ref (destination);
data->flags = flags;
data->progress_cb = progress_callback;
data->progress_cb_data = progress_callback_data;
task = g_task_new (source, cancellable, callback, user_data);
g_task_set_source_tag (task, g_file_real_move_async);
g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) move_async_data_free);
g_task_set_priority (task, io_priority);
g_task_run_in_thread (task, move_async_thread);
g_object_unref (task);
}
static gboolean
g_file_real_move_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);
}
static void
make_directory_async_thread (GTask *task,
gpointer object,
@ -6401,12 +6619,12 @@ typedef struct {
CopyAsyncData *data;
goffset current_num_bytes;
goffset total_num_bytes;
} ProgressData;
} CopyProgressData;
static gboolean
copy_async_progress_in_main (gpointer user_data)
{
ProgressData *progress = user_data;
CopyProgressData *progress = user_data;
CopyAsyncData *data = progress->data;
data->progress_cb (progress->current_num_bytes,
@ -6423,9 +6641,9 @@ copy_async_progress_callback (goffset current_num_bytes,
{
GTask *task = user_data;
CopyAsyncData *data = g_task_get_task_data (task);
ProgressData *progress;
CopyProgressData *progress;
progress = g_new (ProgressData, 1);
progress = g_new (CopyProgressData, 1);
progress->data = data;
progress->current_num_bytes = current_num_bytes;
progress->total_num_bytes = total_num_bytes;

View File

@ -121,8 +121,8 @@ typedef struct _GFileIface GFileIface;
* @copy_async: Asynchronously copies a file.
* @copy_finish: Finishes an asynchronous copy operation.
* @move: Moves a file.
* @_move_async: Asynchronously moves a file.
* @_move_finish: Finishes an asynchronous move operation.
* @move_async: Asynchronously moves a file. Since: 2.72
* @move_finish: Finishes an asynchronous move operation. Since: 2.72
* @mount_mountable: Mounts a mountable object.
* @mount_mountable_finish: Finishes a mounting operation.
* @unmount_mountable: Unmounts a mountable object.
@ -424,8 +424,18 @@ struct _GFileIface
GFileProgressCallback progress_callback,
gpointer progress_callback_data,
GError **error);
void (* _move_async) (void);
void (* _move_finish) (void);
void (* move_async) (GFile *source,
GFile *destination,
GFileCopyFlags flags,
int io_priority,
GCancellable *cancellable,
GFileProgressCallback progress_callback,
gpointer progress_callback_data,
GAsyncReadyCallback callback,
gpointer user_data);
gboolean (* move_finish) (GFile *file,
GAsyncResult *result,
GError **error);
void (* mount_mountable) (GFile *file,
GMountMountFlags flags,
@ -926,6 +936,20 @@ gboolean g_file_move (GFile
GFileProgressCallback progress_callback,
gpointer progress_callback_data,
GError **error);
GLIB_AVAILABLE_IN_2_72
void g_file_move_async (GFile *source,
GFile *destination,
GFileCopyFlags flags,
int io_priority,
GCancellable *cancellable,
GFileProgressCallback progress_callback,
gpointer progress_callback_data,
GAsyncReadyCallback callback,
gpointer user_data);
GLIB_AVAILABLE_IN_2_72
gboolean g_file_move_finish (GFile *file,
GAsyncResult *result,
GError **error);
GLIB_AVAILABLE_IN_ALL
gboolean g_file_make_directory (GFile *file,
GCancellable *cancellable,

View File

@ -3006,6 +3006,111 @@ test_build_attribute_list_for_copy (void)
g_clear_object (&tmpfile);
}
typedef struct
{
GError *error;
gboolean done;
gboolean res;
} MoveAsyncData;
static void
test_move_async_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
GFile *file = G_FILE (object);
MoveAsyncData *data = user_data;
GError *error = NULL;
data->res = g_file_move_finish (file, result, &error);
data->error = error;
data->done = TRUE;
}
typedef struct
{
goffset total_num_bytes;
} MoveAsyncProgressData;
static void
test_move_async_progress_cb (goffset current_num_bytes,
goffset total_num_bytes,
gpointer user_data)
{
MoveAsyncProgressData *data = user_data;
data->total_num_bytes = total_num_bytes;
}
/* Test that move_async() moves the file correctly */
static void
test_move_async (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};
source = g_file_new_tmp ("g_file_move_XXXXXX", &iostream, NULL);
destination_path = g_build_path (G_DIR_SEPARATOR_S, g_get_tmp_dir (), "g_file_move_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 amount 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);
g_file_move_async (source,
destination,
G_FILE_COPY_NONE,
0,
NULL,
test_move_async_progress_cb,
&progress_data,
test_move_async_cb,
&data);
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);
}
int
main (int argc, char *argv[])
{
@ -3049,6 +3154,7 @@ main (int argc, char *argv[])
g_test_add_func ("/file/writev/async_all-to-big-vectors", test_writev_async_all_too_big_vectors);
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);
return g_test_run ();
}