/* GStreamer base utils library plugin install support for applications
 * Copyright (C) 2007 Tim-Philipp Müller <tim centricular net>
 * Copyright (C) 2006 Ryan Lortie <desrt desrt ca>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

/**
 * SECTION:gstpbutilsinstallplugins
 * @short_description: Missing plugin installation support for applications
 *
 * <refsect2>
 * <title>Overview</title>
 * <para>
 * Using this API, applications can request the installation of missing
 * GStreamer plugins. These may be missing decoders/demuxers or encoders/muxers
 * for a certain format, sources or sinks for a certain URI protocol
 * (e.g. 'http'), or certain elements known by their element factory name
 * ('audioresample').
 * </para>
 * <para>
 * Whether plugin installation is supported or not depends on the operating
 * system and/or distribution in question. The vendor of the operating system
 * needs to make sure the necessary hooks and mechanisms are in place for
 * plugin installation to work. See below for more detailed information.
 * </para>
 * <para>
 * From the application perspective, plugin installation is usually triggered
 * either
 * <itemizedlist>
 * <listitem><para>
 * when the application itself has found that it wants or needs to install a
 * certain element
 * </para></listitem>
 * <listitem><para>
 * when the application has been notified by an element (such as playbin or
 * decodebin) that one or more plugins are missing <emphasis>and</emphasis>
 * the application has decided that it wants to install one or more of those
 * missing plugins
 * </para></listitem>
 * </itemizedlist>
 * </para>
 * <title>Detail Strings</title>
 * <para>
 * The install functions in this section all take one or more 'detail strings'.
 * These detail strings contain information about the type of plugin that
 * needs to be installed (decoder, encoder, source, sink, or named element),
 * and some additional information such GStreamer version used and a
 * human-readable description of the component to install for user dialogs.
 * </para>
 * <para>
 * Applications should not concern themselves with the composition of the
 * string itself. They should regard the string as if it was a shared secret
 * between GStreamer and the plugin installer application.
 * </para>
 * <para>
 * Detail strings can be obtained using the function
 * gst_missing_plugin_message_get_installer_detail() on a missing-plugin
 * message. Such a message will either have been found by the application on
 * a pipeline's #GstBus, or the application will have created it itself using
 * gst_missing_element_message_new(), gst_missing_decoder_message_new(),
 * gst_missing_encoder_message_new(), gst_missing_uri_sink_message_new(), or
 * gst_missing_uri_source_message_new().
 * </para>
 * <title>Plugin Installation from the Application Perspective</title>
 * <para>
 * For each GStreamer element/plugin/component that should be installed, the
 * application needs one of those 'installer detail' string mentioned in the
 * previous section. This string can be obtained, as already mentioned above,
 * from a missing-plugin message using the function
 * gst_missing_plugin_message_get_installer_detail(). The missing-plugin
 * message is either posted by another element and then found on the bus
 * by the application, or the application has created it itself as described
 * above.
 * </para>
 * <para>
 * The application will then call gst_install_plugins_async(), passing a
 * NULL-terminated array of installer detail strings, and a function that
 * should be called when the installation of the plugins has finished
 * (successfully or not). Optionally, a #GstInstallPluginsContext created
 * with gst_install_plugins_context_new() may be passed as well. This way
 * additional optional arguments like the application window's XID can be
 * passed to the external installer application.
 * </para>
 * <para>
 * gst_install_plugins_async() will return almost immediately, with the
 * return code indicating whether plugin installation was started or not.
 * If the necessary hooks for plugin installation are in place and an
 * external installer application has in fact been called, the passed in
 * function will be called with a result code as soon as the external installer
 * has finished. If the result code indicates that new plugins have been
 * installed, the application will want to call gst_update_registry() so the
 * run-time plugin registry is updated and the new plugins are made available
 * to the application.
 * <note>
 * A Gtk/GLib main loop must be running in order for the result function to
 * be called when the external installer has finished. If this is not the case,
 * make sure to regularly call
 * <programlisting>
 * g_main_context_iteration (NULL,FALSE);
 * </programlisting>
 * from your code.
 * </note>
 * </para>
 * <title>Plugin Installation from the Vendor/Distribution Perspective</title>
 * <para>
 * <emphasis>1. Installer hook</emphasis>
 * </para>
 * <para>
 * When GStreamer applications initiate plugin installation via
 * gst_install_plugins_async() or gst_install_plugins_sync(), a pre-defined
 * helper application will be called.
 * </para>
 * <para>
 * The exact path of the helper application to be called is set at compile
 * time, usually by the <literal>./configure</literal> script based on the
 * install prefix. For a normal package build into the <literal>/usr</literal>
 * prefix, this will usually default to
 * <filename>/usr/libexec/gst-install-plugins-helper</filename> or
 * <filename>/usr/lib/gst-install-plugins-helper</filename>.
 * </para>
 * <para>
 * Vendors/distros who want to support GStreamer plugin installation should
 * either provide such a helper script/application or use the
 * <literal>./configure</literal> option
 * <literal>--with-install-plugins-helper=/path/to/installer</literal> to
 * make GStreamer call an installer of their own directly.
 * </para>
 * <para>
 * It is strongly recommended that vendors provide a small helper application
 * as interlocutor to the real installer though, even more so if command line
 * argument munging is required to transform the command line arguments
 * passed by GStreamer to the helper application into arguments that are
 * understood by the real installer.
 * </para>
 * <para>
 * The helper application path defined at compile time can be overriden at
 * runtime by setting the <envar>GST_INSTALL_PLUGINS_HELPER</envar>
 * environment variable. This can be useful for testing/debugging purposes.
 * </para>
 * <para>
 * <emphasis>2. Arguments passed to the install helper</emphasis>
 * </para>
 * <para>
 * GStreamer will pass the following arguments to the install helper (this is
 * in addition to the path of the executable itself, which is by convention
 * argv[0]):
 * <itemizedlist>
 *  <listitem><para>
 *    none to many optional arguments in the form of
 *    <literal>--foo-bar=val</literal>. Example:
 *    <literal>--transient-for=XID</literal> where XID is the X Window ID of
 *    the main window of the calling application (so the installer can make
 *    itself transient to that window). Unknown optional arguments should
 *    be ignored by the installer.
 *  </para></listitem>
 *  <listitem><para>
 *    one 'installer detail string' argument for each plugin to be installed;
 *    these strings will have a <literal>gstreamer</literal> prefix; the
 *    exact format of the detail string is explained below
 *  </para></listitem>
 * </itemizedlist>
 * </para>
 * <para>
 * <emphasis>3. Detail string describing the missing plugin</emphasis>
 * </para>
 * <para>
 * The string is in UTF-8 encoding and is made up of several fields, separated
 * by '|' characters (but neither the first nor the last character is a '|').
 * The fields are:
 * <itemizedlist>
 *   <listitem><para>
 *    plugin system identifier, ie. "gstreamer"
 *   </para><para>
 *    This identifier determines the format of the rest of the detail string.
 *    Automatic plugin installers should not process detail strings with
 *    unknown identifiers. This allows other plugin-based libraries to use
 *    the same mechanism for their automatic plugin installation needs, or
 *    for the format to be changed should it turn out to be insufficient.
 *   </para></listitem>
 *   <listitem><para>
 *    plugin system version, e.g. "0.10"
 *   </para><para>
 *    This is required so that when there is a GStreamer-0.12 or GStreamer-1.0
 *    at some point in future, the different major versions can still co-exist
 *    and use the same plugin install mechanism in the same way.
 *   </para></listitem>
 *   <listitem><para>
 *    application identifier, e.g. "totem"
 *   </para><para>
 *    This may also be in the form of "pid/12345" if the program name can't
 *    be obtained for some reason.
 *   </para></listitem>
 *   <listitem><para>
 *    human-readable localised description of the required component,
 *    e.g. "Vorbis audio decoder"
 *   </para></listitem>
 *   <listitem><para>
 *    identifier string for the required component (see below for details about
 *    how to map this to the package/plugin that needs installing), e.g.
 *    <itemizedlist>
 *     <listitem><para>
 *       urisource-$(PROTOCOL_REQUIRED), e.g. urisource-http or urisource-mms
 *     </para></listitem>
 *     <listitem><para>
 *       element-$(ELEMENT_REQUIRED), e.g. element-videoconvert
 *     </para></listitem>
 *     <listitem><para>
 *       decoder-$(CAPS_REQUIRED), e.g. (do read below for more details!):
 *       <itemizedlist>
 *         <listitem><para>decoder-audio/x-vorbis</para></listitem>
 *         <listitem><para>decoder-application/ogg</para></listitem>
 *         <listitem><para>decoder-audio/mpeg, mpegversion=(int)4</para></listitem>
 *         <listitem><para>decoder-video/mpeg, systemstream=(boolean)true, mpegversion=(int)2</para></listitem>
         </itemizedlist>
 *     </para></listitem>
 *     <listitem><para>
 *       encoder-$(CAPS_REQUIRED), e.g. encoder-audio/x-vorbis
 *     </para></listitem>
 *    </itemizedlist>
 *   </para></listitem>
 *   <listitem><para>
 *     optional further fields not yet specified
 *   </para></listitem>
 * </itemizedlist>
 * </para>
 * <para>
 * An entire ID string might then look like this, for example:
 * <literal>
 * gstreamer|0.10|totem|Vorbis audio decoder|decoder-audio/x-vorbis
 * </literal>
 * </para>
 * <para>
 * Plugin installers parsing this ID string should expect further fields also
 * separated by '|' symbols and either ignore them, warn the user, or error
 * out when encountering them.
 * </para>
 * <para>
 * Those unfamiliar with the GStreamer 'caps' system should note a few things
 * about the caps string used in the above decoder/encoder case:
 *   <itemizedlist>
 *     <listitem><para>
 *       the first part ("video/mpeg") of the caps string is a GStreamer media
 *       type and <emphasis>not</emphasis> a MIME type. Wherever possible, the
 *       GStreamer media type will be the same as the corresponding MIME type,
 *       but often it is not.
 *     </para></listitem>
 *     <listitem><para>
 *       a caps string may or may not have additional comma-separated fields
 *       of various types (as seen in the examples above)
 *     </para></listitem>
 *     <listitem><para>
 *       the caps string of a 'required' component (as above) will always have
 *       fields with fixed values, whereas an introspected string (see below)
 *       may have fields with non-fixed values. Compare for example:
 *       <itemizedlist>
 *         <listitem><para>
 *           <literal>audio/mpeg, mpegversion=(int)4</literal> vs.
 *           <literal>audio/mpeg, mpegversion=(int){2, 4}</literal>
 *         </para></listitem>
 *         <listitem><para>
 *           <literal>video/mpeg, mpegversion=(int)2</literal> vs.
 *           <literal>video/mpeg, systemstream=(boolean){ true, false}, mpegversion=(int)[1, 2]</literal>
 *         </para></listitem>
 *       </itemizedlist>
 *     </para></listitem>
 *   </itemizedlist>
 * </para>
 * <para>
 * <emphasis>4. Exit codes the installer should return</emphasis>
 * </para>
 * <para>
 * The installer should return one of the following exit codes when it exits:
 * <itemizedlist>
 *   <listitem><para>
 *     0 if all of the requested plugins could be installed
 *     (#GST_INSTALL_PLUGINS_SUCCESS)
 *   </para></listitem>
 *   <listitem><para>
 *     1 if no appropriate installation candidate for any of the requested
 *     plugins could be found. Only return this if nothing has been installed
 *     (#GST_INSTALL_PLUGINS_NOT_FOUND)
 *   </para></listitem>
 *   <listitem><para>
 *     2 if an error occured during the installation. The application will
 *     assume that the user will already have seen an error message by the
 *     installer in this case and will usually not show another one
 *     (#GST_INSTALL_PLUGINS_ERROR)
 *   </para></listitem>
 *   <listitem><para>
 *     3 if some of the requested plugins could be installed, but not all
 *     (#GST_INSTALL_PLUGINS_PARTIAL_SUCCESS)
 *   </para></listitem>
 *   <listitem><para>
 *     4 if the user aborted the installation (#GST_INSTALL_PLUGINS_USER_ABORT)
 *   </para></listitem>
 * </itemizedlist>
 * </para>
 * <para>
 * <emphasis>5. How to map the required detail string to packages</emphasis>
 * </para>
 * <para>
 * It is up to the vendor to find mechanism to map required components from
 * the detail string to the actual packages/plugins to install. This could
 * be a hardcoded list of mappings, for example, or be part of the packaging
 * system metadata.
 * </para>
 * <para>
 * GStreamer plugin files can be introspected for this information. The
 * <literal>gst-inspect</literal> utility has a special command line option
 * that will output information similar to what is required. For example
 * <command>
 * $ gst-inspect-1.0 --print-plugin-auto-install-info /path/to/libgstvorbis.so
 * </command>
 * should output something along the lines of
 * <computeroutput>
 * decoder-audio/x-vorbis
 * element-vorbisdec
 * element-vorbisenc
 * element-vorbisparse
 * element-vorbistag
 * encoder-audio/x-vorbis
 * </computeroutput>
 * Note that in the encoder and decoder case the introspected caps can be more
 * complex with additional fields, e.g.
 * <literal>audio/mpeg,mpegversion=(int){2,4}</literal>, so they will not
 * always exactly match the caps wanted by the application. It is up to the
 * installer to deal with this (either by doing proper caps intersection using
 * the GStreamer #GstCaps API, or by only taking into account the media type).
 * </para>
 * <para>
 * Another potential source of problems are plugins such as ladspa or
 * libvisual where the list of elements depends on the installed
 * ladspa/libvisual plugins at the time. This is also up to the distribution
 * to handle (but usually not relevant for playback applications).
 * </para>
 * </refsect2>
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "install-plugins.h"

#include <gst/gstinfo.h>

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif

#ifdef HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif

#include <string.h>

/* best effort to make things compile and possibly even work on win32 */
#ifndef WEXITSTATUS
# define WEXITSTATUS(status) ((((guint)(status)) & 0xff00) >> 8)
#endif
#ifndef WIFEXITED
# define WIFEXITED(status) ((((guint)(status)) & 0x7f) == 0)
#endif

