[Spice-devel,spice-gtk,v1,7/9] file-xfer: move to spice-file-transfer-task.c

Submitted by Victor Toso on May 19, 2016, 11:21 a.m.

Details

Message ID 1463656909-25267-7-git-send-email-victortoso@redhat.com
State Superseded
Headers show
Series "Series without cover letter" ( rev: 2 1 ) in Spice

Not browsing as part of any series.

Commit Message

Victor Toso May 19, 2016, 11:21 a.m.
Previous six patches are related to this change. 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                  | 682 +----------------------------------
 src/spice-file-transfer-task-priv.h |  56 +++
 src/spice-file-transfer-task.c      | 697 ++++++++++++++++++++++++++++++++++++
 4 files changed, 756 insertions(+), 681 deletions(-)
 create mode 100644 src/spice-file-transfer-task-priv.h
 create mode 100644 src/spice-file-transfer-task.c

Patch hide | download patch | download mbox

diff --git a/src/Makefile.am b/src/Makefile.am
index 73bb39c..35ac906 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					\
diff --git a/src/channel-main.c b/src/channel-main.c
index efe559c..2fcc382 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,87 +54,6 @@ 
 
 typedef struct spice_migrate spice_migrate;
 
-/**
- * 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
- */
-G_DEFINE_TYPE(SpiceFileTransferTask, spice_file_transfer_task, G_TYPE_OBJECT)
-
-#define FILE_TRANSFER_TASK_PRIVATE(o) \
-        (G_TYPE_INSTANCE_GET_PRIVATE((o), SPICE_TYPE_FILE_TRANSFER_TASK, SpiceFileTransferTaskPrivate))
-
-#define FILE_XFER_CHUNK_SIZE (VD_AGENT_MAX_DATA_SIZE * 32)
-
-typedef void (*SpiceFileTransferTaskFlushCb)(SpiceFileTransferTask *xfer_task,
-                                             void *buffer,
-                                             gssize count,
-                                             gpointer user_data);
-
-static SpiceMainChannel *spice_file_transfer_task_get_channel(SpiceFileTransferTask *self);
-static GCancellable *spice_file_transfer_task_get_cancellable(SpiceFileTransferTask *self);
-static void spice_file_transfer_task_flush_done(SpiceFileTransferTask *self, GError *error);
-static GList *spice_file_transfer_task_create_tasks(SpiceMainChannel *channel,
-                                                    GFile **files,
-                                                    GFileCopyFlags flags,
-                                                    GCancellable *cancellable,
-                                                    SpiceFileTransferTaskFlushCb flush_callback,
-                                                    gpointer flush_callback_data,
-                                                    GAsyncReadyCallback callback,
-                                                    gpointer user_data);
-static void spice_file_transfer_task_start_task(SpiceFileTransferTask *self);
-
-struct _SpiceFileTransferTaskPrivate
-
-/* private */
-{
-    uint32_t                       id;
-    gboolean                       pending;
-    GFile                          *file;
-    SpiceMainChannel               *channel;
-    GFileInputStream               *file_stream;
-    GFileCopyFlags                 flags;
-    GCancellable                   *cancellable;
-    SpiceFileTransferTaskFlushCb   flush_callback;
-    gpointer                       flush_callback_data;
-    GAsyncReadyCallback            callback;
-    gpointer                       user_data;
-    char                           *buffer;
-    uint64_t                       read_bytes;
-    uint64_t                       file_size;
-    gint64                         start_time;
-    gint64                         last_update;
-    GError                         *error;
-};
-
-enum {
-    PROP_TASK_ID = 1,
-    PROP_TASK_CHANNEL,
-    PROP_TASK_CANCELLABLE,
-    PROP_TASK_FILE,
-    PROP_TASK_PROGRESS,
-};
-
-enum {
-    SIGNAL_FINISHED,
-    SIGNAL_FILE_INFO,
-    LAST_TASK_SIGNAL
-};
-
-static guint task_signals[LAST_TASK_SIGNAL];
-
 typedef enum {
     DISPLAY_UNDEFINED,
     DISPLAY_DISABLED,
@@ -253,8 +172,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 file_xfer_continue_read(SpiceFileTransferTask *task);
-static void spice_file_transfer_task_completed(SpiceFileTransferTask *self, GError *error);
 static void file_xfer_flushed(SpiceMainChannel *channel, gboolean success);
 static void spice_main_set_max_clipboard(SpiceMainChannel *self, gint max);
 static void set_agent_connected(SpiceMainChannel *channel, gboolean connected);
@@ -1814,59 +1731,6 @@  static void main_handle_agent_disconnected(SpiceChannel *channel, SpiceMsgIn *in
     agent_stopped(SPICE_MAIN_CHANNEL(channel));
 }
 
-/* main context */
-static void file_xfer_close_cb(GObject      *object,
-                               GAsyncResult *close_res,
-                               gpointer      user_data)
-{
-    GTask *task;
-    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);
-        }
-    }
-
-    /* Notify to user that files have been transferred or something error
-       happened. */
-    task = g_task_new(self->priv->channel,
-                      self->priv->cancellable,
-                      self->priv->callback,
-                      self->priv->user_data);
-
-    if (self->priv->error) {
-        g_task_return_error(task, self->priv->error);
-    } else {
-        g_task_return_boolean(task, TRUE);
-        if (spice_util_get_debug()) {
-            gint64 now = g_get_monotonic_time();
-            gchar *basename = g_file_get_basename(self->priv->file);
-            double seconds = (double) (now - self->priv->start_time) / G_TIME_SPAN_SECOND;
-            gchar *file_size_str = g_format_size(self->priv->file_size);
-            gchar *transfer_speed_str = g_format_size(self->priv->file_size / seconds);
-
-            g_warn_if_fail(self->priv->read_bytes == self->priv->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(task);
-
-    g_object_unref(self);
-}
-
 static void file_xfer_data_flushed_cb(GObject *source_object,
                                       GAsyncResult *res,
                                       gpointer user_data)
@@ -1913,93 +1777,6 @@  static void file_xfer_flush_callback(SpiceFileTransferTask *xfer_task,
     file_xfer_flush_async(main_channel, cancellable, file_xfer_data_flushed_cb, xfer_task);
 }
 
-/* main context */
-static void file_xfer_read_cb(GObject *source_object,
-                              GAsyncResult *res,
-                              gpointer user_data)
-{
-    SpiceFileTransferTask *self = user_data;
-    gssize count;
-    GError *error = NULL;
-
-    self->priv->pending = FALSE;
-    count = g_input_stream_read_finish(G_INPUT_STREAM(self->priv->file_stream),
-                                       res, &error);
-    /* Check for pending earlier errors */
-    if (self->priv->error) {
-        spice_file_transfer_task_completed(self, error);
-        return;
-    }
-
-    if (count > 0 || self->priv->file_size == 0) {
-        self->priv->read_bytes += count;
-        g_object_notify(G_OBJECT(self), "progress");
-
-        if (self->priv->flush_callback) {
-            self->priv->pending = TRUE;
-            self->priv->flush_callback(self, self->priv->buffer, count, self->priv->flush_callback_data);
-        }
-    } else if (error) {
-        spice_channel_wakeup(SPICE_CHANNEL(self->priv->channel), FALSE);
-        spice_file_transfer_task_completed(self, error);
-    }
-    /* else EOF, do nothing (wait for VD_AGENT_FILE_XFER_STATUS from agent) */
-}
-
-/* coroutine context */
-static void file_xfer_continue_read(SpiceFileTransferTask *self)
-{
-    g_input_stream_read_async(G_INPUT_STREAM(self->priv->file_stream),
-                              self->priv->buffer,
-                              FILE_XFER_CHUNK_SIZE,
-                              G_PRIORITY_DEFAULT,
-                              self->priv->cancellable,
-                              file_xfer_read_cb,
-                              self);
-    self->priv->pending = TRUE;
-}
-
-/* coroutine context */
-static void spice_file_transfer_task_handle_status(SpiceFileTransferTask *task,
-                                                   VDAgentFileXferStatusMessage *msg)
-{
-    GError *error = NULL;
-    g_return_if_fail(task != NULL);
-
-    SPICE_DEBUG("task %d received response %d", msg->id, msg->result);
-
-    switch (msg->result) {
-    case VD_AGENT_FILE_XFER_STATUS_CAN_SEND_DATA:
-        if (task->priv->pending) {
-            error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-                           "transfer received CAN_SEND_DATA in pending state");
-            break;
-        }
-        file_xfer_continue_read(task);
-        return;
-    case VD_AGENT_FILE_XFER_STATUS_CANCELLED:
-        error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-                            "transfer is cancelled by spice agent");
-        break;
-    case VD_AGENT_FILE_XFER_STATUS_ERROR:
-        error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-                            "some errors occurred in the spice agent");
-        break;
-    case VD_AGENT_FILE_XFER_STATUS_SUCCESS:
-        if (task->priv->pending)
-            error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-                                "transfer received success in pending state");
-        break;
-    default:
-        g_warn_if_reached();
-        error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-                            "unhandled status type: %u", msg->result);
-        break;
-    }
-
-    spice_file_transfer_task_completed(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)
@@ -2953,88 +2730,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->priv->error)
-        g_clear_error(&error);
-    if (error) {
-        gchar *path = g_file_get_path(self->priv->file);
-        SPICE_DEBUG("File %s xfer failed: %s",
-                    path, error->message);
-        g_free(path);
-        self->priv->error = error;
-    }
-
-    if (self->priv->pending)
-        return;
-
-    if (!self->priv->file_stream) {
-        file_xfer_close_cb(NULL, NULL, self);
-        goto signal;
-    }
-
-    g_input_stream_close_async(G_INPUT_STREAM(self->priv->file_stream),
-                               G_PRIORITY_DEFAULT,
-                               self->priv->cancellable,
-                               file_xfer_close_cb,
-                               self);
-    self->priv->pending = TRUE;
-signal:
-    g_signal_emit(self, task_signals[SIGNAL_FINISHED], 0, self->priv->error);
-}
-
-
-static void file_xfer_info_async_cb(GObject *obj, GAsyncResult *res, gpointer data)
-{
-    GFileInfo *info;
-    GFile *file = G_FILE(obj);
-    GError *error = NULL;
-    SpiceFileTransferTask *self = SPICE_FILE_TRANSFER_TASK(data);
-
-    self->priv->pending = FALSE;
-    info = g_file_query_info_finish(file, res, &error);
-    if (error || self->priv->error)
-        goto failed;
-
-    self->priv->file_size =
-        g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_STANDARD_SIZE);
-    g_signal_emit(self, task_signals[SIGNAL_FILE_INFO], 0, info);
-    g_object_notify(G_OBJECT(self), "progress");
-
-    return;
-
-failed:
-    spice_file_transfer_task_completed(self, error);
-}
-
-static void file_xfer_read_async_cb(GObject *obj, GAsyncResult *res, gpointer data)
-{
-    GFile *file = G_FILE(obj);
-    SpiceFileTransferTask *self = SPICE_FILE_TRANSFER_TASK(data);
-    GError *error = NULL;
-
-    self->priv->pending = FALSE;
-    self->priv->file_stream = g_file_read_finish(file, res, &error);
-    if (error || self->priv->error) {
-        spice_file_transfer_task_completed(self, error);
-        return;
-    }
-
-    g_file_query_info_async(self->priv->file,
-                            "standard::*",
-                            G_FILE_QUERY_INFO_NONE,
-                            G_PRIORITY_DEFAULT,
-                            self->priv->cancellable,
-                            file_xfer_info_async_cb,
-                            self);
-    self->priv->pending = TRUE;
-}
-
-static SpiceFileTransferTask *spice_file_transfer_task_new(SpiceMainChannel *channel,
-                                                           GFile *file,
-                                                           GCancellable *cancellable);
 static void file_xfer_on_file_info(SpiceFileTransferTask *xfer_task,
                                    GFileInfo *info,
                                    gpointer data)
