Index: configure.ac
===================================================================
RCS file: /cvs/gstreamer/gst-plugins-base/configure.ac,v
retrieving revision 1.666
diff -u -p -r1.666 configure.ac
--- configure.ac	9 Jan 2007 14:20:07 -0000	1.666
+++ configure.ac	28 Jan 2007 12:25:08 -0000
@@ -182,8 +182,8 @@ AC_CHECK_HEADERS([sys/socket.h], 
   HAVE_SYS_SOCKET_H="yes", HAVE_SYS_SOCKET_H="no")
 AM_CONDITIONAL(HAVE_SYS_SOCKET_H, test "x$HAVE_SYS_SOCKET_H" = "xyes")
 
-dnl used in gst-libs/gst/utils
-AC_CHECK_HEADERS([process.h])
+dnl used in gst-libs/gst/utils and associated unit test
+AC_CHECK_HEADERS([process.h sys/types.h sys/wait.h sys/stat.h])
 
 dnl ffmpegcolorspace includes _stdint.h
 dnl also, Windows does not have long long
Index: gst-libs/gst/utils/base-utils.h
===================================================================
RCS file: /cvs/gstreamer/gst-plugins-base/gst-libs/gst/utils/base-utils.h,v
retrieving revision 1.1
diff -u -p -r1.1 base-utils.h
--- gst-libs/gst/utils/base-utils.h	9 Jan 2007 14:20:08 -0000	1.1
+++ gst-libs/gst/utils/base-utils.h	28 Jan 2007 12:25:08 -0000
@@ -24,6 +24,7 @@
 
 #include <gst/utils/descriptions.h>
 #include <gst/utils/missing-plugins.h>
+#include <gst/utils/install-plugins.h>
 
 G_BEGIN_DECLS
 
Index: gst-libs/gst/utils/Makefile.am
===================================================================
RCS file: /cvs/gstreamer/gst-plugins-base/gst-libs/gst/utils/Makefile.am,v
retrieving revision 1.1
diff -u -p -r1.1 Makefile.am
--- gst-libs/gst/utils/Makefile.am	9 Jan 2007 14:20:08 -0000	1.1
+++ gst-libs/gst/utils/Makefile.am	28 Jan 2007 12:25:08 -0000
@@ -5,6 +5,8 @@ libgstbaseutils_@GST_MAJORMINOR@_la_SOUR
 	base-utils.h      \
 	descriptions.c    \
 	descriptions.h    \
+	install-plugins.c \
+	install-plugins.h \
 	missing-plugins.c \
 	missing-plugins.h
 
@@ -12,6 +14,7 @@ libgstbaseutils_@GST_MAJORMINOR@included
 libgstbaseutils_@GST_MAJORMINOR@include_HEADERS = \
 	base-utils.h      \
 	descriptions.h    \
+	install-plugins.h \
 	missing-plugins.h
 
 libgstbaseutils_@GST_MAJORMINOR@_la_LIBADD = $(GST_LIBS)