static gboolean install_in_progress;    /* FALSE */

/* private struct */
struct _GstInstallPluginsContext
{
  gchar *confirm_search;
  gchar *desktop_id;
  gchar *startup_notification_id;
  guint xid;
};

/**
 * gst_install_plugins_context_set_confirm_search:
 * @ctx: a #GstInstallPluginsContext
 * @confirm_search: whether to ask for confirmation before searching for plugins
 *
 * This function is used to tell the external installer process whether it
 * should ask for confirmation or not before searching for missing plugins.
 *
 * If set, this option will be passed to the installer via a
 * --interaction=[show-confirm-search|hide-confirm-search] command line option.
 *
 * Since: 1.6
 */
void
gst_install_plugins_context_set_confirm_search (GstInstallPluginsContext * ctx,
    gboolean confirm_search)
{
  g_return_if_fail (ctx != NULL);

  if (confirm_search)
    ctx->confirm_search = g_strdup ("show-confirm-search");
  else
    ctx->confirm_search = g_strdup ("hide-confirm-search");
}

/**
 * gst_install_plugins_context_set_desktop_id:
 * @ctx: a #GstInstallPluginsContext
 * @desktop_id: the desktop file ID of the calling application
 *
 * This function is used to pass the calling application's desktop file ID to
 * the external installer process.
 *
 * A desktop file ID is the basename of the desktop file, including the
 * .desktop extension.
 *
 * If set, the desktop file ID will be passed to the installer via a
 * --desktop-id= command line option.
 *
 * Since: 1.6
 */
