Hi<br><div class="gmail_extra"><br><div class="gmail_quote">On Wed, Dec 5, 2012 at 5:22 AM, Dunrong Huang <span dir="ltr"><<a href="mailto:riegamaths@gmail.com" target="_blank">riegamaths@gmail.com</a>></span> wrote:<br>
<blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">This patch is aimed to handle various file xfer messages.<br>
<br>
How it works:<br>
0) our main channel introduces a API spice_main_file_xfer().<br></blockquote><div><br>In general we prefer more human name in API, such as spice_main_file_copy()<br><br>Imho, it should follow gio async model, so a client can track progress of each calls. (look at g_file_copy_async)<br>
<br>I think a client API such as this one would fit better:<br><br>spice_main_file_copy_async (GFile**, SpiceFileCopyFlags .., GCancellable, GFileProgressCallback, gpointer, GAsyncReadyCallback, gpointer)<br><br> <br></div>
<blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
1) When user drags a file and drop to spice client, spice client will<br>
   catch a signal "drag-data-received", then it should call<br>
   spice_main_file_xfer() for transfering file to guest.<br>
<br>
2) In main channel: when spice_main_file_xfer() get called with file<br>
   list passed, the API will send a start message which includes file<br>
   and other needed information for each file. Then it will create a<br>
   new xfer task and insert task list for each file, and return to caller.<br></blockquote><div><br>We shouldn't create several tasks simultaneously for a single call, it should be serialized to minimize completion time of each file.<br>
</div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
<br>
3) According to the response message sent from guest, our main channel<br>
   decides whether send more data, or cancel this xfer task.<br>
<br>
4) When file transfer has finished, file xfer task will be removed from<br>
   task list.<br>
<br>
Signed-off-by: Dunrong Huang <<a href="mailto:riegamaths@gmail.com">riegamaths@gmail.com</a>><br>
---<br>
 gtk/channel-main.c      | 202 ++++++++++++++++++++++++++++++++++++++++++++++++<br>
 gtk/channel-main.h      |   1 +<br>
 gtk/map-file            |   1 +<br>
 gtk/spice-glib-sym-file |   1 +<br>
 4 files changed, 205 insertions(+)<br>
<br>
diff --git a/gtk/channel-main.c b/gtk/channel-main.c<br>
index 6b9ba8d..b2253a6 100644<br>
--- a/gtk/channel-main.c<br>
+++ b/gtk/channel-main.c<br>
@@ -18,6 +18,7 @@<br>
 #include <math.h><br>
 #include <spice/vd_agent.h><br>
 #include <common/rect.h><br>
+#include <glib/gstdio.h><br>
<br>
 #include "glib-compat.h"<br>
 #include "spice-client.h"<br>
@@ -51,6 +52,12 @@<br>
<br>
 typedef struct spice_migrate spice_migrate;<br>
