[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