<div dir="ltr"><br><div class="gmail_extra">Hi!<br></div><div class="gmail_extra"><br><div class="gmail_quote">On Fri, Dec 28, 2012 at 2:33 PM, 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">Agent channel is a flow-control channel. That means before<br>
we send agent data to server, we must obtain tokens distributed<br>
from spice server, if we do not do that, spice server will get error,<br>
or at least, the data will be discarded.<br>
<br>
Other type of agent data will be cached to agent_msg_queue if there<br>
are no more tokens. But for file-xfer data, if we cache too much of<br>
those data, our memory will be exhausted pretty quickly if file is<br>
too big.<br>
<br>
We also should make other agent data(clipboard, mouse, ...) get<br>
through when file-xfer data are sending.<br>
<br>
So, for the reason of above, we can not fill file-xfer data to agent queue<br>
too quickly, we must consider the tokens, and other messages.<br>
<br>
Marc-André suggested me to call spice_channel_flush_async() and wait the<br>
queued data to be sent, but the API does not consider the available<br>
tokens, so I use a new algorithm/API(file_xfer_flush_async) based on<br>
spice_channel_flush_async() to send file-xfer data.<br>
<br></blockquote><div><br></div><div>Right<br></div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
gtk/channel-main.c | 476 ++++++++++++++++++++++++++++++++++++++++++++++++<br>
gtk/channel-main.h | 8 +<br>
gtk/map-file | 1 +<br>
gtk/spice-glib-sym-file | 1 +<br>
4 files changed, 486 insertions(+)<br>
<br>
diff --git a/gtk/channel-main.c b/gtk/channel-main.c<br>
index 6b9ba8d..b1496bd 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,24 @@<br>
<br>
typedef struct spice_migrate spice_migrate;<br>
<br>
+#define FILE_XFER_CHUNK_SIZE (VD_AGENT_MAX_DATA_SIZE * 32)<br>
+typedef struct SpiceFileXferTask {<br>
+ uint32_t id;<br>
+ uint32_t group_id;<br>
+ GFile *file;<br>
+ SpiceMainChannel *channel;<br>
+ GFileInputStream *file_stream;<br>
+ GFileCopyFlags flags;<br>
+ GCancellable *cancellable;<br>
+ GFileProgressCallback progress_callback;<br>
+ gpointer progress_callback_data;<br>
+ GAsyncReadyCallback callback;<br>
+ gpointer user_data;<br>
+ char buffer[FILE_XFER_CHUNK_SIZE];<br>
+ uint64_t read_bytes;<br>
+ uint64_t file_size;<br>
+} SpiceFileXferTask;<br>
+<br>
struct _SpiceMainChannelPrivate {<br>
enum SpiceMouseMode mouse_mode;<br>
bool agent_connected;<br>
@@ -79,6 +98,8 @@ struct _SpiceMainChannelPrivate {<br>
} display[MAX_DISPLAY];<br>
gint timer_id;<br>
GQueue *agent_msg_queue;<br>
+ GList *file_xfer_task_list;<br>
+ GSList *flushing;<br>
<br>
guint switch_host_delayed_id;<br>
guint migrate_delayed_id;<br>
@@ -802,6 +823,63 @@ static void agent_free_msg_queue(SpiceMainChannel *channel)<br>
c->agent_msg_queue = NULL;<br>
}<br>
<br>
+/* Here, flushing algorithm is stolen from spice-channel.c */<br>
+static void<br>
+file_xfer_flushed(SpiceMainChannel *channel, gboolean success)<br>
+{<br>
+ SpiceMainChannelPrivate *c = channel->priv;<br>
+ GSList *l;<br>
+<br>
+ for (l = c->flushing; l != NULL; l = l->next) {<br>
+ GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT(l->data);<br>
+ g_simple_async_result_set_op_res_gboolean(result, success);<br>
+ g_simple_async_result_complete_in_idle(result);<br>
+ }<br>
+<br>
+ g_slist_free_full(c->flushing, g_object_unref);<br>
+ c->flushing = NULL;<br>
+}<br>
+<br>
+static void<br>
+file_xfer_flush_async(SpiceMainChannel *channel, GCancellable *cancellable,<br>
+ GAsyncReadyCallback callback, gpointer user_data)<br>
+{<br>
+ GSimpleAsyncResult *simple;<br>
+ SpiceMainChannelPrivate *c = channel->priv;<br>
+ gboolean was_empty;<br>
+<br>
+ simple = g_simple_async_result_new(G_OBJECT(channel), callback, user_data,<br>
+ file_xfer_flush_async);<br>
+<br>
+ was_empty = g_queue_is_empty(c->agent_msg_queue);<br>
+ if (was_empty) {<br>
+ g_simple_async_result_set_op_res_gboolean(simple, TRUE);<br>
+ g_simple_async_result_complete_in_idle(simple);<br>
+ return;<br>
+ }<br>
+<br>
+ c->flushing = g_slist_append(c->flushing, simple);<br>
+}<br>
+<br>
+static gboolean<br>
+file_xfer_flush_finish(SpiceMainChannel *channel, GAsyncResult *result,<br>
+ GError **error)<br>
+{<br>
+ GSimpleAsyncResult *simple;<br>
+<br>
+ simple = (GSimpleAsyncResult *)result;<br>
+<br>
+ if (g_simple_async_result_propagate_error(simple, error)) {<br>
+ return -1;<br>
+ }<br>
+<br>
+ g_return_val_if_fail(g_simple_async_result_is_valid(result,<br>
+ G_OBJECT(channel), file_xfer_flush_async), FALSE);<br>
+<br>
+ CHANNEL_DEBUG(channel, "flushed finished!");<br>
+ return g_simple_async_result_get_op_res_gboolean(simple);<br>
+}<br>
+<br>
/* coroutine context */<br>
static void agent_send_msg_queue(SpiceMainChannel *channel)<br>
{<br>
@@ -814,6 +892,9 @@ static void agent_send_msg_queue(SpiceMainChannel *channel)<br>
out = g_queue_pop_head(c->agent_msg_queue);<br>
spice_msg_out_send_internal(out);<br>
}<br>
+ if (g_queue_is_empty(c->agent_msg_queue) && c->flushing != NULL) {<br>
+ file_xfer_flushed(channel, TRUE);<br>
+ }<br>
}<br>
<br>
/* any context: the message is not flushed immediately,<br>
@@ -1384,6 +1465,203 @@ static void main_handle_agent_disconnected(SpiceChannel *channel, SpiceMsgIn *in<br>
agent_stopped(SPICE_MAIN_CHANNEL(channel));<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_read_cb(GObject *source_object,<br>
+ GAsyncResult *res,<br>
+ gpointer user_data);<br>
+<br>
+static gboolean report_progress(gpointer user_data)<br>
+{<br>
+ SpiceFileXferTask *task = user_data;<br>
+ SpiceMainChannelPrivate *c = task->channel->priv;<br>
+<br>
+ if (task->progress_callback) {<br>
+ uint64_t all_read_bytes = 0, all_bytes = 0;<br>
+ GList *it;<br>
+ for (it = g_list_first(c->file_xfer_task_list);<br>
+ it != NULL; it = g_list_next(it)) {<br>
+ SpiceFileXferTask *t;<br>
+ t = it->data;<br>
+ /* Calculate all remain bytes through group id, NB: we dont<br>
+ * consider the task that has been finished */<br>
+ if (t->group_id == task->id) {<br>
+ all_read_bytes += t->read_bytes;<br>
+ all_bytes += t->file_size;<br>
+ }<br>
+ }<br>
+ task->progress_callback(all_read_bytes, all_bytes,<br>
+ task->progress_callback_data);<br>
+ }<br>
+<br>
+ return FALSE;<br>
+}<br>
+<br>
+static void data_flushed_cb(GObject *source_object,<br>
+ GAsyncResult *res,<br>
+ gpointer user_data)<br>
+{<br>
+ SpiceFileXferTask *task = user_data;<br>
+ SpiceMainChannel *channel = (SpiceMainChannel *)source_object;<br>
+ GError *error = NULL;<br>
+<br>
+ file_xfer_flush_finish(channel, res, &error);<br></blockquote><div><br></div><div>Even if the function currently doesn't report error, you should treat error case at least with a g_warning().<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">
+ /* Report progress */<br>
+ report_progress(task);<br>
+<br>
+ /* Read more data */<br>
+ g_input_stream_read_async(G_INPUT_STREAM(task->file_stream),<br>
+ task->buffer,<br>
+ FILE_XFER_CHUNK_SIZE,<br>
+ G_PRIORITY_DEFAULT,<br>
+ task->cancellable,<br>
+ file_read_cb,<br>
+ task);<br>
+}<br>
+<br>
+static void<br>
+file_xfer_queue(SpiceFileXferTask *task, int data_size)<br>
+{<br>
+ VDAgentFileXferDataMessage *msg;<br>
+ SpiceMainChannel *channel = SPICE_MAIN_CHANNEL(task->channel);<br>
+<br>
+ msg = g_alloca(sizeof(VDAgentFileXferDataMessage));<br>
+ msg->id = task->id;<br>
+ msg->size = data_size;<br>
+ agent_msg_queue_many(channel, VD_AGENT_FILE_XFER_DATA, msg,<br>
+ sizeof(VDAgentFileXferDataMessage), task->buffer,<br>
+ data_size, NULL);<br>
+ spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);<br>
+}<br>
+<br>
+static void report_finish(SpiceFileXferTask *task)<br>
+{<br>
+ if (task->callback) {<br>
+ GSimpleAsyncResult *res;<br>
+ res = g_simple_async_result_new(G_OBJECT(task->file), task->callback,<br>
+ task->user_data, report_finish);<br>
+ g_simple_async_result_set_op_res_gboolean(res, TRUE);<br>
+ g_simple_async_result_complete_in_idle(res);<br>
+ g_object_unref(res);<br>
+ }<br>
+}<br>
+<br>
+/* main context */<br>
+static void<br>
+file_close_cb(GObject *object,<br>
+ GAsyncResult *res,<br>
+ gpointer user_data)<br>
+{<br>
+ SpiceFileXferTask *task = user_data;<br>
+ GInputStream *stream = G_INPUT_STREAM(object);<br>
+ SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(task->channel)->priv;<br>
+ GError *error = NULL;<br>
+<br>
+ g_input_stream_close_finish(stream, res, &error);<br>
+ if (error) {<br>
+ SPICE_DEBUG("close file error: %s", error->message);<br>
+ g_clear_error(&error);<br>
+ }<br>
+<br>
+ c->file_xfer_task_list = g_list_remove(c->file_xfer_task_list, task);<br>
+<br>
+ /* If all tasks have been finished, notify to user */<br>
+ if (g_list_length(c->file_xfer_task_list) == 0) {<br>
+ report_finish(task);<br>
+ }<br>
+ g_object_unref(task->file);<br>
+ g_object_unref(task->file_stream);<br>
+ g_free(task);<br>
+}<br>
+<br>
+/* main context */<br>
+static void file_read_cb(GObject *source_object,<br>
+ GAsyncResult *res,<br>
+ gpointer user_data)<br>
+{<br>
+ SpiceFileXferTask *task = user_data;<br>
+ SpiceMainChannel *channel = task->channel;<br>
+ gssize count;<br>
+ GError *error = NULL;<br>
+<br>
+ count = g_input_stream_read_finish(G_INPUT_STREAM(task->file_stream),<br>
+ res, &error);<br>
+ if (count > 0) {<br>
+ task->read_bytes += count;<br>
+ file_xfer_queue(task, count);<br>
+ file_xfer_flush_async(channel, task->cancellable,<br>
+ data_flushed_cb, task);<br>
+ } else {<br>
+ g_input_stream_close_async(G_INPUT_STREAM(task->file_stream),<br>
+ G_PRIORITY_DEFAULT,<br>
+ task->cancellable,<br>
+ file_close_cb,<br>
+ task);<br></blockquote><div><br>You should report error to caller (probably with g_simple_async_report_gerror_in_idle)<br> <br></div><div>There will be some issues with the fact that there are multiple outstanding async in the background. The common pattern is to _always_ complete one async() call with one result (succesful or error). There shouldn't be "lost" async, or you may "block" some client execution path.<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>
+<br>
+/* coroutine context */<br>
+static void file_xfer_send_data_msg(SpiceMainChannel *channel, uint32_t id)<br>
+{<br>
+ SpiceMainChannelPrivate *c = channel->priv;<br>
+ GList *l;<br>
+ SpiceFileXferTask *task;<br>
+<br>
+ l = g_list_find_custom(c->file_xfer_task_list, &id,<br>
+ file_xfer_task_find);<br>
+<br>
+ g_return_if_fail(l != NULL);<br>
+<br>
+ task = l->data;<br>
+ g_input_stream_read_async(G_INPUT_STREAM(task->file_stream),<br>
+ task->buffer,<br>
+ FILE_XFER_CHUNK_SIZE,<br>
+ G_PRIORITY_DEFAULT,<br>
+ task->cancellable,<br>
+ file_read_cb,<br>
+ task);<br>
+}<br>
+<br>
+/* coroutine context */<br>
+static void file_xfer_handle_status(SpiceMainChannel *channel,<br>
+ VDAgentFileXferStatusMessage *msg)<br>
+{<br>
+ SPICE_DEBUG("task %d received response %d", msg->id, msg->result);<br></blockquote><div><br></div><div> You could lookup the task only once here.<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>
+ if (msg->result == VD_AGENT_FILE_XFER_STATUS_CAN_SEND_DATA) {<br>
+ file_xfer_send_data_msg(channel, msg->id);<br></blockquote><div><br></div><div>and can call g_input_stream_read_async() directly here, or perhaps move the read_async() in a seperate function task_continue_read() which will be called also from data_flushed_cb()<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">
+ } else {<br></blockquote><div><br></div><div>Please check precisely the other msg->result values, and do a g_warn_if_reached() for unknown values.<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">
+ /* Error, remove this task */<br>
+ SpiceMainChannelPrivate *c = channel->priv;<br>
+ GList *l;<br>
+ SpiceFileXferTask *task;<br>
+<br>
+ l = g_list_find_custom(c->file_xfer_task_list, &msg->id,<br>
+ file_xfer_task_find);<br>
+ g_return_if_fail(l != NULL);<br>
+<br>
+ task = l->data;<br>
+ SPICE_DEBUG("user removed task %d, result: %d", msg->id,<br>
+ msg->result);<br></blockquote><div> <br></div>You should report error to caller (probably with g_simple_async_report_gerror_in_idle)<br><br><br><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
+ c->file_xfer_task_list = g_list_remove(c->file_xfer_task_list,<br>
+ task);<br>
+ g_object_unref(task->file);<br>
+ g_object_unref(task->file_stream);<br>
+ g_free(task); </blockquote><div><br></div><div>Those last 4 lines could probably be in a seperate function file_xfer_task_free ()<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>
+<br>
/* coroutine context */<br>
static void main_agent_handle_msg(SpiceChannel *channel,<br>
VDAgentMessage *msg, gpointer payload)<br>
@@ -1487,6 +1765,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(SPICE_MAIN_CHANNEL(channel), 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>
@@ -1563,6 +1844,7 @@ static void main_handle_agent_token(SpiceChannel *channel, SpiceMsgIn *in)<br>
SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;<br>
<br>
c->agent_tokens += tokens->num_tokens;<br>
+<br>
agent_send_msg_queue(SPICE_MAIN_CHANNEL(channel));<br>
}<br>
<br>
@@ -2246,3 +2528,197 @@ void spice_main_set_display_enabled(SpiceMainChannel *channel, int id, gboolean<br>
c->display[id].enabled = enabled;<br>
}<br>
}<br>
+<br>
+static void<br>
+file_info_async_cb(GObject *obj, GAsyncResult *res, gpointer data)<br>
+{<br>
+ GFileInfo *info;<br>
+ GFile *file = G_FILE(obj);<br>
+ GError *error = NULL;<br>
+ GKeyFile *keyfile = NULL;<br>
+ gchar *basename = NULL;<br>
+ VDAgentFileXferStartMessage *msg;<br>
+ gsize msg_size, data_len;<br>
+ gchar *string;<br>
+ SpiceFileXferTask *task = (SpiceFileXferTask *)data;<br>
+ SpiceMainChannelPrivate *c = task->channel->priv;<br>
+<br>
+ info = g_file_query_info_finish(file, res, &error);<br>
+ if (error) {<br>
+ SPICE_DEBUG("couldn't get size of file %s: %s",<br>
+ g_file_get_path(file),<br>
+ error->message);<br>
+ goto failed;<br>
+ }<br>
+ task->file_size = g_file_info_get_attribute_uint64(info,<br>
+ G_FILE_ATTRIBUTE_STANDARD_SIZE);<br>
+<br>
+ keyfile = g_key_file_new();<br>
+ if (keyfile == NULL) {<br>
+ SPICE_DEBUG("failed to create key file: %s", error->message);<br>
+ goto failed;<br>
+ }<br>
+<br>
+ /* File name */<br>
+ basename = g_file_get_basename(file);<br>
+ if (basename == NULL) {<br>
+ SPICE_DEBUG("failed to get file basename: %s", error->message);<br>
+ goto failed;<br>
+ }<br>
+ g_key_file_set_string(keyfile, "vdagent-file-xfer", "name", basename);<br>
+ g_free(basename);<br>
+<br>
+ /* File size */<br>
+ g_key_file_set_uint64(keyfile, "vdagent-file-xfer", "size",<br>
+ task->file_size);<br>
+<br>
+ /* Save keyfile content to memory. TODO: more file attributions<br>
+ need to be sent to guest */<br>
+ string = g_key_file_to_data(keyfile, &data_len, &error);<br>
+ g_key_file_free(keyfile);<br>
+ if (error) {<br>
+ goto failed;<br>
+ }<br>
+<br>
+ /* Create file-xfer start message */<br>
+ msg_size = sizeof(VDAgentFileXferStartMessage) + data_len + 1;<br>
+ msg = g_malloc0(msg_size);<br></blockquote><div><br>This allocation and copy seems unnecessary, can you use "string" directly?<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 = task->id;<br>
+ memcpy(msg->data, string, data_len + 1);<br>
+ g_free(string);<br>
+<br>
+ CHANNEL_DEBUG(task->channel, "Insert a xfer task:%d to task list",<br>
+ task->id);<br>
+ c->file_xfer_task_list = g_list_append(c->file_xfer_task_list, task);<br>
+<br>
+ agent_msg_queue(task->channel, VD_AGENT_FILE_XFER_START, msg_size, msg);<br>
+ g_free(msg);<br>
+ spice_channel_wakeup(SPICE_CHANNEL(task->channel), FALSE);<br>
+ return ;<br>
+<br>
+failed:<br>
+ g_clear_error(&error);<br>
+ g_object_unref(task->file);<br>
+ g_object_unref(task->file_stream);<br>
+ g_free(task);<br>
+}<br>
+<br>
+static void<br>
+read_async_cb(GObject *obj, GAsyncResult *res, gpointer data)<br>
+{<br>
+ GFile *file = G_FILE(obj);<br>
+ SpiceFileXferTask *task = (SpiceFileXferTask *)data;<br>
+ GError *error = NULL;<br>
+<br>
+ task->file_stream = g_file_read_finish(file, res, &error);<br>
+<br>
+ if (task->file_stream) {<br>
+ g_file_query_info_async(task->file,<br>
+ G_FILE_ATTRIBUTE_STANDARD_SIZE,<br>
+ G_FILE_QUERY_INFO_NONE,<br>
+ G_PRIORITY_DEFAULT,<br>
+ task->cancellable,<br>
+ file_info_async_cb,<br>
+ task);<br>
+ } else {<br>
+ SPICE_DEBUG("create file stream for %s error: %s",<br>
+ g_file_get_path(file), error->message);<br>
+ g_clear_error(&error);<br>
+ g_object_unref(task->file);<br>
+ g_free(task);<br></blockquote><div><br></div><div>You should report error to caller (probably with g_simple_async_report_gerror_in_idle)<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>
+<br>
+static void<br>
+file_xfer_send_start_msg_async(SpiceMainChannel *channel,<br>
+ GFile *file,<br>
+ GFileCopyFlags flags,<br>
+ GCancellable *cancellable,<br>
+ GFileProgressCallback progress_callback,<br>
+ gpointer progress_callback_data,<br>
+ GAsyncReadyCallback callback,<br>
+ gpointer user_data,<br>
+ uint32_t group_id)<br>
+{<br>
+ SpiceFileXferTask *task;<br>
+ static uint32_t xfer_id; /* Used to identify task id */<br>
+<br>
+ xfer_id = (xfer_id > UINT32_MAX) ? 0 : xfer_id;<br>
+<br>
+ task = spice_malloc0(sizeof(SpiceFileXferTask));<br>
+ task->id = ++xfer_id;<br>
+ task->group_id = group_id;<br>
+ task->channel = channel;<br>
+ task->file = g_object_ref(file);<br>
+ task->flags = flags;<br>
+ task->cancellable = cancellable;<br>
+ task->progress_callback = progress_callback;<br>
+ task->progress_callback_data = progress_callback_data;<br>
+ task->callback = callback;<br>
+ task->user_data = user_data;<br>
+<br>
+ g_file_read_async(file,<br>
+ G_PRIORITY_DEFAULT,<br>
+ cancellable,<br>
+ read_async_cb,<br>
+ task);<br>
+<br>
+}<br>
+<br>
+/**<br>
+ * spice_main_file_copy_async:<br>
+ * @sources: #GFile to be transfer<br>
+ * @flags: set of #GFileCopyFlags<br>
+ * @cancellable: (allow-none): optional #GCancellable object, %NULL to ignore<br>
+ * @progress_callback: (allow-none) (scope call): function to callback with<br>
+ * progress information, or %NULL if progress information is not needed<br>
+ * @progress_callback_data: (closure): user data to pass to @progress_callback<br>
+ * @error: #GError to set on error, or %NULL<br>
+ *<br>
+ * Copies the file @sources to guest<br>
+ *<br>
+ * If @cancellable is not %NULL, then the operation can be cancelled by<br>
+ * triggering the cancellable object from another thread. If the operation<br>
+ * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned.<br>
+ *<br>
+ * If @progress_callback is not %NULL, then the operation can be monitored by<br>
+ * setting this to a #GFileProgressCallback function. @progress_callback_data<br>
+ * will be passed to this function. It is guaranteed that this callback will<br>
+ * be called after all data has been transferred with the total number of bytes<br>
+ * copied during the operation.<br>
+ *<br>
+ * When the operation is finished, callback will be called.<br>
+ *<br>
+ **/<br>
+void spice_main_file_copy_async(SpiceMainChannel *channel,<br>
+ GFile **sources,<br>
+ GFileCopyFlags flags,<br>
+ GCancellable *cancellable,<br>
+ GFileProgressCallback progress_callback,<br>
+ gpointer progress_callback_data,<br>
+ GAsyncReadyCallback callback,<br>
+ gpointer user_data)<br>
+{<br>
+ int i = 0;<br>
+ static uint32_t xfer_group_id;<br>
+<br>
+ g_return_if_fail(channel != NULL);<br>
+ g_return_if_fail(SPICE_IS_MAIN_CHANNEL(channel));<br>
+ g_return_if_fail(sources != NULL);<br>
+<br>
+ xfer_group_id++;<br>
+ xfer_group_id = (xfer_group_id > UINT32_MAX) ? 0 : xfer_group_id;<br>
+ while (sources[i]) {<br>
+ /* All tasks created from below function have same group id */<br></blockquote><div><br></div><div>I am worried by the server side handling of sharing the same group id for several requests. But I am okay with this communication pattern that can be later improved if needed.<br>
</div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
+ file_xfer_send_start_msg_async(channel,<br>
+ sources[i],<br>
+ flags,<br>
+ cancellable,<br>
+ progress_callback,<br>
+ progress_callback_data,<br>
+ callback,<br>
+ user_data,<br>
+ xfer_group_id);<br>
+ i++;<br>
+ }<br>
+}<br>
diff --git a/gtk/channel-main.h b/gtk/channel-main.h<br>
index 1a5ab54..d00490f 100644<br>
--- a/gtk/channel-main.h<br>
+++ b/gtk/channel-main.h<br>
@@ -78,6 +78,14 @@ 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>
+void spice_main_file_copy_async(SpiceMainChannel *channel,<br>
+ GFile **sources,<br>
+ GFileCopyFlags flags,<br>
+ GCancellable *cancellable,<br>
+ GFileProgressCallback progress_callback,<br>
+ gpointer progress_callback_data,<br>
+ GAsyncReadyCallback callback,<br>
+ gpointer user_data);<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 516764c..9988e7d 100644<br>
--- a/gtk/map-file<br>
+++ b/gtk/map-file<br>
@@ -55,6 +55,7 @@ spice_inputs_motion;<br>
spice_inputs_position;<br>
spice_inputs_set_key_locks;<br>
spice_main_agent_test_capability;<br>
+spice_main_file_copy_async;<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 641ff4d..81fedd5 100644<br>
--- a/gtk/spice-glib-sym-file<br>
+++ b/gtk/spice-glib-sym-file<br>
@@ -31,6 +31,7 @@ spice_inputs_motion<br>
spice_inputs_position<br>
spice_inputs_set_key_locks<br>
spice_main_agent_test_capability<br>
+spice_main_file_copy_async<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 a lot for your work!<br clear="all"><br>-- <br>Marc-André Lureau
</div></div>