*** /dev/null	2006-12-24 14:33:46.000000000 +0000
--- gst-libs/gst/utils/install-plugins.h	2007-01-27 21:10:48.000000000 +0000
***************
*** 0 ****
--- 1,103 ----
+ /* 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., 59 Temple Place - Suite 330,
+  * Boston, MA 02111-1307, USA.
+  */
+ 
+ #ifndef __GST_BASE_UTILS_INSTALL_PLUGINSS_H__
+ #define __GST_BASE_UTILS_INSTALL_PLUGINSS_H__
+ 
+ #include <glib.h>
+ 
+ G_BEGIN_DECLS
+ 
+ /*
+  * functions for use by applications to initiate installation of missing plugins
+  */
+ 
+ /**
+  * GstInstallPluginsReturn:
+  * @GST_INSTALL_PLUGINS_SUCCESS: all of the requested plugins could be installed
+  * @GST_INSTALL_PLUGINS_NOT_FOUND: no appropriate installation candidate for
+  *     any of the requested plugins could be found
+  * @GST_INSTALL_PLUGINS_ERROR: an error occured during the installation. If
+  *     this happens, the  user has already seen an error message and another
+  *     one should not be displayed
+  * @GST_INSTALL_PLUGINS_CRASHED: the installer had an unclean exit code
+  *     (ie. death by signal)
+  * @GST_INSTALL_PLUGINS_PARTIAL_SUCCESS: some of the requested plugins could
+  *     be installed, but not all
+  * @GST_INSTALL_PLUGINS_USER_ABORT: the user has aborted the installation
+  * @GST_INSTALL_PLUGINS_STARTED_OK: returned by gst_install_plugins_async() to
+  *     indicate that everything went fine so far and the provided callback
+  *     will be called with the result of the installation later
+  * @GST_INSTALL_PLUGINS_INTERNAL_FAILURE: some internal failure has
+  *     occured when trying to start the installer
+  * @GST_INSTALL_PLUGINS_HELPER_MISSING: the helper script to call the
+  *     actual installer is not installed
+  * @GST_INSTALL_PLUGINS_INSTALL_IN_PROGRESS: a previously-started plugin
+  *     installation is still in progress, try again later
+  * @GST_INSTALL_PLUGINS_INVALID: the helper returned an invalid status code
+  *
+  * Result codes returned by gst_install_plugins_async() and
+  * gst_install_plugin_sync(). These indicate success or failure of starting
+  * an external installer program and to what extent the requested plugins
+  * could be installed.
+  */
+ typedef enum {
+   /* Return codes from the installer */
+   GST_INSTALL_PLUGINS_SUCCESS = 0,
+   GST_INSTALL_PLUGINS_NOT_FOUND,
+   GST_INSTALL_PLUGINS_ERROR,
+   GST_INSTALL_PLUGINS_CRASHED,
+   GST_INSTALL_PLUGINS_PARTIAL_SUCCESS,
+   GST_INSTALL_PLUGINS_USER_ABORT,
+ 
+   /* Return codes from starting the external helper */
+   GST_INSTALL_PLUGINS_STARTED_OK = 32,
+   GST_INSTALL_PLUGINS_INTERNAL_FAILURE,
+   GST_INSTALL_PLUGINS_HELPER_MISSING,
+   GST_INSTALL_PLUGINS_INSTALL_IN_PROGRESS,
+   GST_INSTALL_PLUGINS_INVALID
+ } GstInstallPluginsReturn;
+ 
+ /**
+  * GstInstallPluginsResultFunc:
+  * @result: whether the installation of the requested plugins succeeded or not
+  * @user_data: the user data passed to gst_install_plugins_async()
+  *
+  * The prototype of the callback function that will be called once the
+  * external plugin installer program has returned. You only need to provide
+  * a callback function if you are using the asynchronous interface.
+  */
+ typedef void (*GstInstallPluginsResultFunc) (GstInstallPluginsReturn  result,
+                                              gpointer                 user_data);
+ 
+ GstInstallPluginsReturn  gst_install_plugins_async (gchar          ** details,
+                                                     guint             xid,
+                                                     GstInstallPluginsResultFunc func,
+                                                     gpointer          user_data);
+ 
+ GstInstallPluginsReturn  gst_install_plugins_sync  (gchar ** details,
+                                                     guint    xid);
+ 
+ const gchar * gst_install_plugins_return_get_name (GstInstallPluginsReturn ret);
+ 
+ G_END_DECLS
+ 
+ #endif /* __GST_BASE_UTILS_INSTALL_PLUGINSS_H__ */
+ 
*** /dev/null	2006-12-24 14:33:46.000000000 +0000
--- gst-libs/gst/utils/install-plugins.c	2007-01-27 21:12:44.000000000 +0000
***************
*** 0 ****
--- 1,271 ----
+ /* 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., 59 Temple Place - Suite 330,
+  * Boston, MA 02111-1307, USA.
+  */
+ 
+ #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
+ 
+ /* 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 */
+ 
+ /* FIXME: this should probably be configurable, for now just hard-code it */
+ #define GST_INSTALL_PLUGINS_HELPER_PATH "/usr/lib/gstreamer-plugin-install-helper"
+ 
+ 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_PATH;
+ 
+   GST_LOG ("Using plugin install helper '%s'", helper);
+   return helper;
+ }
+ 
+ static gboolean
+ gst_install_plugins_spawn_child (gchar ** details, guint xid, GPid * child_pid,
+     gint * exit_status)
+ {
+   GError *err = NULL;
+   gboolean ret;
+   gchar **argv, xid_str[30];
+   guint i, num_details;
+ 
+   num_details = g_strv_length (details);
+ 
+   /* +3 to make room for the argv[0] (program name) and arg[1] (xid)
+    * and because the list must be null terminated */
+   argv = g_new0 (gchar *, num_details + 3);
+ 
+   g_snprintf (xid_str, sizeof (xid_str), "%u", xid);
+ 
+   argv[0] = (gchar *) gst_install_plugins_get_helper ();
+   argv[1] = xid_str;
+ 
+   for (i = 0; i < num_details; ++i)
+     argv[2+i] = details[i];
+ 
+   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_assert_not_reached ();
+   }
+ 
+   if (!ret) {
+     GST_WARNING ("Error spawning plugin install helper: %s", err->message);
+     g_error_free (err);
+   }
+ 
+   g_free (argv);    /* yes, non-deep free here */
+   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 ((ret < 0 || ret >= GST_INSTALL_PLUGINS_STARTED_OK) &&
+       ret != GST_INSTALL_PLUGINS_INTERNAL_FAILURE) {
+       ret = GST_INSTALL_PLUGINS_INVALID;
+     }
+   }
+ 
+   GST_LOG ("plugin installer exited with status %02x => %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: NULL-terminated array of installer string details
+  * @xid: the XID of the top-level application window, or 0
+  * @func: the function to call when the installer program returns
+  * @user_data: 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.
+  *
+  * Returns: result code whether an external installer could be started
+  */
+ 
+ GstInstallPluginsReturn 
+ gst_install_plugins_async (gchar ** details, guint xid,
+     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, xid, &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: NULL-terminated array of installer string details
+  * @xid: the XID of the top-level application window, or 0
+  * 
+  * Requests plugin installation and block until the plugins have been
+  * installed or installation has failed.
+  *
+  * Returns: the result of the installation.
+  */
+ GstInstallPluginsReturn
+ gst_install_plugins_sync (gchar ** details, guint xid)
+ {
+   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, xid, 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)";
+ }
+ 
Index: tests/check/libs/utils.c
===================================================================
RCS file: /cvs/gstreamer/gst-plugins-base/tests/check/libs/utils.c,v
retrieving revision 1.1
diff -u -p -r1.1 utils.c
--- tests/check/libs/utils.c	9 Jan 2007 14:20:08 -0000	1.1
+++ tests/check/libs/utils.c	28 Jan 2007 12:25:37 -0000
@@ -24,7 +24,22 @@
 
 #include <gst/check/gstcheck.h>
 #include <gst/utils/base-utils.h>
-#include <unistd.h>
+
+#include <stdio.h>
+#include <glib/gstdio.h>
+#include <glib/gprintf.h>
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>  /* for unlink() */
+#endif
+
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h> /* for fchmod() */
+#endif
+
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h> /* for fchmod() */
+#endif
 
 static void
 missing_msg_check_getters (GstMessage * msg)
@@ -400,6 +415,8 @@ GST_START_TEST (test_base_utils_get_code
 }
 
 GST_END_TEST;
+
+
 GST_START_TEST (test_base_utils_taglist_add_codec_info)
 {
   GstTagList *list;
@@ -430,6 +447,109 @@ GST_START_TEST (test_base_utils_taglist_
 }
 
 GST_END_TEST;
+
+static gint marker;
+
+static void
+result_cb (GstInstallPluginsReturn result, gpointer user_data)
+{
+  GST_LOG ("result = %u, user_data = %p", result, user_data);
+
+  fail_unless (user_data == (gpointer) &marker);
+
+  marker = result;
+}
+
+GST_START_TEST (test_base_utils_install_plugins)
+{
+  GstInstallPluginsReturn ret;
+  gchar *details[] = { "detail1", "detail2" };
+
+  ASSERT_CRITICAL(
+    ret = gst_install_plugins_sync (NULL, 99);
+  );
+  ASSERT_CRITICAL(
+    ret = gst_install_plugins_async (NULL, 0, result_cb, (gpointer) &marker);
+  );
+  ASSERT_CRITICAL(
+    ret = gst_install_plugins_async (details, 0, NULL, (gpointer) &marker);
+  );
+
+  /* make sure the functions return the right error code if the helper does
+   * not exist */
+  g_setenv ("GST_INSTALL_PLUGINS_HELPER", "/does/not/ex/is.t", 1);
+  ret = gst_install_plugins_sync (details, 0);
+  fail_unless_equals_int (ret, GST_INSTALL_PLUGINS_HELPER_MISSING);
+
+  marker = -333;
+  ret = gst_install_plugins_async (details, 0, result_cb, (gpointer) &marker);
+  fail_unless_equals_int (ret, GST_INSTALL_PLUGINS_HELPER_MISSING);
+  /* and check that the callback wasn't called */
+  fail_unless_equals_int (marker, -333);
+
+  /* make sure our script gets called with the right parameters */
+#ifdef G_OS_UNIX
+#define FAKE_INSTALL_PLUGINS_HELPER "/tmp/gst-plugins-base-unit-test-helper"
+#define FAKE_INSTALL_PLUGINS_SCRIPT \
+    "#!/bin/sh\n"                                  \
+    "if test x$1 != x42; then exit 21; fi;\n"      \
+    "if test x$2 != xdetail1; then exit 22; fi;\n" \
+    "if test x$3 != xdetail2; then exit 23; fi;\n" \
+    "exit 2\n"
+  {
+    FILE *f;
+
+    if ((f = g_fopen (FAKE_INSTALL_PLUGINS_HELPER, "w"))) {
+      if (g_fprintf (f, "%s", FAKE_INSTALL_PLUGINS_SCRIPT) > 0 &&
+          fchmod (fileno (f),  S_IRUSR | S_IWUSR | S_IXUSR) == 0) {
+        fclose (f);
+        g_setenv ("GST_INSTALL_PLUGINS_HELPER", FAKE_INSTALL_PLUGINS_HELPER, 1);
+
+        /* test sync callout */
+        ret = gst_install_plugins_sync (details, 42);
+        fail_unless (ret == GST_INSTALL_PLUGINS_HELPER_MISSING || ret == 2,
+            "gst_install_plugins_sync() failed with unexpected ret %d", ret);
+
+        /* test async callout */
+        marker = -333;
+        ret = gst_install_plugins_async (details, 42, result_cb,
+            (gpointer) &marker);
+        fail_unless (ret == GST_INSTALL_PLUGINS_HELPER_MISSING ||
+            ret == GST_INSTALL_PLUGINS_STARTED_OK,
+            "gst_install_plugins_async() failed with unexpected ret %d", ret);
+        if (ret == GST_INSTALL_PLUGINS_STARTED_OK) {
+          while (marker == -333) {
+            g_usleep (500);
+            g_main_context_iteration (NULL, FALSE);
+          }
+          /* and check that the callback was called with the expected code */
+          fail_unless_equals_int (marker, 2);
+        }
+      } else {
+        fclose (f);
+      }
+      unlink (FAKE_INSTALL_PLUGINS_HELPER);
+    }
+  }
+#endif /* G_OS_UNIX */
+
+  /* completely silly test to check gst_install_plugins_return_get_name()
+   * is somewhat well-behaved */
+  {
+    gint i;
+
+    for (i = -99; i < 16738; ++i) {
+      const gchar *s;
+
+      s = gst_install_plugins_return_get_name ((GstInstallPluginsReturn) i);
+      fail_unless (s != NULL);
+      /* GST_LOG ("%5d = %s", i, s); */
+    }
+  }
+}
+
+GST_END_TEST;
+
 static Suite *
 libgstbaseutils_suite (void)
 {
@@ -441,6 +561,7 @@ libgstbaseutils_suite (void)
   tcase_add_test (tc_chain, test_base_utils_post_missing_messages);
   tcase_add_test (tc_chain, test_base_utils_taglist_add_codec_info);
   tcase_add_test (tc_chain, test_base_utils_get_codec_description);
+  tcase_add_test (tc_chain, test_base_utils_install_plugins);
   return s;
 }
 
