[Spice-devel] [PATCH spice-gtk v3 2/2] New file transfer API
Jonathon Jongsma
jjongsma at redhat.com
Fri Oct 9 13:45:27 PDT 2015
Thanks, pushed
Jonathon
On Fri, 2015-10-09 at 17:48 +0200, Pavel Grunt wrote:
> Hi Jonathon,
>
> I tested the series, it behaves much more reasonable.
> One comment below.
>
> Thanks and ack from me.
>
> On Tue, 2015-10-06 at 14:05 -0500, Jonathon Jongsma wrote:
> > 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_CHANN
> > EL,
> > + G_PARAM_CONSTRUCT_ONL
> > Y | G_PARAM_READWRITE |
> > + G_PARAM_STATIC_STRING
> > S));
> > +
> > + /**
> > + * 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_ONL
> > Y | G_PARAM_READWRITE |
> > + G_PARAM_STATIC_STRING
> > S));
> > +
> > + /**
> > + * 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_ONL
> > Y | G_PARAM_READWRITE |
> > + G_PARAM_STATIC_STRING
> > S));
> > +
> > + /**
> > + * 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_STRING
> > S));
> > +
> > + /**
> > + * 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"
> >
> It is not needed to change the "channel-main.h", "spice-client.h" includes
> both "spice-channel.h" and "spice-file-transfer-task.h"
>
> Pavel
>
> > 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_TOOLB
> > AR);
> > +#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_wid
> > gets_free);
> > connections++;
> > SPICE_DEBUG("%s (%d)", __FUNCTION__, connections);
> > return conn;
More information about the Spice-devel
mailing list