void
gst_install_plugins_context_set_desktop_id (GstInstallPluginsContext * ctx,
    const gchar * desktop_id)
{
  g_return_if_fail (ctx != NULL);

  ctx->desktop_id = g_strdup (desktop_id);
}

/**
 * gst_install_plugins_context_set_startup_notification_id:
 * @ctx: a #GstInstallPluginsContext
 * @startup_id: the startup notification ID
 *
 * Sets the startup notification ID for the launched process.
 *
 * This is typically used to to pass the current X11 event timestamp to the
 * external installer process.
 *
 * Startup notification IDs are defined in the
 * [FreeDesktop.Org Startup Notifications standard](http://standards.freedesktop.org/startup-notification-spec/startup-notification-latest.txt).
 *
 * If set, the ID will be passed to the installer via a
 * --startup-notification-id= command line option.
 *
 * GTK+/GNOME applications should be able to create a startup notification ID
 * like this:
 * <programlisting>
 *   timestamp = gtk_get_current_event_time ();
 *   startup_id = g_strdup_printf ("_TIME%u", timestamp);
 * ...
 * </programlisting>
 *
 * Since: 1.6
 */
void gst_install_plugins_context_set_startup_notification_id
    (GstInstallPluginsContext * ctx, const gchar * startup_id)
{
  g_return_if_fail (ctx != NULL);

  ctx->startup_notification_id = g_strdup (startup_id);
}

