[Spice-devel] [PATCH spice-gtk v3 2/2] New file transfer API
Jonathon Jongsma
jjongsma at redhat.com
Tue Oct 6 12:05:44 PDT 2015
There were several shortcomings to the existing file transfer API,
particularly in terms of monitoring ongoing file transfers. The major
issue is that spice_main_file_copy_async() allows you to pass an array
of files, but the progress callback does not provide a way to
identify which file the callback is associated with. This makes it
nearly impossible for an application to monitor file transfers.
In addition, the SpiceDisplay widget automatically handles drag-and-drop
actions on the widget, and initiates file transfers without allowing the
application to specify a progress callback. So there's no way for an app
to monitor file transfers that are initiated via drag and drop.
http://lists.freedesktop.org/archives/spice-devel/2015-September/021931.html
has a more detailed explanation of the issues.
This change doesn't break the existing API, but adds some new API that
will allow an application to monitor file transfer progress, even for
transfers that are initiated within spice-gtk itself.
- A new public SpiceFileTransferTask object is added.
- The SpiceMainChannel object gains a "new-file-transfer" signal that is
emitted whenever a new file transfer is initiated. The
SpiceFileTransferTask object is passed to the signal handler.
- The application can retain this object and monitor its 'progress'
property to be notified when the progress of the file transfer
changes. The SpiceFileTransferTask::finished signal indicates when the
given file transfer has completed. The application can also cancel the
file transfer by calling the _cancel() method.
The 'spicy' test application has been updated to use this new API and
display a simple dialog showing the progress of individual files.
---
Changes since v2
- fixed documentation to since 0.31 instead of 0.30
- rebased on master so the patches apply cleanly
- added spice-file-transfer-task.h include to spice-client.h
- fixed some other includes
- remove commented-out code left over from previous iteration
- documented all symbols
doc/reference/spice-gtk-docs.xml | 1 +
doc/reference/spice-gtk-sections.txt | 21 ++
doc/reference/spice-gtk.types | 2 +
src/Makefile.am | 2 +
src/channel-main.c | 582 +++++++++++++++++++++++++++--------
src/channel-main.h | 3 +-
src/map-file | 5 +
src/spice-client.h | 1 +
src/spice-file-transfer-task.h | 68 ++++
src/spice-glib-sym-file | 5 +
src/spicy.c | 152 +++++++++
11 files changed, 705 insertions(+), 137 deletions(-)
create mode 100644 src/spice-file-transfer-task.h
diff --git a/doc/reference/spice-gtk-docs.xml b/doc/reference/spice-gtk-docs.xml
index de68004..db5dd3d 100644
--- a/doc/reference/spice-gtk-docs.xml
+++ b/doc/reference/spice-gtk-docs.xml
@@ -55,6 +55,7 @@
<xi:include href="xml/spice-util.xml"/>
<xi:include href="xml/spice-version.xml"/>
<xi:include href="xml/spice-uri.xml"/>
+ <xi:include href="xml/file-transfer-task.xml"/>
</chapter>
</part>
diff --git a/doc/reference/spice-gtk-sections.txt b/doc/reference/spice-gtk-sections.txt
index fe24f9f..d8c4c79 100644
--- a/doc/reference/spice-gtk-sections.txt
+++ b/doc/reference/spice-gtk-sections.txt
@@ -495,3 +495,24 @@ spice_webdav_channel_get_type
<SUBSECTION Private>
SpiceWebdavChannelPrivate
</SECTION>
+
+<SECTION>
+<FILE>file-transfer-task</FILE>
+<TITLE>SpiceFileTransferTask</TITLE>
+SpiceFileTransferTask
+SpiceFileTransferTaskClass
+<SUBSECTION>
+spice_file_transfer_task_get_progress
+spice_file_transfer_task_get_filename
+spice_file_transfer_task_cancel
+<SUBSECTION Standard>
+SPICE_FILE_TRANSFER_TASK
+SPICE_IS_FILE_TRANSFER_TASK
+SPICE_TYPE_FILE_TRANSFER_TASK
+spice_file_transfer_task_get_type
+SPICE_FILE_TRANSFER_TASK_CLASS
+SPICE_IS_FILE_TRANSFER_TASK_CLASS
+SPICE_FILE_TRANSFER_TASK_GET_CLASS
+<SUBSECTION Private>
+SpiceFileTransferTaskPrivate
+</SECTION>
diff --git a/doc/reference/spice-gtk.types b/doc/reference/spice-gtk.types
index acd616d..e14ae1b 100644
--- a/doc/reference/spice-gtk.types
+++ b/doc/reference/spice-gtk.types
@@ -20,6 +20,7 @@
#include "smartcard-manager.h"
#include "usb-device-manager.h"
#include "usb-device-widget.h"
+#include "spice-file-transfer-task.h"
spice_audio_get_type
spice_channel_event_get_type
@@ -45,3 +46,4 @@ spice_usb_device_manager_get_type
spice_usb_device_widget_get_type
spice_port_channel_get_type
spice_webdav_channel_get_type
+spice_file_transfer_task_get_type
diff --git a/src/Makefile.am b/src/Makefile.am
index cf02198..a58e414 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -134,6 +134,7 @@ SPICE_GTK_SOURCES_COMMON = \
spice-gtk-session-priv.h \
spice-widget.c \
spice-widget-priv.h \
+ spice-file-transfer-task.h \
vncdisplaykeymap.c \
vncdisplaykeymap.h \
spice-grabsequence.c \
@@ -315,6 +316,7 @@ libspice_client_glibinclude_HEADERS = \
channel-webdav.h \
usb-device-manager.h \
smartcard-manager.h \
+ spice-file-transfer-task.h \
$(NULL)
nodist_libspice_client_glibinclude_HEADERS = \
diff --git a/src/channel-main.c b/src/channel-main.c
index 8bf1354..cc67536 100644
--- a/src/channel-main.c
+++ b/src/channel-main.c
@@ -31,6 +31,7 @@
#include "spice-channel-priv.h"
#include "spice-session-priv.h"
#include "spice-audio-priv.h"
+#include "spice-file-transfer-task.h"
/**
* SECTION:channel-main
@@ -55,8 +56,33 @@
typedef struct spice_migrate spice_migrate;
+/**
+ * SECTION:file-transfer-task
+ * @short_description: Monitoring file transfers
+ * @title: File Transfer Task
+ * @section_id:
+ * @see_also: #SpiceMainChannel
+ * @stability: Stable
+ * @include: spice-client.h
+ *
+ * SpiceFileTransferTask is an object that represents a particular file
+ * transfer between the client and the guest. The properties and signals of the
+ * object can be used to monitor the status and result of the transfer. The
+ * Main Channel's #SpiceMainChannel::new-file-transfer signal will be emitted
+ * whenever a new file transfer task is initiated.
+ *
+ * Since: 0.31
+ */
+G_DEFINE_TYPE(SpiceFileTransferTask, spice_file_transfer_task, G_TYPE_OBJECT)
+
+#define FILE_TRANSFER_TASK_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE((o), SPICE_TYPE_FILE_TRANSFER_TASK, SpiceFileTransferTaskPrivate))
+
#define FILE_XFER_CHUNK_SIZE (VD_AGENT_MAX_DATA_SIZE * 32)
-typedef struct SpiceFileXferTask {
+struct _SpiceFileTransferTaskPrivate
+
+/* private */
+{
uint32_t id;
gboolean pending;
GFile *file;
@@ -68,13 +94,28 @@ typedef struct SpiceFileXferTask {
gpointer progress_callback_data;
GAsyncReadyCallback callback;
gpointer user_data;
- char buffer[FILE_XFER_CHUNK_SIZE];
+ char *buffer;
uint64_t read_bytes;
uint64_t file_size;
gint64 start_time;
gint64 last_update;
GError *error;
-} SpiceFileXferTask;
+};
+
+enum {
+ PROP_TASK_ID = 1,
+ PROP_TASK_CHANNEL,
+ PROP_TASK_CANCELLABLE,
+ PROP_TASK_FILE,
+ PROP_TASK_PROGRESS,
+};
+
+enum {
+ SIGNAL_FINISHED,
+ LAST_TASK_SIGNAL
+};
+
+static guint task_signals[LAST_TASK_SIGNAL];
typedef enum {
DISPLAY_UNDEFINED,
@@ -168,6 +209,7 @@ enum {
SPICE_MAIN_CLIPBOARD_SELECTION_REQUEST,
SPICE_MAIN_CLIPBOARD_SELECTION_RELEASE,
SPICE_MIGRATION_STARTED,
+ SPICE_MAIN_NEW_FILE_TRANSFER,
SPICE_MAIN_LAST_SIGNAL,
};
@@ -181,8 +223,8 @@ 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);
-static void file_xfer_completed(SpiceFileXferTask *task, GError *error);
+static void file_xfer_continue_read(SpiceFileTransferTask *task);
+static void spice_file_transfer_task_completed(SpiceFileTransferTask *self, GError *error);
static void file_xfer_flushed(SpiceMainChannel *channel, gboolean success);
static void spice_main_set_max_clipboard(SpiceMainChannel *self, gint max);
static void set_agent_connected(SpiceMainChannel *channel, gboolean connected);
@@ -245,7 +287,8 @@ static void spice_main_channel_init(SpiceMainChannel *channel)
c = channel->priv = SPICE_MAIN_CHANNEL_GET_PRIVATE(channel);
c->agent_msg_queue = g_queue_new();
- c->file_xfer_tasks = g_hash_table_new(g_direct_hash, g_direct_equal);
+ c->file_xfer_tasks = g_hash_table_new_full(g_direct_hash, g_direct_equal,
+ NULL, g_object_unref);
c->cancellable_volume_info = g_cancellable_new();
spice_main_channel_reset_capabilties(SPICE_CHANNEL(channel));
@@ -409,11 +452,11 @@ static void spice_main_channel_reset_agent(SpiceMainChannel *channel)
tasks = g_hash_table_get_values(c->file_xfer_tasks);
for (l = tasks; l != NULL; l = l->next) {
- SpiceFileXferTask *task = (SpiceFileXferTask *)l->data;
+ SpiceFileTransferTask *task = (SpiceFileTransferTask *)l->data;
error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
"Agent connection closed");
- file_xfer_completed(task, error);
+ spice_file_transfer_task_completed(task, error);
}
g_list_free(tasks);
file_xfer_flushed(channel, FALSE);
@@ -828,6 +871,28 @@ static void spice_main_channel_class_init(SpiceMainChannelClass *klass)
1,
G_TYPE_OBJECT);
+ /**
+ * SpiceMainChannel::new-file-transfer:
+ * @main: the #SpiceMainChannel that emitted the signal
+ * @task: a #SpiceFileTransferTask
+ *
+ * This signal is emitted when a new file transfer task has been initiated
+ * on this channel. Client applications may take a reference on the @task
+ * object and use it to monitor the status of the file transfer task.
+ *
+ * Since: 0.31
+ **/
+ signals[SPICE_MAIN_NEW_FILE_TRANSFER] =
+ g_signal_new("new-file-transfer",
+ G_OBJECT_CLASS_TYPE(gobject_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_OBJECT);
+
g_type_class_add_private(klass, sizeof(SpiceMainChannelPrivate));
channel_set_handlers(SPICE_CHANNEL_CLASS(klass));
}
@@ -1691,31 +1756,16 @@ static void main_handle_agent_disconnected(SpiceChannel *channel, SpiceMsgIn *in
agent_stopped(SPICE_MAIN_CHANNEL(channel));
}
-static void file_xfer_task_free(SpiceFileXferTask *task)
-{
- SpiceMainChannelPrivate *c;
-
- g_return_if_fail(task != NULL);
-
- c = task->channel->priv;
- g_hash_table_remove(c->file_xfer_tasks, GUINT_TO_POINTER(task->id));
-
- g_clear_object(&task->channel);
- g_clear_object(&task->file);
- g_clear_object(&task->file_stream);
- g_free(task);
-}
-
/* main context */
static void file_xfer_close_cb(GObject *object,
GAsyncResult *close_res,
gpointer user_data)
{
GSimpleAsyncResult *res;
- SpiceFileXferTask *task;
+ SpiceFileTransferTask *self;
GError *error = NULL;
- task = user_data;
+ self = user_data;
if (object) {
GInputStream *stream = G_INPUT_STREAM(object);
@@ -1729,23 +1779,23 @@ static void file_xfer_close_cb(GObject *object,
/* 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,
+ res = g_simple_async_result_new(G_OBJECT(self->priv->channel),
+ self->priv->callback,
+ self->priv->user_data,
spice_main_file_copy_async);
- if (task->error) {
- g_simple_async_result_take_error(res, task->error);
+ if (self->priv->error) {
+ g_simple_async_result_take_error(res, self->priv->error);
g_simple_async_result_set_op_res_gboolean(res, FALSE);
} else {
g_simple_async_result_set_op_res_gboolean(res, TRUE);
if (spice_util_get_debug()) {
gint64 now = g_get_monotonic_time();
- gchar *basename = g_file_get_basename(task->file);
- double seconds = (double) (now - task->start_time) / G_TIME_SPAN_SECOND;
- gchar *file_size_str = g_format_size(task->file_size);
- gchar *transfer_speed_str = g_format_size(task->file_size / seconds);
+ gchar *basename = g_file_get_basename(self->priv->file);
+ double seconds = (double) (now - self->priv->start_time) / G_TIME_SPAN_SECOND;
+ gchar *file_size_str = g_format_size(self->priv->file_size);
+ gchar *transfer_speed_str = g_format_size(self->priv->file_size / seconds);
- g_warn_if_fail(task->read_bytes == task->file_size);
+ g_warn_if_fail(self->priv->read_bytes == self->priv->file_size);
SPICE_DEBUG("transferred file %s of %s size in %.1f seconds (%s/s)",
basename, file_size_str, seconds, transfer_speed_str);
@@ -1757,21 +1807,21 @@ static void file_xfer_close_cb(GObject *object,
g_simple_async_result_complete_in_idle(res);
g_object_unref(res);
- file_xfer_task_free(task);
+ g_object_unref(self);
}
static void file_xfer_data_flushed_cb(GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
- SpiceFileXferTask *task = user_data;
+ SpiceFileTransferTask *self = user_data;
SpiceMainChannel *channel = (SpiceMainChannel *)source_object;
GError *error = NULL;
- task->pending = FALSE;
+ self->priv->pending = FALSE;
file_xfer_flush_finish(channel, res, &error);
- if (error || task->error) {
- file_xfer_completed(task, error);
+ if (error || self->priv->error) {
+ spice_file_transfer_task_completed(self, error);
return;
}
@@ -1779,19 +1829,19 @@ static void file_xfer_data_flushed_cb(GObject *source_object,
const GTimeSpan interval = 20 * G_TIME_SPAN_SECOND;
gint64 now = g_get_monotonic_time();
- if (interval < now - task->last_update) {
- gchar *basename = g_file_get_basename(task->file);
- task->last_update = now;
+ if (interval < now - self->priv->last_update) {
+ gchar *basename = g_file_get_basename(self->priv->file);
+ self->priv->last_update = now;
SPICE_DEBUG("transferred %.2f%% of the file %s",
- 100.0 * task->read_bytes / task->file_size, basename);
+ 100.0 * self->priv->read_bytes / self->priv->file_size, basename);
g_free(basename);
}
}
- if (task->progress_callback) {
+ if (self->priv->progress_callback) {
goffset read = 0;
goffset total = 0;
- SpiceMainChannel *main_channel = task->channel;
+ SpiceMainChannel *main_channel = self->priv->channel;
GHashTableIter iter;
gpointer key, value;
@@ -1800,28 +1850,28 @@ static void file_xfer_data_flushed_cb(GObject *source_object,
* current transfers */
g_hash_table_iter_init(&iter, main_channel->priv->file_xfer_tasks);
while (g_hash_table_iter_next(&iter, &key, &value)) {
- SpiceFileXferTask *t = (SpiceFileXferTask *)value;
- read += t->read_bytes;
- total += t->file_size;
+ SpiceFileTransferTask *t = (SpiceFileTransferTask *)value;
+ read += t->priv->read_bytes;
+ total += t->priv->file_size;
}
- task->progress_callback(read, total, task->progress_callback_data);
+ self->priv->progress_callback(read, total, self->priv->progress_callback_data);
}
/* Read more data */
- file_xfer_continue_read(task);
+ file_xfer_continue_read(self);
}
-static void file_xfer_queue(SpiceFileXferTask *task, int data_size)
+static void file_xfer_queue(SpiceFileTransferTask *self, int data_size)
{
VDAgentFileXferDataMessage msg;
- SpiceMainChannel *channel = SPICE_MAIN_CHANNEL(task->channel);
+ SpiceMainChannel *channel = SPICE_MAIN_CHANNEL(self->priv->channel);
- msg.id = task->id;
+ msg.id = self->priv->id;
msg.size = data_size;
agent_msg_queue_many(channel, VD_AGENT_FILE_XFER_DATA,
&msg, sizeof(msg),
- task->buffer, data_size, NULL);
+ self->priv->buffer, data_size, NULL);
spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);
}
@@ -1830,52 +1880,53 @@ static void file_xfer_read_cb(GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
- SpiceFileXferTask *task = user_data;
- SpiceMainChannel *channel = task->channel;
+ SpiceFileTransferTask *self = user_data;
+ SpiceMainChannel *channel = self->priv->channel;
gssize count;
GError *error = NULL;
- task->pending = FALSE;
- count = g_input_stream_read_finish(G_INPUT_STREAM(task->file_stream),
+ self->priv->pending = FALSE;
+ count = g_input_stream_read_finish(G_INPUT_STREAM(self->priv->file_stream),
res, &error);
/* Check for pending earlier errors */
- if (task->error) {
- file_xfer_completed(task, error);
+ if (self->priv->error) {
+ spice_file_transfer_task_completed(self, error);
return;
}
- if (count > 0 || task->file_size == 0) {
- task->read_bytes += count;
- file_xfer_queue(task, count);
+ if (count > 0 || self->priv->file_size == 0) {
+ self->priv->read_bytes += count;
+ g_object_notify(G_OBJECT(self), "progress");
+ file_xfer_queue(self, count);
if (count == 0)
return;
- file_xfer_flush_async(channel, task->cancellable,
- file_xfer_data_flushed_cb, task);
- task->pending = TRUE;
+ file_xfer_flush_async(channel, self->priv->cancellable,
+ file_xfer_data_flushed_cb, self);
+ self->priv->pending = TRUE;
} else if (error) {
VDAgentFileXferStatusMessage msg = {
- .id = task->id,
+ .id = self->priv->id,
.result = VD_AGENT_FILE_XFER_STATUS_ERROR,
};
- agent_msg_queue_many(task->channel, VD_AGENT_FILE_XFER_STATUS,
+ agent_msg_queue_many(self->priv->channel, VD_AGENT_FILE_XFER_STATUS,
&msg, sizeof(msg), NULL);
- spice_channel_wakeup(SPICE_CHANNEL(task->channel), FALSE);
- file_xfer_completed(task, error);
+ spice_channel_wakeup(SPICE_CHANNEL(self->priv->channel), FALSE);
+ spice_file_transfer_task_completed(self, error);
}
/* else EOF, do nothing (wait for VD_AGENT_FILE_XFER_STATUS from agent) */
}
/* coroutine context */
-static void file_xfer_continue_read(SpiceFileXferTask *task)
+static void file_xfer_continue_read(SpiceFileTransferTask *self)
{
- g_input_stream_read_async(G_INPUT_STREAM(task->file_stream),
- task->buffer,
+ g_input_stream_read_async(G_INPUT_STREAM(self->priv->file_stream),
+ self->priv->buffer,
FILE_XFER_CHUNK_SIZE,
G_PRIORITY_DEFAULT,
- task->cancellable,
+ self->priv->cancellable,
file_xfer_read_cb,
- task);
- task->pending = TRUE;
+ self);
+ self->priv->pending = TRUE;
}
/* coroutine context */
@@ -1883,10 +1934,9 @@ static void file_xfer_handle_status(SpiceMainChannel *channel,
VDAgentFileXferStatusMessage *msg)
{
SpiceMainChannelPrivate *c = channel->priv;
- SpiceFileXferTask *task;
+ SpiceFileTransferTask *task;
GError *error = NULL;
-
task = g_hash_table_lookup(c->file_xfer_tasks, GUINT_TO_POINTER(msg->id));
if (task == NULL) {
SPICE_DEBUG("cannot find task %d", msg->id);
@@ -1897,7 +1947,7 @@ static void file_xfer_handle_status(SpiceMainChannel *channel,
switch (msg->result) {
case VD_AGENT_FILE_XFER_STATUS_CAN_SEND_DATA:
- if (task->pending) {
+ if (task->priv->pending) {
error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
"transfer received CAN_SEND_DATA in pending state");
break;
@@ -1913,7 +1963,7 @@ static void file_xfer_handle_status(SpiceMainChannel *channel,
"some errors occurred in the spice agent");
break;
case VD_AGENT_FILE_XFER_STATUS_SUCCESS:
- if (task->pending)
+ if (task->priv->pending)
error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
"transfer received success in pending state");
break;
@@ -1924,7 +1974,7 @@ static void file_xfer_handle_status(SpiceMainChannel *channel,
break;
}
- file_xfer_completed(task, error);
+ spice_file_transfer_task_completed(task, error);
}
/* any context: the message is not flushed immediately,
@@ -2868,33 +2918,36 @@ void spice_main_set_display_enabled(SpiceMainChannel *channel, int id, gboolean
spice_main_update_display_enabled(channel, id, enabled, TRUE);
}
-static void file_xfer_completed(SpiceFileXferTask *task, GError *error)
+static void spice_file_transfer_task_completed(SpiceFileTransferTask *self,
+ GError *error)
{
/* In case of multiple errors we only report the first error */
- if (task->error)
+ if (self->priv->error)
g_clear_error(&error);
if (error) {
SPICE_DEBUG("File %s xfer failed: %s",
- g_file_get_path(task->file), error->message);
- task->error = error;
+ g_file_get_path(self->priv->file), error->message);
+ self->priv->error = error;
}
- if (task->pending)
+ if (self->priv->pending)
return;
- if (!task->file_stream) {
- file_xfer_close_cb(NULL, NULL, task);
+ if (!self->priv->file_stream) {
+ file_xfer_close_cb(NULL, NULL, self);
return;
}
- g_input_stream_close_async(G_INPUT_STREAM(task->file_stream),
+ g_input_stream_close_async(G_INPUT_STREAM(self->priv->file_stream),
G_PRIORITY_DEFAULT,
- task->cancellable,
+ self->priv->cancellable,
file_xfer_close_cb,
- task);
- task->pending = TRUE;
+ self);
+ self->priv->pending = TRUE;
+ g_signal_emit(self, task_signals[SIGNAL_FINISHED], 0, error);
}
+
static void file_xfer_info_async_cb(GObject *obj, GAsyncResult *res, gpointer data)
{
GFileInfo *info;
@@ -2905,15 +2958,16 @@ static void file_xfer_info_async_cb(GObject *obj, GAsyncResult *res, gpointer da
VDAgentFileXferStartMessage msg;
gsize /*msg_size*/ data_len;
gchar *string;
- SpiceFileXferTask *task = (SpiceFileXferTask *)data;
+ SpiceFileTransferTask *self = SPICE_FILE_TRANSFER_TASK(data);
- task->pending = FALSE;
+ self->priv->pending = FALSE;
info = g_file_query_info_finish(file, res, &error);
- if (error || task->error)
+ if (error || self->priv->error)
goto failed;
- task->file_size =
+ self->priv->file_size =
g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_STANDARD_SIZE);
+ g_object_notify(G_OBJECT(self), "progress");
keyfile = g_key_file_new();
/* File name */
@@ -2921,7 +2975,7 @@ static void file_xfer_info_async_cb(GObject *obj, GAsyncResult *res, gpointer da
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", self->priv->file_size);
/* Save keyfile content to memory. TODO: more file attributions
need to be sent to guest */
@@ -2931,41 +2985,45 @@ static void file_xfer_info_async_cb(GObject *obj, GAsyncResult *res, gpointer da
goto failed;
/* Create file-xfer start message */
- msg.id = task->id;
- agent_msg_queue_many(task->channel, VD_AGENT_FILE_XFER_START,
+ msg.id = self->priv->id;
+ agent_msg_queue_many(self->priv->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);
+ spice_channel_wakeup(SPICE_CHANNEL(self->priv->channel), FALSE);
return;
failed:
- file_xfer_completed(task, error);
+ spice_file_transfer_task_completed(self, error);
}
static void file_xfer_read_async_cb(GObject *obj, GAsyncResult *res, gpointer data)
{
GFile *file = G_FILE(obj);
- SpiceFileXferTask *task = (SpiceFileXferTask *)data;
+ SpiceFileTransferTask *self = SPICE_FILE_TRANSFER_TASK(data);
GError *error = NULL;
- task->pending = FALSE;
- task->file_stream = g_file_read_finish(file, res, &error);
- if (error || task->error) {
- file_xfer_completed(task, error);
+ self->priv->pending = FALSE;
+ self->priv->file_stream = g_file_read_finish(file, res, &error);
+ if (error || self->priv->error) {
+ spice_file_transfer_task_completed(self, error);
return;
}
- g_file_query_info_async(task->file,
+ g_file_query_info_async(self->priv->file,
G_FILE_ATTRIBUTE_STANDARD_SIZE,
G_FILE_QUERY_INFO_NONE,
G_PRIORITY_DEFAULT,
- task->cancellable,
+ self->priv->cancellable,
file_xfer_info_async_cb,
- task);
- task->pending = TRUE;
+ self);
+ self->priv->pending = TRUE;
}
+static SpiceFileTransferTask *spice_file_transfer_task_new(SpiceMainChannel *channel,
+ GFile *file,
+ GCancellable *cancellable);
+
static void file_xfer_send_start_msg_async(SpiceMainChannel *channel,
GFile **files,
GFileCopyFlags flags,
@@ -2976,39 +3034,41 @@ static void file_xfer_send_start_msg_async(SpiceMainChannel *channel,
gpointer user_data)
{
SpiceMainChannelPrivate *c = channel->priv;
- SpiceFileXferTask *task;
- static uint32_t xfer_id; /* Used to identify task id */
+ SpiceFileTransferTask *task;
gint i;
for (i = 0; files[i] != NULL && !g_cancellable_is_cancelled(cancellable); i++) {
- task = g_malloc0(sizeof(SpiceFileXferTask));
- task->id = ++xfer_id;
- task->channel = g_object_ref(channel);
- task->file = g_object_ref(files[i]);
- 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;
-
- if (spice_util_get_debug()) {
- gchar *basename = g_file_get_basename(task->file);
- task->start_time = g_get_monotonic_time();
- task->last_update = task->start_time;
-
- SPICE_DEBUG("transfer of file %s has started", basename);
- g_free(basename);
- }
- CHANNEL_DEBUG(task->channel, "Insert a xfer task:%d to task list", task->id);
- g_hash_table_insert(c->file_xfer_tasks, GUINT_TO_POINTER(task->id), task);
+ GCancellable *task_cancellable = cancellable;
+ /* if a cancellable object was not provided for the overall operation,
+ * create a separate object for each file so that they can be cancelled
+ * separately */
+ if (!task_cancellable)
+ task_cancellable = g_cancellable_new();
+
+ task = spice_file_transfer_task_new(channel, files[i], task_cancellable);
+ task->priv->flags = flags;
+ task->priv->progress_callback = progress_callback;
+ task->priv->progress_callback_data = progress_callback_data;
+ task->priv->callback = callback;
+ task->priv->user_data = user_data;
+
+ CHANNEL_DEBUG(channel, "Insert a xfer task:%d to task list",
+ task->priv->id);
+ g_hash_table_insert(c->file_xfer_tasks,
+ GUINT_TO_POINTER(task->priv->id),
+ task);
+ g_signal_emit(channel, signals[SPICE_MAIN_NEW_FILE_TRANSFER], 0, task);
g_file_read_async(files[i],
G_PRIORITY_DEFAULT,
cancellable,
file_xfer_read_async_cb,
task);
- task->pending = TRUE;
+ task->priv->pending = TRUE;
+
+ /* if we created a per-task cancellable above, free it */
+ if (!cancellable)
+ g_object_unref(task_cancellable);
}
}
@@ -3038,7 +3098,8 @@ static void file_xfer_send_start_msg_async(SpiceMainChannel *channel,
* was broken since it only provided status for a single file transfer, but did
* not provide a way to determine which file it referred to. In release 0.31,
* this behavior was changed so that progress_callback provides the status of
- * all ongoing file transfers.
+ * all ongoing file transfers. If you need to monitor the status of individual
+ * files, please connect to the #SpiceMainChannel::new-file-transfer signal.
*
* 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.
@@ -3108,3 +3169,252 @@ gboolean spice_main_file_copy_finish(SpiceMainChannel *channel,
return g_simple_async_result_get_op_res_gboolean(simple);
}
+
+
+
+static void
+spice_file_transfer_task_get_property(GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SpiceFileTransferTask *self = SPICE_FILE_TRANSFER_TASK(object);
+
+ switch (property_id)
+ {
+ case PROP_TASK_ID:
+ g_value_set_uint(value, self->priv->id);
+ break;
+ case PROP_TASK_FILE:
+ g_value_set_object(value, self->priv->file);
+ break;
+ case PROP_TASK_PROGRESS:
+ g_value_set_double(value, spice_file_transfer_task_get_progress(self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ }
+}
+
+static void
+spice_file_transfer_task_set_property(GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SpiceFileTransferTask *self = SPICE_FILE_TRANSFER_TASK(object);
+
+ switch (property_id)
+ {
+ case PROP_TASK_ID:
+ self->priv->id = g_value_get_uint(value);
+ break;
+ case PROP_TASK_FILE:
+ self->priv->file = g_value_dup_object(value);
+ break;
+ case PROP_TASK_CHANNEL:
+ self->priv->channel = g_value_dup_object(value);
+ break;
+ case PROP_TASK_CANCELLABLE:
+ self->priv->cancellable = g_value_dup_object(value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ }
+}
+
+static void
+spice_file_transfer_task_dispose(GObject *object)
+{
+ SpiceFileTransferTask *self = SPICE_FILE_TRANSFER_TASK(object);
+
+ g_clear_object(&self->priv->file);
+
+ G_OBJECT_CLASS(spice_file_transfer_task_parent_class)->dispose(object);
+}
+
+static void
+spice_file_transfer_task_finalize(GObject *object)
+{
+ SpiceFileTransferTask *self = SPICE_FILE_TRANSFER_TASK(object);
+
+ g_free(self->priv->buffer);
+
+ G_OBJECT_CLASS(spice_file_transfer_task_parent_class)->finalize(object);
+}
+
+static void
+spice_file_transfer_task_constructed(GObject *object)
+{
+ SpiceFileTransferTask *self = SPICE_FILE_TRANSFER_TASK(object);
+
+ if (spice_util_get_debug()) {
+ gchar *basename = g_file_get_basename(self->priv->file);
+ self->priv->start_time = g_get_monotonic_time();
+ self->priv->last_update = self->priv->start_time;
+
+ SPICE_DEBUG("transfer of file %s has started", basename);
+ g_free(basename);
+ }
+}
+
+static void
+spice_file_transfer_task_class_init(SpiceFileTransferTaskClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ g_type_class_add_private(klass, sizeof(SpiceFileTransferTaskPrivate));
+
+ object_class->get_property = spice_file_transfer_task_get_property;
+ object_class->set_property = spice_file_transfer_task_set_property;
+ object_class->finalize = spice_file_transfer_task_finalize;
+ object_class->dispose = spice_file_transfer_task_dispose;
+ object_class->constructed = spice_file_transfer_task_constructed;
+
+ /**
+ * SpiceFileTransferTask:id:
+ *
+ * The ID of the file transfer task
+ **/
+ g_object_class_install_property(object_class, PROP_TASK_ID,
+ g_param_spec_uint("id",
+ "id",
+ "The id of the task",
+ 0, G_MAXUINT, 0,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * SpiceFileTransferTask:channel:
+ *
+ * The main channel that owns the file transfer task
+ **/
+ g_object_class_install_property(object_class, PROP_TASK_CHANNEL,
+ g_param_spec_object("channel",
+ "channel",
+ "The channel transferring the file",
+ SPICE_TYPE_MAIN_CHANNEL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * SpiceFileTransferTask:cancellable:
+ *
+ * A cancellable object used to cancel the file transfer
+ **/
+ g_object_class_install_property(object_class, PROP_TASK_CANCELLABLE,
+ g_param_spec_object("cancellable",
+ "cancellable",
+ "The object used to cancel the task",
+ G_TYPE_CANCELLABLE,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * SpiceFileTransferTask:file:
+ *
+ * The file that is being transferred in this file transfer task
+ **/
+ g_object_class_install_property(object_class, PROP_TASK_FILE,
+ g_param_spec_object("file",
+ "File",
+ "The file being transferred",
+ G_TYPE_FILE,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * SpiceFileTransferTask:progress:
+ *
+ * The current state of the file transfer. This value indicates a
+ * percentage, and ranges from 0 to 100. Listen for change notifications on
+ * this property to be updated whenever the file transfer progress changes.
+ **/
+ g_object_class_install_property(object_class, PROP_TASK_PROGRESS,
+ g_param_spec_double("progress",
+ "Progress",
+ "The percentage of the file transferred",
+ 0.0, 100.0, 0.0,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * SpiceFileTransferTask::finished:
+ * @task: the file transfer task that emitted the signal
+ * @error: (transfer none): the error state of the transfer. Will be %NULL
+ * if the file transfer was successful.
+ *
+ * The #SpiceFileTransferTask::finished signal is emitted when the file
+ * transfer has completed transferring to the guest.
+ **/
+ task_signals[SIGNAL_FINISHED] = g_signal_new("finished", SPICE_TYPE_FILE_TRANSFER_TASK,
+ G_SIGNAL_RUN_FIRST,
+ 0, NULL, NULL,
+ g_cclosure_marshal_VOID__BOXED,
+ G_TYPE_NONE, 1,
+ G_TYPE_ERROR);
+}
+
+static void
+spice_file_transfer_task_init(SpiceFileTransferTask *self)
+{
+ self->priv = FILE_TRANSFER_TASK_PRIVATE(self);
+ self->priv->buffer = g_malloc0(FILE_XFER_CHUNK_SIZE);
+}
+
+SpiceFileTransferTask *
+spice_file_transfer_task_new(SpiceMainChannel *channel, GFile *file, GCancellable *cancellable)
+{
+ static uint32_t xfer_id = 0; /* Used to identify task id */
+
+ return g_object_new(SPICE_TYPE_FILE_TRANSFER_TASK,
+ "id", xfer_id++,
+ "file", file,
+ "channel", channel,
+ "cancellable", cancellable,
+ NULL);
+}
+
+/**
+ * spice_file_transfer_task_get_progress:
+ * @self: a file transfer task
+ *
+ * Convenience function for retrieving the current progress of this file
+ * transfer task.
+ *
+ * Returns: A percentage value between 0 and 100
+ **/
+double spice_file_transfer_task_get_progress(SpiceFileTransferTask *self)
+{
+ if (self->priv->file_size == 0)
+ return 0.0;
+
+ return (double)self->priv->read_bytes / self->priv->file_size;
+}
+
+/**
+ * spice_file_transfer_task_cancel:
+ * @self: a file transfer task
+ *
+ * Cancels the file transfer task. Note that depending on how the file transfer
+ * was initiated, multiple file transfer tasks may share a single
+ * #SpiceFileTransferTask::cancellable object, so canceling one task may result
+ * in the cancellation of other tasks.
+ **/
+void spice_file_transfer_task_cancel(SpiceFileTransferTask *self)
+{
+ g_cancellable_cancel(self->priv->cancellable);
+}
+
+/**
+ * spice_file_transfer_task_get_filename:
+ * @self: a file transfer task
+ *
+ * Gets the name of the file being transferred in this task
+ *
+ * Returns: (transfer none): The basename of the file
+ **/
+char* spice_file_transfer_task_get_filename(SpiceFileTransferTask *self)
+{
+ return g_file_get_basename(self->priv->file);
+}
diff --git a/src/channel-main.h b/src/channel-main.h
index 86bb46b..332a71b 100644
--- a/src/channel-main.h
+++ b/src/channel-main.h
@@ -18,7 +18,8 @@
#ifndef __SPICE_CLIENT_MAIN_CHANNEL_H__
#define __SPICE_CLIENT_MAIN_CHANNEL_H__
-#include "spice-client.h"
+#include "spice-channel.h"
+#include "spice-file-transfer-task.h"
G_BEGIN_DECLS
diff --git a/src/map-file b/src/map-file
index a9abc61..92a9883 100644
--- a/src/map-file
+++ b/src/map-file
@@ -33,6 +33,11 @@ spice_display_new_with_monitor;
spice_display_paste_from_guest;
spice_display_send_keys;
spice_display_set_grab_keys;
+spice_file_transfer_task_cancel;
+spice_file_transfer_task_get_filename;
+spice_file_transfer_task_get_finished;
+spice_file_transfer_task_get_progress;
+spice_file_transfer_task_get_type;
spice_get_option_group;
spice_grab_sequence_as_string;
spice_grab_sequence_copy;
diff --git a/src/spice-client.h b/src/spice-client.h
index 5a4d838..1891867 100644
--- a/src/spice-client.h
+++ b/src/spice-client.h
@@ -48,6 +48,7 @@
#include "smartcard-manager.h"
#include "usb-device-manager.h"
#include "spice-audio.h"
+#include "spice-file-transfer-task.h"
G_BEGIN_DECLS
diff --git a/src/spice-file-transfer-task.h b/src/spice-file-transfer-task.h
new file mode 100644
index 0000000..b97d107
--- /dev/null
+++ b/src/spice-file-transfer-task.h
@@ -0,0 +1,68 @@
+/*
+ Copyright (C) 2010-2015 Red Hat, Inc.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __SPICE_FILE_TRANSFER_TASK_H__
+#define __SPICE_FILE_TRANSFER_TASK_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_FILE_TRANSFER_TASK spice_file_transfer_task_get_type()
+
+#define SPICE_FILE_TRANSFER_TASK(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_FILE_TRANSFER_TASK, SpiceFileTransferTask))
+#define SPICE_FILE_TRANSFER_TASK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_FILE_TRANSFER_TASK, SpiceFileTransferTaskClass))
+#define SPICE_IS_FILE_TRANSFER_TASK(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_FILE_TRANSFER_TASK))
+#define SPICE_IS_FILE_TRANSFER_TASK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_FILE_TRANSFER_TASK))
+#define SPICE_FILE_TRANSFER_TASK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_FILE_TRANSFER_TASK, SpiceFileTransferTaskClass))
+
+typedef struct _SpiceFileTransferTask SpiceFileTransferTask;
+typedef struct _SpiceFileTransferTaskClass SpiceFileTransferTaskClass;
+typedef struct _SpiceFileTransferTaskPrivate SpiceFileTransferTaskPrivate;
+
+/**
+ * SpiceFileTransferTask:
+ *
+ * The #FileTransferTask struct is opaque and should not be accessed directly.
+ */
+struct _SpiceFileTransferTask
+{
+ GObject parent;
+
+ SpiceFileTransferTaskPrivate *priv;
+};
+
+/**
+ * SpiceFileTransferTaskClass:
+ * @parent_class: Parent class.
+ *
+ * Class structure for #SpiceFileTransferTask.
+ */
+struct _SpiceFileTransferTaskClass
+{
+ GObjectClass parent_class;
+};
+
+GType spice_file_transfer_task_get_type(void) G_GNUC_CONST;
+
+char* spice_file_transfer_task_get_filename(SpiceFileTransferTask *self);
+void spice_file_transfer_task_cancel(SpiceFileTransferTask *self);
+double spice_file_transfer_task_get_progress(SpiceFileTransferTask *self);
+
+G_END_DECLS
+
+#endif /* __SPICE_FILE_TRANSFER_TASK_H__ */
diff --git a/src/spice-glib-sym-file b/src/spice-glib-sym-file
index 1d62716..3817a46 100644
--- a/src/spice-glib-sym-file
+++ b/src/spice-glib-sym-file
@@ -20,6 +20,11 @@ spice_client_error_quark
spice_cursor_channel_get_type
spice_display_channel_get_type
spice_display_get_primary
+spice_file_transfer_task_cancel
+spice_file_transfer_task_get_filename
+spice_file_transfer_task_get_finished
+spice_file_transfer_task_get_progress
+spice_file_transfer_task_get_type
spice_get_option_group
spice_g_signal_connect_object
spice_inputs_button_press
diff --git a/src/spicy.c b/src/spicy.c
index f48a220..4de56d9 100644
--- a/src/spicy.c
+++ b/src/spicy.c
@@ -91,6 +91,7 @@ G_DEFINE_TYPE (SpiceWindow, spice_window, G_TYPE_OBJECT);
#define CHANNELID_MAX 4
#define MONITORID_MAX 4
+
// FIXME: turn this into an object, get rid of fixed wins array, use
// signals to replace the various callback that iterate over wins array
struct spice_connection {
@@ -104,6 +105,10 @@ struct spice_connection {
gboolean agent_connected;
int channels;
int disconnecting;
+
+ /* key: SpiceFileTransferTask, value: TransferTaskWidgets */
+ GHashTable *transfers;
+ GtkWidget *transfer_dialog;
};
static spice_connection *connection_new(void);
@@ -1386,6 +1391,148 @@ static void port_data(SpicePortChannel *port,
}
}
+typedef struct {
+ GtkWidget *vbox;
+ GtkWidget *hbox;
+ GtkWidget *progress;
+ GtkWidget *label;
+ GtkWidget *cancel;
+} TransferTaskWidgets;
+
+static void transfer_update_progress(GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ spice_connection *conn = user_data;
+ TransferTaskWidgets *widgets = g_hash_table_lookup(conn->transfers, object);
+ g_return_if_fail(widgets);
+ gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(widgets->progress),
+ spice_file_transfer_task_get_progress(SPICE_FILE_TRANSFER_TASK(object)));
+}
+
+static void transfer_task_finished(SpiceFileTransferTask *task, GError *error, spice_connection *conn)
+{
+ if (error)
+ g_warning("%s", error->message);
+ g_hash_table_remove(conn->transfers, task);
+ if (!g_hash_table_size(conn->transfers))
+ gtk_widget_hide(conn->transfer_dialog);
+}
+
+static void dialog_response_cb(GtkDialog *dialog,
+ gint response_id,
+ gpointer user_data)
+{
+ spice_connection *conn = user_data;
+ g_print("Reponse: %i\n", response_id);
+
+ if (response_id == GTK_RESPONSE_CANCEL) {
+ GHashTableIter iter;
+ gpointer key, value;
+
+ g_hash_table_iter_init(&iter, conn->transfers);
+ while (g_hash_table_iter_next(&iter, &key, &value)) {
+ SpiceFileTransferTask *task = key;
+ spice_file_transfer_task_cancel(task);
+ }
+ }
+}
+
+void task_cancel_cb(GtkButton *button,
+ gpointer user_data)
+{
+ SpiceFileTransferTask *task = SPICE_FILE_TRANSFER_TASK(user_data);
+ spice_file_transfer_task_cancel(task);
+}
+
+TransferTaskWidgets *transfer_task_widgets_new(SpiceFileTransferTask *task)
+{
+ TransferTaskWidgets *widgets = g_new0(TransferTaskWidgets, 1);
+
+#if GTK_CHECK_VERSION(3,0,0)
+ widgets->vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+ widgets->hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
+ widgets->cancel = gtk_button_new_from_icon_name(GTK_STOCK_CANCEL,
+ GTK_ICON_SIZE_SMALL_TOOLBAR);
+#else
+ widgets->vbox = gtk_vbox_new(FALSE, 0);
+ widgets->hbox = gtk_hbox_new(FALSE, 6);
+ widgets->cancel = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
+#endif
+
+ widgets->progress = gtk_progress_bar_new();
+ widgets->label = gtk_label_new(spice_file_transfer_task_get_filename(task));
+
+#if GTK_CHECK_VERSION(3,0,0)
+ gtk_widget_set_halign(widgets->label, GTK_ALIGN_START);
+ gtk_widget_set_valign(widgets->label, GTK_ALIGN_BASELINE);
+ gtk_widget_set_valign(widgets->progress, GTK_ALIGN_CENTER);
+ gtk_widget_set_hexpand(widgets->progress, TRUE);
+ gtk_widget_set_valign(widgets->cancel, GTK_ALIGN_CENTER);
+ gtk_widget_set_hexpand(widgets->progress, FALSE);
+#endif
+
+ gtk_box_pack_start(GTK_BOX(widgets->hbox), widgets->progress,
+ TRUE, TRUE, 0);
+ gtk_box_pack_start(GTK_BOX(widgets->hbox), widgets->cancel,
+ FALSE, TRUE, 0);
+
+ gtk_box_pack_start(GTK_BOX(widgets->vbox), widgets->label,
+ TRUE, TRUE, 0);
+ gtk_box_pack_start(GTK_BOX(widgets->vbox), widgets->hbox,
+ TRUE, TRUE, 0);
+
+ g_signal_connect(widgets->cancel, "clicked",
+ G_CALLBACK(task_cancel_cb), task);
+
+ gtk_widget_show_all(widgets->vbox);
+
+ return widgets;
+}
+
+void transfer_task_widgets_free(TransferTaskWidgets *widgets)
+{
+ /* child widgets will be destroyed automatically */
+ gtk_widget_destroy(widgets->vbox);
+ g_free(widgets);
+}
+
+static void spice_connection_add_task(spice_connection *conn, SpiceFileTransferTask *task)
+{
+ TransferTaskWidgets *widgets;
+ GtkWidget *content = NULL;
+
+ g_signal_connect(task, "notify::progress",
+ G_CALLBACK(transfer_update_progress), conn);
+ g_signal_connect(task, "finished",
+ G_CALLBACK(transfer_task_finished), conn);
+ if (!conn->transfer_dialog) {
+ conn->transfer_dialog = gtk_dialog_new_with_buttons("File Transfers",
+ GTK_WINDOW(conn->wins[0]->toplevel), 0,
+ "Cancel", GTK_RESPONSE_CANCEL, NULL);
+ gtk_dialog_set_default_response(GTK_DIALOG(conn->transfer_dialog),
+ GTK_RESPONSE_CANCEL);
+ gtk_window_set_resizable(GTK_WINDOW(conn->transfer_dialog), FALSE);
+ g_signal_connect(conn->transfer_dialog, "response",
+ G_CALLBACK(dialog_response_cb), conn);
+ }
+ gtk_widget_show(conn->transfer_dialog);
+ content = gtk_dialog_get_content_area(GTK_DIALOG(conn->transfer_dialog));
+ gtk_container_set_border_width(GTK_CONTAINER(content), 12);
+
+ widgets = transfer_task_widgets_new(task);
+ g_hash_table_insert(conn->transfers, g_object_ref(task), widgets);
+ gtk_box_pack_start(GTK_BOX(content),
+ widgets->vbox, TRUE, TRUE, 6);
+}
+
+static void new_file_transfer(SpiceMainChannel *main, SpiceFileTransferTask *task, gpointer user_data)
+{
+ spice_connection *conn = user_data;
+ g_debug("new file transfer task");
+ spice_connection_add_task(conn, task);
+}
+
static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data)
{
spice_connection *conn = data;
@@ -1404,6 +1551,8 @@ static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data)
G_CALLBACK(main_mouse_update), conn);
g_signal_connect(channel, "main-agent-update",
G_CALLBACK(main_agent_update), conn);
+ g_signal_connect(channel, "new-file-transfer",
+ G_CALLBACK(new_file_transfer), conn);
main_mouse_update(channel, conn);
main_agent_update(channel, conn);
}
@@ -1515,6 +1664,9 @@ static spice_connection *connection_new(void)
G_CALLBACK(usb_connect_failed), NULL);
}
+ conn->transfers = g_hash_table_new_full(g_direct_hash, g_direct_equal,
+ g_object_unref,
+ (GDestroyNotify)transfer_task_widgets_free);
connections++;
SPICE_DEBUG("%s (%d)", __FUNCTION__, connections);
return conn;
--
2.1.0
More information about the Spice-devel
mailing list