[Spice-devel] [spice-gtk v4 20/24] file-xfer: move to spice-file-transfer-task.c

Victor Toso lists at victortoso.com
Tue Jun 28 12:53:30 UTC 2016


Hi,

On Mon, Jun 27, 2016 at 03:57:10PM -0500, Jonathon Jongsma wrote:
> 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.

Indeed, I'll fix it
> 
> 
> > 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>

Thanks! I plan to send a v5 with the changes after we agree on the
pending discussions on previous patches :)

Cheers!
  toso

>
> _______________________________________________
> Spice-devel mailing list
> Spice-devel at lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/spice-devel


More information about the Spice-devel mailing list