/**
 * gst_install_plugins_context_set_xid:
 * @ctx: a #GstInstallPluginsContext
 * @xid: the XWindow ID (XID) of the top-level application
 *
 * This function is for X11-based applications (such as most Gtk/Qt
 * applications on linux/unix) only. You can use it to tell the external
 * installer the XID of your main application window. That way the installer
 * can make its own window transient to your application window during the
 * installation.
 *
 * If set, the XID will be passed to the installer via a --transient-for=XID
 * command line option.
 *
 * Gtk+/Gnome application should be able to obtain the XID of the top-level
 * window like this:
 * <programlisting>
 * ##include &lt;gtk/gtk.h&gt;
 * ##ifdef GDK_WINDOWING_X11
 * ##include &lt;gdk/gdkx.h&gt;
 * ##endif
 * ...
 * ##ifdef GDK_WINDOWING_X11
 *   xid = GDK_WINDOW_XWINDOW (GTK_WIDGET (application_window)-&gt;window);
 * ##endif
 * ...
 * </programlisting>
 */
void
gst_install_plugins_context_set_xid (GstInstallPluginsContext * ctx, guint xid)
{
  g_return_if_fail (ctx != NULL);

  ctx->xid = xid;
}

/**
 * gst_install_plugins_context_new:
 *
 * Creates a new #GstInstallPluginsContext.
 *
 * Returns: a new #GstInstallPluginsContext. Free with
 * gst_install_plugins_context_free() when no longer needed
 */
