[Spice-devel] [spice-gtk v4 12/24] file-xfer: a FileTransferOperation per transfer call

Jonathon Jongsma jjongsma at redhat.com
Fri Jun 24 21:45:51 UTC 2016


On Thu, 2016-06-23 at 19:37 +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 in a GHashTable.
> 
> To ease the review process, this first patch introduces the structure
> and organize the logic around the four following helpers:
> 
> * file_transfer_operation_end
> * file_transfer_operation_reset_all
> * file_transfer_operation_find_task_by_id
> * file_transfer_operation_task_finished
> 
> The _task_finished function is the new callback for "finished" signal
> from SpiceFileTransferTask.
> 
> The file_xfer_tasks GHashTable is now mapped as:
>  (key) SpiceFileTransferTask ID -> (value) FileTransferOperation
> 
> This patch leaves a FIXME regarding progress_callback which will be
> addressed in a later patch in this series.
> 
> This change is related to split SpiceFileTransferTask from
> channel-main.
> ---
>  src/channel-main.c | 150 ++++++++++++++++++++++++++++++++++++++------------
> ---
>  1 file changed, 109 insertions(+), 41 deletions(-)
> 
> diff --git a/src/channel-main.c b/src/channel-main.c
> index 5e23acb..689b709 100644
> --- a/src/channel-main.c
> +++ b/src/channel-main.c
> @@ -159,6 +159,11 @@ typedef struct {
>      SpiceDisplayState       display_state;
>  } SpiceDisplayConfig;
>  
> +typedef struct {
> +    GHashTable                 *xfer_task_ht;

Personally, I prefer not to embed the type into the name. Something like
'xfer_tasks' perhaps. 

> +    SpiceMainChannel           *channel;
> +} FileTransferOperation;
> +
>  struct _SpiceMainChannelPrivate  {
>      enum SpiceMouseMode         mouse_mode;
>      enum SpiceMouseMode         requested_mouse_mode;
> @@ -262,6 +267,14 @@ static void file_xfer_read_async_cb(GObject
> *source_object,
>  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_end(FileTransferOperation *xfer_op);
> +static void file_transfer_operation_reset_all(SpiceMainChannel *channel);

this appears to be a SpiceMainChannel method instead of a FileTransferOperation
method, so i'd prefer the name to reflect that. e.g.
spice_main_channel_reset_all_xfer_operations()? Maybe there's better options.

> +static SpiceFileTransferTask
> *file_transfer_operation_find_task_by_id(SpiceMainChannel *channel,
> +                                                                      guint32
> task_id);

Same here.

> +static void file_transfer_operation_task_finished(SpiceFileTransferTask
> *xfer_task,
> +                                                  GError *error,
> +                                                  gpointer userdata);
> +
>  /* ------------------------------------------------------------------ */
>  
>  static const char *agent_msg_types[] = {
> @@ -320,10 +333,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_full(g_direct_hash, g_direct_equal,
> -                                               NULL, g_object_unref);
> -    c->flushing = 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(g_direct_hash, g_direct_equal);
>      c->cancellable_volume_info = g_cancellable_new();
>  
>      spice_main_channel_reset_capabilties(SPICE_CHANNEL(channel));
> @@ -475,9 +486,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;
> @@ -486,15 +494,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);
>  }
>  
> @@ -1899,12 +1899,17 @@ static void
> file_xfer_send_progress(SpiceFileTransferTask *xfer_task)
>  
>      channel = spice_file_transfer_task_get_channel(xfer_task);
>  
> +    /* FIXME: This iterates to all SpiceFileTransferTasks, including ones
> from
> +     * different FileTransferOperation. The progress_callback should only
> return
> +     * the info related to its FileTransferOperation */
>      /* 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, channel->priv->file_xfer_tasks);
>      while (g_hash_table_iter_next(&iter, &key, &value)) {
> -        SpiceFileTransferTask *t = (SpiceFileTransferTask *)value;
> +        SpiceFileTransferTask *t;
> +
> +        t = file_transfer_operation_find_task_by_id(channel,
> GPOINTER_TO_UINT(key));
>          read += t->read_bytes;
>          total += t->file_size;
>      }
> @@ -1997,11 +2002,9 @@ static void file_xfer_handle_status(SpiceMainChannel
> *channel,
>                                      VDAgentFileXferStatusMessage *msg)
>  {
>      SpiceFileTransferTask *xfer_task;
> -    SpiceMainChannelPrivate *c;
>      GError *error = NULL;
>  
> -    c = channel->priv;
> -    xfer_task = g_hash_table_lookup(c->file_xfer_tasks, GUINT_TO_POINTER(msg-
> >id));
> +    xfer_task = file_transfer_operation_find_task_by_id(channel, msg->id);
>      g_return_if_fail(xfer_task != NULL);
>  
>      SPICE_DEBUG("xfer-task %u received response %u", msg->id, msg->result);
> @@ -3063,18 +3066,82 @@ failed:
>      spice_file_transfer_task_completed(xfer_task, error);
>  }
>  
> -static SpiceFileTransferTask *spice_file_transfer_task_new(SpiceMainChannel
> *channel,
> -                                                           GFile *file,
> -                                                           GCancellable
> *cancellable);
> +static void file_transfer_operation_end(FileTransferOperation *xfer_op)
> +{
> +    g_return_if_fail(xfer_op != NULL);
> +    spice_debug("Freeing file-transfer-operation %p", xfer_op);
> +
> +    /* SpiceFileTransferTask itself is freed after it emits "finish" */
> +    g_hash_table_unref(xfer_op->xfer_task_ht);
> +    g_free(xfer_op);
> +}