<br>
+typedef struct SpiceFileXferTask {<br>
+    uint32_t                       id;<br>
+    SpiceChannel                   *channel;<br>
+    GFileInputStream               *file_stream;<br>
+} SpiceFileXferTask;<br>
+<br>
 struct _SpiceMainChannelPrivate  {<br>
     enum SpiceMouseMode         mouse_mode;<br>
     bool                        agent_connected;<br>
@@ -79,6 +86,7 @@ struct _SpiceMainChannelPrivate  {<br>
     } display[MAX_DISPLAY];<br>
     gint                        timer_id;<br>
     GQueue                      *agent_msg_queue;<br>
+    GList                       *file_xfer_task_list;<br>
<br>
     guint                       switch_host_delayed_id;<br>
     guint                       migrate_delayed_id;<br>
@@ -1384,6 +1392,100 @@ static void main_handle_agent_disconnected(SpiceChannel *channel, SpiceMsgIn *in<br>
     agent_stopped(SPICE_MAIN_CHANNEL(channel));<br>
 }<br>
<br>
+static gboolean send_data(gpointer opaque)<br>
+{<br>
+#define FILE_XFER_CHUNK_SIZE (VD_AGENT_MAX_DATA_SIZE - sizeof(VDAgentMessage))<br>
+    SpiceFileXferTask *task = (SpiceFileXferTask *)opaque;<br>
+<br>
+    SpiceMainChannel *channel = SPICE_MAIN_CHANNEL(task->channel);<br>
+    SpiceMainChannelPrivate *c = channel->priv;<br>
+    gssize len;<br>
+    GError *error = NULL;<br>
+    VDAgentFileXferDataMessage *msg;<br>
+    uint32_t msg_size;<br>
+<br>
+    if (!g_queue_is_empty(c->agent_msg_queue)) {<br>
+        spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);<br>
+        return TRUE;<br></blockquote><div><br>You can't use Idle functions this way, you are going to busy-loop. You need to handle filling the queue differently.<br> <br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">

+    }<br>
+<br>
+    msg = g_alloca(sizeof(VDAgentFileXferDataMessage) + FILE_XFER_CHUNK_SIZE);<br>
+<br>
+    len = g_input_stream_read(G_INPUT_STREAM(task->file_stream), msg->data,<br>
+                              FILE_XFER_CHUNK_SIZE, NULL, &error);<br></blockquote><div><br>Use async API g_input_stream_read_async instead.<br> <br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">

+    if (len == -1) {<br>
+        /* TODO: error handler, tell guest cancel xfer */<br>
+        CHANNEL_DEBUG(channel, "Read file error: %s", error->message);<br>
+        g_clear_error(&error);<br>
+        goto done;<br>
+    } else if (len == 0) {<br>
+        CHANNEL_DEBUG(channel, "Finished Reading file");<br>
+        goto done;<br>
+    }<br>
+<br>
+    msg_size = sizeof(VDAgentFileXferDataMessage) + len;<br>
+    msg->id = task->id;<br>
+    msg->size = len;<br>
+    agent_msg_queue(channel, VD_AGENT_FILE_XFER_DATA, msg_size, msg);<br>
+    spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);<br>
+<br>
+    return TRUE;<br>
+<br>
+done:<br>
+    c->file_xfer_task_list = g_list_remove(c->file_xfer_task_list, task);<br>
+    g_object_unref(task->file_stream);<br>
+    g_free(task);<br>
+    return FALSE;<br>
+}<br>
+<br>
+static gint file_xfer_task_find(gconstpointer a, gconstpointer b)<br>
+{<br>
+    SpiceFileXferTask *task = (SpiceFileXferTask *)a;<br>
+    uint32_t id = *(uint32_t *)b;<br>
+<br>
+    if (task->id == id) {<br>
+        return 0;<br>
+    }<br>
+<br>
+    return 1;<br>
+}<br>
+<br>
+static void file_xfer_send_data_msg(SpiceChannel *channel, uint32_t id)<br>
+{<br>
+    SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;<br>
+    GList *l;<br>
+<br>
+    l = g_list_find_custom(c->file_xfer_task_list, &id,<br>
+                              file_xfer_task_find);<br>
+    if (l) {<br></blockquote><div><br>Use g_return_if_fail(l != NULL); instead, it will log a warning in this case, and you don't need a conditionnal block.<br><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">

+        g_idle_add(send_data, l->data);<br>
+    }<br>
+}<br>
+<br>
+/* coroutine context */<br>
+static void file_xfer_handle_status(SpiceChannel *channel,<br>
+                                    VDAgentFileXferStatusMessage *msg)<br>
+{<br>
+    SPICE_DEBUG("task %d received response %d", msg->id, msg->result);<br>
+<br>
+    if (msg->result == VD_AGENT_FILE_XFER_STATUS_CAN_SEND_DATA) {<br>
+        file_xfer_send_data_msg(channel, msg->id);<br>
+    } else {<br>
+        /* Error, remove this task */<br>
+        SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;<br>
+        GList *l;<br>
+        l = g_list_find_custom(c->file_xfer_task_list, &msg->id,<br>
+                                  file_xfer_task_find);<br>
+        if (l) {<br></blockquote><div><br>Use g_return_if_fail(l != NULL); instead, it will log a warning in this case, and you don't need a conditionnal block.<br> <br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">

+            SpiceFileXferTask *task = l->data;<br>
+            SPICE_DEBUG("user removed task %d, result: %d", msg->id, msg->result);<br>
+            c->file_xfer_task_list = g_list_remove(c->file_xfer_task_list, task);<br>
+            g_object_unref(task->file_stream);<br>
+            g_free(task);<br>
+        }<br>
+    }<br>
+}<br>
+<br>
 /* coroutine context */<br>
 static void main_agent_handle_msg(SpiceChannel *channel,<br>
                                   VDAgentMessage *msg, gpointer payload)<br>
