[Spice-commits] 9 commits - gtk/channel-main.c gtk/channel-main.h gtk/map-file gtk/spice-glib-sym-file gtk/spice-widget.c spice-common

Marc-André Lureau elmarco at kemper.freedesktop.org
Fri Jan 11 16:03:01 PST 2013


 gtk/channel-main.c      |  488 ++++++++++++++++++++++++++++++++++++++++++++++++
 gtk/channel-main.h      |   12 +
 gtk/map-file            |    2 
 gtk/spice-glib-sym-file |    2 
 gtk/spice-widget.c      |   46 ++++
 spice-common            |    2 
 6 files changed, 551 insertions(+), 1 deletion(-)

New commits:
commit dfa97ae5a7cca82731778d2e8e7e2daf51266ca7
Author: Marc-André Lureau <marcandre.lureau at redhat.com>
Date:   Sat Jan 12 00:57:54 2013 +0100

    file-xfer: always take error if set in xfer_read_cb()

diff --git a/gtk/channel-main.c b/gtk/channel-main.c
index a62501d..706c119 100644
--- a/gtk/channel-main.c
+++ b/gtk/channel-main.c
@@ -1589,7 +1589,7 @@ static void file_xfer_read_cb(GObject *source_object,
                               file_xfer_data_flushed_cb, task);
     } else {
         /* Error or EOF, close the file */
-        if (count == -1) {
+        if (error) {
             task->error = error;
         }
         g_input_stream_close_async(G_INPUT_STREAM(task->file_stream),
commit 11a0fb4091ee63777ccc708fc0dc6db7fc9daad6
Author: Marc-André Lureau <marcandre.lureau at redhat.com>
Date:   Sat Jan 12 00:55:02 2013 +0100

    file-xfer: use file_xfer_..() prefix for all internal copy functions

diff --git a/gtk/channel-main.c b/gtk/channel-main.c
index 0e68010..a62501d 100644
--- a/gtk/channel-main.c
+++ b/gtk/channel-main.c
@@ -162,6 +162,7 @@ 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(SpiceFileXferTask *task);
 
 /* ------------------------------------------------------------------ */
 
@@ -824,8 +825,7 @@ static void agent_free_msg_queue(SpiceMainChannel *channel)
 }
 
 /* Here, flushing algorithm is stolen from spice-channel.c */
-static void
-file_xfer_flushed(SpiceMainChannel *channel, gboolean success)
+static void file_xfer_flushed(SpiceMainChannel *channel, gboolean success)
 {
     SpiceMainChannelPrivate *c = channel->priv;
     GSList *l;
@@ -840,9 +840,8 @@ file_xfer_flushed(SpiceMainChannel *channel, gboolean success)
     c->flushing = NULL;
 }
 
-static void
-file_xfer_flush_async(SpiceMainChannel *channel, GCancellable *cancellable,
-                      GAsyncReadyCallback callback, gpointer user_data)
+static void file_xfer_flush_async(SpiceMainChannel *channel, GCancellable *cancellable,
+                                  GAsyncReadyCallback callback, gpointer user_data)
 {
     GSimpleAsyncResult *simple;
     SpiceMainChannelPrivate *c = channel->priv;
@@ -862,9 +861,8 @@ file_xfer_flush_async(SpiceMainChannel *channel, GCancellable *cancellable,
     c->flushing = g_slist_append(c->flushing, simple);
 }
 
-static gboolean
-file_xfer_flush_finish(SpiceMainChannel *channel, GAsyncResult *result,
-                       GError **error)
+static gboolean file_xfer_flush_finish(SpiceMainChannel *channel, GAsyncResult *result,
+                                       GError **error)
 {
     GSimpleAsyncResult *simple;
 
@@ -1478,21 +1476,7 @@ static gint file_xfer_task_find(gconstpointer a, gconstpointer b)
     return 1;
 }
 
-static void file_read_cb(GObject *source_object,
-                         GAsyncResult *res,
-                         gpointer user_data);
-static void file_xfer_continue_read(SpiceFileXferTask *task);
-
-static void report_progress(SpiceFileXferTask *task)
-{
-    if (task->progress_callback) {
-        task->progress_callback(task->read_bytes, task->file_size,
-                                task->progress_callback_data);
-    }
-}
-
-static void
-file_xfer_task_free(SpiceFileXferTask *task)
+static void file_xfer_task_free(SpiceFileXferTask *task)
 {
     SpiceMainChannelPrivate *c;
 
@@ -1507,10 +1491,9 @@ file_xfer_task_free(SpiceFileXferTask *task)
 }
 
 /* main context */
-static void
-file_close_cb(GObject      *object,
-              GAsyncResult *close_res,
-              gpointer      user_data)
+static void file_xfer_close_cb(GObject      *object,
+                               GAsyncResult *close_res,
+                               gpointer      user_data)
 {
     GSimpleAsyncResult *res;
     SpiceFileXferTask *task;
@@ -1545,9 +1528,9 @@ file_close_cb(GObject      *object,
     file_xfer_task_free(task);
 }
 
-static void data_flushed_cb(GObject *source_object,
-                            GAsyncResult *res,
-                            gpointer user_data)
+static void file_xfer_data_flushed_cb(GObject *source_object,
+                                      GAsyncResult *res,
+                                      gpointer user_data)
 {
     SpiceFileXferTask *task = user_data;
     SpiceMainChannel *channel = (SpiceMainChannel *)source_object;
@@ -1561,20 +1544,20 @@ static void data_flushed_cb(GObject *source_object,
         g_input_stream_close_async(G_INPUT_STREAM(task->file_stream),
                                    G_PRIORITY_DEFAULT,
                                    task->cancellable,
-                                   file_close_cb,
+                                   file_xfer_close_cb,
                                    task);
         return;
     }
 
-    /* Report progress */
-    report_progress(task);
+    if (task->progress_callback)
+        task->progress_callback(task->read_bytes, task->file_size,
+                                task->progress_callback_data);
 
     /* Read more data */
     file_xfer_continue_read(task);
 }
 
-static void
-file_xfer_queue(SpiceFileXferTask *task, int data_size)
+static void file_xfer_queue(SpiceFileXferTask *task, int data_size)
 {
     VDAgentFileXferDataMessage msg;
     SpiceMainChannel *channel = SPICE_MAIN_CHANNEL(task->channel);
@@ -1588,9 +1571,9 @@ file_xfer_queue(SpiceFileXferTask *task, int data_size)
 }
 
 /* main context */
-static void file_read_cb(GObject *source_object,
-                         GAsyncResult *res,
-                         gpointer user_data)
+static void file_xfer_read_cb(GObject *source_object,
+                              GAsyncResult *res,
+                              gpointer user_data)
 {
     SpiceFileXferTask *task = user_data;
     SpiceMainChannel *channel = task->channel;
@@ -1603,7 +1586,7 @@ static void file_read_cb(GObject *source_object,
         task->read_bytes += count;
         file_xfer_queue(task, count);
         file_xfer_flush_async(channel, task->cancellable,
-                              data_flushed_cb, task);
+                              file_xfer_data_flushed_cb, task);
     } else {
         /* Error or EOF, close the file */
         if (count == -1) {
@@ -1612,7 +1595,7 @@ static void file_read_cb(GObject *source_object,
         g_input_stream_close_async(G_INPUT_STREAM(task->file_stream),
                                    G_PRIORITY_DEFAULT,
                                    task->cancellable,
-                                   file_close_cb,
+                                   file_xfer_close_cb,
                                    task);
     }
 }
@@ -1625,7 +1608,7 @@ static void file_xfer_continue_read(SpiceFileXferTask *task)
                               FILE_XFER_CHUNK_SIZE,
                               G_PRIORITY_DEFAULT,
                               task->cancellable,
-                              file_read_cb,
+                              file_xfer_read_cb,
                               task);
 }
 
@@ -1669,7 +1652,7 @@ static void file_xfer_handle_status(SpiceMainChannel *channel,
     g_input_stream_close_async(G_INPUT_STREAM(task->file_stream),
                                G_PRIORITY_DEFAULT,
                                task->cancellable,
-                               file_close_cb,
+                               file_xfer_close_cb,
                                task);
 }
 
@@ -2545,8 +2528,7 @@ void spice_main_set_display_enabled(SpiceMainChannel *channel, int id, gboolean
     c->timer_id = g_timeout_add_seconds(1, timer_set_display, channel);
 }
 
-static void
-file_xfer_failed(SpiceFileXferTask *task, GError *error)
+static void file_xfer_failed(SpiceFileXferTask *task, GError *error)
 {
     SPICE_DEBUG("File %s xfer failed: %s",
                 g_file_get_path(task->file), error->message);
@@ -2555,12 +2537,11 @@ file_xfer_failed(SpiceFileXferTask *task, GError *error)
     g_input_stream_close_async(G_INPUT_STREAM(task->file_stream),
                                G_PRIORITY_DEFAULT,
                                task->cancellable,
-                               file_close_cb,
+                               file_xfer_close_cb,
                                task);
 }
 
-static void
-file_info_async_cb(GObject *obj, GAsyncResult *res, gpointer data)
+static void file_xfer_info_async_cb(GObject *obj, GAsyncResult *res, gpointer data)
 {
     GFileInfo *info;
     GFile *file = G_FILE(obj);
@@ -2611,8 +2592,7 @@ failed:
     file_xfer_failed(task, error);
 }
 
-static void
-read_async_cb(GObject *obj, GAsyncResult *res, gpointer data)
+static void file_xfer_read_async_cb(GObject *obj, GAsyncResult *res, gpointer data)
 {
     GFile *file = G_FILE(obj);
     SpiceFileXferTask *task = (SpiceFileXferTask *)data;
@@ -2629,19 +2609,18 @@ read_async_cb(GObject *obj, GAsyncResult *res, gpointer data)
                             G_FILE_QUERY_INFO_NONE,
                             G_PRIORITY_DEFAULT,
                             task->cancellable,
-                            file_info_async_cb,
+                            file_xfer_info_async_cb,
                             task);
 }
 
-static void
-file_xfer_send_start_msg_async(SpiceMainChannel *channel,
-                               GFile *file,
-                               GFileCopyFlags flags,
-                               GCancellable *cancellable,
-                               GFileProgressCallback progress_callback,
-                               gpointer progress_callback_data,
-                               GAsyncReadyCallback callback,
-                               gpointer user_data)
+static void file_xfer_send_start_msg_async(SpiceMainChannel *channel,
+                                           GFile *file,
+                                           GFileCopyFlags flags,
+                                           GCancellable *cancellable,
+                                           GFileProgressCallback progress_callback,
+                                           gpointer progress_callback_data,
+                                           GAsyncReadyCallback callback,
+                                           gpointer user_data)
 {
     SpiceFileXferTask *task;
     static uint32_t xfer_id;    /* Used to identify task id */
@@ -2662,7 +2641,7 @@ file_xfer_send_start_msg_async(SpiceMainChannel *channel,
     g_file_read_async(file,
                       G_PRIORITY_DEFAULT,
                       cancellable,
-                      read_async_cb,
+                      file_xfer_read_async_cb,
                       task);
 
 }
commit 26431486f5de594f5ba264d22af65c4455f9201a
Author: Marc-André Lureau <marcandre.lureau at redhat.com>
Date:   Sat Jan 12 00:39:23 2013 +0100

    file-xfer: move file_close_cb() above all to ease reading

diff --git a/gtk/channel-main.c b/gtk/channel-main.c
index a9bbf52..0e68010 100644
--- a/gtk/channel-main.c
+++ b/gtk/channel-main.c
@@ -1492,61 +1492,14 @@ static void report_progress(SpiceFileXferTask *task)
 }
 
 static void
-file_close_cb(GObject      *object,
-              GAsyncResult *close_res,
-              gpointer      user_data);
-
-static void data_flushed_cb(GObject *source_object,
-                            GAsyncResult *res,
-                            gpointer user_data)
-{
-    SpiceFileXferTask *task = user_data;
-    SpiceMainChannel *channel = (SpiceMainChannel *)source_object;
-    GError *error = NULL;
-
-    file_xfer_flush_finish(channel, res, &error);
-
-    if (error != NULL) {
-        g_warning("failed to flush xfer queue: %s", error->message);
-        task->error = error;
-        g_input_stream_close_async(G_INPUT_STREAM(task->file_stream),
-                                   G_PRIORITY_DEFAULT,
-                                   task->cancellable,
-                                   file_close_cb,
-                                   task);
-        return;
-    }
-
-    /* Report progress */
-    report_progress(task);
-
-    /* Read more data */
-    file_xfer_continue_read(task);
-}
-
-static void
-file_xfer_queue(SpiceFileXferTask *task, int data_size)
-{
-    VDAgentFileXferDataMessage msg;
-    SpiceMainChannel *channel = SPICE_MAIN_CHANNEL(task->channel);
-
-    msg.id = task->id;
-    msg.size = data_size;
-    agent_msg_queue_many(channel, VD_AGENT_FILE_XFER_DATA,
-                         &msg, sizeof(msg),
-                         task->buffer, data_size, NULL);
-    spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);
-}
-
-static void file_xfer_task_free(SpiceFileXferTask *task)
+file_xfer_task_free(SpiceFileXferTask *task)
 {
     SpiceMainChannelPrivate *c;
 
     g_return_if_fail(task != NULL);
 
     c = task->channel->priv;
-    c->file_xfer_task_list = g_list_remove(c->file_xfer_task_list,
-                                           task);
+    c->file_xfer_task_list = g_list_remove(c->file_xfer_task_list, task);
 
     g_clear_object(&task->file);
     g_clear_object(&task->file_stream);
@@ -1592,6 +1545,48 @@ file_close_cb(GObject      *object,
     file_xfer_task_free(task);
 }
 
+static void data_flushed_cb(GObject *source_object,
+                            GAsyncResult *res,
+                            gpointer user_data)
+{
+    SpiceFileXferTask *task = user_data;
+    SpiceMainChannel *channel = (SpiceMainChannel *)source_object;
+    GError *error = NULL;
+
+    file_xfer_flush_finish(channel, res, &error);
+
+    if (error != NULL) {
+        g_warning("failed to flush xfer queue: %s", error->message);
+        task->error = error;
+        g_input_stream_close_async(G_INPUT_STREAM(task->file_stream),
+                                   G_PRIORITY_DEFAULT,
+                                   task->cancellable,
+                                   file_close_cb,
+                                   task);
+        return;
+    }
+
+    /* Report progress */
+    report_progress(task);
+
+    /* Read more data */
+    file_xfer_continue_read(task);
+}
+
+static void
+file_xfer_queue(SpiceFileXferTask *task, int data_size)
+{
+    VDAgentFileXferDataMessage msg;
+    SpiceMainChannel *channel = SPICE_MAIN_CHANNEL(task->channel);
+
+    msg.id = task->id;
+    msg.size = data_size;
+    agent_msg_queue_many(channel, VD_AGENT_FILE_XFER_DATA,
+                         &msg, sizeof(msg),
+                         task->buffer, data_size, NULL);
+    spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);
+}
+
 /* main context */
 static void file_read_cb(GObject *source_object,
                          GAsyncResult *res,
commit c6fac8b863472b4c88c38d3f4e85a92493cc5e22
Author: Marc-André Lureau <marcandre.lureau at redhat.com>
Date:   Sat Jan 12 00:38:04 2013 +0100

    file-xfer: try to report any error from file_info_async_cb()

diff --git a/gtk/channel-main.c b/gtk/channel-main.c
index c8a1013..a9bbf52 100644
--- a/gtk/channel-main.c
+++ b/gtk/channel-main.c
@@ -2551,6 +2551,20 @@ void spice_main_set_display_enabled(SpiceMainChannel *channel, int id, gboolean
 }
 
 static void
+file_xfer_failed(SpiceFileXferTask *task, GError *error)
+{
+    SPICE_DEBUG("File %s xfer failed: %s",
+                g_file_get_path(task->file), error->message);
+
+    task->error = error;
+    g_input_stream_close_async(G_INPUT_STREAM(task->file_stream),
+                               G_PRIORITY_DEFAULT,
+                               task->cancellable,
+                               file_close_cb,
+                               task);
+}
+
+static void
 file_info_async_cb(GObject *obj, GAsyncResult *res, gpointer data)
 {
     GFileInfo *info;
@@ -2565,41 +2579,26 @@ file_info_async_cb(GObject *obj, GAsyncResult *res, gpointer data)
     SpiceMainChannelPrivate *c = task->channel->priv;
 
     info = g_file_query_info_finish(file, res, &error);
-    if (error) {
-        SPICE_DEBUG("couldn't get size of file %s: %s",
-                    g_file_get_path(file),
-                    error->message);
+    if (error)
         goto failed;
-    }
-    task->file_size = g_file_info_get_attribute_uint64(info,
-                                        G_FILE_ATTRIBUTE_STANDARD_SIZE);
 
+    task->file_size =
+        g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_STANDARD_SIZE);
     keyfile = g_key_file_new();
-    if (keyfile == NULL) {
-        SPICE_DEBUG("failed to create key file: %s", error->message);
-        goto failed;
-    }
 
     /* File name */
     basename = g_file_get_basename(file);
-    if (basename == NULL) {
-        SPICE_DEBUG("failed to get file basename: %s", error->message);
-        goto failed;
-    }
     g_key_file_set_string(keyfile, "vdagent-file-xfer", "name", basename);
     g_free(basename);
-
     /* File size */
-    g_key_file_set_uint64(keyfile, "vdagent-file-xfer", "size",
-                          task->file_size);
+    g_key_file_set_uint64(keyfile, "vdagent-file-xfer", "size", task->file_size);
 
     /* Save keyfile content to memory. TODO: more file attributions
        need to be sent to guest */
     string = g_key_file_to_data(keyfile, &data_len, &error);
     g_key_file_free(keyfile);
-    if (error) {
+    if (error)
         goto failed;
-    }
 
     /* Create file-xfer start message */
     CHANNEL_DEBUG(task->channel, "Insert a xfer task:%d to task list", task->id);
@@ -2611,11 +2610,10 @@ file_info_async_cb(GObject *obj, GAsyncResult *res, gpointer data)
                          string, data_len + 1, NULL);
     g_free(string);
     spice_channel_wakeup(SPICE_CHANNEL(task->channel), FALSE);
-    return ;
+    return;
 
 failed:
-    g_clear_error(&error);
-    file_xfer_task_free(task);
+    file_xfer_failed(task, error);
 }
 
 static void
