[Spice-devel] [PATCH spice-gtk V2 1/3] drag-n-drop: handling various transfer messages in main channel
Dunrong Huang
riegamaths at gmail.com
Fri Nov 23 03:11:38 PST 2012
This patch is aimed to handle various xfer messages.
How it works:
0) our main channel introduces a API spice_main_file_xfer().
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_xfer() for transfering file to guest.
2) In main channel: when spice_main_file_xfer() 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 to 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>
---
gtk/channel-main.c | 208 +++++++++++++++++++++++++++++++++++++++++++++++-
gtk/channel-main.h | 1 +
gtk/map-file | 1 +
gtk/spice-glib-sym-file | 1 +
4 files changed, 210 insertions(+), 1 deletion(-)
diff --git a/gtk/channel-main.c b/gtk/channel-main.c
index 6b9ba8d..3ea3958 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,13 @@
typedef struct spice_migrate spice_migrate;
+typedef struct SpiceFileXferTask {
+ uint32_t id;
+ SpiceChannel *channel;
+ GFileInputStream *file_stream;
+ VDAgentFileXferStartMessage *start_msg;
+} SpiceFileXferTask;
+
struct _SpiceMainChannelPrivate {
enum SpiceMouseMode mouse_mode;
bool agent_connected;
@@ -79,6 +87,7 @@ struct _SpiceMainChannelPrivate {
} display[MAX_DISPLAY];
gint timer_id;
GQueue *agent_msg_queue;
+ GList *file_xfer_task_list;
guint switch_host_delayed_id;
guint migrate_delayed_id;
@@ -1384,6 +1393,106 @@ static void main_handle_agent_disconnected(SpiceChannel *channel, SpiceMsgIn *in
agent_stopped(SPICE_MAIN_CHANNEL(channel));
}
+static gboolean send_data(gpointer opaque)
+{
+#define FILE_XFER_CHUNK_SIZE (VD_AGENT_MAX_DATA_SIZE - sizeof(VDAgentMessage))
+ SpiceFileXferTask *task = (SpiceFileXferTask *)opaque;
+
+ SpiceMainChannel *channel = SPICE_MAIN_CHANNEL(task->channel);
+ SpiceMainChannelPrivate *c = channel->priv;
+ gssize len;
+ GError *error = NULL;
+ VDAgentFileXferDataMessage *msg;
+ uint32_t msg_size;
+
+ if (!g_queue_is_empty(c->agent_msg_queue)) {
+ spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);
+ return TRUE;
+ }
+
+ msg = g_malloc(sizeof(VDAgentFileXferDataMessage) + FILE_XFER_CHUNK_SIZE);
+
+ len = g_input_stream_read(G_INPUT_STREAM(task->file_stream), msg->data,
+ FILE_XFER_CHUNK_SIZE, NULL, &error);
+ if (len == -1) {
+ /* TODO: error handler, tell guest cancel xfer */
+ CHANNEL_DEBUG(channel, "Read file error: %s", error->message);
+ g_clear_error(&error);
+ goto done;
+ } else if (len == 0) {
+ CHANNEL_DEBUG(channel, "Finished Reading file");
+ goto done;
+ }
+
+ msg_size = sizeof(VDAgentFileXferDataMessage) + len;
+ msg->id = task->id;
+ msg->size = len;
+ agent_msg_queue(channel, VD_AGENT_FILE_XFER_DATA, msg_size, msg);
+ g_free(msg);
+ spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);
+
+ return TRUE;
+
+done:
+ g_object_unref(task->file_stream);
+ c->file_xfer_task_list = g_list_remove(c->file_xfer_task_list, task);
+ g_free(task->start_msg);
+ g_free(task);
+ g_free(msg);
+ return FALSE;
+}
+
+static void file_xfer_send_data_msg(SpiceChannel *channel, uint32_t id)
+{
+ GList *l;
+ SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
+ SpiceFileXferTask *task = NULL;
+
+ for (l = c->file_xfer_task_list; l != NULL; l = l->next) {
+ SpiceFileXferTask *t = NULL;
+ t = l->data;
+ if (t->id == id) {
+ task = t;
+ break;
+ }
+ }
+
+ /* FIXME: g_assert(task); ? */
+ if (task) {
+ g_idle_add(send_data, task);
+ }
+}
+
+/* coroutine context */
+static void file_xfer_handle_status(SpiceChannel *channel,
+ VDAgentFileXferStatusMessage *msg)
+{
+ SPICE_DEBUG("task %d received response %d", msg->id, msg->result);
+
+ if (msg->result == VD_AGENT_FILE_XFER_RESULT_SUCCESS) {
+ file_xfer_send_data_msg(channel, msg->id);
+ } else {
+ /* Error, remove this task */
+ SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
+ GList *l;
+ SpiceFileXferTask *task = NULL;
+ for (l = c->file_xfer_task_list; l != NULL; l = l->next) {
+ SpiceFileXferTask *t = NULL;
+ t = l->data;
+ if (t->id == msg->id) {
+ task = t;
+ break;
+ }
+ }
+ if (task) {
+ SPICE_DEBUG("user removed task %d, result: %d", msg->id, msg->result);
+ c->file_xfer_task_list = g_list_remove(c->file_xfer_task_list, task);
+ g_free(task->start_msg);
+ g_free(task);
+ }
+ }
+}
+
/* coroutine context */
static void main_agent_handle_msg(SpiceChannel *channel,
VDAgentMessage *msg, gpointer payload)
@@ -1487,6 +1596,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(channel, (VDAgentFileXferStatusMessage *)payload);
+ break;
default:
g_warning("unhandled agent message type: %u (%s), size %u",
msg->type, NAME(agent_msg_types, msg->type), msg->size);
@@ -2243,6 +2355,100 @@ void spice_main_set_display_enabled(SpiceMainChannel *channel, int id, gboolean
c->display[i].enabled = enabled;
} else {
g_return_if_fail(id < G_N_ELEMENTS(c->display));
- c->display[id].enabled = enabled;
+ c->display[id].enabled = enabled
+ ;
+ }
+}
+
+static GFileInputStream *file_xfer_file_stream_new(const gchar *file_path)
+{
+ GFile *f;
+ GFileInputStream *stream;
+ GError *error = NULL;
+
+ f = g_file_new_for_path(file_path);
+ stream = g_file_read(f, NULL, &error);
+ if (error) {
+ SPICE_DEBUG("create file stream for %s error: %s",
+ file_path, error->message);
+ g_error_free(error);
}
+ g_object_unref(f);
+
+ return stream;
+}
+
+/* TODO:
+ 1) support compressing data
+ 2) file mode(unix only) */
+static VDAgentFileXferStartMessage *file_xfer_start_msg_new(gchar *file_path, int *msg_size)
+{
+ static uint32_t xfer_id = 0; /* Used to identify msg id, limit to 10000 */
+ GStatBuf st;
+ VDAgentFileXferStartMessage *msg;
+ gchar *basename;
+
+ basename = g_path_get_basename(file_path);
+ g_stat(file_path, &st);
+
+ *msg_size = sizeof(VDAgentFileXferStartMessage) + strlen(basename) + 1;
+ msg = g_malloc0(*msg_size);
+ msg->id = ++xfer_id;
+ xfer_id = (xfer_id > 10000) ? 0 : xfer_id;
+ msg->is_dir = FALSE;
+ msg->compressed_format = VD_AGENT_FILE_XFER_FORMAT_RAW;
+ msg->file_size = st.st_size;
+ /* msg->file_mode = mode; */
+ g_snprintf((gchar *)msg->file_name, strlen(basename) + 1, "%s", basename);
+
+ g_free(basename);
+ return msg;
+}
+
+static void file_xfer_send_start_msg(gpointer data, gpointer user_data)
+{
+ gchar *file_path, *unescape_uri;
+ GFileInputStream *file_stream;
+ SpiceFileXferTask *xfer;
+ VDAgentFileXferStartMessage *msg;
+ int msg_size;
+ SpiceMainChannel *channel = user_data;
+
+ /* Parse file absolute path */
+ unescape_uri = g_uri_unescape_string((gchar *)data, NULL);
+ file_path = unescape_uri;
+ if (g_str_has_prefix(file_path, "file://")) {
+ file_path += strlen("file://");
+ }
+
+ file_stream = file_xfer_file_stream_new(file_path);
+ if (file_stream == NULL) {
+ g_free(unescape_uri);
+ return ;
+ }
+
+ msg = file_xfer_start_msg_new(file_path, &msg_size);
+ g_free(unescape_uri);
+
+ /* Create a new xfer task and insert into task list */
+ xfer = spice_malloc0(sizeof(SpiceFileXferTask));
+ xfer->id = msg->id;
+ xfer->channel = (SpiceChannel *)channel;
+ xfer->file_stream = file_stream;
+ xfer->start_msg = msg;
+
+ CHANNEL_DEBUG(channel, "Insert a xfer task:%d to task list", xfer->id);
+ channel->priv->file_xfer_task_list = g_list_append(
+ channel->priv->file_xfer_task_list, xfer);
+
+ agent_msg_queue(channel, VD_AGENT_FILE_XFER_START, msg_size, msg);
+ spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);
+}
+
+gboolean spice_main_file_xfer(SpiceMainChannel *channel, GList *files, GError *err)
+{
+ /* Send VD_AGENT_FILE_XFER_START message for every file */
+ g_list_foreach(files, file_xfer_send_start_msg, channel);
+ g_list_free_full(files, g_free);
+ return TRUE;
}
diff --git a/gtk/channel-main.h b/gtk/channel-main.h
index 1a5ab54..31997f1 100644
--- a/gtk/channel-main.h
+++ b/gtk/channel-main.h
@@ -78,6 +78,7 @@ 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);
+gboolean spice_main_file_xfer(SpiceMainChannel *channel, GList *files, GError *err);
#ifndef SPICE_DISABLE_DEPRECATED
SPICE_DEPRECATED_FOR(spice_main_clipboard_selection_grab)
diff --git a/gtk/map-file b/gtk/map-file
index ed2c07f..7827726 100644
--- a/gtk/map-file
+++ b/gtk/map-file
@@ -53,6 +53,7 @@ spice_inputs_motion;
spice_inputs_position;
spice_inputs_set_key_locks;
spice_main_agent_test_capability;
+spice_main_file_xfer;
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 82955f0..4bf92c3 100644
--- a/gtk/spice-glib-sym-file
+++ b/gtk/spice-glib-sym-file
@@ -29,6 +29,7 @@ spice_inputs_motion
spice_inputs_position
spice_inputs_set_key_locks
spice_main_agent_test_capability
+spice_main_file_xfer
spice_main_channel_get_type
spice_main_clipboard_grab
spice_main_clipboard_notify
--
1.8.0
More information about the Spice-devel
mailing list