[Spice-devel] [spice-gtk v4 15/24] file-xfer: call user callback once per operation

Victor Toso victortoso at redhat.com
Thu Jun 23 17:37:47 UTC 2016


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) {
+        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);
-- 
2.7.4



More information about the Spice-devel mailing list