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>