@@ -2626,25 +2624,18 @@ read_async_cb(GObject *obj, GAsyncResult *res, gpointer data)
     GError *error = NULL;
 
     task->file_stream = g_file_read_finish(file, res, &error);
-
-    if (task->file_stream) {
-        g_file_query_info_async(task->file,
-                                G_FILE_ATTRIBUTE_STANDARD_SIZE,
-                                G_FILE_QUERY_INFO_NONE,
-                                G_PRIORITY_DEFAULT,
-                                task->cancellable,
-                                file_info_async_cb,
-                                task);
-    } else {
-        SPICE_DEBUG("create file stream for %s error: %s",
-                    g_file_get_path(file), error->message);
-        task->error = error;
-        g_input_stream_close_async(G_INPUT_STREAM(task->file_stream),
-                                   G_PRIORITY_DEFAULT,
-                                   task->cancellable,
-                                   file_close_cb,
-                                   task);
+    if (error) {
+        file_xfer_failed (task, error);
+        return;
     }
+
+    g_file_query_info_async(task->file,
+                            G_FILE_ATTRIBUTE_STANDARD_SIZE,
+                            G_FILE_QUERY_INFO_NONE,
+                            G_PRIORITY_DEFAULT,
+                            task->cancellable,
+                            file_info_async_cb,
+                            task);
 }
 
 static void
