mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-04-01 13:23:07 +02:00
Merge branch 'feature/move_async' into 'main'
Implement async file movement See merge request GNOME/glib!2469
This commit is contained in:
commit
50d23f9680
@ -149,6 +149,8 @@ g_file_copy
|
|||||||
g_file_copy_async
|
g_file_copy_async
|
||||||
g_file_copy_finish
|
g_file_copy_finish
|
||||||
g_file_move
|
g_file_move
|
||||||
|
g_file_move_async
|
||||||
|
g_file_move_finish
|
||||||
g_file_make_directory
|
g_file_make_directory
|
||||||
g_file_make_directory_async
|
g_file_make_directory_async
|
||||||
g_file_make_directory_finish
|
g_file_make_directory_finish
|
||||||
|
226
gio/gfile.c
226
gio/gfile.c
@ -247,6 +247,18 @@ static void g_file_real_trash_async (GFile
|
|||||||
static gboolean g_file_real_trash_finish (GFile *file,
|
static gboolean g_file_real_trash_finish (GFile *file,
|
||||||
GAsyncResult *res,
|
GAsyncResult *res,
|
||||||
GError **error);
|
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,
|
static void g_file_real_make_directory_async (GFile *file,
|
||||||
int io_priority,
|
int io_priority,
|
||||||
GCancellable *cancellable,
|
GCancellable *cancellable,
|
||||||
@ -381,6 +393,8 @@ g_file_default_init (GFileIface *iface)
|
|||||||
iface->delete_file_finish = g_file_real_delete_finish;
|
iface->delete_file_finish = g_file_real_delete_finish;
|
||||||
iface->trash_async = g_file_real_trash_async;
|
iface->trash_async = g_file_real_trash_async;
|
||||||
iface->trash_finish = g_file_real_trash_finish;
|
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_async = g_file_real_make_directory_async;
|
||||||
iface->make_directory_finish = g_file_real_make_directory_finish;
|
iface->make_directory_finish = g_file_real_make_directory_finish;
|
||||||
iface->open_readwrite_async = g_file_real_open_readwrite_async;
|
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);
|
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:
|
* g_file_make_directory:
|
||||||
* @file: input #GFile
|
* @file: input #GFile
|
||||||
@ -6002,6 +6101,125 @@ g_file_real_trash_finish (GFile *file,
|
|||||||
return g_task_propagate_boolean (G_TASK (res), error);
|
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
|
static void
|
||||||
make_directory_async_thread (GTask *task,
|
make_directory_async_thread (GTask *task,
|
||||||
gpointer object,
|
gpointer object,
|
||||||
@ -6401,12 +6619,12 @@ typedef struct {
|
|||||||
CopyAsyncData *data;
|
CopyAsyncData *data;
|
||||||
goffset current_num_bytes;
|
goffset current_num_bytes;
|
||||||
goffset total_num_bytes;
|
goffset total_num_bytes;
|
||||||
} ProgressData;
|
} CopyProgressData;
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
copy_async_progress_in_main (gpointer user_data)
|
copy_async_progress_in_main (gpointer user_data)
|
||||||
{
|
{
|
||||||
ProgressData *progress = user_data;
|
CopyProgressData *progress = user_data;
|
||||||
CopyAsyncData *data = progress->data;
|
CopyAsyncData *data = progress->data;
|
||||||
|
|
||||||
data->progress_cb (progress->current_num_bytes,
|
data->progress_cb (progress->current_num_bytes,
|
||||||
@ -6423,9 +6641,9 @@ copy_async_progress_callback (goffset current_num_bytes,
|
|||||||
{
|
{
|
||||||
GTask *task = user_data;
|
GTask *task = user_data;
|
||||||
CopyAsyncData *data = g_task_get_task_data (task);
|
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->data = data;
|
||||||
progress->current_num_bytes = current_num_bytes;
|
progress->current_num_bytes = current_num_bytes;
|
||||||
progress->total_num_bytes = total_num_bytes;
|
progress->total_num_bytes = total_num_bytes;
|
||||||
|
32
gio/gfile.h
32
gio/gfile.h
@ -121,8 +121,8 @@ typedef struct _GFileIface GFileIface;
|
|||||||
* @copy_async: Asynchronously copies a file.
|
* @copy_async: Asynchronously copies a file.
|
||||||
* @copy_finish: Finishes an asynchronous copy operation.
|
* @copy_finish: Finishes an asynchronous copy operation.
|
||||||
* @move: Moves a file.
|
* @move: Moves a file.
|
||||||
* @_move_async: Asynchronously moves a file.
|
* @move_async: Asynchronously moves a file. Since: 2.72
|
||||||
* @_move_finish: Finishes an asynchronous move operation.
|
* @move_finish: Finishes an asynchronous move operation. Since: 2.72
|
||||||
* @mount_mountable: Mounts a mountable object.
|
* @mount_mountable: Mounts a mountable object.
|
||||||
* @mount_mountable_finish: Finishes a mounting operation.
|
* @mount_mountable_finish: Finishes a mounting operation.
|
||||||
* @unmount_mountable: Unmounts a mountable object.
|
* @unmount_mountable: Unmounts a mountable object.
|
||||||
@ -424,8 +424,18 @@ struct _GFileIface
|
|||||||
GFileProgressCallback progress_callback,
|
GFileProgressCallback progress_callback,
|
||||||
gpointer progress_callback_data,
|
gpointer progress_callback_data,
|
||||||
GError **error);
|
GError **error);
|
||||||
void (* _move_async) (void);
|
void (* move_async) (GFile *source,
|
||||||
void (* _move_finish) (void);
|
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,
|
void (* mount_mountable) (GFile *file,
|
||||||
GMountMountFlags flags,
|
GMountMountFlags flags,
|
||||||
@ -926,6 +936,20 @@ gboolean g_file_move (GFile
|
|||||||
GFileProgressCallback progress_callback,
|
GFileProgressCallback progress_callback,
|
||||||
gpointer progress_callback_data,
|
gpointer progress_callback_data,
|
||||||
GError **error);
|
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
|
GLIB_AVAILABLE_IN_ALL
|
||||||
gboolean g_file_make_directory (GFile *file,
|
gboolean g_file_make_directory (GFile *file,
|
||||||
GCancellable *cancellable,
|
GCancellable *cancellable,
|
||||||
|
106
gio/tests/file.c
106
gio/tests/file.c
@ -3006,6 +3006,111 @@ test_build_attribute_list_for_copy (void)
|
|||||||
g_clear_object (&tmpfile);
|
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
|
int
|
||||||
main (int argc, char *argv[])
|
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-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/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/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 ();
|
return g_test_run ();
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user