[Spice-devel] [spice-gtk v4 20/24] file-xfer: move to spice-file-transfer-task.c
Jonathon Jongsma
jjongsma at redhat.com
Mon Jun 27 20:57:10 UTC 2016
On Thu, 2016-06-23 at 19:37 +0200, Victor Toso wrote:
> This patch moves:
> * GObject boilerplate
> * External API related to SpiceFileTransferTask
> * Internal API needed by channel-main
> * Helpers that belong to this object
> ---
> src/Makefile.am | 2 +
> src/channel-main.c | 696 +----------------------------------
> src/spice-file-transfer-task-priv.h | 59 +++
> src/spice-file-transfer-task.c | 713
> ++++++++++++++++++++++++++++++++++++
> 4 files changed, 776 insertions(+), 694 deletions(-)
> create mode 100644 src/spice-file-transfer-task-priv.h
> create mode 100644 src/spice-file-transfer-task.c
>
> diff --git a/src/Makefile.am b/src/Makefile.am
> index 6fb8507..779d655 100644
> --- a/src/Makefile.am
> +++ b/src/Makefile.am
> @@ -233,6 +233,8 @@ libspice_client_glib_2_0_la_SOURCES =
> \
> spice-channel.c \
> spice-channel-cache.h \
> spice-channel-priv.h \
> + spice-file-transfer-task.c \
> + spice-file-transfer-task-priv.h \
> coroutine.h \
> gio-coroutine.c \
> gio-coroutine.h \
Looks like there's some alignment issues here.
> diff --git a/src/channel-main.c b/src/channel-main.c
> index 190a366..a0b2748 100644
> --- a/src/channel-main.c
> +++ b/src/channel-main.c
> @@ -29,7 +29,7 @@
> #include "spice-channel-priv.h"
> #include "spice-session-priv.h"
> #include "spice-audio-priv.h"
> -#include "spice-file-transfer-task.h"
> +#include "spice-file-transfer-task-priv.h"
>
> /**
> * SECTION:channel-main
> @@ -54,94 +54,6 @@
>
> typedef struct spice_migrate spice_migrate;
>
> -static guint32 spice_file_transfer_task_get_id(SpiceFileTransferTask *self);
> -static SpiceMainChannel
> *spice_file_transfer_task_get_channel(SpiceFileTransferTask *self);
> -static GCancellable
> *spice_file_transfer_task_get_cancellable(SpiceFileTransferTask *self);
> -static GHashTable *spice_file_transfer_task_create_tasks(GFile **files,
> - SpiceMainChannel
> *channel,
> - GFileCopyFlags
> flags,
> - GCancellable
> *cancellable);
> -static void spice_file_transfer_task_init_task_async(SpiceFileTransferTask
> *self,
> - GAsyncReadyCallback
> callback,
> - gpointer userdata);
> -static GFileInfo
> *spice_file_transfer_task_init_task_finish(SpiceFileTransferTask *xfer_task,
> - GAsyncResult
> *result,
> - GError **error);
> -static void spice_file_transfer_task_read_async(SpiceFileTransferTask *self,
> - GAsyncReadyCallback callback,
> - gpointer userdata);
> -static gssize spice_file_transfer_task_read_finish(SpiceFileTransferTask
> *self,
> - GAsyncResult *result,
> - char **buffer,
> - GError **error);
> -static guint64 spice_file_transfer_task_get_file_size(SpiceFileTransferTask
> *self);
> -static guint64 spice_file_transfer_task_get_bytes_read(SpiceFileTransferTask
> *self);
> -static void spice_file_transfer_task_debug_info(SpiceFileTransferTask *self);
> -
> -/**
> - * SECTION:file-transfer-task
> - * @short_description: Monitoring file transfers
> - * @title: File Transfer Task
> - * @section_id:
> - * @see_also: #SpiceMainChannel
> - * @stability: Stable
> - * @include: spice-client.h
> - *
> - * SpiceFileTransferTask is an object that represents a particular file
> - * transfer between the client and the guest. The properties and signals of
> the
> - * object can be used to monitor the status and result of the transfer. The
> - * Main Channel's #SpiceMainChannel::new-file-transfer signal will be emitted
> - * whenever a new file transfer task is initiated.
> - *
> - * Since: 0.31
> - */
> -
> -struct _SpiceFileTransferTask
> -{
> - GObject parent;
> -
> - uint32_t id;
> - gboolean pending;
> - GFile *file;
> - SpiceMainChannel *channel;
> - GFileInputStream *file_stream;
> - GFileCopyFlags flags;
> - GCancellable *cancellable;
> - GAsyncReadyCallback callback;
> - gpointer user_data;
> - char *buffer;
> - uint64_t read_bytes;
> - GFileInfo *file_info;
> - uint64_t file_size;
> - gint64 start_time;
> - gint64 last_update;
> - GError *error;
> -};
> -
> -struct _SpiceFileTransferTaskClass
> -{
> - GObjectClass parent_class;
> -};
> -
> -G_DEFINE_TYPE(SpiceFileTransferTask, spice_file_transfer_task, G_TYPE_OBJECT)
> -
> -#define FILE_XFER_CHUNK_SIZE (VD_AGENT_MAX_DATA_SIZE * 32)
> -
> -enum {
> - PROP_TASK_ID = 1,
> - PROP_TASK_CHANNEL,
> - PROP_TASK_CANCELLABLE,
> - PROP_TASK_FILE,
> - PROP_TASK_PROGRESS,
> -};
> -
> -enum {
> - SIGNAL_FINISHED,
> - LAST_TASK_SIGNAL
> -};
> -
> -static guint task_signals[LAST_TASK_SIGNAL];
> -
> typedef enum {
> DISPLAY_UNDEFINED,
> DISPLAY_DISABLED,
> @@ -267,7 +179,6 @@ static void migrate_channel_event_cb(SpiceChannel
> *channel, SpiceChannelEvent ev
> gpointer data);
> static gboolean main_migrate_handshake_done(gpointer data);
> static void spice_main_channel_send_migration_handshake(SpiceChannel
> *channel);
> -static void spice_file_transfer_task_completed(SpiceFileTransferTask *self,
> GError *error);
> static void file_xfer_flushed(SpiceMainChannel *channel, gboolean success);
> static void file_xfer_read_async_cb(GObject *source_object,
> GAsyncResult *res,
> @@ -1840,44 +1751,6 @@ static void main_handle_agent_disconnected(SpiceChannel
> *channel, SpiceMsgIn *in
> agent_stopped(SPICE_MAIN_CHANNEL(channel));
> }
>
> -/* main context */
> -static void spice_file_transfer_task_close_stream_cb(GObject *object,
> - GAsyncResult *close_res,
> - gpointer user_data)
> -{
> - SpiceFileTransferTask *self;
> - GError *error = NULL;
> -
> - self = user_data;
> -
> - if (object) {
> - GInputStream *stream = G_INPUT_STREAM(object);
> - g_input_stream_close_finish(stream, close_res, &error);
> - if (error) {
> - /* This error dont need to report to user, just print a log */
> - SPICE_DEBUG("close file error: %s", error->message);
> - g_clear_error(&error);
> - }
> - }
> -
> - if (self->error == NULL && spice_util_get_debug()) {
> - gint64 now = g_get_monotonic_time();
> - gchar *basename = g_file_get_basename(self->file);
> - double seconds = (double) (now - self->start_time) /
> G_TIME_SPAN_SECOND;
> - gchar *file_size_str = g_format_size(self->file_size);
> - gchar *transfer_speed_str = g_format_size(self->file_size / seconds);
> -
> - g_warn_if_fail(self->read_bytes == self->file_size);
> - SPICE_DEBUG("transferred file %s of %s size in %.1f seconds (%s/s)",
> - basename, file_size_str, seconds, transfer_speed_str);
> -
> - g_free(basename);
> - g_free(file_size_str);
> - g_free(transfer_speed_str);
> - }
> - g_object_unref(self);
> -}
> -
> static void file_xfer_data_flushed_cb(GObject *source_object,
> GAsyncResult *res,
> gpointer user_data)
> @@ -1990,6 +1863,7 @@ static void file_xfer_handle_status(SpiceMainChannel
> *channel,
> spice_file_transfer_task_completed(xfer_task, error);
> }
>
> +
> /* any context: the message is not flushed immediately,
> you can wakeup() the channel coroutine or send_msg_queue() */
> static void agent_max_clipboard(SpiceMainChannel *self)
> @@ -2933,38 +2807,6 @@ void spice_main_set_display_enabled(SpiceMainChannel
> *channel, int id, gboolean
> spice_main_update_display_enabled(channel, id, enabled, TRUE);
> }
>
> -static void spice_file_transfer_task_completed(SpiceFileTransferTask *self,
> - GError *error)
> -{
> - /* In case of multiple errors we only report the first error */
> - if (self->error)
> - g_clear_error(&error);
> - if (error) {
> - gchar *path = g_file_get_path(self->file);
> - SPICE_DEBUG("File %s xfer failed: %s",
> - path, error->message);
> - g_free(path);
> - self->error = error;
> - }
> -
> - if (self->pending)
> - return;
> -
> - if (!self->file_stream) {
> - spice_file_transfer_task_close_stream_cb(NULL, NULL, self);
> - goto signal;
> - }
> -
> - g_input_stream_close_async(G_INPUT_STREAM(self->file_stream),
> - G_PRIORITY_DEFAULT,
> - self->cancellable,
> - spice_file_transfer_task_close_stream_cb,
> - self);
> - self->pending = TRUE;
> -signal:
> - g_signal_emit(self, task_signals[SIGNAL_FINISHED], 0, self->error);
> -}
> -
>
> static void file_xfer_init_task_async_cb(GObject *obj, GAsyncResult *res,
> gpointer data)
> {
> @@ -3146,10 +2988,6 @@ static void
> file_transfer_operation_send_progress(SpiceFileTransferTask *xfer_ta
> xfer_op->progress_callback_data);
> }
>
> -static SpiceFileTransferTask *spice_file_transfer_task_new(SpiceMainChannel
> *channel,
> - GFile *file,
> - GCancellable
> *cancellable);
> -
> /**
> * spice_main_file_copy_async:
> * @channel: a #SpiceMainChannel
> @@ -3269,533 +3107,3 @@ gboolean spice_main_file_copy_finish(SpiceMainChannel
> *channel,
>
> return g_task_propagate_boolean(task, error);
> }
> -
> -static guint32 spice_file_transfer_task_get_id(SpiceFileTransferTask *self)
> -{
> - g_return_val_if_fail(self != NULL, 0);
> - return self->id;
> -}
> -
> -static SpiceMainChannel
> *spice_file_transfer_task_get_channel(SpiceFileTransferTask *self)
> -{
> - g_return_val_if_fail(self != NULL, NULL);
> - return self->channel;
> -}
> -
> -static GCancellable
> *spice_file_transfer_task_get_cancellable(SpiceFileTransferTask *self)
> -{
> - g_return_val_if_fail(self != NULL, NULL);
> - return self->cancellable;
> -}
> -
> -static guint64 spice_file_transfer_task_get_file_size(SpiceFileTransferTask
> *self)
> -{
> - g_return_val_if_fail(self != NULL, 0);
> - return self->file_size;
> -}
> -
> -static guint64 spice_file_transfer_task_get_bytes_read(SpiceFileTransferTask
> *self)
> -{
> - g_return_val_if_fail(self != NULL, 0);
> - return self->read_bytes;
> -}
> -
> -/* Helper function which only creates a SpiceFileTransferTask per GFile
> - * in @files and returns a HashTable mapping task-id to the task itself
> - * Note that the HashTable does not free its values uppon destruction:
> - * The reference created here should be freed by
> - * spice_file_transfer_task_completed */
> -static GHashTable *spice_file_transfer_task_create_tasks(GFile **files,
> - SpiceMainChannel
> *channel,
> - GFileCopyFlags
> flags,
> - GCancellable
> *cancellable)
> -{
> - GHashTable *xfer_ht;
> - gint i;
> -
> - g_return_val_if_fail(files != NULL && files[0] != NULL, NULL);
> -
> - xfer_ht = g_hash_table_new(g_direct_hash, g_direct_equal);
> - for (i = 0; files[i] != NULL && !g_cancellable_is_cancelled(cancellable);
> i++) {
> - SpiceFileTransferTask *xfer_task;
> - guint32 task_id;
> - GCancellable *task_cancellable = cancellable;
> -
> - /* if a cancellable object was not provided for the overall
> operation,
> - * create a separate object for each file so that they can be
> cancelled
> - * separately */
> - if (!task_cancellable)
> - task_cancellable = g_cancellable_new();
> -
> - xfer_task = spice_file_transfer_task_new(channel, files[i],
> task_cancellable);
> - xfer_task->flags = flags;
> -
> - task_id = spice_file_transfer_task_get_id(xfer_task);
> - g_hash_table_insert(xfer_ht, GUINT_TO_POINTER(task_id), xfer_task);
> -
> - /* if we created a per-task cancellable above, free it */
> - if (!cancellable)
> - g_object_unref(task_cancellable);
> - }
> - return xfer_ht;
> -}
> -
> -static void spice_file_transfer_task_query_info_cb(GObject *obj,
> - GAsyncResult *res,
> - gpointer user_data)
> -{
> - SpiceFileTransferTask *self;
> - GFileInfo *info;
> - GTask *task;
> - GError *error = NULL;
> -
> - task = G_TASK(user_data);
> - self = g_task_get_source_object(task);
> -
> - g_return_if_fail(self->pending == TRUE);
> - self->pending = FALSE;
> -
> - info = g_file_query_info_finish(G_FILE(obj), res, &error);
> - if (error || self->error) {
> - error = (error == NULL) ? self->error : error;
> - g_task_return_error(task, error);
> - return;
> - }
> -
> - self->file_info = info;
> - self->file_size =
> - g_file_info_get_attribute_uint64(info,
> G_FILE_ATTRIBUTE_STANDARD_SIZE);
> -
> - /* SpiceFileTransferTask's init is done, handshake for file-trasfer will
> - * start soon. First "progress" can be emitted ~ 0% */
> - g_object_notify(G_OBJECT(self), "progress");
> -
> - g_task_return_boolean(task, TRUE);
> -}
> -
> -static void spice_file_transfer_task_read_file_cb(GObject *obj,
> - GAsyncResult *res,
> - gpointer user_data)
> -{
> - SpiceFileTransferTask *self;
> - GTask *task;
> - GError *error = NULL;
> -
> - task = G_TASK(user_data);
> - self = g_task_get_source_object(task);
> -
> - g_return_if_fail(self->pending == TRUE);
> -
> - self->file_stream = g_file_read_finish(G_FILE(obj), res, &error);
> - if (error || self->error) {
> - self->pending = FALSE;
> - error = (error == NULL) ? self->error : error;
> - g_task_return_error(task, error);
> - return;
> - }
> -
> - g_file_query_info_async(self->file,
> - "standard::*",
> - G_FILE_QUERY_INFO_NONE,
> - G_PRIORITY_DEFAULT,
> - self->cancellable,
> - spice_file_transfer_task_query_info_cb,
> - task);
> -}
> -
> -static void spice_file_transfer_task_init_task_async(SpiceFileTransferTask
> *self,
> - GAsyncReadyCallback
> callback,
> - gpointer userdata)
> -{
> - GTask *task;
> -
> - g_return_if_fail(self != NULL);
> - g_return_if_fail(self->pending == FALSE);
> -
> - task = g_task_new(self, self->cancellable, callback, userdata);
> -
> - self->pending = TRUE;
> - g_file_read_async(self->file,
> - G_PRIORITY_DEFAULT,
> - self->cancellable,
> - spice_file_transfer_task_read_file_cb,
> - task);
> -}
> -
> -static GFileInfo
> *spice_file_transfer_task_init_task_finish(SpiceFileTransferTask *self,
> - GAsyncResult
> *result,
> - GError **error)
> -{
> - GTask *task = G_TASK(result);
> -
> - g_return_val_if_fail(self != NULL, NULL);
> -
> - if (g_task_propagate_boolean(task, error))
> - return self->file_info;
> -
> - return NULL;
> -}
> -
> -static void spice_file_transfer_task_read_stream_cb(GObject *source_object,
> - GAsyncResult *res,
> - gpointer userdata)
> -{
> - SpiceFileTransferTask *self;
> - GTask *task;
> - gssize nbytes;
> - GError *error = NULL;
> -
> - task = G_TASK(userdata);
> - self = g_task_get_source_object(task);
> -
> - g_return_if_fail(self->pending == TRUE);
> - self->pending = FALSE;
> -
> - nbytes = g_input_stream_read_finish(G_INPUT_STREAM(self->file_stream),
> res, &error);
> - if (error || self->error) {
> - error = (error == NULL) ? self->error : error;
> - g_task_return_error(task, error);
> - return;
> - }
> -
> - /* The progress here means the amount of data we have _read_ and not what
> - * was actually sent to the agent. On the next "progress", the previous
> data
> - * read was sent. This means that when user see 100%, we are sending the
> - * last chunk to the guest */
> - self->read_bytes += nbytes;
> - g_object_notify(G_OBJECT(self), "progress");
> -
> - g_task_return_int(task, nbytes);
> -}
> -
> -/* Any context */
> -static void spice_file_transfer_task_read_async(SpiceFileTransferTask *self,
> - GAsyncReadyCallback callback,
> - gpointer userdata)
> -{
> - GTask *task;
> -
> - g_return_if_fail(self != NULL);
> - if (self->pending) {
> - g_task_report_new_error(self, callback, userdata,
> - spice_file_transfer_task_read_async,
> - SPICE_CLIENT_ERROR,
> - SPICE_CLIENT_ERROR_FAILED,
> - "Cannot read data in pending state");
> - return;
> - }
> -
> - task = g_task_new(self, self->cancellable, callback, userdata);
> -
> - if (self->read_bytes == self->file_size) {
> - /* channel-main might can request data after reading the whole file
> as
> - * it expects EOF. Let's return immediately its request as we don't
> want
> - * to reach a state where agent says file-transfer SUCCEED but we are
> in
> - * a PENDING state in SpiceFileTransferTask due reading in idle */
> - g_task_return_int(task, 0);
> - return;
> - }
> -
> - self->pending = TRUE;
> - g_input_stream_read_async(G_INPUT_STREAM(self->file_stream),
> - self->buffer,
> - FILE_XFER_CHUNK_SIZE,
> - G_PRIORITY_DEFAULT,
> - self->cancellable,
> - spice_file_transfer_task_read_stream_cb,
> - task);
> -}
> -
> -static gssize spice_file_transfer_task_read_finish(SpiceFileTransferTask
> *self,
> - GAsyncResult *result,
> - char **buffer,
> - GError **error)
> -{
> - gssize nbytes;
> - GTask *task = G_TASK(result);
> -
> - g_return_val_if_fail(self != NULL, -1);
> -
> - nbytes = g_task_propagate_int(task, error);
> - if (nbytes >= 0 && buffer != NULL)
> - *buffer = self->buffer;
> -
> - return nbytes;
> -}
> -
> -static void spice_file_transfer_task_debug_info(SpiceFileTransferTask *self)
> -{
> - const GTimeSpan interval = 20 * G_TIME_SPAN_SECOND;
> - gint64 now = g_get_monotonic_time();
> -
> - if (interval < now - self->last_update) {
> - gchar *basename = g_file_get_basename(self->file);
> - self->last_update = now;
> - SPICE_DEBUG("transferred %.2f%% of the file %s",
> - 100.0 * self->read_bytes / self->file_size, basename);
> - g_free(basename);
> - }
> -}
> -
> -static void
> -spice_file_transfer_task_get_property(GObject *object,
> - guint property_id,
> - GValue *value,
> - GParamSpec *pspec)
> -{
> - SpiceFileTransferTask *self = SPICE_FILE_TRANSFER_TASK(object);
> -
> - switch (property_id)
> - {
> - case PROP_TASK_ID:
> - g_value_set_uint(value, self->id);
> - break;
> - case PROP_TASK_FILE:
> - g_value_set_object(value, self->file);
> - break;
> - case PROP_TASK_PROGRESS:
> - g_value_set_double(value,
> spice_file_transfer_task_get_progress(self));
> - break;
> - default:
> - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
> - }
> -}
> -
> -static void
> -spice_file_transfer_task_set_property(GObject *object,
> - guint property_id,
> - const GValue *value,
> - GParamSpec *pspec)
> -{
> - SpiceFileTransferTask *self = SPICE_FILE_TRANSFER_TASK(object);
> -
> - switch (property_id)
> - {
> - case PROP_TASK_ID:
> - self->id = g_value_get_uint(value);
> - break;
> - case PROP_TASK_FILE:
> - self->file = g_value_dup_object(value);
> - break;
> - case PROP_TASK_CHANNEL:
> - self->channel = g_value_dup_object(value);
> - break;
> - case PROP_TASK_CANCELLABLE:
> - self->cancellable = g_value_dup_object(value);
> - break;
> - default:
> - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
> - }
> -}
> -
> -static void
> -spice_file_transfer_task_dispose(GObject *object)
> -{
> - SpiceFileTransferTask *self = SPICE_FILE_TRANSFER_TASK(object);
> -
> - g_clear_object(&self->file);
> - g_clear_object(&self->file_info);
> -
> - G_OBJECT_CLASS(spice_file_transfer_task_parent_class)->dispose(object);
> -}
> -
> -static void
> -spice_file_transfer_task_finalize(GObject *object)
> -{
> - SpiceFileTransferTask *self = SPICE_FILE_TRANSFER_TASK(object);
> -
> - g_free(self->buffer);
> -
> - G_OBJECT_CLASS(spice_file_transfer_task_parent_class)->finalize(object);
> -}
> -
> -static void
> -spice_file_transfer_task_constructed(GObject *object)
> -{
> - SpiceFileTransferTask *self = SPICE_FILE_TRANSFER_TASK(object);
> -
> - if (spice_util_get_debug()) {
> - gchar *basename = g_file_get_basename(self->file);
> - self->start_time = g_get_monotonic_time();
> - self->last_update = self->start_time;
> -
> - SPICE_DEBUG("transfer of file %s has started", basename);
> - g_free(basename);
> - }
> -}
> -
> -static void
> -spice_file_transfer_task_class_init(SpiceFileTransferTaskClass *klass)
> -{
> - GObjectClass *object_class = G_OBJECT_CLASS(klass);
> -
> - object_class->get_property = spice_file_transfer_task_get_property;
> - object_class->set_property = spice_file_transfer_task_set_property;
> - object_class->finalize = spice_file_transfer_task_finalize;
> - object_class->dispose = spice_file_transfer_task_dispose;
> - object_class->constructed = spice_file_transfer_task_constructed;
> -
> - /**
> - * SpiceFileTransferTask:id:
> - *
> - * The ID of the file transfer task
> - *
> - * Since: 0.31
> - **/
> - g_object_class_install_property(object_class, PROP_TASK_ID,
> - g_param_spec_uint("id",
> - "id",
> - "The id of the task",
> - 0, G_MAXUINT, 0,
> - G_PARAM_CONSTRUCT_ONLY
> | G_PARAM_READWRITE |
> - G_PARAM_STATIC_STRINGS)
> );
> -
> - /**
> - * SpiceFileTransferTask:channel:
> - *
> - * The main channel that owns the file transfer task
> - *
> - * Since: 0.31
> - **/
> - g_object_class_install_property(object_class, PROP_TASK_CHANNEL,
> - g_param_spec_object("channel",
> - "channel",
> - "The channel
> transferring the file",
> - SPICE_TYPE_MAIN_CHANN
> EL,
> - G_PARAM_CONSTRUCT_ONL
> Y | G_PARAM_READWRITE |
> - G_PARAM_STATIC_STRING
> S));
> -
> - /**
> - * SpiceFileTransferTask:cancellable:
> - *
> - * A cancellable object used to cancel the file transfer
> - *
> - * Since: 0.31
> - **/
> - g_object_class_install_property(object_class, PROP_TASK_CANCELLABLE,
> - g_param_spec_object("cancellable",
> - "cancellable",
> - "The object used to
> cancel the task",
> - G_TYPE_CANCELLABLE,
> - G_PARAM_CONSTRUCT_ONL
> Y | G_PARAM_READWRITE |
> - G_PARAM_STATIC_STRING
> S));
> -
> - /**
> - * SpiceFileTransferTask:file:
> - *
> - * The file that is being transferred in this file transfer task
> - *
> - * Since: 0.31
> - **/
> - g_object_class_install_property(object_class, PROP_TASK_FILE,
> - g_param_spec_object("file",
> - "File",
> - "The file being
> transferred",
> - G_TYPE_FILE,
> - G_PARAM_CONSTRUCT_ONL
> Y | G_PARAM_READWRITE |
> - G_PARAM_STATIC_STRING
> S));
> -
> - /**
> - * SpiceFileTransferTask:progress:
> - *
> - * The current state of the file transfer. This value indicates a
> - * percentage, and ranges from 0 to 100. Listen for change notifications
> on
> - * this property to be updated whenever the file transfer progress
> changes.
> - *
> - * Since: 0.31
> - **/
> - g_object_class_install_property(object_class, PROP_TASK_PROGRESS,
> - g_param_spec_double("progress",
> - "Progress",
> - "The percentage of
> the file transferred",
> - 0.0, 100.0, 0.0,
> - G_PARAM_READABLE |
> - G_PARAM_STATIC_STRING
> S));
> -
> - /**
> - * SpiceFileTransferTask::finished:
> - * @task: the file transfer task that emitted the signal
> - * @error: (transfer none): the error state of the transfer. Will be
> %NULL
> - * if the file transfer was successful.
> - *
> - * The #SpiceFileTransferTask::finished signal is emitted when the file
> - * transfer has completed transferring to the guest.
> - *
> - * Since: 0.31
> - **/
> - task_signals[SIGNAL_FINISHED] = g_signal_new("finished",
> SPICE_TYPE_FILE_TRANSFER_TASK,
> - G_SIGNAL_RUN_FIRST,
> - 0, NULL, NULL,
> - g_cclosure_marshal_VOID__BOXED,
> - G_TYPE_NONE, 1,
> - G_TYPE_ERROR);
> -}
> -
> -static void
> -spice_file_transfer_task_init(SpiceFileTransferTask *self)
> -{
> - self->buffer = g_malloc0(FILE_XFER_CHUNK_SIZE);
> -}
> -
> -static SpiceFileTransferTask *
> -spice_file_transfer_task_new(SpiceMainChannel *channel, GFile *file,
> GCancellable *cancellable)
> -{
> - static uint32_t xfer_id = 1; /* Used to identify task id */
> -
> - return g_object_new(SPICE_TYPE_FILE_TRANSFER_TASK,
> - "id", xfer_id++,
> - "file", file,
> - "channel", channel,
> - "cancellable", cancellable,
> - NULL);
> -}
> -
> -/**
> - * spice_file_transfer_task_get_progress:
> - * @self: a file transfer task
> - *
> - * Convenience function for retrieving the current progress of this file
> - * transfer task.
> - *
> - * Returns: A percentage value between 0 and 100
> - *
> - * Since: 0.31
> - **/
> -double spice_file_transfer_task_get_progress(SpiceFileTransferTask *self)
> -{
> - if (self->file_size == 0)
> - return 0.0;
> -
> - return (double)self->read_bytes / self->file_size;
> -}
> -
> -/**
> - * spice_file_transfer_task_cancel:
> - * @self: a file transfer task
> - *
> - * Cancels the file transfer task. Note that depending on how the file
> transfer
> - * was initiated, multiple file transfer tasks may share a single
> - * #SpiceFileTransferTask::cancellable object, so canceling one task may
> result
> - * in the cancellation of other tasks.
> - *
> - * Since: 0.31
> - **/
> -void spice_file_transfer_task_cancel(SpiceFileTransferTask *self)
> -{
> - g_cancellable_cancel(self->cancellable);
> -}
> -
> -/**
> - * spice_file_transfer_task_get_filename:
> - * @self: a file transfer task
> - *
> - * Gets the name of the file being transferred in this task
> - *
> - * Returns: (transfer none): The basename of the file
> - *
> - * Since: 0.31
> - **/
> -char* spice_file_transfer_task_get_filename(SpiceFileTransferTask *self)
> -{
> - return g_file_get_basename(self->file);
> -}
> diff --git a/src/spice-file-transfer-task-priv.h b/src/spice-file-transfer-
> task-priv.h
> new file mode 100644
> index 0000000..df3ea93
> --- /dev/null
> +++ b/src/spice-file-transfer-task-priv.h
> @@ -0,0 +1,59 @@
> +/*
> + Copyright (C) 2016 Red Hat, Inc.
> +
> + This library is free software; you can redistribute it and/or
> + modify it under the terms of the GNU Lesser General Public
> + License as published by the Free Software Foundation; either
> + version 2.1 of the License, or (at your option) any later version.
> +
> + This library is distributed in the hope that it will be useful,
> + but WITHOUT ANY WARRANTY; without even the implied warranty of
> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + Lesser General Public License for more details.
> +
> + You should have received a copy of the GNU Lesser General Public
> + License along with this library; if not, see <http://www.gnu.org/licenses/
> >.
> +*/
> +
> +#ifndef __SPICE_FILE_TRANSFER_TASK_PRIV_H__
> +#define __SPICE_FILE_TRANSFER_TASK_PRIV_H__
> +
> +#include "config.h"
> +
> +#include <spice/vd_agent.h>
> +
> +#include "spice-client.h"
> +#include "channel-main.h"
> +#include "spice-file-transfer-task.h"
> +#include "spice-channel-priv.h"
> +
> +G_BEGIN_DECLS
> +
> +void spice_file_transfer_task_completed(SpiceFileTransferTask *self, GError
> *error);
> +guint32 spice_file_transfer_task_get_id(SpiceFileTransferTask *self);
> +SpiceMainChannel *spice_file_transfer_task_get_channel(SpiceFileTransferTask
> *self);
> +GCancellable *spice_file_transfer_task_get_cancellable(SpiceFileTransferTask
> *self);
> +GHashTable *spice_file_transfer_task_create_tasks(GFile **files,
> + SpiceMainChannel *channel,
> + GFileCopyFlags flags,
> + GCancellable *cancellable);
> +void spice_file_transfer_task_init_task_async(SpiceFileTransferTask *self,
> + GAsyncReadyCallback callback,
> + gpointer userdata);
> +GFileInfo *spice_file_transfer_task_init_task_finish(SpiceFileTransferTask
> *xfer_task,
> + GAsyncResult *result,
> + GError **error);
> +void spice_file_transfer_task_read_async(SpiceFileTransferTask *self,
> + GAsyncReadyCallback callback,
> + gpointer userdata);
> +gssize spice_file_transfer_task_read_finish(SpiceFileTransferTask *self,
> + GAsyncResult *result,
> + char **buffer,
> + GError **error);
> +guint64 spice_file_transfer_task_get_file_size(SpiceFileTransferTask *self);
> +guint64 spice_file_transfer_task_get_bytes_read(SpiceFileTransferTask *self);
> +void spice_file_transfer_task_debug_info(SpiceFileTransferTask *self);
> +
> +G_END_DECLS
> +
> +#endif /* __SPICE_FILE_TRANSFER_TASK_PRIV_H__ */
> diff --git a/src/spice-file-transfer-task.c b/src/spice-file-transfer-task.c
> new file mode 100644
> index 0000000..8bfb1ae
> --- /dev/null
> +++ b/src/spice-file-transfer-task.c
> @@ -0,0 +1,713 @@
> +/*
> + Copyright (C) 2016 Red Hat, Inc.
> +
> + This library is free software; you can redistribute it and/or
> + modify it under the terms of the GNU Lesser General Public
> + License as published by the Free Software Foundation; either
> + version 2.1 of the License, or (at your option) any later version.
> +
> + This library is distributed in the hope that it will be useful,
> + but WITHOUT ANY WARRANTY; without even the implied warranty of
> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + Lesser General Public License for more details.
> +
> + You should have received a copy of the GNU Lesser General Public
> + License along with this library; if not, see <http://www.gnu.org/licenses/
> >.
> +*/
> +
> +#include "config.h"
> +
> +#include "spice-file-transfer-task-priv.h"
> +
> +/**
> + * SECTION:file-transfer-task
> + * @short_description: Monitoring file transfers
> + * @title: File Transfer Task
> + * @section_id:
> + * @see_also: #SpiceMainChannel
> + * @stability: Stable
> + * @include: spice-client.h
> + *
> + * SpiceFileTransferTask is an object that represents a particular file
> + * transfer between the client and the guest. The properties and signals of
> the
> + * object can be used to monitor the status and result of the transfer. The
> + * Main Channel's #SpiceMainChannel::new-file-transfer signal will be emitted
> + * whenever a new file transfer task is initiated.
> + *
> + * Since: 0.31
> + */
> +
> +struct _SpiceFileTransferTask
> +{
> + GObject parent;
> +
> + uint32_t id;
> + gboolean pending;
> + GFile *file;
> + SpiceMainChannel *channel;
> + GFileInputStream *file_stream;
> + GFileCopyFlags flags;
> + GCancellable *cancellable;
> + GAsyncReadyCallback callback;
> + gpointer user_data;
> + char *buffer;
> + uint64_t read_bytes;
> + GFileInfo *file_info;
> + uint64_t file_size;
> + gint64 start_time;
> + gint64 last_update;
> + GError *error;
> +};
> +
> +struct _SpiceFileTransferTaskClass
> +{
> + GObjectClass parent_class;
> +};
> +
> +G_DEFINE_TYPE(SpiceFileTransferTask, spice_file_transfer_task, G_TYPE_OBJECT)
> +
> +#define FILE_XFER_CHUNK_SIZE (VD_AGENT_MAX_DATA_SIZE * 32)
> +
> +enum {
> + PROP_TASK_ID = 1,
> + PROP_TASK_CHANNEL,
> + PROP_TASK_CANCELLABLE,
> + PROP_TASK_FILE,
> + PROP_TASK_PROGRESS,
> +};
> +
> +enum {
> + SIGNAL_FINISHED,
> + LAST_TASK_SIGNAL
> +};
> +
> +static guint task_signals[LAST_TASK_SIGNAL];
> +
> +/****************************************************************************
> ***
> + * Helpers
> +
> ******************************************************************************
> /
> +
> +static SpiceFileTransferTask *
> +spice_file_transfer_task_new(SpiceMainChannel *channel, GFile *file,
> GCancellable *cancellable)
> +{
> + static uint32_t xfer_id = 1; /* Used to identify task id */
> +
> + return g_object_new(SPICE_TYPE_FILE_TRANSFER_TASK,
> + "id", xfer_id++,
> + "file", file,
> + "channel", channel,
> + "cancellable", cancellable,
> + NULL);
> +}
> +
> +static void spice_file_transfer_task_query_info_cb(GObject *obj,
> + GAsyncResult *res,
> + gpointer user_data)
> +{
> + SpiceFileTransferTask *self;
> + GFileInfo *info;
> + GTask *task;
> + GError *error = NULL;
> +
> + task = G_TASK(user_data);
> + self = g_task_get_source_object(task);
> +
> + g_return_if_fail(self->pending == TRUE);
> + self->pending = FALSE;
> +
> + info = g_file_query_info_finish(G_FILE(obj), res, &error);
> + if (error || self->error) {
> + error = (error == NULL) ? self->error : error;
> + g_task_return_error(task, error);
> + return;
> + }
> +
> + self->file_info = info;
> + self->file_size =
> + g_file_info_get_attribute_uint64(info,
> G_FILE_ATTRIBUTE_STANDARD_SIZE);
> +
> + /* SpiceFileTransferTask's init is done, handshake for file-trasfer will
> + * start soon. First "progress" can be emitted ~ 0% */
> + g_object_notify(G_OBJECT(self), "progress");
> +
> + g_task_return_boolean(task, TRUE);
> +}
> +
> +static void spice_file_transfer_task_read_file_cb(GObject *obj,
> + GAsyncResult *res,
> + gpointer user_data)
> +{
> + SpiceFileTransferTask *self;
> + GTask *task;
> + GError *error = NULL;
> +
> + task = G_TASK(user_data);
> + self = g_task_get_source_object(task);
> +
> + g_return_if_fail(self->pending == TRUE);
> +
> + self->file_stream = g_file_read_finish(G_FILE(obj), res, &error);
> + if (error || self->error) {
> + self->pending = FALSE;
> + error = (error == NULL) ? self->error : error;
> + g_task_return_error(task, error);
> + return;
> + }
> +
> + g_file_query_info_async(self->file,
> + "standard::*",
> + G_FILE_QUERY_INFO_NONE,
> + G_PRIORITY_DEFAULT,
> + self->cancellable,
> + spice_file_transfer_task_query_info_cb,
> + task);
> +}
> +
> +static void spice_file_transfer_task_read_stream_cb(GObject *source_object,
> + GAsyncResult *res,
> + gpointer userdata)
> +{
> + SpiceFileTransferTask *self;
> + GTask *task;
> + gssize nbytes;
> + GError *error = NULL;
> +
> + task = G_TASK(userdata);
> + self = g_task_get_source_object(task);
> +
> + g_return_if_fail(self->pending == TRUE);
> + self->pending = FALSE;
> +
> + nbytes = g_input_stream_read_finish(G_INPUT_STREAM(self->file_stream),
> res, &error);
> + if (error || self->error) {
> + error = (error == NULL) ? self->error : error;
> + g_task_return_error(task, error);
> + return;
> + }
> +
> + /* The progress here means the amount of data we have _read_ and not what
> + * was actually sent to the agent. On the next "progress", the previous
> data
> + * read was sent. This means that when user see 100%, we are sending the
> + * last chunk to the guest */
> + self->read_bytes += nbytes;
> + g_object_notify(G_OBJECT(self), "progress");
> +
> + g_task_return_int(task, nbytes);
> +}
> +
> +/* main context */
> +static void spice_file_transfer_task_close_stream_cb(GObject *object,
> + GAsyncResult *close_res,
> + gpointer user_data)
> +{
> + SpiceFileTransferTask *self;
> + GError *error = NULL;
> +
> + self = user_data;
> +
> + if (object) {
> + GInputStream *stream = G_INPUT_STREAM(object);
> + g_input_stream_close_finish(stream, close_res, &error);
> + if (error) {
> + /* This error dont need to report to user, just print a log */
> + SPICE_DEBUG("close file error: %s", error->message);
> + g_clear_error(&error);
> + }
> + }
> +
> + if (self->error == NULL && spice_util_get_debug()) {
> + gint64 now = g_get_monotonic_time();
> + gchar *basename = g_file_get_basename(self->file);
> + double seconds = (double) (now - self->start_time) /
> G_TIME_SPAN_SECOND;
> + gchar *file_size_str = g_format_size(self->file_size);
> + gchar *transfer_speed_str = g_format_size(self->file_size / seconds);
> +
> + g_warn_if_fail(self->read_bytes == self->file_size);
> + SPICE_DEBUG("transferred file %s of %s size in %.1f seconds (%s/s)",
> + basename, file_size_str, seconds, transfer_speed_str);
> +
> + g_free(basename);
> + g_free(file_size_str);
> + g_free(transfer_speed_str);
> + }
> + g_object_unref(self);
> +}
> +
> +
> +/****************************************************************************
> ***
> + * Internal API
> +
> ******************************************************************************
> /
> +
> +G_GNUC_INTERNAL
> +void spice_file_transfer_task_completed(SpiceFileTransferTask *self,
> + GError *error)
> +{
> + /* In case of multiple errors we only report the first error */
> + if (self->error)
> + g_clear_error(&error);
> + if (error) {
> + gchar *path = g_file_get_path(self->file);
> + SPICE_DEBUG("File %s xfer failed: %s",
> + path, error->message);
> + g_free(path);
> + self->error = error;
> + }
> +
> + if (self->pending)
> + return;
> +
> + if (!self->file_stream) {
> + spice_file_transfer_task_close_stream_cb(NULL, NULL, self);
> + goto signal;
> + }
> +
> + g_input_stream_close_async(G_INPUT_STREAM(self->file_stream),
> + G_PRIORITY_DEFAULT,
> + self->cancellable,
> + spice_file_transfer_task_close_stream_cb,
> + self);
> + self->pending = TRUE;
> +signal:
> + g_signal_emit(self, task_signals[SIGNAL_FINISHED], 0, self->error);
> +}
> +
> +G_GNUC_INTERNAL
> +guint32 spice_file_transfer_task_get_id(SpiceFileTransferTask *self)
> +{
> + g_return_val_if_fail(self != NULL, 0);
> + return self->id;
> +}
> +
> +G_GNUC_INTERNAL
> +SpiceMainChannel *spice_file_transfer_task_get_channel(SpiceFileTransferTask
> *self)
> +{
> + g_return_val_if_fail(self != NULL, NULL);
> + return self->channel;
> +}
> +
> +G_GNUC_INTERNAL
> +GCancellable *spice_file_transfer_task_get_cancellable(SpiceFileTransferTask
> *self)
> +{
> + g_return_val_if_fail(self != NULL, NULL);
> + return self->cancellable;
> +}
> +
> +G_GNUC_INTERNAL
> +guint64 spice_file_transfer_task_get_file_size(SpiceFileTransferTask *self)
> +{
> + g_return_val_if_fail(self != NULL, 0);
> + return self->file_size;
> +}
> +
> +G_GNUC_INTERNAL
> +guint64 spice_file_transfer_task_get_bytes_read(SpiceFileTransferTask *self)
> +{
> + g_return_val_if_fail(self != NULL, 0);
> + return self->read_bytes;
> +}
> +
> +/* Helper function which only creates a SpiceFileTransferTask per GFile
> + * in @files and returns a HashTable mapping task-id to the task itself
> + * Note that the HashTable does not free its values uppon destruction:
> + * The reference created here should be freed by
> + * spice_file_transfer_task_completed */
> +G_GNUC_INTERNAL
> +GHashTable *spice_file_transfer_task_create_tasks(GFile **files,
> + SpiceMainChannel *channel,
> + GFileCopyFlags flags,
> + GCancellable *cancellable)
> +{
> + GHashTable *xfer_ht;
> + gint i;
> +
> + g_return_val_if_fail(files != NULL && files[0] != NULL, NULL);
> +
> + xfer_ht = g_hash_table_new(g_direct_hash, g_direct_equal);
> + for (i = 0; files[i] != NULL && !g_cancellable_is_cancelled(cancellable);
> i++) {
> + SpiceFileTransferTask *xfer_task;
> + guint32 task_id;
> + GCancellable *task_cancellable = cancellable;
> +
> + /* if a cancellable object was not provided for the overall
> operation,
> + * create a separate object for each file so that they can be
> cancelled
> + * separately */
> + if (!task_cancellable)
> + task_cancellable = g_cancellable_new();
> +
> + xfer_task = spice_file_transfer_task_new(channel, files[i],
> task_cancellable);
> + xfer_task->flags = flags;
> +
> + task_id = spice_file_transfer_task_get_id(xfer_task);
> + g_hash_table_insert(xfer_ht, GUINT_TO_POINTER(task_id), xfer_task);
> +
> + /* if we created a per-task cancellable above, free it */
> + if (!cancellable)
> + g_object_unref(task_cancellable);
> + }
> + return xfer_ht;
> +}
> +
> +G_GNUC_INTERNAL
> +void spice_file_transfer_task_init_task_async(SpiceFileTransferTask *self,
> + GAsyncReadyCallback callback,
> + gpointer userdata)
> +{
> + GTask *task;
> +
> + g_return_if_fail(self != NULL);
> + g_return_if_fail(self->pending == FALSE);
> +
> + task = g_task_new(self, self->cancellable, callback, userdata);
> +
> + self->pending = TRUE;
> + g_file_read_async(self->file,
> + G_PRIORITY_DEFAULT,
> + self->cancellable,
> + spice_file_transfer_task_read_file_cb,
> + task);
> +}
> +
> +G_GNUC_INTERNAL
> +GFileInfo *spice_file_transfer_task_init_task_finish(SpiceFileTransferTask
> *self,
> + GAsyncResult *result,
> + GError **error)
> +{
> + GTask *task = G_TASK(result);
> +
> + g_return_val_if_fail(self != NULL, NULL);
> +
> + if (g_task_propagate_boolean(task, error))
> + return self->file_info;
> +
> + return NULL;
> +}
> +
> +/* Any context */
> +G_GNUC_INTERNAL
> +void spice_file_transfer_task_read_async(SpiceFileTransferTask *self,
> + GAsyncReadyCallback callback,
> + gpointer userdata)
> +{
> + GTask *task;
> +
> + g_return_if_fail(self != NULL);
> + if (self->pending) {
> + g_task_report_new_error(self, callback, userdata,
> + spice_file_transfer_task_read_async,
> + SPICE_CLIENT_ERROR,
> + SPICE_CLIENT_ERROR_FAILED,
> + "Cannot read data in pending state");
> + return;
> + }
> +
> + task = g_task_new(self, self->cancellable, callback, userdata);
> +
> + if (self->read_bytes == self->file_size) {
> + /* channel-main might can request data after reading the whole file
> as
> + * it expects EOF. Let's return immediately its request as we don't
> want
> + * to reach a state where agent says file-transfer SUCCEED but we are
> in
> + * a PENDING state in SpiceFileTransferTask due reading in idle */
> + g_task_return_int(task, 0);
> + return;
> + }
> +
> + self->pending = TRUE;
> + g_input_stream_read_async(G_INPUT_STREAM(self->file_stream),
> + self->buffer,
> + FILE_XFER_CHUNK_SIZE,
> + G_PRIORITY_DEFAULT,
> + self->cancellable,
> + spice_file_transfer_task_read_stream_cb,
> + task);
> +}
> +
> +G_GNUC_INTERNAL
> +gssize spice_file_transfer_task_read_finish(SpiceFileTransferTask *self,
> + GAsyncResult *result,
> + char **buffer,
> + GError **error)
> +{
> + gssize nbytes;
> + GTask *task = G_TASK(result);
> +
> + g_return_val_if_fail(self != NULL, -1);
> +
> + nbytes = g_task_propagate_int(task, error);
> + if (nbytes >= 0 && buffer != NULL)
> + *buffer = self->buffer;
> +
> + return nbytes;
> +}
> +
> +G_GNUC_INTERNAL
> +void spice_file_transfer_task_debug_info(SpiceFileTransferTask *self)
> +{
> + const GTimeSpan interval = 20 * G_TIME_SPAN_SECOND;
> + gint64 now = g_get_monotonic_time();
> +
> + if (interval < now - self->last_update) {
> + gchar *basename = g_file_get_basename(self->file);
> + self->last_update = now;
> + SPICE_DEBUG("transferred %.2f%% of the file %s",
> + 100.0 * self->read_bytes / self->file_size, basename);
> + g_free(basename);
> + }
> +}
> +
> +/****************************************************************************
> ***
> + * External API
> +
> ******************************************************************************
> /
> +
> +/**
> + * spice_file_transfer_task_get_progress:
> + * @self: a file transfer task
> + *
> + * Convenience function for retrieving the current progress of this file
> + * transfer task.
> + *
> + * Returns: A percentage value between 0 and 100
> + *
> + * Since: 0.31
> + **/
> +double spice_file_transfer_task_get_progress(SpiceFileTransferTask *self)
> +{
> + if (self->file_size == 0)
> + return 0.0;
> +
> + return (double)self->read_bytes / self->file_size;
> +}
> +
> +/**
> + * spice_file_transfer_task_cancel:
> + * @self: a file transfer task
> + *
> + * Cancels the file transfer task. Note that depending on how the file
> transfer
> + * was initiated, multiple file transfer tasks may share a single
> + * #SpiceFileTransferTask::cancellable object, so canceling one task may
> result
> + * in the cancellation of other tasks.
> + *
> + * Since: 0.31
> + **/
> +void spice_file_transfer_task_cancel(SpiceFileTransferTask *self)
> +{
> + g_cancellable_cancel(self->cancellable);
> +}
> +
> +/**
> + * spice_file_transfer_task_get_filename:
> + * @self: a file transfer task
> + *
> + * Gets the name of the file being transferred in this task
> + *
> + * Returns: (transfer none): The basename of the file
> + *
> + * Since: 0.31
> + **/
> +char* spice_file_transfer_task_get_filename(SpiceFileTransferTask *self)
> +{
> + return g_file_get_basename(self->file);
> +}
> +
> +/****************************************************************************
> ***
> + * GObject
> +
> ******************************************************************************
> /
> +
> +static void
> +spice_file_transfer_task_get_property(GObject *object,
> + guint property_id,
> + GValue *value,
> + GParamSpec *pspec)
> +{
> + SpiceFileTransferTask *self = SPICE_FILE_TRANSFER_TASK(object);
> +
> + switch (property_id)
> + {
> + case PROP_TASK_ID:
> + g_value_set_uint(value, self->id);
> + break;
> + case PROP_TASK_FILE:
> + g_value_set_object(value, self->file);
> + break;
> + case PROP_TASK_PROGRESS:
> + g_value_set_double(value,
> spice_file_transfer_task_get_progress(self));
> + break;
> + default:
> + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
> + }
> +}
> +
> +static void
> +spice_file_transfer_task_set_property(GObject *object,
> + guint property_id,
> + const GValue *value,
> + GParamSpec *pspec)
> +{
> + SpiceFileTransferTask *self = SPICE_FILE_TRANSFER_TASK(object);
> +
> + switch (property_id)
> + {
> + case PROP_TASK_ID:
> + self->id = g_value_get_uint(value);
> + break;
> + case PROP_TASK_FILE:
> + self->file = g_value_dup_object(value);
> + break;
> + case PROP_TASK_CHANNEL:
> + self->channel = g_value_dup_object(value);
> + break;
> + case PROP_TASK_CANCELLABLE:
> + self->cancellable = g_value_dup_object(value);
> + break;
> + default:
> + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
> + }
> +}
> +
> +static void
> +spice_file_transfer_task_dispose(GObject *object)
> +{
> + SpiceFileTransferTask *self = SPICE_FILE_TRANSFER_TASK(object);
> +
> + g_clear_object(&self->file);
> + g_clear_object(&self->file_info);
> +
> + G_OBJECT_CLASS(spice_file_transfer_task_parent_class)->dispose(object);
> +}
> +
> +static void
> +spice_file_transfer_task_finalize(GObject *object)
> +{
> + SpiceFileTransferTask *self = SPICE_FILE_TRANSFER_TASK(object);
> +
> + g_free(self->buffer);
> +
> + G_OBJECT_CLASS(spice_file_transfer_task_parent_class)->finalize(object);
> +}
> +
> +static void
> +spice_file_transfer_task_constructed(GObject *object)
> +{
> + SpiceFileTransferTask *self = SPICE_FILE_TRANSFER_TASK(object);
> +
> + if (spice_util_get_debug()) {
> + gchar *basename = g_file_get_basename(self->file);
> + self->start_time = g_get_monotonic_time();
> + self->last_update = self->start_time;
> +
> + SPICE_DEBUG("transfer of file %s has started", basename);
> + g_free(basename);
> + }
> +}
> +
> +static void
> +spice_file_transfer_task_class_init(SpiceFileTransferTaskClass *klass)
> +{
> + GObjectClass *object_class = G_OBJECT_CLASS(klass);
> +
> + object_class->get_property = spice_file_transfer_task_get_property;
> + object_class->set_property = spice_file_transfer_task_set_property;
> + object_class->finalize = spice_file_transfer_task_finalize;
> + object_class->dispose = spice_file_transfer_task_dispose;
> + object_class->constructed = spice_file_transfer_task_constructed;
> +
> + /**
> + * SpiceFileTransferTask:id:
> + *
> + * The ID of the file transfer task
> + *
> + * Since: 0.31
> + **/
> + g_object_class_install_property(object_class, PROP_TASK_ID,
> + g_param_spec_uint("id",
> + "id",
> + "The id of the task",
> + 0, G_MAXUINT, 0,
> + G_PARAM_CONSTRUCT_ONLY
> | G_PARAM_READWRITE |
> + G_PARAM_STATIC_STRINGS)
> );
> +
> + /**
> + * SpiceFileTransferTask:channel:
> + *
> + * The main channel that owns the file transfer task
> + *
> + * Since: 0.31
> + **/
> + g_object_class_install_property(object_class, PROP_TASK_CHANNEL,
> + g_param_spec_object("channel",
> + "channel",
> + "The channel
> transferring the file",
> + SPICE_TYPE_MAIN_CHANN
> EL,
> + G_PARAM_CONSTRUCT_ONL
> Y | G_PARAM_READWRITE |
> + G_PARAM_STATIC_STRING
> S));
> +
> + /**
> + * SpiceFileTransferTask:cancellable:
> + *
> + * A cancellable object used to cancel the file transfer
> + *
> + * Since: 0.31
> + **/
> + g_object_class_install_property(object_class, PROP_TASK_CANCELLABLE,
> + g_param_spec_object("cancellable",
> + "cancellable",
> + "The object used to
> cancel the task",
> + G_TYPE_CANCELLABLE,
> + G_PARAM_CONSTRUCT_ONL
> Y | G_PARAM_READWRITE |
> + G_PARAM_STATIC_STRING
> S));
> +
> + /**
> + * SpiceFileTransferTask:file:
> + *
> + * The file that is being transferred in this file transfer task
> + *
> + * Since: 0.31
> + **/
> + g_object_class_install_property(object_class, PROP_TASK_FILE,
> + g_param_spec_object("file",
> + "File",
> + "The file being
> transferred",
> + G_TYPE_FILE,
> + G_PARAM_CONSTRUCT_ONL
> Y | G_PARAM_READWRITE |
> + G_PARAM_STATIC_STRING
> S));
> +
> + /**
> + * SpiceFileTransferTask:progress:
> + *
> + * The current state of the file transfer. This value indicates a
> + * percentage, and ranges from 0 to 100. Listen for change notifications
> on
> + * this property to be updated whenever the file transfer progress
> changes.
> + *
> + * Since: 0.31
> + **/
> + g_object_class_install_property(object_class, PROP_TASK_PROGRESS,
> + g_param_spec_double("progress",
> + "Progress",
> + "The percentage of
> the file transferred",
> + 0.0, 100.0, 0.0,
> + G_PARAM_READABLE |
> + G_PARAM_STATIC_STRING
> S));
> +
> + /**
> + * SpiceFileTransferTask::finished:
> + * @task: the file transfer task that emitted the signal
> + * @error: (transfer none): the error state of the transfer. Will be
> %NULL
> + * if the file transfer was successful.
> + *
> + * The #SpiceFileTransferTask::finished signal is emitted when the file
> + * transfer has completed transferring to the guest.
> + *
> + * Since: 0.31
> + **/
> + task_signals[SIGNAL_FINISHED] = g_signal_new("finished",
> SPICE_TYPE_FILE_TRANSFER_TASK,
> + G_SIGNAL_RUN_FIRST,
> + 0, NULL, NULL,
> + g_cclosure_marshal_VOID__BOXED,
> + G_TYPE_NONE, 1,
> + G_TYPE_ERROR);
> +}
> +
> +static void
> +spice_file_transfer_task_init(SpiceFileTransferTask *self)
> +{
> + self->buffer = g_malloc0(FILE_XFER_CHUNK_SIZE);
> +}
Acked-by: Jonathon Jongsma <jjongsma at redhat.com>
More information about the Spice-devel
mailing list