commit 0fb90dea38d521ab696966718140853c74be9f72
Author: Marc-André Lureau <marcandre.lureau at redhat.com>
Date:   Sat Jan 12 00:25:16 2013 +0100

    file-xfer: non-programming errors should be reported in async
    
    It is fine to not return async errors for programming errors via
    g_return_if_fail() and friends, however, we need to return proper
    error if it's a normal run-time error.

diff --git a/gtk/channel-main.c b/gtk/channel-main.c
index ef699ae..c8a1013 100644
--- a/gtk/channel-main.c
+++ b/gtk/channel-main.c
@@ -2723,12 +2723,20 @@ void spice_main_file_copy_async(SpiceMainChannel *channel,
     g_return_if_fail(SPICE_IS_MAIN_CHANNEL(channel));
     g_return_if_fail(sources != NULL && sources[0] != NULL);
 
-    g_return_if_fail(c->agent_connected);
-
     /* At the moment, the copy() method is limited to a single file,
        support for copying multi-files will be implemented later. */
     g_return_if_fail(sources[1] == NULL);
 
+    if (!c->agent_connected) {
+        g_simple_async_report_error_in_idle(G_OBJECT(channel),
+                                            callback,
+                                            user_data,
+                                            SPICE_CLIENT_ERROR,
+                                            SPICE_CLIENT_ERROR_FAILED,
+                                            "The agent is not connected");
+        return;
+    }
+
     file_xfer_send_start_msg_async(channel,
                                    sources[0],
                                    flags,
commit 4835d06f95c977ec8af5b862b19e057968358986
Author: Marc-André Lureau <marcandre.lureau at redhat.com>
Date:   Sat Jan 12 00:16:47 2013 +0100

    file-xfer: avoid g_alloca() usage when possible

diff --git a/gtk/channel-main.c b/gtk/channel-main.c
index 1725127..ef699ae 100644
--- a/gtk/channel-main.c
+++ b/gtk/channel-main.c
@@ -1527,15 +1527,14 @@ static void data_flushed_cb(GObject *source_object,
 static void
 file_xfer_queue(SpiceFileXferTask *task, int data_size)
 {
-    VDAgentFileXferDataMessage *msg;
+    VDAgentFileXferDataMessage msg;
     SpiceMainChannel *channel = SPICE_MAIN_CHANNEL(task->channel);
 
-    msg = g_alloca(sizeof(VDAgentFileXferDataMessage));
-    msg->id = task->id;
-    msg->size = data_size;
-    agent_msg_queue_many(channel, VD_AGENT_FILE_XFER_DATA, msg,
-                         sizeof(VDAgentFileXferDataMessage), task->buffer,
-                         data_size, NULL);
+    msg.id = task->id;
+    msg.size = data_size;
+    agent_msg_queue_many(channel, VD_AGENT_FILE_XFER_DATA,
+                         &msg, sizeof(msg),
+                         task->buffer, data_size, NULL);
     spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);
 }
 
@@ -2559,7 +2558,7 @@ file_info_async_cb(GObject *obj, GAsyncResult *res, gpointer data)
     GError *error = NULL;
     GKeyFile *keyfile = NULL;
     gchar *basename = NULL;
-    VDAgentFileXferStartMessage *msg;
+    VDAgentFileXferStartMessage msg;
     gsize /*msg_size*/ data_len;
     gchar *string;
     SpiceFileXferTask *task = (SpiceFileXferTask *)data;
@@ -2603,16 +2602,13 @@ file_info_async_cb(GObject *obj, GAsyncResult *res, gpointer data)
     }
 
     /* Create file-xfer start message */
-    msg = g_alloca(sizeof(VDAgentFileXferStartMessage));
-    msg->id = task->id;
-
-    CHANNEL_DEBUG(task->channel, "Insert a xfer task:%d to task list",
-                  task->id);
+    CHANNEL_DEBUG(task->channel, "Insert a xfer task:%d to task list", task->id);
     c->file_xfer_task_list = g_list_append(c->file_xfer_task_list, task);
 
-    agent_msg_queue_many(task->channel, VD_AGENT_FILE_XFER_START, msg,
-                         sizeof(VDAgentFileXferStartMessage), string,
-                         data_len + 1, NULL);
+    msg.id = task->id;
+    agent_msg_queue_many(task->channel, VD_AGENT_FILE_XFER_START,
+                         &msg, sizeof(msg),
+                         string, data_len + 1, NULL);
     g_free(string);
     spice_channel_wakeup(SPICE_CHANNEL(task->channel), FALSE);
     return ;
diff --git a/spice-common b/spice-common
index b46d36b..81f40cc 160000
--- a/spice-common
+++ b/spice-common
@@ -1 +1 @@
-Subproject commit b46d36bc1c01ca17a64262e157022fd21ad1e795
+Subproject commit 81f40cca5f930bb256da62760859ac802f11b3a7
commit a512832ba0847c660ba4849895bded3889744874
Author: Dunrong Huang <riegamaths at gmail.com>
Date:   Thu Jan 10 11:02:20 2013 +0800

    file-xfer: handle "drag-data-received" signal
    
    When user drags a file to SpiceDisplay and drops it, a signal named
    "drag-data-received" will be emitted, the signal will be received by
    SpiceDisplay, then our signal handler should receive data which contains
    file path, and call spice_main_file_copy_async() to transfer file to guest.
    
    Signed-off-by: Dunrong Huang <riegamaths at gmail.com>
    Signed-off-by: mathslinux <riegamaths at gmail.com>

diff --git a/gtk/spice-widget.c b/gtk/spice-widget.c
index a8d3ce9..17adf8f 100644
--- a/gtk/spice-widget.c
+++ b/gtk/spice-widget.c
@@ -464,14 +464,60 @@ static gboolean grab_broken(SpiceDisplay *self, GdkEventGrabBroken *event,
     return false;
 }
 
+static void drag_data_received_callback(SpiceDisplay *self,
+                                        GdkDragContext *drag_context,
+                                        gint x,
+                                        gint y,
+                                        GtkSelectionData *data,
+                                        guint info,
+                                        guint time,
+                                        gpointer *user_data)
+{
+    int len;
+    char *buf;
+    gchar **file_urls;
+    int n_files;
+    SpiceDisplayPrivate *d = SPICE_DISPLAY_GET_PRIVATE(self);
+    int i = 0;
+    GFile **files;
+
+    /* We get a buf like:
+     * file:///root/a.txt\r\nfile:///root/b.txt\r\n
+     */
+    SPICE_DEBUG("%s: drag a file", __FUNCTION__);
+    buf = (char *)gtk_selection_data_get_data_with_length(data, &len);
+    g_return_if_fail(buf != NULL);
+
+    file_urls = g_uri_list_extract_uris(buf);
+    n_files = g_strv_length(file_urls);
+    files = g_malloc0(sizeof(GFile *) * (n_files + 1));
+    for (i = 0; i < n_files; i++) {
+        files[i] = g_file_new_for_uri(file_urls[i]);
+    }
+    g_strfreev(file_urls);
+
+    spice_main_file_copy_async(d->main, files, 0, NULL, NULL,
+                               NULL, NULL, NULL);
+    for (i = 0; i < n_files; i++) {
+        g_object_unref(files[i]);
+    }
+    g_free(files);
+
+    gtk_drag_finish(drag_context, TRUE, FALSE, time);
+}
+
 static void spice_display_init(SpiceDisplay *display)
 {
     GtkWidget *widget = GTK_WIDGET(display);
     SpiceDisplayPrivate *d;
+    GtkTargetEntry targets = {"text/plain", 0, 0};
 
     d = display->priv = SPICE_DISPLAY_GET_PRIVATE(display);
 
     g_signal_connect(display, "grab-broken-event", G_CALLBACK(grab_broken), NULL);
+    gtk_drag_dest_set(widget, GTK_DEST_DEFAULT_ALL, &targets, 1, GDK_ACTION_COPY);
+    g_signal_connect(display, "drag-data-received",
+                     G_CALLBACK(drag_data_received_callback), NULL);
     gtk_widget_add_events(widget,
                           GDK_STRUCTURE_MASK |
                           GDK_POINTER_MOTION_MASK |
commit 2ff897f492222eb7d4b752c2f7276aae931f0acf
Author: Dunrong Huang <riegamaths at gmail.com>
Date:   Thu Jan 10 11:02:21 2013 +0800

    file-xfer: disable file-xfer when agent is not connected
    
    Signed-off-by: Dunrong Huang <riegamaths at gmail.com>
    Signed-off-by: mathslinux <riegamaths at gmail.com>

diff --git a/gtk/channel-main.c b/gtk/channel-main.c
index cf578be..1725127 100644
--- a/gtk/channel-main.c
+++ b/gtk/channel-main.c
@@ -2721,10 +2721,14 @@ void spice_main_file_copy_async(SpiceMainChannel *channel,
                                 GAsyncReadyCallback callback,
                                 gpointer user_data)
 {
+    SpiceMainChannelPrivate *c = channel->priv;
+
     g_return_if_fail(channel != NULL);
     g_return_if_fail(SPICE_IS_MAIN_CHANNEL(channel));
     g_return_if_fail(sources != NULL && sources[0] != NULL);
 
+    g_return_if_fail(c->agent_connected);
+
     /* At the moment, the copy() method is limited to a single file,
        support for copying multi-files will be implemented later. */
     g_return_if_fail(sources[1] == NULL);
commit 249dd73132a7ecc1ceb32b4fea6529491ca219d3
Author: Dunrong Huang <riegamaths at gmail.com>
Date:   Thu Jan 10 11:02:19 2013 +0800

    file-xfer: handling various transfer messages in main channel
    
    This patch is aimed to handle various file xfer messages.
    
    How it works:
    0) our main channel introduces a API spice_main_file_copy_async().
    
    1) When user drags a file and drop to spice client, spice client will
       catch a signal "drag-data-received", then it should call
       spice_main_file_copy_async() for transfering file to guest.
    
    2) In main channel: when spice_main_file_copy_async() get called with file
       list passed, the API will send a start message which includes file
       and other needed information for each file. Then it will create a
       new xfer task and insert task list for each file, and return to caller.
    
    3) According to the response message sent from guest, our main channel
       decides whether send more data, or cancel this xfer task.
    
    4) When file transfer has finished, file xfer task will be removed from
       task list.
    
    Signed-off-by: Dunrong Huang <riegamaths at gmail.com>
    Signed-off-by: mathslinux <riegamaths at gmail.com>

