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; _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx http://lists.freedesktop.org/mailman/listinfo/spice-devel