[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