diff --git a/gtk/channel-main.c b/gtk/channel-main.c
index 653b989..cf578be 100644
--- a/gtk/channel-main.c
+++ b/gtk/channel-main.c
@@ -18,6 +18,7 @@
 #include <math.h>
 #include <spice/vd_agent.h>
 #include <common/rect.h>
+#include <glib/gstdio.h>
 
 #include "glib-compat.h"
 #include "spice-client.h"
@@ -51,6 +52,24 @@
 
 typedef struct spice_migrate spice_migrate;
 
+#define FILE_XFER_CHUNK_SIZE (VD_AGENT_MAX_DATA_SIZE * 32)
+typedef struct SpiceFileXferTask {
+    uint32_t                       id;
+    GFile                          *file;
+    SpiceMainChannel               *channel;
+    GFileInputStream               *file_stream;
+    GFileCopyFlags                 flags;
+    GCancellable                   *cancellable;
+    GFileProgressCallback          progress_callback;
+    gpointer                       progress_callback_data;
+    GAsyncReadyCallback            callback;
+    gpointer                       user_data;
+    char                           buffer[FILE_XFER_CHUNK_SIZE];
+    uint64_t                       read_bytes;
+    uint64_t                       file_size;
+    GError                         *error;
+} SpiceFileXferTask;
+
 struct _SpiceMainChannelPrivate  {
     enum SpiceMouseMode         mouse_mode;
     bool                        agent_connected;
@@ -79,6 +98,8 @@ struct _SpiceMainChannelPrivate  {
     } display[MAX_DISPLAY];
     gint                        timer_id;
     GQueue                      *agent_msg_queue;
+    GList                       *file_xfer_task_list;
+    GSList                      *flushing;
 
     guint                       switch_host_delayed_id;
     guint                       migrate_delayed_id;
@@ -802,6 +823,64 @@ static void agent_free_msg_queue(SpiceMainChannel *channel)
     c->agent_msg_queue = NULL;
 }
 