@@ -3299,378 +2994,3 @@  gboolean spice_main_file_copy_finish(SpiceMainChannel *channel,
 
     return g_task_propagate_boolean(task, error);
 }
-
-static SpiceMainChannel *spice_file_transfer_task_get_channel(SpiceFileTransferTask *self)
-{
-    g_assert_nonnull(self);
-    return self->priv->channel;
-}
-
-static GCancellable *spice_file_transfer_task_get_cancellable(SpiceFileTransferTask *self)
-{
-    g_assert_nonnull(self);
-    return self->priv->cancellable;
-}
-
-static void spice_file_transfer_task_flush_done(SpiceFileTransferTask *self, GError *error)
-{
-    g_assert_nonnull(self);
-    g_return_if_fail(self->priv->pending);
-
-    self->priv->pending = FALSE;
-
-    if (error || self->priv->error) {
-        spice_file_transfer_task_completed(self, error);
-        return;
-    }
-
-    if (spice_util_get_debug()) {
-        const GTimeSpan interval = 20 * G_TIME_SPAN_SECOND;
-        gint64 now = g_get_monotonic_time();
-
-        if (interval < now - self->priv->last_update) {
-            gchar *basename = g_file_get_basename(self->priv->file);
-            self->priv->last_update = now;
-            SPICE_DEBUG("transferred %.2f%% of the file %s",
-                        100.0 * self->priv->read_bytes / self->priv->file_size, basename);
-            g_free(basename);
-        }
-    }
-
-    /* Read more data */
-    file_xfer_continue_read(self);
-}
-
-static GList *spice_file_transfer_task_create_tasks(SpiceMainChannel *channel,
-                                                    GFile **files,
-                                                    GFileCopyFlags flags,
-                                                    GCancellable *cancellable,
-                                                    SpiceFileTransferTaskFlushCb flush_callback,
-                                                    gpointer flush_callback_data,
-                                                    GAsyncReadyCallback callback,
-                                                    gpointer user_data)
-{
-    SpiceFileTransferTask *task;
-    gint i;
-    GList *tasks = NULL;
-
-    for (i = 0; files[i] != NULL && !g_cancellable_is_cancelled(cancellable); i++) {
-        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();
-
-        task = spice_file_transfer_task_new(channel, files[i], task_cancellable);
-        task->priv->flags = flags;
-        task->priv->flush_callback = flush_callback;
-        task->priv->flush_callback_data = flush_callback_data;
-        task->priv->callback = callback;
-        task->priv->user_data = user_data;
-
-        CHANNEL_DEBUG(channel, "Insert a xfer task:%d to task list",
-                      task->priv->id);
-        tasks = g_list_prepend(tasks, task);
-
-        /* if we created a per-task cancellable above, free it */
-        if (!cancellable)
-            g_object_unref(task_cancellable);
-    }
-
-    return g_list_reverse(tasks);
-}
-
-static void spice_file_transfer_task_start_task(SpiceFileTransferTask *self)
-{
-    g_assert_nonnull(self);
-    self->priv->pending = TRUE;
-    g_file_read_async(self->priv->file,
-                      G_PRIORITY_DEFAULT,
-                      self->priv->cancellable,
-                      file_xfer_read_async_cb,
-                      g_object_ref(self));
-}
-
-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->priv->id);
-            break;
-        case PROP_TASK_FILE:
-            g_value_set_object(value, self->priv->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->priv->id = g_value_get_uint(value);
-            break;
-        case PROP_TASK_FILE:
-            self->priv->file = g_value_dup_object(value);
-            break;
-        case PROP_TASK_CHANNEL:
-            self->priv->channel = g_value_dup_object(value);
-            break;
-        case PROP_TASK_CANCELLABLE:
-            self->priv->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->priv->file);
-
-    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->priv->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->priv->file);
-        self->priv->start_time = g_get_monotonic_time();
-        self->priv->last_update = self->priv->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);
-
-    g_type_class_add_private(klass, sizeof(SpiceFileTransferTaskPrivate));
-
-    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_CHANNEL,
-                                                        G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
-                                                        G_PARAM_STATIC_STRINGS));
-
-    /**
-     * 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_ONLY | G_PARAM_READWRITE |
-                                                        G_PARAM_STATIC_STRINGS));
-
-    /**
-     * 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_ONLY | G_PARAM_READWRITE |
-                                                        G_PARAM_STATIC_STRINGS));
-
-    /**
-     * 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_STRINGS));
-
-    /**
-     * SpiceFileTransferTask::file-info
-     * @task: the file transfer task that emitted the signal
-     * @file_info: (transfer none): A GFileInfo object to retrieve file
-     * information. Only keys from standard namespace are supported.
-     *
-     * The #SpiceFileTransferTask::file-info signal is emitted just before the file
-     * transfer effectively starts.
-     *
-     * Since: 0.32
-     **/
-    task_signals[SIGNAL_FILE_INFO] = g_signal_new("file-info", SPICE_TYPE_FILE_TRANSFER_TASK,
-                                             G_SIGNAL_RUN_FIRST,
-                                             0, NULL, NULL,
-                                             g_cclosure_marshal_VOID__BOXED,
-                                             G_TYPE_NONE, 1,
-                                             G_TYPE_FILE_INFO);
-
-    /**
-     * 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->priv = FILE_TRANSFER_TASK_PRIVATE(self);
-    self->priv->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 = 0;    /* 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->priv->file_size == 0)
-        return 0.0;
-
-    return (double)self->priv->read_bytes / self->priv->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->priv->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->priv->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..fa28d00
--- /dev/null
+++ b/src/spice-file-transfer-task-priv.h
@@ -0,0 +1,56 @@ 
+/*
+   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
+
+typedef void (*SpiceFileTransferTaskFlushCb)(SpiceFileTransferTask *xfer_task,
+                                             void *buffer,
+                                             gssize count,
+                                             gpointer user_data);
+
+SpiceMainChannel *spice_file_transfer_task_get_channel(SpiceFileTransferTask *self);
+GCancellable *spice_file_transfer_task_get_cancellable(SpiceFileTransferTask *self);
+void spice_file_transfer_task_flush_done(SpiceFileTransferTask *self, GError *error);
+GList *spice_file_transfer_task_create_tasks(SpiceMainChannel *channel,
+                                             GFile **files,
+                                             GFileCopyFlags flags,
+                                             GCancellable *cancellable,
+                                             SpiceFileTransferTaskFlushCb flush_callback,
+                                             gpointer flush_callback_data,
+                                             GAsyncReadyCallback callback,
+                                             gpointer user_data);
+void spice_file_transfer_task_start_task(SpiceFileTransferTask *self);
+void spice_file_transfer_task_completed(SpiceFileTransferTask *self, GError *error);
+void spice_file_transfer_task_handle_status(SpiceFileTransferTask *task,
+                                            VDAgentFileXferStatusMessage *msg);
+
+
+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..8d006b5
--- /dev/null
+++ b/src/spice-file-transfer-task.c
@@ -0,0 +1,697 @@ 
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2010-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
+ */
+G_DEFINE_TYPE(SpiceFileTransferTask, spice_file_transfer_task, G_TYPE_OBJECT)
+
+#define FILE_TRANSFER_TASK_PRIVATE(o) \
+        (G_TYPE_INSTANCE_GET_PRIVATE((o), SPICE_TYPE_FILE_TRANSFER_TASK, SpiceFileTransferTaskPrivate))
+
+#define FILE_XFER_CHUNK_SIZE (VD_AGENT_MAX_DATA_SIZE * 32)
+
+struct _SpiceFileTransferTaskPrivate
+{
+    uint32_t                       id;
+    gboolean                       pending;
+    GFile                         *file;
+    SpiceMainChannel              *channel;
+    GFileInputStream              *file_stream;
+    GFileCopyFlags                 flags;
+    GCancellable                  *cancellable;
+    SpiceFileTransferTaskFlushCb   flush_callback;
+    gpointer                       flush_callback_data;
+    GAsyncReadyCallback            callback;
+    gpointer                       user_data;
+    char                          *buffer;
+    uint64_t                       read_bytes;
+    uint64_t                       file_size;
+    gint64                         start_time;
+    gint64                         last_update;
+    GError                        *error;
+};
+
+enum {
+    PROP_TASK_ID = 1,
+    PROP_TASK_CHANNEL,
+    PROP_TASK_CANCELLABLE,
+    PROP_TASK_FILE,
+    PROP_TASK_PROGRESS,
+};
+
+enum {
+    SIGNAL_FINISHED,
+    SIGNAL_FILE_INFO,
+    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 = 0;    /* Used to identify task id */
+
+    return g_object_new(SPICE_TYPE_FILE_TRANSFER_TASK,
+                        "id", xfer_id++,
+                        "file", file,
+                        "channel", channel,
+                        "cancellable", cancellable,
+                        NULL);
+}
+
+/* main context */
+static void file_xfer_close_cb(GObject      *object,
+                               GAsyncResult *close_res,
+                               gpointer      user_data)
+{
+    GTask *task;
+    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);
+        }
+    }
+
+    /* Notify to user that files have been transferred or something error
+       happened. */
+    task = g_task_new(self->priv->channel,
+                      self->priv->cancellable,
+                      self->priv->callback,
+                      self->priv->user_data);
+
+    if (self->priv->error) {
+        g_task_return_error(task, self->priv->error);
+    } else {
+        g_task_return_boolean(task, TRUE);
+        if (spice_util_get_debug()) {
+            gint64 now = g_get_monotonic_time();
+            gchar *basename = g_file_get_basename(self->priv->file);
+            double seconds = (double) (now - self->priv->start_time) / G_TIME_SPAN_SECOND;
+            gchar *file_size_str = g_format_size(self->priv->file_size);
+            gchar *transfer_speed_str = g_format_size(self->priv->file_size / seconds);
+
+            g_warn_if_fail(self->priv->read_bytes == self->priv->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(task);
+
+    g_object_unref(self);
+}
+
+/* main context */
+static void file_xfer_read_cb(GObject *source_object,
+                              GAsyncResult *res,
+                              gpointer user_data)
+{
+    SpiceFileTransferTask *self = user_data;
+    gssize count;
+    GError *error = NULL;
+
+    self->priv->pending = FALSE;
+    count = g_input_stream_read_finish(G_INPUT_STREAM(self->priv->file_stream),
+                                       res, &error);
+    /* Check for pending earlier errors */
+    if (self->priv->error) {
+        spice_file_transfer_task_completed(self, error);
+        return;
+    }
+
+    if (count > 0 || self->priv->file_size == 0) {
+        self->priv->read_bytes += count;
+        g_object_notify(G_OBJECT(self), "progress");
+
+        if (self->priv->flush_callback) {
+            self->priv->pending = TRUE;
+            self->priv->flush_callback(self, self->priv->buffer, count, self->priv->flush_callback_data);
+        }
+    } else if (error) {
+        spice_channel_wakeup(SPICE_CHANNEL(self->priv->channel), FALSE);
+        spice_file_transfer_task_completed(self, error);
+    }
+    /* else EOF, do nothing (wait for VD_AGENT_FILE_XFER_STATUS from agent) */
+}
+
+/* coroutine context */
+static void file_xfer_continue_read(SpiceFileTransferTask *self)
+{
+    g_input_stream_read_async(G_INPUT_STREAM(self->priv->file_stream),
+                              self->priv->buffer,
+                              FILE_XFER_CHUNK_SIZE,
+                              G_PRIORITY_DEFAULT,
+                              self->priv->cancellable,
+                              file_xfer_read_cb,
+                              self);
+    self->priv->pending = TRUE;
+}
+
+static void file_xfer_info_async_cb(GObject *obj, GAsyncResult *res, gpointer data)
+{
+    GFileInfo *info;
+    GFile *file = G_FILE(obj);
+    GError *error = NULL;
+    SpiceFileTransferTask *self = SPICE_FILE_TRANSFER_TASK(data);
+
+    self->priv->pending = FALSE;
+    info = g_file_query_info_finish(file, res, &error);
+    if (error || self->priv->error)
+        goto failed;
+
+    self->priv->file_size =
+        g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_STANDARD_SIZE);
+    g_signal_emit(self, task_signals[SIGNAL_FILE_INFO], 0, info);
+    g_object_notify(G_OBJECT(self), "progress");
+
+    return;
+
+failed:
+    spice_file_transfer_task_completed(self, error);
+}
+
+static void file_xfer_read_async_cb(GObject *obj, GAsyncResult *res, gpointer data)
+{
+    GFile *file = G_FILE(obj);
+    SpiceFileTransferTask *self = SPICE_FILE_TRANSFER_TASK(data);
+    GError *error = NULL;
+
+    self->priv->pending = FALSE;
+    self->priv->file_stream = g_file_read_finish(file, res, &error);
+    if (error || self->priv->error) {
+        spice_file_transfer_task_completed(self, error);
+        return;
+    }
+
+    g_file_query_info_async(self->priv->file,
+                            "standard::*",
+                            G_FILE_QUERY_INFO_NONE,
+                            G_PRIORITY_DEFAULT,
+                            self->priv->cancellable,
+                            file_xfer_info_async_cb,
+                            self);
+    self->priv->pending = TRUE;
+}
+
+/*******************************************************************************
+ * Internal API
+ ******************************************************************************/
+
+/* coroutine context */
+G_GNUC_INTERNAL
+void spice_file_transfer_task_handle_status(SpiceFileTransferTask *task,
+                                            VDAgentFileXferStatusMessage *msg)
+{
+    GError *error = NULL;
+    g_return_if_fail(task != NULL);
+
+    SPICE_DEBUG("task %d received response %d", msg->id, msg->result);
+
+    switch (msg->result) {
+    case VD_AGENT_FILE_XFER_STATUS_CAN_SEND_DATA:
+        if (task->priv->pending) {
+            error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                           "transfer received CAN_SEND_DATA in pending state");
+            break;
+        }
+        file_xfer_continue_read(task);
+        return;
+    case VD_AGENT_FILE_XFER_STATUS_CANCELLED:
+        error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                            "transfer is cancelled by spice agent");
+        break;
+    case VD_AGENT_FILE_XFER_STATUS_ERROR:
+        error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                            "some errors occurred in the spice agent");
+        break;
+    case VD_AGENT_FILE_XFER_STATUS_SUCCESS:
+        if (task->priv->pending)
+            error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                                "transfer received success in pending state");
+        break;
+    default:
+        g_warn_if_reached();
+        error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                            "unhandled status type: %u", msg->result);
+        break;
+    }
+
+    spice_file_transfer_task_completed(task, error);
+}
+
+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->priv->error)
+        g_clear_error(&error);
+    if (error) {
+        gchar *path = g_file_get_path(self->priv->file);
+        SPICE_DEBUG("File %s xfer failed: %s",
+                    path, error->message);
+        g_free(path);
+        self->priv->error = error;
+    }
+
+    if (self->priv->pending)
+        return;
+
+    if (!self->priv->file_stream) {
+        file_xfer_close_cb(NULL, NULL, self);
+        goto signal;
+    }
+
+    g_input_stream_close_async(G_INPUT_STREAM(self->priv->file_stream),
+                               G_PRIORITY_DEFAULT,
+                               self->priv->cancellable,
+                               file_xfer_close_cb,
+                               self);
+    self->priv->pending = TRUE;
+signal:
+    g_signal_emit(self, task_signals[SIGNAL_FINISHED], 0, self->priv->error);
+}
+
+G_GNUC_INTERNAL
+SpiceMainChannel *spice_file_transfer_task_get_channel(SpiceFileTransferTask *self)
+{
+    g_assert_nonnull(self);
+    return self->priv->channel;
+}
+
+G_GNUC_INTERNAL
+GCancellable *spice_file_transfer_task_get_cancellable(SpiceFileTransferTask *self)
+{
+    g_assert_nonnull(self);
+    return self->priv->cancellable;
+}
+
+G_GNUC_INTERNAL
+void spice_file_transfer_task_flush_done(SpiceFileTransferTask *self, GError *error)
+{
+    g_assert_nonnull(self);
+    g_return_if_fail(self->priv->pending);
+
+    self->priv->pending = FALSE;
+
+    if (error || self->priv->error) {
+        spice_file_transfer_task_completed(self, error);
+        return;
+    }
+
+    if (spice_util_get_debug()) {
+        const GTimeSpan interval = 20 * G_TIME_SPAN_SECOND;
+        gint64 now = g_get_monotonic_time();
+
+        if (interval < now - self->priv->last_update) {
+            gchar *basename = g_file_get_basename(self->priv->file);
+            self->priv->last_update = now;
+            SPICE_DEBUG("transferred %.2f%% of the file %s",
+                        100.0 * self->priv->read_bytes / self->priv->file_size, basename);
+            g_free(basename);
+        }
+    }
+
+    /* Read more data */
+    file_xfer_continue_read(self);
+}
+
+G_GNUC_INTERNAL
+GList *spice_file_transfer_task_create_tasks(SpiceMainChannel *channel,
+                                             GFile **files,
+                                             GFileCopyFlags flags,
+                                             GCancellable *cancellable,
+                                             SpiceFileTransferTaskFlushCb flush_callback,
+                                             gpointer flush_callback_data,
+                                             GAsyncReadyCallback callback,
+                                             gpointer user_data)
+{
+    SpiceFileTransferTask *task;
+    gint i;
+    GList *tasks = NULL;
+
+    for (i = 0; files[i] != NULL && !g_cancellable_is_cancelled(cancellable); i++) {
+        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();
+
+        task = spice_file_transfer_task_new(channel, files[i], task_cancellable);
+        task->priv->flags = flags;
+        task->priv->flush_callback = flush_callback;
+        task->priv->flush_callback_data = flush_callback_data;
+        task->priv->callback = callback;
+        task->priv->user_data = user_data;
+
+        CHANNEL_DEBUG(channel, "Insert a xfer task:%d to task list",
+                      task->priv->id);
+        tasks = g_list_prepend(tasks, task);
+
+        /* if we created a per-task cancellable above, free it */
+        if (!cancellable)
+            g_object_unref(task_cancellable);
+    }
+
+    return g_list_reverse(tasks);
+}
+
+G_GNUC_INTERNAL
+void spice_file_transfer_task_start_task(SpiceFileTransferTask *self)
+{
+    g_assert_nonnull(self);
+    self->priv->pending = TRUE;
+    g_file_read_async(self->priv->file,
+                      G_PRIORITY_DEFAULT,
+                      self->priv->cancellable,
+                      file_xfer_read_async_cb,
+                      g_object_ref(self));
+}
+
+/*******************************************************************************
+ * 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->priv->file_size == 0)
+        return 0.0;
+
+    return (double)self->priv->read_bytes / self->priv->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->priv->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->priv->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->priv->id);
+            break;
+        case PROP_TASK_FILE:
+            g_value_set_object(value, self->priv->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->priv->id = g_value_get_uint(value);
+            break;
+        case PROP_TASK_FILE:
+            self->priv->file = g_value_dup_object(value);
+            break;
+        case PROP_TASK_CHANNEL:
+            self->priv->channel = g_value_dup_object(value);
+            break;
+        case PROP_TASK_CANCELLABLE:
+            self->priv->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->priv->file);
+
+    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->priv->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->priv->file);
+        self->priv->start_time = g_get_monotonic_time();
+        self->priv->last_update = self->priv->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);
+
+    g_type_class_add_private(klass, sizeof(SpiceFileTransferTaskPrivate));
+
+    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_CHANNEL,
+                                                        G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+                                                        G_PARAM_STATIC_STRINGS));
+
+    /**
+     * 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_ONLY | G_PARAM_READWRITE |
+                                                        G_PARAM_STATIC_STRINGS));
+
+    /**
+     * 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_ONLY | G_PARAM_READWRITE |
+                                                        G_PARAM_STATIC_STRINGS));
+
+    /**
+     * 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_STRINGS));
+
+    /**
+     * SpiceFileTransferTask::file-info
+     * @task: the file transfer task that emitted the signal
+     * @file_info: (transfer none): A GFileInfo object to retrieve file
+     * information. Only keys from standard namespace are supported.
+     *
+     * The #SpiceFileTransferTask::file-info signal is emitted just before the file
+     * transfer effectively starts.
+     *
+     * Since: 0.32
+     **/
+    task_signals[SIGNAL_FILE_INFO] = g_signal_new("file-info", SPICE_TYPE_FILE_TRANSFER_TASK,
+                                             G_SIGNAL_RUN_FIRST,
+                                             0, NULL, NULL,
+                                             g_cclosure_marshal_VOID__BOXED,
+                                             G_TYPE_NONE, 1,
+                                             G_TYPE_FILE_INFO);
+
+    /**
+     * 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->priv = FILE_TRANSFER_TASK_PRIVATE(self);
+    self->priv->buffer = g_malloc0(FILE_XFER_CHUNK_SIZE);
+}