[Spice-devel] [spice-gtk v1 6/9] file-xfer: a FileTransferOperation per transfer call
Victor Toso
lists at victortoso.com
Fri May 20 12:30:04 UTC 2016
On Thu, May 19, 2016 at 01:21:46PM +0200, Victor Toso wrote:
> Each call to spice_main_file_copy_async will now create a
> FileTransferOperation which groups all SpiceFileTransferTasks of the
> copy operation and also the progress_callback passed from Application.
>
> As pointed in the fix 113093dd00a1cf10f6d3c3589b7589a184cec081, the
> progress_callback should provide information of the whole transfer
> operation; For that reason, there is no need to keep progress_callback
> and progress_callback_data per SpiceFileTransferTask but per
> FileTransferOperation.
>
> The file_xfer_tasks hash table now has FileTransferOperation instead
> of SpiceFileTransferTask. To improve handling this new operation, I've
> created the helpers:
>
> * file_transfer_operation_send_progress
> * file_transfer_operation_free_op
> * file_transfer_operation_reset_all
> * file_transfer_operation_find_task_by_id
> * file_transfer_operation_remove_task
>
> This change is related to split SpiceFileTransferTask from
> channel-main.
> ---
> src/channel-main.c | 203 ++++++++++++++++++++++++++++++++++++-----------------
> 1 file changed, 138 insertions(+), 65 deletions(-)
>
> diff --git a/src/channel-main.c b/src/channel-main.c
> index 28ffe80..efe559c 100644
> --- a/src/channel-main.c
> +++ b/src/channel-main.c
> @@ -90,8 +90,6 @@ static GList *spice_file_transfer_task_create_tasks(SpiceMainChannel *channel,
> GFile **files,
> GFileCopyFlags flags,
> GCancellable *cancellable,
> - GFileProgressCallback progress_callback,
> - gpointer progress_callback_data,
> SpiceFileTransferTaskFlushCb flush_callback,
> gpointer flush_callback_data,
> GAsyncReadyCallback callback,
> @@ -109,8 +107,6 @@ struct _SpiceFileTransferTaskPrivate
> GFileInputStream *file_stream;
> GFileCopyFlags flags;
> GCancellable *cancellable;
> - GFileProgressCallback progress_callback;
> - gpointer progress_callback_data;
> SpiceFileTransferTaskFlushCb flush_callback;
> gpointer flush_callback_data;
> GAsyncReadyCallback callback;
> @@ -153,6 +149,15 @@ typedef struct {
> SpiceDisplayState display_state;
> } SpiceDisplayConfig;
>
> +typedef struct {
> + GList *tasks;
> + SpiceMainChannel *channel;
> + GFileProgressCallback progress_callback;
> + gpointer progress_callback_data;
> + goffset total_sent;
> + goffset transfer_size;
> +} FileTransferOperation;
> +
> struct _SpiceMainChannelPrivate {
> enum SpiceMouseMode mouse_mode;
> enum SpiceMouseMode requested_mouse_mode;
> @@ -254,6 +259,13 @@ 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);
>
> +static void file_transfer_operation_send_progress(SpiceMainChannel *channel, SpiceFileTransferTask *xfer_task);
> +static void file_transfer_operation_free_op(FileTransferOperation *op);
> +static void file_transfer_operation_reset_all(SpiceMainChannel *channel);
> +static SpiceFileTransferTask *file_transfer_operation_find_task_by_id(SpiceMainChannel *channel,
> + guint32 task_id);
> +static void file_transfer_operation_remove_task(SpiceMainChannel *channel, SpiceFileTransferTask *task);
> +
> /* ------------------------------------------------------------------ */
>
> static const char *agent_msg_types[] = {
> @@ -312,8 +324,7 @@ 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_full(g_direct_hash, g_direct_equal,
> - NULL, g_object_unref);
> + c->file_xfer_tasks = g_hash_table_new(g_direct_hash, g_direct_equal);
> c->flushing = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL,
> g_object_unref);
> c->cancellable_volume_info = g_cancellable_new();
> @@ -467,9 +478,6 @@ static void spice_channel_iterate_write(SpiceChannel *channel)
> static void spice_main_channel_reset_agent(SpiceMainChannel *channel)
> {
> SpiceMainChannelPrivate *c = channel->priv;
> - GError *error;
> - GList *tasks;
> - GList *l;
>
> c->agent_connected = FALSE;
> c->agent_caps_received = FALSE;
> @@ -478,15 +486,7 @@ static void spice_main_channel_reset_agent(SpiceMainChannel *channel)
> g_clear_pointer(&c->agent_msg_data, g_free);
> c->agent_msg_size = 0;
>
> - tasks = g_hash_table_get_values(c->file_xfer_tasks);
> - for (l = tasks; l != NULL; l = l->next) {
> - SpiceFileTransferTask *task = (SpiceFileTransferTask *)l->data;
> -
> - error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
> - "Agent connection closed");
> - spice_file_transfer_task_completed(task, error);
> - }
> - g_list_free(tasks);
> + file_transfer_operation_reset_all(channel);
> file_xfer_flushed(channel, FALSE);
> }
>
> @@ -1875,7 +1875,9 @@ static void file_xfer_data_flushed_cb(GObject *source_object,
> SpiceMainChannel *channel = (SpiceMainChannel *)source_object;
> GError *error = NULL;
>
> - file_xfer_flush_finish(channel, res, &error);
> + if (file_xfer_flush_finish(channel, res, &error))
> + file_transfer_operation_send_progress(channel, self);
> +
> spice_file_transfer_task_flush_done(self, error);
> }
>
> @@ -1899,7 +1901,9 @@ static void file_xfer_flush_callback(SpiceFileTransferTask *xfer_task,
> {
> SpiceMainChannel *main_channel;
> GCancellable *cancellable;
> + FileTransferOperation *xfer_op = user_data;
>
> + xfer_op->total_sent += count;
> file_xfer_queue(xfer_task, buffer, count);
> if (count == 0)
> return;
> @@ -2141,7 +2145,7 @@ static void main_agent_handle_msg(SpiceChannel *channel,
> SpiceFileTransferTask *task;
> VDAgentFileXferStatusMessage *msg = payload;
>
> - task = g_hash_table_lookup(c->file_xfer_tasks, GUINT_TO_POINTER(msg->id));
> + task = file_transfer_operation_find_task_by_id(self, msg->id);
> if (task != NULL) {
> spice_file_transfer_task_handle_status(task, msg);
> } else {
> @@ -3035,21 +3039,22 @@ static void file_xfer_on_file_info(SpiceFileTransferTask *xfer_task,
> GFileInfo *info,
> gpointer data)
> {
> - SpiceMainChannel *channel;
> GKeyFile *keyfile;
> VDAgentFileXferStartMessage msg;
> gchar *string, *basename;
> guint64 file_size;
> gsize data_len;
> GError *error = NULL;
> + FileTransferOperation *xfer_op;
>
> g_return_if_fail(info != NULL);
> -
> - channel = SPICE_MAIN_CHANNEL(data);
> + xfer_op = data;
>
> basename = g_file_info_get_attribute_as_string(info, G_FILE_ATTRIBUTE_STANDARD_NAME);
> file_size = g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_STANDARD_SIZE);
>
> + xfer_op->transfer_size += file_size;
> +
> keyfile = g_key_file_new();
> g_key_file_set_string(keyfile, "vdagent-file-xfer", "name", basename);
> g_key_file_set_uint64(keyfile, "vdagent-file-xfer", "size", file_size);
> @@ -3065,17 +3070,107 @@ static void file_xfer_on_file_info(SpiceFileTransferTask *xfer_task,
>
> /* Create file-xfer start message */
> g_object_get(xfer_task, "id", &msg.id, NULL);
> - agent_msg_queue_many(channel, VD_AGENT_FILE_XFER_START,
> + agent_msg_queue_many(xfer_op->channel, VD_AGENT_FILE_XFER_START,
> &msg, sizeof(msg),
> string, data_len + 1, NULL);
> g_free(string);
> - spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);
> + spice_channel_wakeup(SPICE_CHANNEL(xfer_op->channel), FALSE);
> return;
>
> failed:
> spice_file_transfer_task_completed(xfer_task, error);
> }
>
> +static void file_transfer_operation_send_progress(SpiceMainChannel *channel, SpiceFileTransferTask *xfer_task)
> +{
> + FileTransferOperation *xfer_op;
> + guint32 task_id;
> +
> + g_object_get (xfer_task, "id", &task_id, NULL);
> + xfer_op = g_hash_table_lookup(channel->priv->file_xfer_tasks, GUINT_TO_POINTER(task_id));
> + g_return_if_fail(xfer_op != NULL);
> +
> + if (xfer_op->progress_callback)
> + xfer_op->progress_callback(xfer_op->total_sent,
> + xfer_op->transfer_size,
> + xfer_op->progress_callback_data);
> +}
> +
> +static void file_transfer_operation_free_op(FileTransferOperation *op)
> +{
> + g_return_if_fail(op != NULL);
> +
> + /* SpiceFileTransferTask itself is freed after it emits "finish" */
> + if (op->tasks != NULL)
> + g_list_free(op->tasks);
> +
> + g_free(op);
> +}
> +
> +static void file_transfer_operation_reset_all(SpiceMainChannel *channel)
> +{
> + GList *list_op, *it_op;
I need to be more careful here.
> +
> + list_op = g_hash_table_get_values(channel->priv->file_xfer_tasks);
each task-id is mapped to a FileTransferOperation, so the loop below
is likely passing more then once to each FileTransferOperation causing a
double free.
I'll send a v2 with improved tests :)
> + for (it_op = list_op; it_op != NULL; it_op = it_op->next) {
> + FileTransferOperation *op = it_op->data;
> + GList *it;
> + for (it = op->tasks; it != NULL; it = it->next) {
> + SpiceFileTransferTask *xfer_task = SPICE_FILE_TRANSFER_TASK(it->data);
> + GError *error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
> + "Agent connection closed");
> + spice_file_transfer_task_completed(xfer_task, error);
> + }
> + file_transfer_operation_free_op(op);
> + }
> + g_list_free(list_op);
> +}
> +
> +static SpiceFileTransferTask *file_transfer_operation_find_task_by_id(SpiceMainChannel *channel,
> + guint32 task_id)
> +{
> + FileTransferOperation *op;
> + GList *it;
> +
> + op = g_hash_table_lookup (channel->priv->file_xfer_tasks, GUINT_TO_POINTER(task_id));
> + g_return_val_if_fail (op != NULL, NULL);
> +
> + for (it = op->tasks; it != NULL; it = it->next) {
> + SpiceFileTransferTask *task = SPICE_FILE_TRANSFER_TASK(it->data);
> + guint32 id;
> + g_object_get(task, "id", &id, NULL);
> + if (id == task_id)
> + return task;
> + }
> + return NULL;
> +}
> +
> +static void file_transfer_operation_remove_task(SpiceMainChannel *channel, SpiceFileTransferTask *task)
> +{
> + FileTransferOperation *op;
> + guint32 task_id;
> +
> + g_object_get (task, "id", &task_id, NULL);
> + op = g_hash_table_lookup(channel->priv->file_xfer_tasks, GUINT_TO_POINTER(task_id));
> + if (op == NULL) {
> + /* Likely the operation has ended before the remove-task was called. One
> + * situation that this can easily happen is if the agent is disconnected
> + * while there are pending files. */
> + g_object_unref(task);
> + return;
> + }
> + /* Remove and free SpiceFileTransferTask */
> + op->tasks = g_list_remove(op->tasks, task);
> + g_object_unref(task);
> +
> + /* Keep file-xfer-tasks up to date. If no more elements, operation is over */
> + g_hash_table_remove(channel->priv->file_xfer_tasks, GUINT_TO_POINTER(task_id));
> +
> + /* No more pending operations */
> + if (op->tasks == NULL)
> + file_transfer_operation_free_op(op);
> +}
> +
> static void task_finished(SpiceFileTransferTask *task,
> GError *error,
> gpointer data)
> @@ -3093,7 +3188,7 @@ static void task_finished(SpiceFileTransferTask *task,
> }
>
> g_object_get(task, "id", &task_id, NULL);
> - g_hash_table_remove(channel->priv->file_xfer_tasks, GUINT_TO_POINTER(task_id));
> + file_transfer_operation_remove_task(channel, task);
> }
>
> /**
> @@ -3139,7 +3234,8 @@ void spice_main_file_copy_async(SpiceMainChannel *channel,
> gpointer user_data)
> {
> SpiceMainChannelPrivate *c = channel->priv;
> - GList *tasks, *it;
> + GList *it;
> + FileTransferOperation *xfer_op;
>
> g_return_if_fail(channel != NULL);
> g_return_if_fail(SPICE_IS_MAIN_CHANNEL(channel));
> @@ -3156,28 +3252,29 @@ void spice_main_file_copy_async(SpiceMainChannel *channel,
> return;
> }
>
> - tasks = spice_file_transfer_task_create_tasks(channel,
> - sources,
> - flags,
> - cancellable,
> - progress_callback,
> - progress_callback_data,
> - file_xfer_flush_callback,
> - NULL,
> - callback,
> - user_data);
> - for (it = tasks; it != NULL; it = it->next) {
> + xfer_op = g_new0(FileTransferOperation, 1);
> + xfer_op->progress_callback = progress_callback;
> + xfer_op->progress_callback_data = progress_callback_data;
> + xfer_op->channel = channel;
> + xfer_op->tasks = spice_file_transfer_task_create_tasks(channel,
> + sources,
> + flags,
> + cancellable,
> + file_xfer_flush_callback,
> + xfer_op,
> + callback,
> + user_data);
> + for (it = xfer_op->tasks; it != NULL; it = it->next) {
> SpiceFileTransferTask *task = SPICE_FILE_TRANSFER_TASK(it->data);
> guint32 task_id;
>
> g_object_get(task, "id", &task_id, NULL);
> - g_hash_table_insert(c->file_xfer_tasks, GUINT_TO_POINTER(task_id), task);
> - g_signal_connect(task, "file-info", G_CALLBACK(file_xfer_on_file_info), channel);
> + g_hash_table_insert(c->file_xfer_tasks, GUINT_TO_POINTER(task_id), xfer_op);
> + g_signal_connect(task, "file-info", G_CALLBACK(file_xfer_on_file_info), xfer_op);
> g_signal_connect(task, "finished", G_CALLBACK(task_finished), channel);
> g_signal_emit(channel, signals[SPICE_MAIN_NEW_FILE_TRANSFER], 0, task);
> spice_file_transfer_task_start_task(task);
> }
> - g_list_free(tasks);
> }
>
> /**
> @@ -3240,26 +3337,6 @@ static void spice_file_transfer_task_flush_done(SpiceFileTransferTask *self, GEr
> }
> }
>
> - if (self->priv->progress_callback) {
> - goffset read = 0;
> - goffset total = 0;
> - SpiceMainChannel *main_channel = self->priv->channel;
> - GHashTableIter iter;
> - gpointer key, value;
> -
> - /* since the progress_callback does not have a parameter to indicate
> - * which file the progress is associated with, report progress on all
> - * current transfers */
> - g_hash_table_iter_init(&iter, main_channel->priv->file_xfer_tasks);
> - while (g_hash_table_iter_next(&iter, &key, &value)) {
> - SpiceFileTransferTask *t = (SpiceFileTransferTask *)value;
> - read += t->priv->read_bytes;
> - total += t->priv->file_size;
> - }
> -
> - self->priv->progress_callback(read, total, self->priv->progress_callback_data);
> - }
> -
> /* Read more data */
> file_xfer_continue_read(self);
> }
> @@ -3268,8 +3345,6 @@ static GList *spice_file_transfer_task_create_tasks(SpiceMainChannel *channel,
> GFile **files,
> GFileCopyFlags flags,
> GCancellable *cancellable,
> - GFileProgressCallback progress_callback,
> - gpointer progress_callback_data,
> SpiceFileTransferTaskFlushCb flush_callback,
> gpointer flush_callback_data,
> GAsyncReadyCallback callback,
> @@ -3289,8 +3364,6 @@ static GList *spice_file_transfer_task_create_tasks(SpiceMainChannel *channel,
>
> 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->flush_callback = flush_callback;
> task->priv->flush_callback_data = flush_callback_data;
> task->priv->callback = callback;
> --
> 2.5.5
>
> _______________________________________________
> Spice-devel mailing list
> Spice-devel at lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/spice-devel
More information about the Spice-devel
mailing list