+/* Here, flushing algorithm is stolen from spice-channel.c */
+static void
+file_xfer_flushed(SpiceMainChannel *channel, gboolean success)
+{
+    SpiceMainChannelPrivate *c = channel->priv;
+    GSList *l;
+
+    for (l = c->flushing; l != NULL; l = l->next) {
+        GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT(l->data);
+        g_simple_async_result_set_op_res_gboolean(result, success);
+        g_simple_async_result_complete_in_idle(result);
+    }
+
+    g_slist_free_full(c->flushing, g_object_unref);
+    c->flushing = NULL;
+}
+
+static void
+file_xfer_flush_async(SpiceMainChannel *channel, GCancellable *cancellable,
+                      GAsyncReadyCallback callback, gpointer user_data)
+{
+    GSimpleAsyncResult *simple;
+    SpiceMainChannelPrivate *c = channel->priv;
+    gboolean was_empty;
+
+    simple = g_simple_async_result_new(G_OBJECT(channel), callback, user_data,
+                                       file_xfer_flush_async);
+
+    was_empty = g_queue_is_empty(c->agent_msg_queue);
+    if (was_empty) {
+        g_simple_async_result_set_op_res_gboolean(simple, TRUE);
+        g_simple_async_result_complete_in_idle(simple);
+        g_object_unref(simple);
+        return;
+    }
+
+    c->flushing = g_slist_append(c->flushing, simple);
+}
+
+static gboolean
+file_xfer_flush_finish(SpiceMainChannel *channel, GAsyncResult *result,
+                       GError **error)
+{
+    GSimpleAsyncResult *simple;
+
+    simple = (GSimpleAsyncResult *)result;
+
+    if (g_simple_async_result_propagate_error(simple, error)) {
+        return FALSE;
+    }
+
+    g_return_val_if_fail(g_simple_async_result_is_valid(result,
+        G_OBJECT(channel), file_xfer_flush_async), FALSE);
+
+    CHANNEL_DEBUG(channel, "flushed finished!");
+    return g_simple_async_result_get_op_res_gboolean(simple);
+}
+
 /* coroutine context */
 static void agent_send_msg_queue(SpiceMainChannel *channel)
 {
@@ -814,6 +893,9 @@ static void agent_send_msg_queue(SpiceMainChannel *channel)
         out = g_queue_pop_head(c->agent_msg_queue);
         spice_msg_out_send_internal(out);
     }