GstInstallPluginsContext *
gst_install_plugins_context_new (void)
{
  return g_new0 (GstInstallPluginsContext, 1);
}

/**
 * gst_install_plugins_context_free:
 * @ctx: a #GstInstallPluginsContext
 *
 * Frees a #GstInstallPluginsContext.
 */
void
gst_install_plugins_context_free (GstInstallPluginsContext * ctx)
{
  g_return_if_fail (ctx != NULL);

  g_free (ctx->confirm_search);
  g_free (ctx->desktop_id);
  g_free (ctx->startup_notification_id);
  g_free (ctx);
}

static GstInstallPluginsContext *
gst_install_plugins_context_copy (GstInstallPluginsContext * ctx)
{
  GstInstallPluginsContext *ret;

  ret = gst_install_plugins_context_new ();
  ret->confirm_search = g_strdup (ctx->confirm_search);
  ret->desktop_id = g_strdup (ctx->desktop_id);
  ret->startup_notification_id = g_strdup (ctx->startup_notification_id);
  ret->xid = ctx->xid;

  return ret;
}

G_DEFINE_BOXED_TYPE (GstInstallPluginsContext, gst_install_plugins_context,
    (GBoxedCopyFunc) gst_install_plugins_context_copy,
    (GBoxedFreeFunc) gst_install_plugins_context_free);

