[Spice-devel] [spice-gtk v3 07/16] file-xfer: call user callback once per operation
Victor Toso
victortoso at redhat.com
Sun Jun 5 09:39:20 UTC 2016
Hi,
On Fri, Jun 03, 2016 at 12:12:14PM -0500, Jonathon Jongsma wrote:
> On Mon, 2016-05-30 at 11:55 +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).
> > ---
> > 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;
>
> In general, a GAsyncReadyCallback is expected to call a _finish() function to
> get the error status. Since this is all internal, we can poke around at the
> implementation of the asynchronous task to find the error, but I think I'd
> prefer to provide a _finish() function to follow the conventions.
>
Agreed, I'll improve it
> > +
> > + 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);
>
> since file_xfer_end_callback() is called once for each file in the operation,
> the above line will overwrite xfer_op->error each time it is called. So we'll
> only report the error status of the last file that finished. We probably don't
> want to overwrite an earlier error with a later success.
>
Not really, because the check for xfer_op->error above, right? If we got
an xfer_op->error before, we will return an error in the operation
unless it is the cancelation case handled below, which does
g_clear_error in the end making it null again.
Nevertheless, can be improved.
Should we somehow include the error of each SpiceFileTransferTask in the
end? Let's say, if we have 8 operations and 2 failed. Should we include
in one GError the fact that 2 xfer_task failed? I'm only returning the
first GError so far (if the logic above is correct)
> > +
> > + /* 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);
> > + }
> > + }
> > +
>
> If FileTransferOperation owned a GTask* member, we wouldn't have to store these
> 'cancellable', 'end_callback', and 'end_callback_data' fields separately. I know
> that it's done elsewhere in the code, but it seems a bit odd to create a GTask
> right as the task is done and essentially use it only to trigger the callback.
> Also, the cancellable doesn't do much good if the GTask only lives long enough
> to be created and return ;)
>
Indeed :)
>
> > /* 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_ca
> > llback,
> > xfer_op,
> > - callback,
> > - user_data);
> > + file_xfer_end_call
> > back,
> > + 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);
>
>
> I assume the following file was accidentally committed?
I have no clue how this file got here, haha.
I'll improve this in v4, thanks!
>
> Reviewed-by: Jonathon Jongsma <jjongsma at redhat.com>
>
>
> > 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 at verbum.org>
> > + *
> > + * 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
> > +};
More information about the Spice-devel
mailing list