[Spice-devel] [spice-gtk v4 15/24] file-xfer: call user callback once per operation
Victor Toso
lists at victortoso.com
Tue Jun 28 12:45:52 UTC 2016
Hi,
On Mon, Jun 27, 2016 at 11:50:01AM -0500, Jonathon Jongsma wrote:
> On Thu, 2016-06-23 at 19:37 +0200, Victor Toso wrote:
> > SpiceFileTransferTask has a callback to be called when operation
> > ended. Til this patch, we were setting the user callback which means
> > that in multiple file-transfers, we were calling the user callback
> > several times.
> >
> > Following the same logic pointed from 113093dd00a1cf10f6d3c3589b7 this
> > is a SpiceMainChannel operation and it should only call the user
> > callback when this operation is over (FileTransferOperation now).
> >
> > Highlights:
> > * Now using GTask to set user callback and callback_data
> > * Handling error on FileTransferOperation:
> > - It is considered an error if no file was successfully transferred due
> > cancellations or any other kind of error;
> > - If any file failed due any error, we will consider the operation a
> > failure even if other files succeed.
> >
> > Note also that SpiceFileTransferTask now does not have a callback and
> > callback_data. The xfer_tasks has ended after its "finish" signal is
> > emitted.
> > ---
> > src/channel-main.c | 90 ++++++++++++++++++++++++++++++-----------------------
> > -
> > 1 file changed, 50 insertions(+), 40 deletions(-)
> >
> > diff --git a/src/channel-main.c b/src/channel-main.c
> > index fba5b7f..0505687 100644
> > --- a/src/channel-main.c
> > +++ b/src/channel-main.c
> > @@ -60,9 +60,7 @@ static GCancellable
> > *spice_file_transfer_task_get_cancellable(SpiceFileTransferT
> > static GHashTable *spice_file_transfer_task_create_tasks(GFile **files,
> > SpiceMainChannel
> > *channel,
> > GFileCopyFlags
> > flags,
> > - GCancellable
> > *cancellable,
> > - GAsyncReadyCallback
> > callback,
> > - gpointer user_data);
> > + GCancellable
> > *cancellable);
> > static void spice_file_transfer_task_init_task_async(SpiceFileTransferTask
> > *self,
> > GAsyncReadyCallback
> > callback,
> > gpointer userdata);
> > @@ -162,9 +160,14 @@ typedef struct {
> > SpiceMainChannel *channel;
> > GFileProgressCallback progress_callback;
> > gpointer progress_callback_data;
> > + GTask *task;
> > struct {
> > goffset total_sent;
> > goffset transfer_size;
> > + guint num_files;
> > + guint succeed;
> > + guint cancelled;
> > + guint failed;
> > } stats;
> > } FileTransferOperation;
> >
> > @@ -1841,7 +1844,6 @@ static void file_xfer_close_cb(GObject *object,
> > GAsyncResult *close_res,
> > gpointer user_data)
> > {
> > - GTask *task;
> > SpiceFileTransferTask *self;
> > GError *error = NULL;
> >
> > @@ -1857,35 +1859,21 @@ static void file_xfer_close_cb(GObject *object,
> > }
> > }
> >
> > - /* Notify to user that files have been transferred or something error
> > - happened. */
> > - task = g_task_new(self->channel,
> > - self->cancellable,
> > - self->callback,
> > - self->user_data);
> > -
> > - if (self->error) {
> > - g_task_return_error(task, self->error);
> > - } else {
> > - g_task_return_boolean(task, TRUE);
> > - if (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);
> > + 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_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_free(basename);
> > + g_free(file_size_str);
> > + g_free(transfer_speed_str);
> > }
> > - g_object_unref(task);
> > -
> > g_object_unref(self);
> > }
> >
> > @@ -3041,10 +3029,25 @@ failed:
> > 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);
> > +
> > + if (xfer_op->stats.failed != 0 || xfer_op->stats.succeed == 0) {
>
> So, if we have a 3-file transfer operation and two of them are cancelled, we
> consider that a success. But if all three are cancelled, we consider that an
> error? I can see the argument for that behavior, but how about something like
> this instead?
>
> if (failed != 0)
> return error (SPICE_CLIENT_ERROR_FAILED)
> else if (cancelled != 0 && succeed == 0)
> return error (G_IO_ERROR_CANCELLED)
> else
> return boolean (TRUE)
Agreed :) I'll change that!
>
>
> > + GError *error = g_error_new(SPICE_CLIENT_ERROR,
> > + SPICE_CLIENT_ERROR_FAILED,
> > + "From %u files: %u succeed, %u cancelled,
> > %u failed",
> > + xfer_op->stats.num_files,
> > + xfer_op->stats.succeed,
> > + xfer_op->stats.cancelled,
> > + xfer_op->stats.failed);
> > + SPICE_DEBUG("Transfer failed (%p) %s", xfer_op, error->message);
> > + g_task_return_error(xfer_op->task, error);
> > + } else {
> > + g_task_return_boolean(xfer_op->task, TRUE);
> > + }
> >
> > /* SpiceFileTransferTask itself is freed after it emits "finish" */
> > g_hash_table_unref(xfer_op->xfer_task_ht);
> > +
> > + spice_debug("Freeing file-transfer-operation %p", xfer_op);
> > g_free(xfer_op);
> > }
> >
> > @@ -3114,6 +3117,13 @@ static void
> > file_transfer_operation_task_finished(SpiceFileTransferTask *xfer_ta
> > if (error) {
> > xfer_op->stats.total_sent -=
> > spice_file_transfer_task_get_bytes_read(xfer_task);
> > xfer_op->stats.transfer_size -=
> > spice_file_transfer_task_get_file_size(xfer_task);
> > + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
> > + xfer_op->stats.cancelled++;
> > + } else {
> > + xfer_op->stats.failed++;
> > + }
> > + } else {
> > + xfer_op->stats.succeed++;
> > }
> >
> > /* Remove and free SpiceFileTransferTask */
> > @@ -3179,7 +3189,11 @@ static SpiceFileTransferTask
> > *spice_file_transfer_task_new(SpiceMainChannel *cha
> > * files, please connect to the #SpiceMainChannel::new-file-transfer signal.
> > *
> > * When the operation is finished, callback will be called. You can then call
> > - * spice_main_file_copy_finish() to get the result of the operation.
> > + * spice_main_file_copy_finish() to get the result of the operation. Note
> > that
> > + * before release 0.33 the callback was called for each file in multiple file
> > + * transfer. This behavior was changed for the same reason as the
> > + * progress_callback (above). If you need to monitor the ending of individual
> > + * files, you can connect to "finished" signal from each
> > SpiceFileTransferTask.
> > *
> > **/
> > void spice_main_file_copy_async(SpiceMainChannel *channel,
> > @@ -3216,12 +3230,12 @@ void spice_main_file_copy_async(SpiceMainChannel
> > *channel,
> > xfer_op->channel = channel;
> > xfer_op->progress_callback = progress_callback;
> > xfer_op->progress_callback_data = progress_callback_data;
> > + xfer_op->task = g_task_new(channel, cancellable, callback, user_data);
> > xfer_op->xfer_task_ht = spice_file_transfer_task_create_tasks(sources,
> > channel,
> > flags,
> > - cancellable
> > ,
> > - callback,
> > - user_data);
> > + cancellable
> > );
> > + xfer_op->stats.num_files = g_hash_table_size(xfer_op->xfer_task_ht);
> > g_hash_table_iter_init(&iter, xfer_op->xfer_task_ht);
> > while (g_hash_table_iter_next(&iter, &key, &value)) {
> > guint32 task_id;
> > @@ -3303,9 +3317,7 @@ static guint64
> > spice_file_transfer_task_get_bytes_read(SpiceFileTransferTask *se
> > static GHashTable *spice_file_transfer_task_create_tasks(GFile **files,
> > SpiceMainChannel
> > *channel,
> > GFileCopyFlags
> > flags,
> > - GCancellable
> > *cancellable,
> > - GAsyncReadyCallback
> > callback,
> > - gpointer user_data)
> > + GCancellable
> > *cancellable)
> > {
> > GHashTable *xfer_ht;
> > gint i;
> > @@ -3326,8 +3338,6 @@ static GHashTable
> > *spice_file_transfer_task_create_tasks(GFile **files,
> >
> > xfer_task = spice_file_transfer_task_new(channel, files[i],
> > task_cancellable);
> > xfer_task->flags = flags;
> > - xfer_task->callback = callback;
> > - xfer_task->user_data = user_data;
> >
> > task_id = spice_file_transfer_task_get_id(xfer_task);
> > g_hash_table_insert(xfer_ht, GUINT_TO_POINTER(task_id), xfer_task);
>
>
> Yet another question above, but otherwise looks good
>
> Acked-by: Jonathon Jongsma <jjongsma at redhat.com>
Thanks!
> _______________________________________________
> 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