+    if (g_queue_is_empty(c->agent_msg_queue) && c->flushing != NULL) {
+        file_xfer_flushed(channel, TRUE);
+    }
 }
 
 /* any context: the message is not flushed immediately,
@@ -1384,6 +1466,219 @@ static void main_handle_agent_disconnected(SpiceChannel *channel, SpiceMsgIn *in
     agent_stopped(SPICE_MAIN_CHANNEL(channel));
 }
 
+static gint file_xfer_task_find(gconstpointer a, gconstpointer b)
+{
+    SpiceFileXferTask *task = (SpiceFileXferTask *)a;
+    uint32_t id = *(uint32_t *)b;
+
+    if (task->id == id) {
+        return 0;
+    }
+
+    return 1;
+}
+
+static void file_read_cb(GObject *source_object,
+                         GAsyncResult *res,
+                         gpointer user_data);
+static void file_xfer_continue_read(SpiceFileXferTask *task);
+
+static void report_progress(SpiceFileXferTask *task)
+{
+    if (task->progress_callback) {
+        task->progress_callback(task->read_bytes, task->file_size,
+                                task->progress_callback_data);
+    }
+}
+
+static void
+file_close_cb(GObject      *object,
+              GAsyncResult *close_res,
+              gpointer      user_data);
+
+static void data_flushed_cb(GObject *source_object,
+                            GAsyncResult *res,
+                            gpointer user_data)
+{
+    SpiceFileXferTask *task = user_data;
+    SpiceMainChannel *channel = (SpiceMainChannel *)source_object;
+    GError *error = NULL;
+
+    file_xfer_flush_finish(channel, res, &error);
+
+    if (error != NULL) {
+        g_warning("failed to flush xfer queue: %s", error->message);
+        task->error = error;
+        g_input_stream_close_async(G_INPUT_STREAM(task->file_stream),
+                                   G_PRIORITY_DEFAULT,
+                                   task->cancellable,
+                                   file_close_cb,
+                                   task);
+        return;
+    }
+
+    /* Report progress */
+    report_progress(task);
+
+    /* Read more data */
+    file_xfer_continue_read(task);
+}
+
+static void
+file_xfer_queue(SpiceFileXferTask *task, int data_size)
+{
+    VDAgentFileXferDataMessage *msg;
+    SpiceMainChannel *channel = SPICE_MAIN_CHANNEL(task->channel);
+
+    msg = g_alloca(sizeof(VDAgentFileXferDataMessage));
+    msg->id = task->id;
+    msg->size = data_size;
+    agent_msg_queue_many(channel, VD_AGENT_FILE_XFER_DATA, msg,
+                         sizeof(VDAgentFileXferDataMessage), task->buffer,
+                         data_size, NULL);
+    spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);
+}
+
+static void file_xfer_task_free(SpiceFileXferTask *task)
+{
+    SpiceMainChannelPrivate *c;
+
+    g_return_if_fail(task != NULL);
+
+    c = task->channel->priv;
+    c->file_xfer_task_list = g_list_remove(c->file_xfer_task_list,
+                                           task);
+
+    g_clear_object(&task->file);
+    g_clear_object(&task->file_stream);
+    g_free(task);
+}
+
+/* main context */
+static void
+file_close_cb(GObject      *object,
+              GAsyncResult *close_res,
+              gpointer      user_data)
+{
+    GSimpleAsyncResult *res;
+    SpiceFileXferTask *task;
+    GInputStream *stream = G_INPUT_STREAM(object);
+    GError *error = NULL;
+
+    stream = G_INPUT_STREAM(object);
+    task = user_data;
+
+    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. */
+    res = g_simple_async_result_new(G_OBJECT(task->channel),
+                                    task->callback,
+                                    task->user_data,
+                                    file_xfer_continue_read);
+    if (task->error) {
+        g_simple_async_result_take_error(res, task->error);
+        g_simple_async_result_set_op_res_gboolean(res, FALSE);
+    } else {
+        g_simple_async_result_set_op_res_gboolean(res, TRUE);
+    }
+    g_simple_async_result_complete_in_idle(res);
+    g_object_unref(res);
+
+    file_xfer_task_free(task);
+}
+
+/* main context */
+static void file_read_cb(GObject *source_object,
+                         GAsyncResult *res,
+                         gpointer user_data)
+{
+    SpiceFileXferTask *task = user_data;
+    SpiceMainChannel *channel = task->channel;
+    gssize count;
+    GError *error = NULL;
+
+    count = g_input_stream_read_finish(G_INPUT_STREAM(task->file_stream),
+                                       res, &error);
+    if (count > 0) {
+        task->read_bytes += count;
+        file_xfer_queue(task, count);
+        file_xfer_flush_async(channel, task->cancellable,
+                              data_flushed_cb, task);
+    } else {
+        /* Error or EOF, close the file */
+        if (count == -1) {
+            task->error = error;
+        }
+        g_input_stream_close_async(G_INPUT_STREAM(task->file_stream),
+                                   G_PRIORITY_DEFAULT,
+                                   task->cancellable,
+                                   file_close_cb,
+                                   task);
+    }
+}
+
+/* coroutine context */
+static void file_xfer_continue_read(SpiceFileXferTask *task)
+{
+    g_input_stream_read_async(G_INPUT_STREAM(task->file_stream),
+                              task->buffer,
+                              FILE_XFER_CHUNK_SIZE,
+                              G_PRIORITY_DEFAULT,
+                              task->cancellable,
+                              file_read_cb,
+                              task);
+}
+
+/* coroutine context */
+static void file_xfer_handle_status(SpiceMainChannel *channel,
+                                    VDAgentFileXferStatusMessage *msg)
+{
+    SpiceMainChannelPrivate *c = channel->priv;
+    GList *l;
+    SpiceFileXferTask *task;
+
+    l = g_list_find_custom(c->file_xfer_task_list, &msg->id,
+                           file_xfer_task_find);
+
+    g_return_if_fail(l != NULL);
+    task = l->data;
+
+    SPICE_DEBUG("task %d received response %d", msg->id, msg->result);
+
+    switch (msg->result) {
+    case VD_AGENT_FILE_XFER_STATUS_CAN_SEND_DATA:
+        file_xfer_continue_read(task);
+        return;
+    case VD_AGENT_FILE_XFER_STATUS_CANCELLED:
+        SPICE_DEBUG("user removed task %d, result: %d", msg->id,
+                    msg->result);
+        task->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:
+        task->error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                                  "some errors occurred in the spice agent");
+        break;
+    default:
+        g_warn_if_reached();
+        task->error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                                  "unhandled status type: %u", msg->result);
+        break;
+    }
+
+    g_input_stream_close_async(G_INPUT_STREAM(task->file_stream),
+                               G_PRIORITY_DEFAULT,
+                               task->cancellable,
+                               file_close_cb,
+                               task);
+}
+
 /* coroutine context */
 static void main_agent_handle_msg(SpiceChannel *channel,
                                   VDAgentMessage *msg, gpointer payload)
