[Spice-devel] [spice-gtk v3 07/16] file-xfer: call user callback once per operation

Victor Toso victortoso at redhat.com
Mon May 30 09:55:03 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).
---
 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 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
+};
-- 
2.5.5



More information about the Spice-devel mailing list