static const gchar *
gst_install_plugins_get_helper (void)
{
  const gchar *helper;

  helper = g_getenv ("GST_INSTALL_PLUGINS_HELPER");
  if (helper == NULL)
    helper = GST_INSTALL_PLUGINS_HELPER;

  GST_LOG ("Using plugin install helper '%s'", helper);
  return helper;
}

static gboolean
ptr_array_contains_string (GPtrArray * arr, const gchar * s)
{
  gint i;

  for (i = 0; i < arr->len; ++i) {
    if (strcmp ((const char *) g_ptr_array_index (arr, i), s) == 0)
      return TRUE;
  }
  return FALSE;
}

static gboolean
gst_install_plugins_spawn_child (const gchar * const *details,
    GstInstallPluginsContext * ctx, GPid * child_pid, gint * exit_status)
{
  GPtrArray *arr;
  gboolean ret;
  GError *err = NULL;
  gchar **argv;

  arr = g_ptr_array_new_with_free_func (g_free);

  /* argv[0] = helper path */
  g_ptr_array_add (arr, g_strdup (gst_install_plugins_get_helper ()));

  /* add any additional command line args from the context */
  if (ctx != NULL && ctx->confirm_search) {
    g_ptr_array_add (arr, g_strdup_printf ("--interaction=%s",
            ctx->confirm_search));
  }
  if (ctx != NULL && ctx->desktop_id != NULL) {
    g_ptr_array_add (arr, g_strdup_printf ("--desktop-id=%s", ctx->desktop_id));
  }
  if (ctx != NULL && ctx->startup_notification_id != NULL) {
    g_ptr_array_add (arr, g_strdup_printf ("--startup-notification-id=%s",
            ctx->startup_notification_id));
  }
  if (ctx != NULL && ctx->xid != 0) {
    g_ptr_array_add (arr, g_strdup_printf ("--transient-for=%u", ctx->xid));
  }

  /* finally, add the detail strings, but without duplicates */
  while (details != NULL && details[0] != NULL) {
    if (!ptr_array_contains_string (arr, details[0]))
      g_ptr_array_add (arr, g_strdup (details[0]));
    ++details;
  }

  /* and NULL-terminate */
  g_ptr_array_add (arr, NULL);

  argv = (gchar **) arr->pdata;

  if (child_pid == NULL && exit_status != NULL) {
    install_in_progress = TRUE;
    ret = g_spawn_sync (NULL, argv, NULL, (GSpawnFlags) 0, NULL, NULL,
        NULL, NULL, exit_status, &err);
    install_in_progress = FALSE;
  } else if (child_pid != NULL && exit_status == NULL) {
    install_in_progress = TRUE;
    ret = g_spawn_async (NULL, argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD, NULL,
        NULL, child_pid, &err);
  } else {
    g_return_val_if_reached (FALSE);
  }

  if (!ret) {
    GST_ERROR ("Error spawning plugin install helper: %s", err->message);
    g_error_free (err);
  }

  g_ptr_array_unref (arr);
  return ret;
}

