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). --- src/channel-main.c | 72 ++- src/tmp-introspect325cwcm0/SpiceClientGtk-3.0.c | 628 ++++++++++++++++++++++++ 2 files changed, 694 insertions(+), 6 deletions(-) create mode 100644 src/tmp-introspect325cwcm0/SpiceClientGtk-3.0.c diff --git a/src/channel-main.c b/src/channel-main.c index f36326d..e204a1e 100644 --- a/src/channel-main.c +++ b/src/channel-main.c @@ -157,6 +157,10 @@ typedef struct { SpiceMainChannel *channel; GFileProgressCallback progress_callback; gpointer progress_callback_data; + GAsyncReadyCallback end_callback; + gpointer end_callback_data; + GError *error; + GCancellable *cancellable; goffset total_sent; goffset transfer_size; } FileTransferOperation; @@ -1838,9 +1842,7 @@ 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, + task = g_task_new(self, self->cancellable, self->callback, self->user_data); @@ -1919,6 +1921,42 @@ static void file_xfer_flush_callback(SpiceFileTransferTask *xfer_task, file_xfer_flush_async(main_channel, cancellable, file_xfer_data_flushed_cb, xfer_task); } +static void file_xfer_end_callback(GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GTask *task; + FileTransferOperation *xfer_op; + + task = G_TASK(res); + if (!g_task_had_error(task)) + /* SpiceFileTransferTask and FileTransferOperation are freed on + * file_transfer_operation_task_finished */ + return; + + xfer_op = user_data; + + if (xfer_op->error != NULL) + return; + + /* Get the GError from SpiceFileTransferTask so we can properly return to + * the application when the FileTransferOperation ends */ + g_task_propagate_boolean(task, &xfer_op->error); + + /* User can cancel a FileTransfer without cancelling the whole + * operation. For that, spice_main_file_copy_async must be called + * without GCancellabe */ + if (g_error_matches(xfer_op->error, G_IO_ERROR, G_IO_ERROR_CANCELLED) && + xfer_op->cancellable == NULL) { + SpiceFileTransferTask *xfer_task; + + xfer_task = SPICE_FILE_TRANSFER_TASK(source_object); + spice_debug("file-transfer %u was cancelled", + spice_file_transfer_task_get_id(xfer_task)); + g_clear_error(&xfer_op->error); + } +} + /* main context */ static void file_xfer_read_cb(GObject *source_object, GAsyncResult *res, @@ -3108,10 +3146,24 @@ 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->end_callback) { + GTask *task = g_task_new(xfer_op->channel, + xfer_op->cancellable, + xfer_op->end_callback, + xfer_op->end_callback_data); + + if (xfer_op->error != NULL) { + g_task_return_error(task, xfer_op->error); + } else { + g_task_return_boolean(task, TRUE); + } + } + /* SpiceFileTransferTask itself is freed after it emits "finish" */ if (xfer_op->tasks != NULL) g_list_free(xfer_op->tasks); + g_clear_object (&xfer_op->cancellable); g_free(xfer_op); } @@ -3225,7 +3277,11 @@ static void task_finished(SpiceFileTransferTask *task, * 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.32 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, @@ -3259,15 +3315,19 @@ void spice_main_file_copy_async(SpiceMainChannel *channel, xfer_op = g_new0(FileTransferOperation, 1); xfer_op->progress_callback = progress_callback; xfer_op->progress_callback_data = progress_callback_data; + xfer_op->end_callback = callback; + xfer_op->end_callback_data = user_data; xfer_op->channel = channel; + xfer_op->error = NULL; + xfer_op->cancellable = (cancellable != NULL) ? g_object_ref(cancellable) : NULL; xfer_op->tasks = spice_file_transfer_task_create_tasks(channel, sources, flags, cancellable, file_xfer_flush_callback, xfer_op, - callback, - user_data); + file_xfer_end_callback, + xfer_op); spice_debug("New file-transfer-operation %p", xfer_op); for (it = xfer_op->tasks; it != NULL; it = it->next) { SpiceFileTransferTask *task = SPICE_FILE_TRANSFER_TASK(it->data); diff --git a/src/tmp-introspect325cwcm0/SpiceClientGtk-3.0.c b/src/tmp-introspect325cwcm0/SpiceClientGtk-3.0.c new file mode 100644 index 0000000..765abbf --- /dev/null +++ b/src/tmp-introspect325cwcm0/SpiceClientGtk-3.0.c @@ -0,0 +1,628 @@ +/* This file is generated, do not edit */ +#include <glib.h> +#include <string.h> +#include <stdlib.h> + +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * GObject introspection: Dump introspection data + * + * Copyright (C) 2008 Colin Walters <walters@xxxxxxxxxx> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include <stdlib.h> + +#include <glib.h> +#include <glib-object.h> +#include <gio/gio.h> + +/* This file is both compiled into libgirepository.so, and installed + * on the filesystem. But for the dumper, we want to avoid linking + * to libgirepository; see + * https://bugzilla.gnome.org/show_bug.cgi?id=630342 + */ +#ifdef G_IREPOSITORY_COMPILATION +#include "config.h" +#include "girepository.h" +#endif + +#include <string.h> + +static void +escaped_printf (GOutputStream *out, const char *fmt, ...) G_GNUC_PRINTF (2, 3); + +static void +escaped_printf (GOutputStream *out, const char *fmt, ...) +{ + char *str; + va_list args; + gsize written; + GError *error = NULL; + + va_start (args, fmt); + + str = g_markup_vprintf_escaped (fmt, args); + if (!g_output_stream_write_all (out, str, strlen (str), &written, NULL, &error)) + { + g_critical ("failed to write to iochannel: %s", error->message); + g_clear_error (&error); + } + g_free (str); + + va_end (args); +} + +static void +goutput_write (GOutputStream *out, const char *str) +{ + gsize written; + GError *error = NULL; + if (!g_output_stream_write_all (out, str, strlen (str), &written, NULL, &error)) + { + g_critical ("failed to write to iochannel: %s", error->message); + g_clear_error (&error); + } +} + +typedef GType (*GetTypeFunc)(void); +typedef GQuark (*ErrorQuarkFunc)(void); + +static GType +invoke_get_type (GModule *self, const char *symbol, GError **error) +{ + GetTypeFunc sym; + GType ret; + + if (!g_module_symbol (self, symbol, (void**)&sym)) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Failed to find symbol '%s'", symbol); + return G_TYPE_INVALID; + } + + ret = sym (); + if (ret == G_TYPE_INVALID) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Function '%s' returned G_TYPE_INVALID", symbol); + } + return ret; +} + +static GQuark +invoke_error_quark (GModule *self, const char *symbol, GError **error) +{ + ErrorQuarkFunc sym; + + if (!g_module_symbol (self, symbol, (void**)&sym)) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Failed to find symbol '%s'", symbol); + return G_TYPE_INVALID; + } + + return sym (); +} + +static void +dump_properties (GType type, GOutputStream *out) +{ + guint i; + guint n_properties; + GParamSpec **props; + + if (G_TYPE_FUNDAMENTAL (type) == G_TYPE_OBJECT) + { + GObjectClass *klass; + klass = g_type_class_ref (type); + props = g_object_class_list_properties (klass, &n_properties); + } + else + { + void *klass; + klass = g_type_default_interface_ref (type); + props = g_object_interface_list_properties (klass, &n_properties); + } + + for (i = 0; i < n_properties; i++) + { + GParamSpec *prop; + + prop = props[i]; + if (prop->owner_type != type) + continue; + + escaped_printf (out, " <property name=\"%s\" type=\"%s\" flags=\"%d\"/>\n", + prop->name, g_type_name (prop->value_type), prop->flags); + } + g_free (props); +} + +static void +dump_signals (GType type, GOutputStream *out) +{ + guint i; + guint n_sigs; + guint *sig_ids; + + sig_ids = g_signal_list_ids (type, &n_sigs); + for (i = 0; i < n_sigs; i++) + { + guint sigid; + GSignalQuery query; + guint j; + + sigid = sig_ids[i]; + g_signal_query (sigid, &query); + + escaped_printf (out, " <signal name=\"%s\" return=\"%s\"", + query.signal_name, g_type_name (query.return_type)); + + if (query.signal_flags & G_SIGNAL_RUN_FIRST) + escaped_printf (out, " when=\"first\""); + else if (query.signal_flags & G_SIGNAL_RUN_LAST) + escaped_printf (out, " when=\"last\""); + else if (query.signal_flags & G_SIGNAL_RUN_CLEANUP) + escaped_printf (out, " when=\"cleanup\""); +#if GLIB_CHECK_VERSION(2, 29, 15) + else if (query.signal_flags & G_SIGNAL_MUST_COLLECT) + escaped_printf (out, " when=\"must-collect\""); +#endif + if (query.signal_flags & G_SIGNAL_NO_RECURSE) + escaped_printf (out, " no-recurse=\"1\""); + + if (query.signal_flags & G_SIGNAL_DETAILED) + escaped_printf (out, " detailed=\"1\""); + + if (query.signal_flags & G_SIGNAL_ACTION) + escaped_printf (out, " action=\"1\""); + + if (query.signal_flags & G_SIGNAL_NO_HOOKS) + escaped_printf (out, " no-hooks=\"1\""); + + goutput_write (out, ">\n"); + + for (j = 0; j < query.n_params; j++) + { + escaped_printf (out, " <param type=\"%s\"/>\n", + g_type_name (query.param_types[j])); + } + goutput_write (out, " </signal>\n"); + } + g_free (sig_ids); +} + +static void +dump_object_type (GType type, const char *symbol, GOutputStream *out) +{ + guint n_interfaces; + guint i; + GType *interfaces; + + escaped_printf (out, " <class name=\"%s\" get-type=\"%s\"", + g_type_name (type), symbol); + if (type != G_TYPE_OBJECT) + { + GString *parent_str; + GType parent; + gboolean first = TRUE; + + parent = g_type_parent (type); + parent_str = g_string_new (""); + while (parent != G_TYPE_INVALID) + { + if (first) + first = FALSE; + else + g_string_append_c (parent_str, ','); + g_string_append (parent_str, g_type_name (parent)); + parent = g_type_parent (parent); + } + + escaped_printf (out, " parents=\"%s\"", parent_str->str); + + g_string_free (parent_str, TRUE); + } + + if (G_TYPE_IS_ABSTRACT (type)) + escaped_printf (out, " abstract=\"1\""); + goutput_write (out, ">\n"); + + interfaces = g_type_interfaces (type, &n_interfaces); + for (i = 0; i < n_interfaces; i++) + { + GType itype = interfaces[i]; + escaped_printf (out, " <implements name=\"%s\"/>\n", + g_type_name (itype)); + } + g_free (interfaces); + + dump_properties (type, out); + dump_signals (type, out); + goutput_write (out, " </class>\n"); +} + +static void +dump_interface_type (GType type, const char *symbol, GOutputStream *out) +{ + guint n_interfaces; + guint i; + GType *interfaces; + + escaped_printf (out, " <interface name=\"%s\" get-type=\"%s\">\n", + g_type_name (type), symbol); + + interfaces = g_type_interface_prerequisites (type, &n_interfaces); + for (i = 0; i < n_interfaces; i++) + { + GType itype = interfaces[i]; + if (itype == G_TYPE_OBJECT) + { + /* Treat this as implicit for now; in theory GInterfaces are + * supported on things like GstMiniObject, but right now + * the introspection system only supports GObject. + * http://bugzilla.gnome.org/show_bug.cgi?id=559706 + */ + continue; + } + escaped_printf (out, " <prerequisite name=\"%s\"/>\n", + g_type_name (itype)); + } + g_free (interfaces); + + dump_properties (type, out); + dump_signals (type, out); + goutput_write (out, " </interface>\n"); +} + +static void +dump_boxed_type (GType type, const char *symbol, GOutputStream *out) +{ + escaped_printf (out, " <boxed name=\"%s\" get-type=\"%s\"/>\n", + g_type_name (type), symbol); +} + +static void +dump_flags_type (GType type, const char *symbol, GOutputStream *out) +{ + guint i; + GFlagsClass *klass; + + klass = g_type_class_ref (type); + escaped_printf (out, " <flags name=\"%s\" get-type=\"%s\">\n", + g_type_name (type), symbol); + + for (i = 0; i < klass->n_values; i++) + { + GFlagsValue *value = &(klass->values[i]); + + escaped_printf (out, " <member name=\"%s\" nick=\"%s\" value=\"%d\"/>\n", + value->value_name, value->value_nick, value->value); + } + goutput_write (out, " </flags>\n"); +} + +static void +dump_enum_type (GType type, const char *symbol, GOutputStream *out) +{ + guint i; + GEnumClass *klass; + + klass = g_type_class_ref (type); + escaped_printf (out, " <enum name=\"%s\" get-type=\"%s\">\n", + g_type_name (type), symbol); + + for (i = 0; i < klass->n_values; i++) + { + GEnumValue *value = &(klass->values[i]); + + escaped_printf (out, " <member name=\"%s\" nick=\"%s\" value=\"%d\"/>\n", + value->value_name, value->value_nick, value->value); + } + goutput_write (out, " </enum>"); +} + +static void +dump_fundamental_type (GType type, const char *symbol, GOutputStream *out) +{ + guint n_interfaces; + guint i; + GType *interfaces; + GString *parent_str; + GType parent; + gboolean first = TRUE; + + + escaped_printf (out, " <fundamental name=\"%s\" get-type=\"%s\"", + g_type_name (type), symbol); + + if (G_TYPE_IS_ABSTRACT (type)) + escaped_printf (out, " abstract=\"1\""); + + if (G_TYPE_IS_INSTANTIATABLE (type)) + escaped_printf (out, " instantiatable=\"1\""); + + parent = g_type_parent (type); + parent_str = g_string_new (""); + while (parent != G_TYPE_INVALID) + { + if (first) + first = FALSE; + else + g_string_append_c (parent_str, ','); + if (!g_type_name (parent)) + break; + g_string_append (parent_str, g_type_name (parent)); + parent = g_type_parent (parent); + } + + if (parent_str->len > 0) + escaped_printf (out, " parents=\"%s\"", parent_str->str); + g_string_free (parent_str, TRUE); + + goutput_write (out, ">\n"); + + interfaces = g_type_interfaces (type, &n_interfaces); + for (i = 0; i < n_interfaces; i++) + { + GType itype = interfaces[i]; + escaped_printf (out, " <implements name=\"%s\"/>\n", + g_type_name (itype)); + } + g_free (interfaces); + goutput_write (out, " </fundamental>\n"); +} + +static void +dump_type (GType type, const char *symbol, GOutputStream *out) +{ + switch (g_type_fundamental (type)) + { + case G_TYPE_OBJECT: + dump_object_type (type, symbol, out); + break; + case G_TYPE_INTERFACE: + dump_interface_type (type, symbol, out); + break; + case G_TYPE_BOXED: + dump_boxed_type (type, symbol, out); + break; + case G_TYPE_FLAGS: + dump_flags_type (type, symbol, out); + break; + case G_TYPE_ENUM: + dump_enum_type (type, symbol, out); + break; + case G_TYPE_POINTER: + /* GValue, etc. Just skip them. */ + break; + default: + dump_fundamental_type (type, symbol, out); + break; + } +} + +static void +dump_error_quark (GQuark quark, const char *symbol, GOutputStream *out) +{ + escaped_printf (out, " <error-quark function=\"%s\" domain=\"%s\"/>\n", + symbol, g_quark_to_string (quark)); +} + +/** + * g_irepository_dump: + * @arg: Comma-separated pair of input and output filenames + * @error: a %GError + * + * Argument specified is a comma-separated pair of filenames; i.e. of + * the form "input.txt,output.xml". The input file should be a + * UTF-8 Unix-line-ending text file, with each line containing either + * "get-type:" followed by the name of a GType _get_type function, or + * "error-quark:" followed by the name of an error quark function. No + * extra whitespace is allowed. + * + * The output file should already exist, but be empty. This function will + * overwrite its contents. + * + * Returns: %TRUE on success, %FALSE on error + */ +#ifndef G_IREPOSITORY_COMPILATION +static gboolean +dump_irepository (const char *arg, GError **error) G_GNUC_UNUSED; +static gboolean +dump_irepository (const char *arg, GError **error) +#else +gboolean +g_irepository_dump (const char *arg, GError **error) +#endif +{ + GHashTable *output_types; + char **args; + GFile *input_file; + GFile *output_file; + GFileInputStream *input; + GFileOutputStream *output; + GDataInputStream *in; + GModule *self; + gboolean caught_error = FALSE; + + self = g_module_open (NULL, 0); + if (!self) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "failed to open self: %s", + g_module_error ()); + return FALSE; + } + + args = g_strsplit (arg, ",", 2); + + input_file = g_file_new_for_path (args[0]); + output_file = g_file_new_for_path (args[1]); + + g_strfreev (args); + + input = g_file_read (input_file, NULL, error); + g_object_unref (input_file); + + if (input == NULL) + { + g_object_unref (output_file); + return FALSE; + } + + output = g_file_replace (output_file, NULL, FALSE, 0, NULL, error); + g_object_unref (output_file); + + if (output == NULL) + { + g_input_stream_close (G_INPUT_STREAM (input), NULL, NULL); + return FALSE; + } + + goutput_write (G_OUTPUT_STREAM (output), "<?xml version=\"1.0\"?>\n"); + goutput_write (G_OUTPUT_STREAM (output), "<dump>\n"); + + output_types = g_hash_table_new (NULL, NULL); + + in = g_data_input_stream_new (G_INPUT_STREAM (input)); + g_object_unref (input); + + while (TRUE) + { + gsize len; + char *line = g_data_input_stream_read_line (in, &len, NULL, NULL); + const char *function; + + if (line == NULL || *line == '\0') + { + g_free (line); + break; + } + + g_strchomp (line); + + if (strncmp (line, "get-type:", strlen ("get-type:")) == 0) + { + GType type; + + function = line + strlen ("get-type:"); + + type = invoke_get_type (self, function, error); + + if (type == G_TYPE_INVALID) + { + g_printerr ("Invalid GType function: '%s'\n", function); + caught_error = TRUE; + g_free (line); + break; + } + + if (g_hash_table_lookup (output_types, (gpointer) type)) + goto next; + g_hash_table_insert (output_types, (gpointer) type, (gpointer) type); + + dump_type (type, function, G_OUTPUT_STREAM (output)); + } + else if (strncmp (line, "error-quark:", strlen ("error-quark:")) == 0) + { + GQuark quark; + function = line + strlen ("error-quark:"); + quark = invoke_error_quark (self, function, error); + + if (quark == 0) + { + g_printerr ("Invalid error quark function: '%s'\n", function); + caught_error = TRUE; + g_free (line); + break; + } + + dump_error_quark (quark, function, G_OUTPUT_STREAM (output)); + } + + + next: + g_free (line); + } + + g_hash_table_destroy (output_types); + + goutput_write (G_OUTPUT_STREAM (output), "</dump>\n"); + + { + GError **ioerror; + /* Avoid overwriting an earlier set error */ + if (caught_error) + ioerror = NULL; + else + ioerror = error; + if (!g_input_stream_close (G_INPUT_STREAM (in), NULL, ioerror)) + return FALSE; + if (!g_output_stream_close (G_OUTPUT_STREAM (output), NULL, ioerror)) + return FALSE; + } + + return !caught_error; +} + + +int +main(int argc, char **argv) +{ + GError *error = NULL; + const char *introspect_dump_prefix = "--introspect-dump="; + +#if !GLIB_CHECK_VERSION(2,35,0) + g_type_init (); +#endif + + + + if (argc != 2 || !g_str_has_prefix (argv[1], introspect_dump_prefix)) + { + g_printerr ("Usage: %s --introspect-dump=input,output", argv[0]); + exit (1); + } + + if (!dump_irepository (argv[1] + strlen(introspect_dump_prefix), &error)) + { + g_printerr ("%s\n", error->message); + exit (1); + } + exit (0); +} +extern GType spice_grab_sequence_get_type(void); +extern GType spice_gtk_session_get_type(void); +extern GType spice_display_key_event_get_type(void); +extern GType spice_display_get_type(void); +extern GType spice_usb_device_widget_get_type(void); +GType (*GI_GET_TYPE_FUNCS_[])(void) = { + spice_grab_sequence_get_type, + spice_gtk_session_get_type, + spice_display_key_event_get_type, + spice_display_get_type, + spice_usb_device_widget_get_type +}; -- 2.5.5 _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/spice-devel