@@ -1487,6 +1782,9 @@ static void main_agent_handle_msg(SpiceChannel *channel,
                     reply->error == VD_AGENT_SUCCESS ? "success" : "error");
         break;
     }
+    case VD_AGENT_FILE_XFER_STATUS:
+        file_xfer_handle_status(SPICE_MAIN_CHANNEL(channel), payload);
+        break;
     default:
         g_warning("unhandled agent message type: %u (%s), size %u",
                   msg->type, NAME(agent_msg_types, msg->type), msg->size);
@@ -1563,6 +1861,7 @@ static void main_handle_agent_token(SpiceChannel *channel, SpiceMsgIn *in)
     SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
 
     c->agent_tokens += tokens->num_tokens;
+
     agent_send_msg_queue(SPICE_MAIN_CHANNEL(channel));
 }
 
@@ -2251,3 +2550,219 @@ void spice_main_set_display_enabled(SpiceMainChannel *channel, int id, gboolean
     }
     c->timer_id = g_timeout_add_seconds(1, timer_set_display, channel);
 }
+
+static void
+file_info_async_cb(GObject *obj, GAsyncResult *res, gpointer data)
+{
+    GFileInfo *info;
+    GFile *file = G_FILE(obj);
+    GError *error = NULL;
+    GKeyFile *keyfile = NULL;
+    gchar *basename = NULL;
+    VDAgentFileXferStartMessage *msg;
+    gsize /*msg_size*/ data_len;
+    gchar *string;
+    SpiceFileXferTask *task = (SpiceFileXferTask *)data;
+    SpiceMainChannelPrivate *c = task->channel->priv;
+
+    info = g_file_query_info_finish(file, res, &error);
+    if (error) {
+        SPICE_DEBUG("couldn't get size of file %s: %s",
+                    g_file_get_path(file),
+                    error->message);
+        goto failed;
+    }
+    task->file_size = g_file_info_get_attribute_uint64(info,
+                                        G_FILE_ATTRIBUTE_STANDARD_SIZE);
+
+    keyfile = g_key_file_new();
+    if (keyfile == NULL) {
+        SPICE_DEBUG("failed to create key file: %s", error->message);
+        goto failed;
+    }
+
+    /* File name */
+    basename = g_file_get_basename(file);
+    if (basename == NULL) {
+        SPICE_DEBUG("failed to get file basename: %s", error->message);
+        goto failed;
+    }
+    g_key_file_set_string(keyfile, "vdagent-file-xfer", "name", basename);
+    g_free(basename);
+
+    /* File size */
+    g_key_file_set_uint64(keyfile, "vdagent-file-xfer", "size",
+                          task->file_size);
+
+    /* Save keyfile content to memory. TODO: more file attributions
+       need to be sent to guest */
+    string = g_key_file_to_data(keyfile, &data_len, &error);
+    g_key_file_free(keyfile);
+    if (error) {
+        goto failed;
+    }
+
+    /* Create file-xfer start message */
+    msg = g_alloca(sizeof(VDAgentFileXferStartMessage));
+    msg->id = task->id;
+
+    CHANNEL_DEBUG(task->channel, "Insert a xfer task:%d to task list",
+                  task->id);
+    c->file_xfer_task_list = g_list_append(c->file_xfer_task_list, task);
+
+    agent_msg_queue_many(task->channel, VD_AGENT_FILE_XFER_START, msg,
+                         sizeof(VDAgentFileXferStartMessage), string,
+                         data_len + 1, NULL);
+    g_free(string);
+    spice_channel_wakeup(SPICE_CHANNEL(task->channel), FALSE);
+    return ;
+
+failed:
+    g_clear_error(&error);
+    file_xfer_task_free(task);
+}
+
+static void
+read_async_cb(GObject *obj, GAsyncResult *res, gpointer data)
+{
+    GFile *file = G_FILE(obj);
+    SpiceFileXferTask *task = (SpiceFileXferTask *)data;
+    GError *error = NULL;
+
+    task->file_stream = g_file_read_finish(file, res, &error);
+
+    if (task->file_stream) {
+        g_file_query_info_async(task->file,
+                                G_FILE_ATTRIBUTE_STANDARD_SIZE,
+                                G_FILE_QUERY_INFO_NONE,
+                                G_PRIORITY_DEFAULT,
+                                task->cancellable,
+                                file_info_async_cb,
+                                task);
+    } else {
+        SPICE_DEBUG("create file stream for %s error: %s",
+                    g_file_get_path(file), error->message);
+        task->error = error;
+        g_input_stream_close_async(G_INPUT_STREAM(task->file_stream),
+                                   G_PRIORITY_DEFAULT,
+                                   task->cancellable,
+                                   file_close_cb,
+                                   task);
+    }
+}
+
+static void
+file_xfer_send_start_msg_async(SpiceMainChannel *channel,
+                               GFile *file,
+                               GFileCopyFlags flags,
+                               GCancellable *cancellable,
+                               GFileProgressCallback progress_callback,
+                               gpointer progress_callback_data,
+                               GAsyncReadyCallback callback,
+                               gpointer user_data)
+{
+    SpiceFileXferTask *task;
+    static uint32_t xfer_id;    /* Used to identify task id */
+
+    xfer_id = (xfer_id > UINT32_MAX) ? 0 : xfer_id;
+
+    task = spice_malloc0(sizeof(SpiceFileXferTask));
+    task->id = ++xfer_id;
+    task->channel = channel;
+    task->file = g_object_ref(file);
+    task->flags = flags;
+    task->cancellable = cancellable;
+    task->progress_callback = progress_callback;
+    task->progress_callback_data = progress_callback_data;
+    task->callback = callback;
+    task->user_data = user_data;
+
+    g_file_read_async(file,
+                      G_PRIORITY_DEFAULT,
+                      cancellable,
+                      read_async_cb,
+                      task);
+
+}
+
+/**
+ * spice_main_file_copy_async:
+ * @sources: #GFile to be transfer
+ * @flags: set of #GFileCopyFlags
+ * @cancellable: (allow-none): optional #GCancellable object, %NULL to ignore
+ * @progress_callback: (allow-none) (scope call): function to callback with
+ *     progress information, or %NULL if progress information is not needed
+ * @progress_callback_data: (closure): user data to pass to @progress_callback
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: the data to pass to callback function
+ *
+ * Copies the file @sources to guest
+ *
+ * If @cancellable is not %NULL, then the operation can be cancelled by
+ * triggering the cancellable object from another thread. If the operation
+ * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned.
+ *
+ * If @progress_callback is not %NULL, then the operation can be monitored by
+ * setting this to a #GFileProgressCallback function. @progress_callback_data
+ * will be passed to this function. It is guaranteed that this callback will
+ * be called after all data has been transferred with the total number of bytes
+ * copied during the operation.
+ *
+ * When the operation is finished, callback will be called. You can then call
+ * spice_main_file_copy_finish() to get the result of the operation.
+ *
+ **/
+void spice_main_file_copy_async(SpiceMainChannel *channel,
+                                GFile **sources,
+                                GFileCopyFlags flags,
+                                GCancellable *cancellable,
+                                GFileProgressCallback progress_callback,
+                                gpointer progress_callback_data,
+                                GAsyncReadyCallback callback,
+                                gpointer user_data)
+{
+    g_return_if_fail(channel != NULL);
+    g_return_if_fail(SPICE_IS_MAIN_CHANNEL(channel));
+    g_return_if_fail(sources != NULL && sources[0] != NULL);
+
+    /* At the moment, the copy() method is limited to a single file,
+       support for copying multi-files will be implemented later. */
+    g_return_if_fail(sources[1] == NULL);
+
+    file_xfer_send_start_msg_async(channel,
+                                   sources[0],
+                                   flags,
+                                   cancellable,
+                                   progress_callback,
+                                   progress_callback_data,
+                                   callback,
+                                   user_data);
+}
+
+/**
+ * spice_main_file_copy_finish:
+ * @result: a #GAsyncResult.
+ * @error: a #GError, or %NULL
+ *
+ * Finishes copying the file started with
+ * spice_main_file_copy_async().
+ *
+ * Returns: a %TRUE on success, %FALSE on error.
+ **/
+gboolean spice_main_file_copy_finish(SpiceMainChannel *channel,
+                                     GAsyncResult *result,
+                                     GError **error)
+{
+    GSimpleAsyncResult *simple;
+
+    g_return_val_if_fail(SPICE_IS_MAIN_CHANNEL(channel), FALSE);
+    g_return_val_if_fail(result != NULL, FALSE);
+
+    simple = (GSimpleAsyncResult *)result;
+
+    if (g_simple_async_result_propagate_error(simple, error)) {
+        return FALSE;
+    }
+
+    return g_simple_async_result_get_op_res_gboolean(simple);
+}
diff --git a/gtk/channel-main.h b/gtk/channel-main.h
index 1a5ab54..adba0a2 100644
--- a/gtk/channel-main.h
+++ b/gtk/channel-main.h
@@ -78,6 +78,18 @@ void spice_main_clipboard_selection_notify(SpiceMainChannel *channel, guint sele
 void spice_main_clipboard_selection_request(SpiceMainChannel *channel, guint selection, guint32 type);
 
 gboolean spice_main_agent_test_capability(SpiceMainChannel *channel, guint32 cap);
+void spice_main_file_copy_async(SpiceMainChannel *channel,
+                                GFile **sources,
+                                GFileCopyFlags flags,
+                                GCancellable *cancellable,
+                                GFileProgressCallback progress_callback,
+                                gpointer progress_callback_data,
+                                GAsyncReadyCallback callback,
+                                gpointer user_data);
+
+gboolean spice_main_file_copy_finish(SpiceMainChannel *channel,
+                                     GAsyncResult *result,
+                                     GError **error);
 
 #ifndef SPICE_DISABLE_DEPRECATED
 SPICE_DEPRECATED_FOR(spice_main_clipboard_selection_grab)
diff --git a/gtk/map-file b/gtk/map-file
index 516764c..4d05597 100644
--- a/gtk/map-file
+++ b/gtk/map-file
@@ -55,6 +55,8 @@ spice_inputs_motion;
 spice_inputs_position;
 spice_inputs_set_key_locks;
 spice_main_agent_test_capability;
+spice_main_file_copy_async;
+spice_main_file_copy_finish;
 spice_main_channel_get_type;
 spice_main_clipboard_grab;
 spice_main_clipboard_notify;
diff --git a/gtk/spice-glib-sym-file b/gtk/spice-glib-sym-file
index 641ff4d..28b54af 100644
--- a/gtk/spice-glib-sym-file
+++ b/gtk/spice-glib-sym-file
@@ -31,6 +31,8 @@ spice_inputs_motion
 spice_inputs_position
 spice_inputs_set_key_locks
 spice_main_agent_test_capability
+spice_main_file_copy_async
+spice_main_file_copy_finish
 spice_main_channel_get_type
 spice_main_clipboard_grab
 spice_main_clipboard_notify


More information about the Spice-commits mailing list