static GstInstallPluginsReturn
gst_install_plugins_return_from_status (gint status)
{
  GstInstallPluginsReturn ret;

  /* did we exit cleanly? */
  if (!WIFEXITED (status)) {
    ret = GST_INSTALL_PLUGINS_CRASHED;
  } else {
    ret = (GstInstallPluginsReturn) WEXITSTATUS (status);

    /* did the helper return an invalid status code? */
    if (((guint) ret) >= GST_INSTALL_PLUGINS_STARTED_OK &&
        ret != GST_INSTALL_PLUGINS_INTERNAL_FAILURE) {
      ret = GST_INSTALL_PLUGINS_INVALID;
    }
  }

  GST_LOG ("plugin installer exited with status 0x%04x = %s", status,
      gst_install_plugins_return_get_name (ret));

  return ret;
}

typedef struct
{
  GstInstallPluginsResultFunc func;
  gpointer user_data;
} GstInstallPluginsAsyncHelper;

static void
gst_install_plugins_installer_exited (GPid pid, gint status, gpointer data)
{
  GstInstallPluginsAsyncHelper *helper;
  GstInstallPluginsReturn ret;

  install_in_progress = FALSE;

  helper = (GstInstallPluginsAsyncHelper *) data;
  ret = gst_install_plugins_return_from_status (status);

  GST_LOG ("calling plugin install result function %p", helper->func);
  helper->func (ret, helper->user_data);

  g_free (helper);
}

/**
 * gst_install_plugins_async:
 * @details: (array zero-terminated=1) (transfer none): NULL-terminated array
 *     of installer string details (see below)
 * @ctx: (allow-none): a #GstInstallPluginsContext, or NULL
 * @func: (scope async): the function to call when the installer program returns
 * @user_data: (closure): the user data to pass to @func when called, or NULL
 * 
 * Requests plugin installation without blocking. Once the plugins have been
 * installed or installation has failed, @func will be called with the result
 * of the installation and your provided @user_data pointer.
 *
 * This function requires a running GLib/Gtk main loop. If you are not
 * running a GLib/Gtk main loop, make sure to regularly call
 * g_main_context_iteration(NULL,FALSE).
 *
 * The installer strings that make up @detail are typically obtained by
 * calling gst_missing_plugin_message_get_installer_detail() on missing-plugin
 * messages that have been caught on a pipeline's bus or created by the
 * application via the provided API, such as gst_missing_element_message_new().
 *
 * It is possible to request the installation of multiple missing plugins in
 * one go (as might be required if there is a demuxer for a certain format
 * installed but no suitable video decoder and no suitable audio decoder).
 *
 * Returns: result code whether an external installer could be started
 */

GstInstallPluginsReturn
gst_install_plugins_async (const gchar * const *details,
    GstInstallPluginsContext * ctx, GstInstallPluginsResultFunc func,
    gpointer user_data)
{
  GstInstallPluginsAsyncHelper *helper;
  GPid pid;

  g_return_val_if_fail (details != NULL, GST_INSTALL_PLUGINS_INTERNAL_FAILURE);
  g_return_val_if_fail (func != NULL, GST_INSTALL_PLUGINS_INTERNAL_FAILURE);

  if (install_in_progress)
    return GST_INSTALL_PLUGINS_INSTALL_IN_PROGRESS;

  /* if we can't access our helper, don't bother */
  if (!g_file_test (gst_install_plugins_get_helper (),
          G_FILE_TEST_IS_EXECUTABLE))
    return GST_INSTALL_PLUGINS_HELPER_MISSING;

  if (!gst_install_plugins_spawn_child (details, ctx, &pid, NULL))
    return GST_INSTALL_PLUGINS_INTERNAL_FAILURE;

  helper = g_new (GstInstallPluginsAsyncHelper, 1);
  helper->func = func;
  helper->user_data = user_data;

  g_child_watch_add (pid, gst_install_plugins_installer_exited, helper);

  return GST_INSTALL_PLUGINS_STARTED_OK;
}