@@ -1487,6 +1589,9 @@ static void main_agent_handle_msg(SpiceChannel *channel,<br>
                     reply->error == VD_AGENT_SUCCESS ? "success" : "error");<br>
         break;<br>
     }<br>
+    case VD_AGENT_FILE_XFER_STATUS:<br>
+        file_xfer_handle_status(channel, (VDAgentFileXferStatusMessage *)payload);<br>
+        break;<br>
     default:<br>
         g_warning("unhandled agent message type: %u (%s), size %u",<br>
                   msg->type, NAME(agent_msg_types, msg->type), msg->size);<br>
@@ -2246,3 +2351,100 @@ void spice_main_set_display_enabled(SpiceMainChannel *channel, int id, gboolean<br>
         c->display[id].enabled = enabled;<br>
     }<br>
 }<br>
+<br>
+static GFileInputStream *file_xfer_file_stream_new(const gchar *file_path)<br>
+{<br>
+    GFile *f;<br>
+    GFileInputStream *stream;<br>
+    GError *error = NULL;<br>
+<br>
+    f = g_file_new_for_path(file_path);<br>
+    stream = g_file_read(f, NULL, &error);<br></blockquote><div><br>Please use g_file_read_async()<br> <br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">

+    if (error) {<br>
+        SPICE_DEBUG("create file stream for %s error: %s",<br>
+                    file_path, error->message);<br>
+        g_error_free(error);<br>
+    }<br>
+    g_object_unref(f);<br>
+<br>
+    return stream;<br>
+}<br>
+<br>
+/* Create a new file xfer start msg, msg_size is total size of this package  */<br>
+static VDAgentFileXferStartMessage *file_xfer_start_msg_new(gchar *file_path, int *msg_size)<br>
+{<br>
+    static uint32_t xfer_id; /* Used to identify msg id */<br>
+    GStatBuf st;<br>
+    VDAgentFileXferStartMessage *msg;<br>
+    gchar *basename;<br>
+    GKeyFile *keyfile;<br>
+    gchar *data;<br>
+    gsize data_len;<br>
+<br>
+    keyfile = g_key_file_new();<br>
+<br>
+    /* File name */<br>
+    basename = g_path_get_basename(file_path);<br>
+    g_key_file_set_string(keyfile, "vdagent-file-xfer", "name", basename);<br>
+    g_free(basename);<br>
+<br>
+    /* File size */<br>
+    g_stat(file_path, &st);<br>
+    g_key_file_set_uint64(keyfile, "vdagent-file-xfer", "size", st.st_size);<br>
<br></blockquote><div>g_stat is a blocking IO API. Please use an asynchronous version, g_file_input_stream_query_info_async ()<br> <br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">

+    data = g_key_file_to_data(keyfile, &data_len, NULL);<br></blockquote><div><br>You may want to check the GError there.<br> <br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">

+    g_key_file_free(keyfile);<br>
+<br>
+    *msg_size = sizeof(VDAgentFileXferStartMessage) + data_len + 1;<br>
+    msg = g_malloc0(*msg_size);<br>
+    xfer_id = (xfer_id > 10000) ? 0 : xfer_id;<br></blockquote><div><br>10000 sounds like a reachable amount for file copy. Why not use the whole uint32 value range?<br> <br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">

+    msg->id = ++xfer_id;<br>
+    memcpy(msg->data, data, data_len + 1);<br>
+<br>
+    g_free(data);<br>
+    return msg;<br>
+}<br>
+<br>
+static void file_xfer_send_start_msg(gpointer data, gpointer user_data)<br>
+{<br>
+    gchar *file_path;<br>
+    GFileInputStream *file_stream;<br>
+    SpiceFileXferTask *xfer;<br>
+    VDAgentFileXferStartMessage *msg;<br>
+    int msg_size;<br>
+    SpiceMainChannel *channel = user_data;<br>
+<br>
+    file_path = g_filename_from_uri((gchar *)data, NULL, NULL);<br>
+    g_return_if_fail(file_path);<br>
+<br>
+    file_stream = file_xfer_file_stream_new(file_path);<br>
+    if (file_stream == NULL) {<br>
+        g_free(file_path);<br>
+        return ;<br>
+    }<br>
+<br>
+    msg = file_xfer_start_msg_new(file_path, &msg_size);<br>
+    g_free(file_path);<br>
+<br>
+    /* Create a new xfer task and insert into task list */<br>
+    xfer = spice_malloc0(sizeof(SpiceFileXferTask));<br>
+    xfer->id = msg->id;<br>
+    xfer->channel = (SpiceChannel *)channel;<br>
+    xfer->file_stream = file_stream;<br>
+<br>
+    CHANNEL_DEBUG(channel, "Insert a xfer task:%d to task list", xfer->id);<br>
+    channel->priv->file_xfer_task_list = g_list_append(<br>
+        channel->priv->file_xfer_task_list, xfer);<br>
+<br>
+    agent_msg_queue(channel, VD_AGENT_FILE_XFER_START, msg_size, msg);<br>
+    g_free(msg);<br>
+    spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);<br>
+}<br>
+<br>
+gboolean spice_main_file_xfer(SpiceMainChannel *channel, GList *files, GError *err)<br>
+{<br></blockquote><div><br>Please check validity of arguments in public functions (see code doing g_return_val_if_fail G_IS_SPICE_MAIN_CHANNEL..)<br> <br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">

+    /* Send VD_AGENT_FILE_XFER_START message to guest for every file */<br></blockquote><div><br>For the reasons given above, please serialize each request or create a single request.<br> <br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">

+    g_list_foreach(files, file_xfer_send_start_msg, channel);<br>
+    g_list_free_full(files, g_free);<br></blockquote><div><br>It's an easier to use API if you don't take the reference on the arguments. Don't request caller to give you allocated strings etc. Arguments should be const if possible.<br>
<br>I suggest it also uses a GFile** array instead of a a glist of strings.<br><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
+    return TRUE;<br>
+}<br>
diff --git a/gtk/channel-main.h b/gtk/channel-main.h<br>
index 1a5ab54..31997f1 100644<br>
--- a/gtk/channel-main.h<br>
+++ b/gtk/channel-main.h<br>
@@ -78,6 +78,7 @@ void spice_main_clipboard_selection_notify(SpiceMainChannel *channel, guint sele<br>
 void spice_main_clipboard_selection_request(SpiceMainChannel *channel, guint selection, guint32 type);<br>
<br>
 gboolean spice_main_agent_test_capability(SpiceMainChannel *channel, guint32 cap);<br>
+gboolean spice_main_file_xfer(SpiceMainChannel *channel, GList *files, GError *err);<br>
<br>
 #ifndef SPICE_DISABLE_DEPRECATED<br>
 SPICE_DEPRECATED_FOR(spice_main_clipboard_selection_grab)<br>
diff --git a/gtk/map-file b/gtk/map-file<br>
index ed2c07f..7827726 100644<br>
--- a/gtk/map-file<br>
+++ b/gtk/map-file<br>
@@ -53,6 +53,7 @@ spice_inputs_motion;<br>
 spice_inputs_position;<br>
 spice_inputs_set_key_locks;<br>
 spice_main_agent_test_capability;<br>
+spice_main_file_xfer;<br>
 spice_main_channel_get_type;<br>
 spice_main_clipboard_grab;<br>
 spice_main_clipboard_notify;<br>
diff --git a/gtk/spice-glib-sym-file b/gtk/spice-glib-sym-file<br>
index 82955f0..4bf92c3 100644<br>
--- a/gtk/spice-glib-sym-file<br>
+++ b/gtk/spice-glib-sym-file<br>
@@ -29,6 +29,7 @@ spice_inputs_motion<br>
 spice_inputs_position<br>
 spice_inputs_set_key_locks<br>
 spice_main_agent_test_capability<br>
+spice_main_file_xfer<br>
 spice_main_channel_get_type<br>
 spice_main_clipboard_grab<br>
 spice_main_clipboard_notify<br>
<span class=""><font color="#888888">--<br>
1.8.0<br>
<br>
_______________________________________________<br>
Spice-devel mailing list<br>
<a href="mailto:Spice-devel@lists.freedesktop.org">Spice-devel@lists.freedesktop.org</a><br>
<a href="http://lists.freedesktop.org/mailman/listinfo/spice-devel" target="_blank">http://lists.freedesktop.org/mailman/listinfo/spice-devel</a><br>
</font></span></blockquote></div><br>thanks again for your work!<br clear="all"><br>-- <br>Marc-André Lureau<br>
</div>