Hi, On Mon, Jun 27, 2016 at 03:57:10PM -0500, Jonathon Jongsma wrote: > On Thu, 2016-06-23 at 19:37 +0200, Victor Toso wrote: > > This patch moves: > > * GObject boilerplate > > * External API related to SpiceFileTransferTask > > * Internal API needed by channel-main > > * Helpers that belong to this object > > --- > > src/Makefile.am | 2 + > > src/channel-main.c | 696 +---------------------------------- > > src/spice-file-transfer-task-priv.h | 59 +++ > > src/spice-file-transfer-task.c | 713 > > ++++++++++++++++++++++++++++++++++++ > > 4 files changed, 776 insertions(+), 694 deletions(-) > > create mode 100644 src/spice-file-transfer-task-priv.h > > create mode 100644 src/spice-file-transfer-task.c > > > > diff --git a/src/Makefile.am b/src/Makefile.am > > index 6fb8507..779d655 100644 > > --- a/src/Makefile.am > > +++ b/src/Makefile.am > > @@ -233,6 +233,8 @@ libspice_client_glib_2_0_la_SOURCES = > > \ > > spice-channel.c \ > > spice-channel-cache.h \ > > spice-channel-priv.h \ > > + spice-file-transfer-task.c \ > > + spice-file-transfer-task-priv.h \ > > coroutine.h \ > > gio-coroutine.c \ > > gio-coroutine.h \ > > Looks like there's some alignment issues here. Indeed, I'll fix it > > > > diff --git a/src/channel-main.c b/src/channel-main.c > > index 190a366..a0b2748 100644 > > --- a/src/channel-main.c > > +++ b/src/channel-main.c > > @@ -29,7 +29,7 @@ > > #include "spice-channel-priv.h" > > #include "spice-session-priv.h" > > #include "spice-audio-priv.h" > > -#include "spice-file-transfer-task.h" > > +#include "spice-file-transfer-task-priv.h" > > > > /** > > * SECTION:channel-main > > @@ -54,94 +54,6 @@ > > > > typedef struct spice_migrate spice_migrate; > > > > -static guint32 spice_file_transfer_task_get_id(SpiceFileTransferTask *self); > > -static SpiceMainChannel > > *spice_file_transfer_task_get_channel(SpiceFileTransferTask *self); > > -static GCancellable > > *spice_file_transfer_task_get_cancellable(SpiceFileTransferTask *self); > > -static GHashTable *spice_file_transfer_task_create_tasks(GFile **files, > > - SpiceMainChannel > > *channel, > > - GFileCopyFlags > > flags, > > - GCancellable > > *cancellable); > > -static void spice_file_transfer_task_init_task_async(SpiceFileTransferTask > > *self, > > - GAsyncReadyCallback > > callback, > > - gpointer userdata); > > -static GFileInfo > > *spice_file_transfer_task_init_task_finish(SpiceFileTransferTask *xfer_task, > > - GAsyncResult > > *result, > > - GError **error); > > -static void spice_file_transfer_task_read_async(SpiceFileTransferTask *self, > > - GAsyncReadyCallback callback, > > - gpointer userdata); > > -static gssize spice_file_transfer_task_read_finish(SpiceFileTransferTask > > *self, > > - GAsyncResult *result, > > - char **buffer, > > - GError **error); > > -static guint64 spice_file_transfer_task_get_file_size(SpiceFileTransferTask > > *self); > > -static guint64 spice_file_transfer_task_get_bytes_read(SpiceFileTransferTask > > *self); > > -static void spice_file_transfer_task_debug_info(SpiceFileTransferTask *self); > > - > > -/** > > - * 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 > > - */ > > - > > -struct _SpiceFileTransferTask > > -{ > > - GObject parent; > > - > > - uint32_t id; > > - gboolean pending; > > - GFile *file; > > - SpiceMainChannel *channel; > > - GFileInputStream *file_stream; > > - GFileCopyFlags flags; > > - GCancellable *cancellable; > > - GAsyncReadyCallback callback; > > - gpointer user_data; > > - char *buffer; > > - uint64_t read_bytes; > > - GFileInfo *file_info; > > - uint64_t file_size; > > - gint64 start_time; > > - gint64 last_update; > > - GError *error; > > -}; > > - > > -struct _SpiceFileTransferTaskClass > > -{ > > - GObjectClass parent_class; > > -}; > > - > > -G_DEFINE_TYPE(SpiceFileTransferTask, spice_file_transfer_task, G_TYPE_OBJECT) > > - > > -#define FILE_XFER_CHUNK_SIZE (VD_AGENT_MAX_DATA_SIZE * 32) > > - > > -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, > > DISPLAY_DISABLED, > > @@ -267,7 +179,6 @@ 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 spice_file_transfer_task_completed(SpiceFileTransferTask *self, > > GError *error); > > static void file_xfer_flushed(SpiceMainChannel *channel, gboolean success); > > static void file_xfer_read_async_cb(GObject *source_object, > > GAsyncResult *res, > > @@ -1840,44 +1751,6 @@ static void main_handle_agent_disconnected(SpiceChannel > > *channel, SpiceMsgIn *in > > agent_stopped(SPICE_MAIN_CHANNEL(channel)); > > } > > > > -/* main context */ > > -static void spice_file_transfer_task_close_stream_cb(GObject *object, > > - GAsyncResult *close_res, > > - gpointer user_data) > > -{ > > - SpiceFileTransferTask *self; > > - GError *error = NULL; > > - > > - self = user_data; > > - > > - if (object) { > > - GInputStream *stream = G_INPUT_STREAM(object); > > - g_input_stream_close_finish(stream, close_res, &error); > > - if (error) { > > - /* This error dont need to report to user, just print a log */ > > - SPICE_DEBUG("close file error: %s", error->message); > > - g_clear_error(&error); > > - } > > - } > > - > > - if (self->error == NULL && spice_util_get_debug()) { > > - gint64 now = g_get_monotonic_time(); > > - gchar *basename = g_file_get_basename(self->file); > > - double seconds = (double) (now - self->start_time) / > > G_TIME_SPAN_SECOND; > > - gchar *file_size_str = g_format_size(self->file_size); > > - gchar *transfer_speed_str = g_format_size(self->file_size / seconds); > > - > > - g_warn_if_fail(self->read_bytes == self->file_size); > > - SPICE_DEBUG("transferred file %s of %s size in %.1f seconds (%s/s)", > > - basename, file_size_str, seconds, transfer_speed_str); > > - > > - g_free(basename); > > - g_free(file_size_str); > > - g_free(transfer_speed_str); > > - } > > - g_object_unref(self); > > -} > > - > > static void file_xfer_data_flushed_cb(GObject *source_object, > > GAsyncResult *res, > > gpointer user_data) > > @@ -1990,6 +1863,7 @@ static void file_xfer_handle_status(SpiceMainChannel > > *channel, > > spice_file_transfer_task_completed(xfer_task, error); > > } > > > > + > > /* any context: the message is not flushed immediately, > > you can wakeup() the channel coroutine or send_msg_queue() */ > > static void agent_max_clipboard(SpiceMainChannel *self) > > @@ -2933,38 +2807,6 @@ void spice_main_set_display_enabled(SpiceMainChannel > > *channel, int id, gboolean > > spice_main_update_display_enabled(channel, id, enabled, TRUE); > > } > > > > -static void spice_file_transfer_task_completed(SpiceFileTransferTask *self, > > - GError *error) > > -{ > > - /* In case of multiple errors we only report the first error */ > > - if (self->error) > > - g_clear_error(&error); > > - if (error) { > > - gchar *path = g_file_get_path(self->file); > > - SPICE_DEBUG("File %s xfer failed: %s", > > - path, error->message); > > - g_free(path); > > - self->error = error; > > - } > > - > > - if (self->pending) > > - return; > > - > > - if (!self->file_stream) { > > - spice_file_transfer_task_close_stream_cb(NULL, NULL, self); > > - goto signal; > > - } > > - > > - g_input_stream_close_async(G_INPUT_STREAM(self->file_stream), > > - G_PRIORITY_DEFAULT, > > - self->cancellable, > > - spice_file_transfer_task_close_stream_cb, > > - self); > > - self->pending = TRUE; > > -signal: > > - g_signal_emit(self, task_signals[SIGNAL_FINISHED], 0, self->error); > > -} > > - > > > > static void file_xfer_init_task_async_cb(GObject *obj, GAsyncResult *res, > > gpointer data) > > { > > @@ -3146,10 +2988,6 @@ static void > > file_transfer_operation_send_progress(SpiceFileTransferTask *xfer_ta > > xfer_op->progress_callback_data); > > } > > > > -static SpiceFileTransferTask *spice_file_transfer_task_new(SpiceMainChannel > > *channel, > > - GFile *file, > > - GCancellable > > *cancellable); > > - > > /** > > * spice_main_file_copy_async: > > * @channel: a #SpiceMainChannel > > @@ -3269,533 +3107,3 @@ gboolean spice_main_file_copy_finish(SpiceMainChannel > > *channel, > > > > return g_task_propagate_boolean(task, error); > > } > > - > > -static guint32 spice_file_transfer_task_get_id(SpiceFileTransferTask *self) > > -{ > > - g_return_val_if_fail(self != NULL, 0); > > - return self->id; > > -} > > - > > -static SpiceMainChannel > > *spice_file_transfer_task_get_channel(SpiceFileTransferTask *self) > > -{ > > - g_return_val_if_fail(self != NULL, NULL); > > - return self->channel; > > -} > > - > > -static GCancellable > > *spice_file_transfer_task_get_cancellable(SpiceFileTransferTask *self) > > -{ > > - g_return_val_if_fail(self != NULL, NULL); > > - return self->cancellable; > > -} > > - > > -static guint64 spice_file_transfer_task_get_file_size(SpiceFileTransferTask > > *self) > > -{ > > - g_return_val_if_fail(self != NULL, 0); > > - return self->file_size; > > -} > > - > > -static guint64 spice_file_transfer_task_get_bytes_read(SpiceFileTransferTask > > *self) > > -{ > > - g_return_val_if_fail(self != NULL, 0); > > - return self->read_bytes; > > -} > > - > > -/* Helper function which only creates a SpiceFileTransferTask per GFile > > - * in @files and returns a HashTable mapping task-id to the task itself > > - * Note that the HashTable does not free its values uppon destruction: > > - * The reference created here should be freed by > > - * spice_file_transfer_task_completed */ > > -static GHashTable *spice_file_transfer_task_create_tasks(GFile **files, > > - SpiceMainChannel > > *channel, > > - GFileCopyFlags > > flags, > > - GCancellable > > *cancellable) > > -{ > > - GHashTable *xfer_ht; > > - gint i; > > - > > - g_return_val_if_fail(files != NULL && files[0] != NULL, NULL); > > - > > - xfer_ht = g_hash_table_new(g_direct_hash, g_direct_equal); > > - for (i = 0; files[i] != NULL && !g_cancellable_is_cancelled(cancellable); > > i++) { > > - SpiceFileTransferTask *xfer_task; > > - guint32 task_id; > > - 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(); > > - > > - xfer_task = spice_file_transfer_task_new(channel, files[i], > > task_cancellable); > > - xfer_task->flags = flags; > > - > > - task_id = spice_file_transfer_task_get_id(xfer_task); > > - g_hash_table_insert(xfer_ht, GUINT_TO_POINTER(task_id), xfer_task); > > - > > - /* if we created a per-task cancellable above, free it */ > > - if (!cancellable) > > - g_object_unref(task_cancellable); > > - } > > - return xfer_ht; > > -} > > - > > -static void spice_file_transfer_task_query_info_cb(GObject *obj, > > - GAsyncResult *res, > > - gpointer user_data) > > -{ > > - SpiceFileTransferTask *self; > > - GFileInfo *info; > > - GTask *task; > > - GError *error = NULL; > > - > > - task = G_TASK(user_data); > > - self = g_task_get_source_object(task); > > - > > - g_return_if_fail(self->pending == TRUE); > > - self->pending = FALSE; > > - > > - info = g_file_query_info_finish(G_FILE(obj), res, &error); > > - if (error || self->error) { > > - error = (error == NULL) ? self->error : error; > > - g_task_return_error(task, error); > > - return; > > - } > > - > > - self->file_info = info; > > - self->file_size = > > - g_file_info_get_attribute_uint64(info, > > G_FILE_ATTRIBUTE_STANDARD_SIZE); > > - > > - /* SpiceFileTransferTask's init is done, handshake for file-trasfer will > > - * start soon. First "progress" can be emitted ~ 0% */ > > - g_object_notify(G_OBJECT(self), "progress"); > > - > > - g_task_return_boolean(task, TRUE); > > -} > > - > > -static void spice_file_transfer_task_read_file_cb(GObject *obj, > > - GAsyncResult *res, > > - gpointer user_data) > > -{ > > - SpiceFileTransferTask *self; > > - GTask *task; > > - GError *error = NULL; > > - > > - task = G_TASK(user_data); > > - self = g_task_get_source_object(task); > > - > > - g_return_if_fail(self->pending == TRUE); > > - > > - self->file_stream = g_file_read_finish(G_FILE(obj), res, &error); > > - if (error || self->error) { > > - self->pending = FALSE; > > - error = (error == NULL) ? self->error : error; > > - g_task_return_error(task, error); > > - return; > > - } > > - > > - g_file_query_info_async(self->file, > > - "standard::*", > > - G_FILE_QUERY_INFO_NONE, > > - G_PRIORITY_DEFAULT, > > - self->cancellable, > > - spice_file_transfer_task_query_info_cb, > > - task); > > -} > > - > > -static void spice_file_transfer_task_init_task_async(SpiceFileTransferTask > > *self, > > - GAsyncReadyCallback > > callback, > > - gpointer userdata) > > -{ > > - GTask *task; > > - > > - g_return_if_fail(self != NULL); > > - g_return_if_fail(self->pending == FALSE); > > - > > - task = g_task_new(self, self->cancellable, callback, userdata); > > - > > - self->pending = TRUE; > > - g_file_read_async(self->file, > > - G_PRIORITY_DEFAULT, > > - self->cancellable, > > - spice_file_transfer_task_read_file_cb, > > - task); > > -} > > - > > -static GFileInfo > > *spice_file_transfer_task_init_task_finish(SpiceFileTransferTask *self, > > - GAsyncResult > > *result, > > - GError **error) > > -{ > > - GTask *task = G_TASK(result); > > - > > - g_return_val_if_fail(self != NULL, NULL); > > - > > - if (g_task_propagate_boolean(task, error)) > > - return self->file_info; > > - > > - return NULL; > > -} > > - > > -static void spice_file_transfer_task_read_stream_cb(GObject *source_object, > > - GAsyncResult *res, > > - gpointer userdata) > > -{ > > - SpiceFileTransferTask *self; > > - GTask *task; > > - gssize nbytes; > > - GError *error = NULL; > > - > > - task = G_TASK(userdata); > > - self = g_task_get_source_object(task); > > - > > - g_return_if_fail(self->pending == TRUE); > > - self->pending = FALSE; > > - > > - nbytes = g_input_stream_read_finish(G_INPUT_STREAM(self->file_stream), > > res, &error); > > - if (error || self->error) { > > - error = (error == NULL) ? self->error : error; > > - g_task_return_error(task, error); > > - return; > > - } > > - > > - /* The progress here means the amount of data we have _read_ and not what > > - * was actually sent to the agent. On the next "progress", the previous > > data > > - * read was sent. This means that when user see 100%, we are sending the > > - * last chunk to the guest */ > > - self->read_bytes += nbytes; > > - g_object_notify(G_OBJECT(self), "progress"); > > - > > - g_task_return_int(task, nbytes); > > -} > > - > > -/* Any context */ > > -static void spice_file_transfer_task_read_async(SpiceFileTransferTask *self, > > - GAsyncReadyCallback callback, > > - gpointer userdata) > > -{ > > - GTask *task; > > - > > - g_return_if_fail(self != NULL); > > - if (self->pending) { > > - g_task_report_new_error(self, callback, userdata, > > - spice_file_transfer_task_read_async, > > - SPICE_CLIENT_ERROR, > > - SPICE_CLIENT_ERROR_FAILED, > > - "Cannot read data in pending state"); > > - return; > > - } > > - > > - task = g_task_new(self, self->cancellable, callback, userdata); > > - > > - if (self->read_bytes == self->file_size) { > > - /* channel-main might can request data after reading the whole file > > as > > - * it expects EOF. Let's return immediately its request as we don't > > want > > - * to reach a state where agent says file-transfer SUCCEED but we are > > in > > - * a PENDING state in SpiceFileTransferTask due reading in idle */ > > - g_task_return_int(task, 0); > > - return; > > - } > > - > > - self->pending = TRUE; > > - g_input_stream_read_async(G_INPUT_STREAM(self->file_stream), > > - self->buffer, > > - FILE_XFER_CHUNK_SIZE, > > - G_PRIORITY_DEFAULT, > > - self->cancellable, > > - spice_file_transfer_task_read_stream_cb, > > - task); > > -} > > - > > -static gssize spice_file_transfer_task_read_finish(SpiceFileTransferTask > > *self, > > - GAsyncResult *result, > > - char **buffer, > > - GError **error) > > -{ > > - gssize nbytes; > > - GTask *task = G_TASK(result); > > - > > - g_return_val_if_fail(self != NULL, -1); > > - > > - nbytes = g_task_propagate_int(task, error); > > - if (nbytes >= 0 && buffer != NULL) > > - *buffer = self->buffer; > > - > > - return nbytes; > > -} > > - > > -static void spice_file_transfer_task_debug_info(SpiceFileTransferTask *self) > > -{ > > - const GTimeSpan interval = 20 * G_TIME_SPAN_SECOND; > > - gint64 now = g_get_monotonic_time(); > > - > > - if (interval < now - self->last_update) { > > - gchar *basename = g_file_get_basename(self->file); > > - self->last_update = now; > > - SPICE_DEBUG("transferred %.2f%% of the file %s", > > - 100.0 * self->read_bytes / self->file_size, basename); > > - g_free(basename); > > - } > > -} > > - > > -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->id); > > - break; > > - case PROP_TASK_FILE: > > - g_value_set_object(value, self->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->id = g_value_get_uint(value); > > - break; > > - case PROP_TASK_FILE: > > - self->file = g_value_dup_object(value); > > - break; > > - case PROP_TASK_CHANNEL: > > - self->channel = g_value_dup_object(value); > > - break; > > - case PROP_TASK_CANCELLABLE: > > - self->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->file); > > - g_clear_object(&self->file_info); > > - > > - 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->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->file); > > - self->start_time = g_get_monotonic_time(); > > - self->last_update = self->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); > > - > > - 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 > > - * > > - * Since: 0.31 > > - **/ > > - 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 > > - * > > - * Since: 0.31 > > - **/ > > - 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 > > - * > > - * Since: 0.31 > > - **/ > > - 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 > > - * > > - * Since: 0.31 > > - **/ > > - 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. > > - * > > - * Since: 0.31 > > - **/ > > - 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. > > - * > > - * Since: 0.31 > > - **/ > > - 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->buffer = g_malloc0(FILE_XFER_CHUNK_SIZE); > > -} > > - > > -static SpiceFileTransferTask * > > -spice_file_transfer_task_new(SpiceMainChannel *channel, GFile *file, > > GCancellable *cancellable) > > -{ > > - static uint32_t xfer_id = 1; /* 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 > > - * > > - * Since: 0.31 > > - **/ > > -double spice_file_transfer_task_get_progress(SpiceFileTransferTask *self) > > -{ > > - if (self->file_size == 0) > > - return 0.0; > > - > > - return (double)self->read_bytes / self->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. > > - * > > - * Since: 0.31 > > - **/ > > -void spice_file_transfer_task_cancel(SpiceFileTransferTask *self) > > -{ > > - g_cancellable_cancel(self->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 > > - * > > - * Since: 0.31 > > - **/ > > -char* spice_file_transfer_task_get_filename(SpiceFileTransferTask *self) > > -{ > > - return g_file_get_basename(self->file); > > -} > > diff --git a/src/spice-file-transfer-task-priv.h b/src/spice-file-transfer- > > task-priv.h > > new file mode 100644 > > index 0000000..df3ea93 > > --- /dev/null > > +++ b/src/spice-file-transfer-task-priv.h > > @@ -0,0 +1,59 @@ > > +/* > > + Copyright (C) 2016 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_PRIV_H__ > > +#define __SPICE_FILE_TRANSFER_TASK_PRIV_H__ > > + > > +#include "config.h" > > + > > +#include <spice/vd_agent.h> > > + > > +#include "spice-client.h" > > +#include "channel-main.h" > > +#include "spice-file-transfer-task.h" > > +#include "spice-channel-priv.h" > > + > > +G_BEGIN_DECLS > > + > > +void spice_file_transfer_task_completed(SpiceFileTransferTask *self, GError > > *error); > > +guint32 spice_file_transfer_task_get_id(SpiceFileTransferTask *self); > > +SpiceMainChannel *spice_file_transfer_task_get_channel(SpiceFileTransferTask > > *self); > > +GCancellable *spice_file_transfer_task_get_cancellable(SpiceFileTransferTask > > *self); > > +GHashTable *spice_file_transfer_task_create_tasks(GFile **files, > > + SpiceMainChannel *channel, > > + GFileCopyFlags flags, > > + GCancellable *cancellable); > > +void spice_file_transfer_task_init_task_async(SpiceFileTransferTask *self, > > + GAsyncReadyCallback callback, > > + gpointer userdata); > > +GFileInfo *spice_file_transfer_task_init_task_finish(SpiceFileTransferTask > > *xfer_task, > > + GAsyncResult *result, > > + GError **error); > > +void spice_file_transfer_task_read_async(SpiceFileTransferTask *self, > > + GAsyncReadyCallback callback, > > + gpointer userdata); > > +gssize spice_file_transfer_task_read_finish(SpiceFileTransferTask *self, > > + GAsyncResult *result, > > + char **buffer, > > + GError **error); > > +guint64 spice_file_transfer_task_get_file_size(SpiceFileTransferTask *self); > > +guint64 spice_file_transfer_task_get_bytes_read(SpiceFileTransferTask *self); > > +void spice_file_transfer_task_debug_info(SpiceFileTransferTask *self); > > + > > +G_END_DECLS > > + > > +#endif /* __SPICE_FILE_TRANSFER_TASK_PRIV_H__ */ > > diff --git a/src/spice-file-transfer-task.c b/src/spice-file-transfer-task.c > > new file mode 100644 > > index 0000000..8bfb1ae > > --- /dev/null > > +++ b/src/spice-file-transfer-task.c > > @@ -0,0 +1,713 @@ > > +/* > > + Copyright (C) 2016 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/ > > >. > > +*/ > > + > > +#include "config.h" > > + > > +#include "spice-file-transfer-task-priv.h" > > + > > +/** > > + * 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 > > + */ > > + > > +struct _SpiceFileTransferTask > > +{ > > + GObject parent; > > + > > + uint32_t id; > > + gboolean pending; > > + GFile *file; > > + SpiceMainChannel *channel; > > + GFileInputStream *file_stream; > > + GFileCopyFlags flags; > > + GCancellable *cancellable; > > + GAsyncReadyCallback callback; > > + gpointer user_data; > > + char *buffer; > > + uint64_t read_bytes; > > + GFileInfo *file_info; > > + uint64_t file_size; > > + gint64 start_time; > > + gint64 last_update; > > + GError *error; > > +}; > > + > > +struct _SpiceFileTransferTaskClass > > +{ > > + GObjectClass parent_class; > > +}; > > + > > +G_DEFINE_TYPE(SpiceFileTransferTask, spice_file_transfer_task, G_TYPE_OBJECT) > > + > > +#define FILE_XFER_CHUNK_SIZE (VD_AGENT_MAX_DATA_SIZE * 32) > > + > > +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]; > > + > > +/**************************************************************************** > > *** > > + * Helpers > > + > > ****************************************************************************** > > / > > + > > +static SpiceFileTransferTask * > > +spice_file_transfer_task_new(SpiceMainChannel *channel, GFile *file, > > GCancellable *cancellable) > > +{ > > + static uint32_t xfer_id = 1; /* Used to identify task id */ > > + > > + return g_object_new(SPICE_TYPE_FILE_TRANSFER_TASK, > > + "id", xfer_id++, > > + "file", file, > > + "channel", channel, > > + "cancellable", cancellable, > > + NULL); > > +} > > + > > +static void spice_file_transfer_task_query_info_cb(GObject *obj, > > + GAsyncResult *res, > > + gpointer user_data) > > +{ > > + SpiceFileTransferTask *self; > > + GFileInfo *info; > > + GTask *task; > > + GError *error = NULL; > > + > > + task = G_TASK(user_data); > > + self = g_task_get_source_object(task); > > + > > + g_return_if_fail(self->pending == TRUE); > > + self->pending = FALSE; > > + > > + info = g_file_query_info_finish(G_FILE(obj), res, &error); > > + if (error || self->error) { > > + error = (error == NULL) ? self->error : error; > > + g_task_return_error(task, error); > > + return; > > + } > > + > > + self->file_info = info; > > + self->file_size = > > + g_file_info_get_attribute_uint64(info, > > G_FILE_ATTRIBUTE_STANDARD_SIZE); > > + > > + /* SpiceFileTransferTask's init is done, handshake for file-trasfer will > > + * start soon. First "progress" can be emitted ~ 0% */ > > + g_object_notify(G_OBJECT(self), "progress"); > > + > > + g_task_return_boolean(task, TRUE); > > +} > > + > > +static void spice_file_transfer_task_read_file_cb(GObject *obj, > > + GAsyncResult *res, > > + gpointer user_data) > > +{ > > + SpiceFileTransferTask *self; > > + GTask *task; > > + GError *error = NULL; > > + > > + task = G_TASK(user_data); > > + self = g_task_get_source_object(task); > > + > > + g_return_if_fail(self->pending == TRUE); > > + > > + self->file_stream = g_file_read_finish(G_FILE(obj), res, &error); > > + if (error || self->error) { > > + self->pending = FALSE; > > + error = (error == NULL) ? self->error : error; > > + g_task_return_error(task, error); > > + return; > > + } > > + > > + g_file_query_info_async(self->file, > > + "standard::*", > > + G_FILE_QUERY_INFO_NONE, > > + G_PRIORITY_DEFAULT, > > + self->cancellable, > > + spice_file_transfer_task_query_info_cb, > > + task); > > +} > > + > > +static void spice_file_transfer_task_read_stream_cb(GObject *source_object, > > + GAsyncResult *res, > > + gpointer userdata) > > +{ > > + SpiceFileTransferTask *self; > > + GTask *task; > > + gssize nbytes; > > + GError *error = NULL; > > + > > + task = G_TASK(userdata); > > + self = g_task_get_source_object(task); > > + > > + g_return_if_fail(self->pending == TRUE); > > + self->pending = FALSE; > > + > > + nbytes = g_input_stream_read_finish(G_INPUT_STREAM(self->file_stream), > > res, &error); > > + if (error || self->error) { > > + error = (error == NULL) ? self->error : error; > > + g_task_return_error(task, error); > > + return; > > + } > > + > > + /* The progress here means the amount of data we have _read_ and not what > > + * was actually sent to the agent. On the next "progress", the previous > > data > > + * read was sent. This means that when user see 100%, we are sending the > > + * last chunk to the guest */ > > + self->read_bytes += nbytes; > > + g_object_notify(G_OBJECT(self), "progress"); > > + > > + g_task_return_int(task, nbytes); > > +} > > + > > +/* main context */ > > +static void spice_file_transfer_task_close_stream_cb(GObject *object, > > + GAsyncResult *close_res, > > + gpointer user_data) > > +{ > > + SpiceFileTransferTask *self; > > + GError *error = NULL; > > + > > + self = user_data; > > + > > + if (object) { > > + GInputStream *stream = G_INPUT_STREAM(object); > > + g_input_stream_close_finish(stream, close_res, &error); > > + if (error) { > > + /* This error dont need to report to user, just print a log */ > > + SPICE_DEBUG("close file error: %s", error->message); > > + g_clear_error(&error); > > + } > > + } > > + > > + if (self->error == NULL && spice_util_get_debug()) { > > + gint64 now = g_get_monotonic_time(); > > + gchar *basename = g_file_get_basename(self->file); > > + double seconds = (double) (now - self->start_time) / > > G_TIME_SPAN_SECOND; > > + gchar *file_size_str = g_format_size(self->file_size); > > + gchar *transfer_speed_str = g_format_size(self->file_size / seconds); > > + > > + g_warn_if_fail(self->read_bytes == self->file_size); > > + SPICE_DEBUG("transferred file %s of %s size in %.1f seconds (%s/s)", > > + basename, file_size_str, seconds, transfer_speed_str); > > + > > + g_free(basename); > > + g_free(file_size_str); > > + g_free(transfer_speed_str); > > + } > > + g_object_unref(self); > > +} > > + > > + > > +/**************************************************************************** > > *** > > + * Internal API > > + > > ****************************************************************************** > > / > > + > > +G_GNUC_INTERNAL > > +void spice_file_transfer_task_completed(SpiceFileTransferTask *self, > > + GError *error) > > +{ > > + /* In case of multiple errors we only report the first error */ > > + if (self->error) > > + g_clear_error(&error); > > + if (error) { > > + gchar *path = g_file_get_path(self->file); > > + SPICE_DEBUG("File %s xfer failed: %s", > > + path, error->message); > > + g_free(path); > > + self->error = error; > > + } > > + > > + if (self->pending) > > + return; > > + > > + if (!self->file_stream) { > > + spice_file_transfer_task_close_stream_cb(NULL, NULL, self); > > + goto signal; > > + } > > + > > + g_input_stream_close_async(G_INPUT_STREAM(self->file_stream), > > + G_PRIORITY_DEFAULT, > > + self->cancellable, > > + spice_file_transfer_task_close_stream_cb, > > + self); > > + self->pending = TRUE; > > +signal: > > + g_signal_emit(self, task_signals[SIGNAL_FINISHED], 0, self->error); > > +} > > + > > +G_GNUC_INTERNAL > > +guint32 spice_file_transfer_task_get_id(SpiceFileTransferTask *self) > > +{ > > + g_return_val_if_fail(self != NULL, 0); > > + return self->id; > > +} > > + > > +G_GNUC_INTERNAL > > +SpiceMainChannel *spice_file_transfer_task_get_channel(SpiceFileTransferTask > > *self) > > +{ > > + g_return_val_if_fail(self != NULL, NULL); > > + return self->channel; > > +} > > + > > +G_GNUC_INTERNAL > > +GCancellable *spice_file_transfer_task_get_cancellable(SpiceFileTransferTask > > *self) > > +{ > > + g_return_val_if_fail(self != NULL, NULL); > > + return self->cancellable; > > +} > > + > > +G_GNUC_INTERNAL > > +guint64 spice_file_transfer_task_get_file_size(SpiceFileTransferTask *self) > > +{ > > + g_return_val_if_fail(self != NULL, 0); > > + return self->file_size; > > +} > > + > > +G_GNUC_INTERNAL > > +guint64 spice_file_transfer_task_get_bytes_read(SpiceFileTransferTask *self) > > +{ > > + g_return_val_if_fail(self != NULL, 0); > > + return self->read_bytes; > > +} > > + > > +/* Helper function which only creates a SpiceFileTransferTask per GFile > > + * in @files and returns a HashTable mapping task-id to the task itself > > + * Note that the HashTable does not free its values uppon destruction: > > + * The reference created here should be freed by > > + * spice_file_transfer_task_completed */ > > +G_GNUC_INTERNAL > > +GHashTable *spice_file_transfer_task_create_tasks(GFile **files, > > + SpiceMainChannel *channel, > > + GFileCopyFlags flags, > > + GCancellable *cancellable) > > +{ > > + GHashTable *xfer_ht; > > + gint i; > > + > > + g_return_val_if_fail(files != NULL && files[0] != NULL, NULL); > > + > > + xfer_ht = g_hash_table_new(g_direct_hash, g_direct_equal); > > + for (i = 0; files[i] != NULL && !g_cancellable_is_cancelled(cancellable); > > i++) { > > + SpiceFileTransferTask *xfer_task; > > + guint32 task_id; > > + 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(); > > + > > + xfer_task = spice_file_transfer_task_new(channel, files[i], > > task_cancellable); > > + xfer_task->flags = flags; > > + > > + task_id = spice_file_transfer_task_get_id(xfer_task); > > + g_hash_table_insert(xfer_ht, GUINT_TO_POINTER(task_id), xfer_task); > > + > > + /* if we created a per-task cancellable above, free it */ > > + if (!cancellable) > > + g_object_unref(task_cancellable); > > + } > > + return xfer_ht; > > +} > > + > > +G_GNUC_INTERNAL > > +void spice_file_transfer_task_init_task_async(SpiceFileTransferTask *self, > > + GAsyncReadyCallback callback, > > + gpointer userdata) > > +{ > > + GTask *task; > > + > > + g_return_if_fail(self != NULL); > > + g_return_if_fail(self->pending == FALSE); > > + > > + task = g_task_new(self, self->cancellable, callback, userdata); > > + > > + self->pending = TRUE; > > + g_file_read_async(self->file, > > + G_PRIORITY_DEFAULT, > > + self->cancellable, > > + spice_file_transfer_task_read_file_cb, > > + task); > > +} > > + > > +G_GNUC_INTERNAL > > +GFileInfo *spice_file_transfer_task_init_task_finish(SpiceFileTransferTask > > *self, > > + GAsyncResult *result, > > + GError **error) > > +{ > > + GTask *task = G_TASK(result); > > + > > + g_return_val_if_fail(self != NULL, NULL); > > + > > + if (g_task_propagate_boolean(task, error)) > > + return self->file_info; > > + > > + return NULL; > > +} > > + > > +/* Any context */ > > +G_GNUC_INTERNAL > > +void spice_file_transfer_task_read_async(SpiceFileTransferTask *self, > > + GAsyncReadyCallback callback, > > + gpointer userdata) > > +{ > > + GTask *task; > > + > > + g_return_if_fail(self != NULL); > > + if (self->pending) { > > + g_task_report_new_error(self, callback, userdata, > > + spice_file_transfer_task_read_async, > > + SPICE_CLIENT_ERROR, > > + SPICE_CLIENT_ERROR_FAILED, > > + "Cannot read data in pending state"); > > + return; > > + } > > + > > + task = g_task_new(self, self->cancellable, callback, userdata); > > + > > + if (self->read_bytes == self->file_size) { > > + /* channel-main might can request data after reading the whole file > > as > > + * it expects EOF. Let's return immediately its request as we don't > > want > > + * to reach a state where agent says file-transfer SUCCEED but we are > > in > > + * a PENDING state in SpiceFileTransferTask due reading in idle */ > > + g_task_return_int(task, 0); > > + return; > > + } > > + > > + self->pending = TRUE; > > + g_input_stream_read_async(G_INPUT_STREAM(self->file_stream), > > + self->buffer, > > + FILE_XFER_CHUNK_SIZE, > > + G_PRIORITY_DEFAULT, > > + self->cancellable, > > + spice_file_transfer_task_read_stream_cb, > > + task); > > +} > > + > > +G_GNUC_INTERNAL > > +gssize spice_file_transfer_task_read_finish(SpiceFileTransferTask *self, > > + GAsyncResult *result, > > + char **buffer, > > + GError **error) > > +{ > > + gssize nbytes; > > + GTask *task = G_TASK(result); > > + > > + g_return_val_if_fail(self != NULL, -1); > > + > > + nbytes = g_task_propagate_int(task, error); > > + if (nbytes >= 0 && buffer != NULL) > > + *buffer = self->buffer; > > + > > + return nbytes; > > +} > > + > > +G_GNUC_INTERNAL > > +void spice_file_transfer_task_debug_info(SpiceFileTransferTask *self) > > +{ > > + const GTimeSpan interval = 20 * G_TIME_SPAN_SECOND; > > + gint64 now = g_get_monotonic_time(); > > + > > + if (interval < now - self->last_update) { > > + gchar *basename = g_file_get_basename(self->file); > > + self->last_update = now; > > + SPICE_DEBUG("transferred %.2f%% of the file %s", > > + 100.0 * self->read_bytes / self->file_size, basename); > > + g_free(basename); > > + } > > +} > > + > > +/**************************************************************************** > > *** > > + * External API > > + > > ****************************************************************************** > > / > > + > > +/** > > + * 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 > > + * > > + * Since: 0.31 > > + **/ > > +double spice_file_transfer_task_get_progress(SpiceFileTransferTask *self) > > +{ > > + if (self->file_size == 0) > > + return 0.0; > > + > > + return (double)self->read_bytes / self->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. > > + * > > + * Since: 0.31 > > + **/ > > +void spice_file_transfer_task_cancel(SpiceFileTransferTask *self) > > +{ > > + g_cancellable_cancel(self->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 > > + * > > + * Since: 0.31 > > + **/ > > +char* spice_file_transfer_task_get_filename(SpiceFileTransferTask *self) > > +{ > > + return g_file_get_basename(self->file); > > +} > > + > > +/**************************************************************************** > > *** > > + * GObject > > + > > ****************************************************************************** > > / > > + > > +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->id); > > + break; > > + case PROP_TASK_FILE: > > + g_value_set_object(value, self->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->id = g_value_get_uint(value); > > + break; > > + case PROP_TASK_FILE: > > + self->file = g_value_dup_object(value); > > + break; > > + case PROP_TASK_CHANNEL: > > + self->channel = g_value_dup_object(value); > > + break; > > + case PROP_TASK_CANCELLABLE: > > + self->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->file); > > + g_clear_object(&self->file_info); > > + > > + 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->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->file); > > + self->start_time = g_get_monotonic_time(); > > + self->last_update = self->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); > > + > > + 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 > > + * > > + * Since: 0.31 > > + **/ > > + 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 > > + * > > + * Since: 0.31 > > + **/ > > + 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 > > + * > > + * Since: 0.31 > > + **/ > > + 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 > > + * > > + * Since: 0.31 > > + **/ > > + 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. > > + * > > + * Since: 0.31 > > + **/ > > + 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. > > + * > > + * Since: 0.31 > > + **/ > > + 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->buffer = g_malloc0(FILE_XFER_CHUNK_SIZE); > > +} > > Acked-by: Jonathon Jongsma <jjongsma@xxxxxxxxxx> Thanks! I plan to send a v5 with the changes after we agree on the pending discussions on previous patches :) Cheers! toso > > _______________________________________________ > Spice-devel mailing list > Spice-devel@xxxxxxxxxxxxxxxxxxxxx > https://lists.freedesktop.org/mailman/listinfo/spice-devel _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/spice-devel