/**
 * gst_install_plugins_sync:
 * @details: (array zero-terminated=1) (transfer none): NULL-terminated array
 *     of installer string details
 * @ctx: (allow-none): a #GstInstallPluginsContext, or NULL
 * 
 * Requests plugin installation and block until the plugins have been
 * installed or installation has failed.
 *
 * This function should almost never be used, it only exists for cases where
 * a non-GLib main loop is running and the user wants to run it in a separate
 * thread and marshal the result back asynchronously into the main thread
 * using the other non-GLib main loop. You should almost always use
 * gst_install_plugins_async() instead of this function.
 *
 * Returns: the result of the installation.
 */
GstInstallPluginsReturn
gst_install_plugins_sync (const gchar * const *details,
    GstInstallPluginsContext * ctx)
{
  gint status;

  g_return_val_if_fail (details != NULL, GST_INSTALL_PLUGINS_INTERNAL_FAILURE);

  if (install_in_progress)
    return GST_INSTALL_PLUGINS_INSTALL_IN_PROGRESS;

  /* if we can't access our helper, don't bother */
  if (!g_file_test (gst_install_plugins_get_helper (),
          G_FILE_TEST_IS_EXECUTABLE))
    return GST_INSTALL_PLUGINS_HELPER_MISSING;

  if (!gst_install_plugins_spawn_child (details, ctx, NULL, &status))
    return GST_INSTALL_PLUGINS_INTERNAL_FAILURE;

  return gst_install_plugins_return_from_status (status);
}

/**
 * gst_install_plugins_return_get_name:
 * @ret: the return status code
 * 
 * Convenience function to return the descriptive string associated
 * with a status code.  This function returns English strings and
 * should not be used for user messages. It is here only to assist
 * in debugging.
 *
 * Returns: a descriptive string for the status code in @ret
 */
const gchar *
gst_install_plugins_return_get_name (GstInstallPluginsReturn ret)
{
  switch (ret) {
    case GST_INSTALL_PLUGINS_SUCCESS:
      return "success";
    case GST_INSTALL_PLUGINS_NOT_FOUND:
      return "not-found";
    case GST_INSTALL_PLUGINS_ERROR:
      return "install-error";
    case GST_INSTALL_PLUGINS_CRASHED:
      return "installer-exit-unclean";
    case GST_INSTALL_PLUGINS_PARTIAL_SUCCESS:
      return "partial-success";
    case GST_INSTALL_PLUGINS_USER_ABORT:
      return "user-abort";
    case GST_INSTALL_PLUGINS_STARTED_OK:
      return "started-ok";
    case GST_INSTALL_PLUGINS_INTERNAL_FAILURE:
      return "internal-failure";
    case GST_INSTALL_PLUGINS_HELPER_MISSING:
      return "helper-missing";
    case GST_INSTALL_PLUGINS_INSTALL_IN_PROGRESS:
      return "install-in-progress";
    case GST_INSTALL_PLUGINS_INVALID:
      return "invalid";
    default:
      break;
  }
  return "(UNKNOWN)";
}

/**
 * gst_install_plugins_installation_in_progress:
 * 
 * Checks whether plugin installation (initiated by this application only)
 * is currently in progress.
 *
 * Returns: TRUE if plugin installation is in progress, otherwise FALSE
 */
gboolean
gst_install_plugins_installation_in_progress (void)
{
  return install_in_progress;
}

/**
 * gst_install_plugins_supported:
 * 
 * Checks whether plugin installation is likely to be supported by the
 * current environment. This currently only checks whether the helper script
 * that is to be provided by the distribution or operating system vendor
 * exists.
 *
 * Returns: TRUE if plugin installation is likely to be supported.
 */
gboolean
gst_install_plugins_supported (void)
{
  return g_file_test (gst_install_plugins_get_helper (),
      G_FILE_TEST_IS_EXECUTABLE);
}