Not sure about this name. the _end() name doesn't necessarily give the
impression that xfer_op cannot be used after calling this function. I think
_free() would be more clear.

>  
> -static void task_finished(SpiceFileTransferTask *task,
> -                          GError *error,
> -                          gpointer data)
> +static void file_transfer_operation_reset_all(SpiceMainChannel *channel)
>  {
> -    SpiceMainChannel *channel = SPICE_MAIN_CHANNEL(data);
> -    g_hash_table_remove(channel->priv->file_xfer_tasks,
> GUINT_TO_POINTER(task->id));
> +    GHashTableIter iter_all_xfer_tasks;
> +    gpointer key, value;
> +
> +    /* Mark each of SpiceFileTransferTask as completed due error */

"due to an error"

> +    g_hash_table_iter_init(&iter_all_xfer_tasks, channel->priv-
> >file_xfer_tasks);
> +    while (g_hash_table_iter_next(&iter_all_xfer_tasks, &key, &value)) {
> +        FileTransferOperation *xfer_op = value;
> +        SpiceFileTransferTask *xfer_task = g_hash_table_lookup(xfer_op-
> >xfer_task_ht, key);
> +        GError *error;
> +
> +        if (xfer_task == NULL)
> +            continue;

expected? maybe print a warning here?

> +
> +        error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
> +                            "Agent connection closed");
> +        spice_file_transfer_task_completed(xfer_task, error);
> +    }
> +}
> +
> +static SpiceFileTransferTask
> *file_transfer_operation_find_task_by_id(SpiceMainChannel *channel,
> +                                                                      guint32
> task_id)
> +{
> +    FileTransferOperation *xfer_op;
> +
> +    xfer_op = g_hash_table_lookup(channel->priv->file_xfer_tasks,
> GUINT_TO_POINTER(task_id));
> +    g_return_val_if_fail(xfer_op != NULL, NULL);
> +    return g_hash_table_lookup(xfer_op->xfer_task_ht,
> GUINT_TO_POINTER(task_id));
> +}
> +
> +static void file_transfer_operation_task_finished(SpiceFileTransferTask
> *xfer_task,
> +                                                  GError *error,
> +                                                  gpointer userdata)
> +{
> +    SpiceMainChannel *channel;
> +    FileTransferOperation *xfer_op;
> +    guint32 task_id;
> +
> +    channel = spice_file_transfer_task_get_channel(xfer_task);

This is not related to this change particularly, but I'm suddenly wondering
whether we should really have this reverse dependency from SpiceFileTransferTask
back to SpiceMainChannel... It would require a fair amount of re-design to get
rid of it though. Just thinking out loud...


> +    task_id = spice_file_transfer_task_get_id(xfer_task);
> +    xfer_op = g_hash_table_lookup(channel->priv->file_xfer_tasks,
> GUINT_TO_POINTER(task_id));
> +    if (xfer_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(xfer_task);
> +        return;
> +    }
> +
> +    /* Remove and free SpiceFileTransferTask */
> +    g_hash_table_remove(xfer_op->xfer_task_ht, GUINT_TO_POINTER(task_id));
> +    g_object_unref(xfer_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 (g_hash_table_size(xfer_op->xfer_task_ht) == 0)
> +        file_transfer_operation_end(xfer_op);
>  }
>  
> +static SpiceFileTransferTask *spice_file_transfer_task_new(SpiceMainChannel
> *channel,
> +                                                           GFile *file,
> +                                                           GCancellable
> *cancellable);
> +
>  /**
>   * spice_main_file_copy_async:
>   * @channel: a #SpiceMainChannel
> @@ -3118,9 +3185,9 @@ void spice_main_file_copy_async(SpiceMainChannel
> *channel,
>                                  gpointer user_data)
>  {
>      SpiceMainChannelPrivate *c;
> -    GHashTable *xfer_ht;
>      GHashTableIter iter;
>      gpointer key, value;
> +    FileTransferOperation *xfer_op;
>  
>      g_return_if_fail(channel != NULL);
>      g_return_if_fail(SPICE_IS_MAIN_CHANNEL(channel));
> @@ -3138,15 +3205,17 @@ void spice_main_file_copy_async(SpiceMainChannel
> *channel,
>          return;
>      }
>  
> -    xfer_ht = spice_file_transfer_task_create_tasks(sources,
> -                                                    channel,
> -                                                    flags,
> -                                                    cancellable,
> -                                                    progress_callback,
> -                                                    progress_callback_data,
> -                                                    callback,
> -                                                    user_data);
> -    g_hash_table_iter_init(&iter, xfer_ht);
> +    xfer_op = g_new0(FileTransferOperation, 1);
> +    xfer_op->channel = channel;
> +    xfer_op->xfer_task_ht = spice_file_transfer_task_create_tasks(sources,
> +                                                                  channel,
> +                                                                  flags,
> +                                                                  cancellable
> ,
> +                                                                  progress_ca
> llback,
> +                                                                  progress_ca
> llback_data,
> +                                                                  callback,
> +                                                                  user_data);
> +    g_hash_table_iter_init(&iter, xfer_op->xfer_task_ht);
>      while (g_hash_table_iter_next(&iter, &key, &value)) {
>          guint32 task_id;
>          SpiceFileTransferTask *xfer_task;
> @@ -3156,15 +3225,14 @@ void spice_main_file_copy_async(SpiceMainChannel
> *channel,
>  
>          SPICE_DEBUG("Insert a xfer task:%u to task list", task_id);
>  
> -        g_hash_table_insert(c->file_xfer_tasks, key,
> g_object_ref(xfer_task));
> -        g_signal_connect(xfer_task, "finished", G_CALLBACK(task_finished),
> channel);
> +        g_hash_table_insert(c->file_xfer_tasks, key, xfer_op);
> +        g_signal_connect(xfer_task, "finished",
> G_CALLBACK(file_transfer_operation_task_finished), NULL);
>          g_signal_emit(channel, signals[SPICE_MAIN_NEW_FILE_TRANSFER], 0,
> xfer_task);
>  
>          spice_file_transfer_task_init_task_async(xfer_task,
>                                                   file_xfer_init_task_async_cb
> ,
>                                                   NULL);
>      }
> -    g_hash_table_unref(xfer_ht);
>  }
>  
>  /**


Reviewed-by: Jonathon Jongsma <jjongsma at redhat.com>




More information about the Spice-devel mailing list