From 17a02da2fdb13c0130d1bbde95a162570f34dbbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Fri, 2 Feb 2007 20:42:08 +0000 Subject: [PATCH] gst-libs/gst/utils/: API: add API for applications to initiate installation of missing plugins, ie. gst_install_plugi... Original commit message from CVS: * gst-libs/gst/utils/Makefile.am: * gst-libs/gst/utils/base-utils.h: * gst-libs/gst/utils/install-plugins.c: (gst_install_plugins_context_set_xid), (gst_install_plugins_context_new), (gst_install_plugins_context_free), (gst_install_plugins_get_helper), (gst_install_plugins_spawn_child), (gst_install_plugins_return_from_status), (gst_install_plugins_installer_exited), (gst_install_plugins_async), (gst_install_plugins_sync), (gst_install_plugins_return_get_name), (gst_install_plugins_installation_in_progress): * gst-libs/gst/utils/install-plugins.h: API: add API for applications to initiate installation of missing plugins, ie. gst_install_plugins_async() primarily. Based on libgimme-codec by Ryan Lortie. * configure.ac: Add --with-install-plugins-helper configure option so distros can specify the path of the helper script or program to call when plugin installation is requested (distros: please do any argument munging in this helper script instead of patching GStreamer to pass arguments differently to another program directly). * docs/libs/gst-plugins-base-libs-docs.sgml: * docs/libs/gst-plugins-base-libs-sections.txt: Build and document new API. * tests/check/libs/utils.c: (result_cb), (test_base_utils_install_plugins_do_callout), (GST_START_TEST), (libgstbaseutils_suite): Some simple checks for the new API. --- ChangeLog | 36 ++ configure.ac | 26 +- docs/libs/gst-plugins-base-libs-docs.sgml | 2 + docs/libs/gst-plugins-base-libs-sections.txt | 17 + gst-libs/gst/pbutils/Makefile.am | 3 + gst-libs/gst/pbutils/install-plugins.c | 388 +++++++++++++++++++ gst-libs/gst/pbutils/install-plugins.h | 137 +++++++ gst-libs/gst/pbutils/pbutils.h | 1 + gst-libs/gst/utils/Makefile.am | 3 + gst-libs/gst/utils/base-utils.h | 1 + gst-libs/gst/utils/install-plugins.c | 388 +++++++++++++++++++ gst-libs/gst/utils/install-plugins.h | 137 +++++++ tests/check/libs/pbutils.c | 155 +++++++- tests/check/libs/utils.c | 155 +++++++- 14 files changed, 1445 insertions(+), 4 deletions(-) create mode 100644 gst-libs/gst/pbutils/install-plugins.c create mode 100644 gst-libs/gst/pbutils/install-plugins.h create mode 100644 gst-libs/gst/utils/install-plugins.c create mode 100644 gst-libs/gst/utils/install-plugins.h diff --git a/ChangeLog b/ChangeLog index 7f4891b712..112e9fdaf8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,39 @@ +2007-02-02 Tim-Philipp Müller + + * gst-libs/gst/utils/Makefile.am: + * gst-libs/gst/utils/base-utils.h: + * gst-libs/gst/utils/install-plugins.c: + (gst_install_plugins_context_set_xid), + (gst_install_plugins_context_new), + (gst_install_plugins_context_free), + (gst_install_plugins_get_helper), + (gst_install_plugins_spawn_child), + (gst_install_plugins_return_from_status), + (gst_install_plugins_installer_exited), + (gst_install_plugins_async), (gst_install_plugins_sync), + (gst_install_plugins_return_get_name), + (gst_install_plugins_installation_in_progress): + * gst-libs/gst/utils/install-plugins.h: + API: add API for applications to initiate installation of missing + plugins, ie. gst_install_plugins_async() primarily. + Based on libgimme-codec by Ryan Lortie. + + * configure.ac: + Add --with-install-plugins-helper configure option so distros can specify + the path of the helper script or program to call when plugin installation + is requested (distros: please do any argument munging in this helper + script instead of patching GStreamer to pass arguments differently + to another program directly). + + * docs/libs/gst-plugins-base-libs-docs.sgml: + * docs/libs/gst-plugins-base-libs-sections.txt: + Build and document new API. + + * tests/check/libs/utils.c: (result_cb), + (test_base_utils_install_plugins_do_callout), (GST_START_TEST), + (libgstbaseutils_suite): + Some simple checks for the new API. + 2007-02-02 Tim-Philipp Müller * tests/check/elements/audioconvert.c: (test_float_conversion): diff --git a/configure.ac b/configure.ac index 5ddd0a779e..b941b2ab43 100644 --- a/configure.ac +++ b/configure.ac @@ -81,6 +81,28 @@ GST_ARG_WITH_PKG_CONFIG_PATH GST_ARG_WITH_PACKAGE_NAME GST_ARG_WITH_PACKAGE_ORIGIN +dnl let distro override plugin install helper path +AC_ARG_WITH(install-plugins-helper, + AC_HELP_STRING([--with-install-plugins-helper], + [specify path of helper script to call to install plugins]), + [ + case "${withval}" in + yes) AC_MSG_ERROR(bad value ${withval} for --with-install-plugins-helper) ;; + no) AC_MSG_ERROR(bad value ${withval} for --with-install-plugins-helper) ;; + *) GST_INSTALL_PLUGINS_HELPER="${withval}" ;; + esac + ], + [ + dnl Default value + AS_AC_EXPAND(GST_INSTALL_PLUGINS_HELPER,${libexecdir}/gst-install-plugins-helper) + ] +) +AC_MSG_NOTICE(Using $GST_INSTALL_PLUGINS_HELPER as plugin install helper) +AC_DEFINE_UNQUOTED(GST_INSTALL_PLUGINS_HELPER, "$GST_INSTALL_PLUGINS_HELPER", + [plugin install helper script]) +AC_SUBST(GST_INSTALL_PLUGINS_HELPER) + + dnl these are all the gst plug-ins, compilable without additional libs GST_PLUGINS_ALL="\ adder \ @@ -182,8 +204,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 diff --git a/docs/libs/gst-plugins-base-libs-docs.sgml b/docs/libs/gst-plugins-base-libs-docs.sgml index 79ff2e2604..8fa84cbb5a 100644 --- a/docs/libs/gst-plugins-base-libs-docs.sgml +++ b/docs/libs/gst-plugins-base-libs-docs.sgml @@ -49,6 +49,7 @@ + @@ -188,6 +189,7 @@ &GstBaseUtils; &GstBaseUtilsDescriptions; &GstBaseUtilsMissingPlugins; + &GstBaseUtilsInstallPlugins; diff --git a/docs/libs/gst-plugins-base-libs-sections.txt b/docs/libs/gst-plugins-base-libs-sections.txt index 5bcba8dacf..c8b4d5dea2 100644 --- a/docs/libs/gst-plugins-base-libs-sections.txt +++ b/docs/libs/gst-plugins-base-libs-sections.txt @@ -941,6 +941,23 @@ gst_missing_uri_sink_message_new gst_missing_element_message_new +
+gstbaseutilsinstallplugins +gst/utils/install-plugins.h + +GstInstallPluginsReturn +GstInstallPluginsResultFunc +gst_install_plugins_async +gst_install_plugins_sync +gst_install_plugins_return_get_name +gst_install_plugins_installation_in_progress + +GstInstallPluginsContext +gst_install_plugins_context_new +gst_install_plugins_context_free +gst_install_plugins_context_set_xid +
+
gstbaseutilsdescriptions gst/utils/descriptions.h diff --git a/gst-libs/gst/pbutils/Makefile.am b/gst-libs/gst/pbutils/Makefile.am index 2ba8f6b003..59dd18a0b0 100644 --- a/gst-libs/gst/pbutils/Makefile.am +++ b/gst-libs/gst/pbutils/Makefile.am @@ -5,6 +5,8 @@ libgstbaseutils_@GST_MAJORMINOR@_la_SOURCES = \ 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@includedir = $(includedir)/gstreamer-@GST_MAJORM libgstbaseutils_@GST_MAJORMINOR@include_HEADERS = \ base-utils.h \ descriptions.h \ + install-plugins.h \ missing-plugins.h libgstbaseutils_@GST_MAJORMINOR@_la_LIBADD = $(GST_LIBS) diff --git a/gst-libs/gst/pbutils/install-plugins.c b/gst-libs/gst/pbutils/install-plugins.c new file mode 100644 index 0000000000..ac23975de9 --- /dev/null +++ b/gst-libs/gst/pbutils/install-plugins.c @@ -0,0 +1,388 @@ +/* GStreamer base utils library plugin install support for applications + * Copyright (C) 2007 Tim-Philipp Müller + * Copyright (C) 2006 Ryan Lortie + * + * 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 + +#ifdef HAVE_SYS_TYPES_H +#include +#endif + +#ifdef HAVE_SYS_WAIT_H +#include +#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 */ + +/* private struct */ +struct _GstInstallPluginsContext +{ + guint xid; +}; + +/** + * 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 + * the XID of your main application window, so the installer can make its + * own window transient to your application windonw 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: + * + * ##include <gtk/gtk.h> + * ##ifdef GDK_WINDOWING_X11 + * ##include <gdk/gdkx.h> + * ##endif + * ... + * ##ifdef GDK_WINDOWING_X11 + * xid = GDK_WINDOW_XWINDOW (GTK_WIDGET (application_window)->window); + * ##endif + * ... + * + * + * Since: 0.10.12 + */ +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 + * + * Since: 0.10.12 + */ +GstInstallPluginsContext * +gst_install_plugins_context_new (void) +{ + return g_new0 (GstInstallPluginsContext, 1); +} + +/** + * gst_install_plugins_context_free: + * @ctx: a #GstInstallPluginsContext + * + * Frees a #GstInstallPluginsContext. + * + * Since: 0.10.12 + */ +void +gst_install_plugins_context_free (GstInstallPluginsContext * ctx) +{ + g_return_if_fail (ctx != NULL); + + g_free (ctx); +} + +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 +gst_install_plugins_spawn_child (gchar ** details, + GstInstallPluginsContext * ctx, GPid * child_pid, gint * exit_status) +{ + GPtrArray *arr; + gboolean ret; + GError *err = NULL; + gchar **argv, xid_str[64] = { 0, }; + + arr = g_ptr_array_new (); + + /* argv[0] = helper path */ + g_ptr_array_add (arr, (gchar *) gst_install_plugins_get_helper ()); + + /* add any additional command line args from the context */ + if (ctx != NULL && ctx->xid != 0) { + g_snprintf (xid_str, sizeof (xid_str), "--transient-for=%u", ctx->xid); + g_ptr_array_add (arr, xid_str); + } + + /* finally, add the detail strings */ + while (details != NULL && details[0] != NULL) { + g_ptr_array_add (arr, 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_assert_not_reached (); + } + + if (!ret) { + GST_WARNING ("Error spawning plugin install helper: %s", err->message); + g_error_free (err); + } + + g_ptr_array_free (arr, TRUE); + 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 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: NULL-terminated array of installer string details (see below) + * @ctx: a #GstInstallPluginsContext, or NULL + * @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. 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(). + * + * Returns: result code whether an external installer could be started + * + * Since: 0.10.12 + */ + +GstInstallPluginsReturn +gst_install_plugins_async (gchar ** 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: NULL-terminated array of installer string details + * @ctx: 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. + * + * Since: 0.10.12 + */ +GstInstallPluginsReturn +gst_install_plugins_sync (gchar ** 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 + * + * Since: 0.10.12 + */ +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 + * + * Since: 0.10.12 + */ +gboolean +gst_install_plugins_installation_in_progress (void) +{ + return install_in_progress; +} diff --git a/gst-libs/gst/pbutils/install-plugins.h b/gst-libs/gst/pbutils/install-plugins.h new file mode 100644 index 0000000000..835ffa8305 --- /dev/null +++ b/gst-libs/gst/pbutils/install-plugins.h @@ -0,0 +1,137 @@ +/* GStreamer base utils library plugin install support for applications + * Copyright (C) 2007 Tim-Philipp Müller + * Copyright (C) 2006 Ryan Lortie + * + * 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_PLUGINS_H__ +#define __GST_BASE_UTILS_INSTALL_PLUGINS_H__ + +#include + +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. Only return this if nothing + * has been installed. Return #GST_INSTALL_PLUGINS_PARTIAL_SUCCESS if + * some (but not all) of the requested plugins could be installed. + * @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_INVALID: the helper returned an invalid status code + * @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 + * + * Result codes returned by gst_install_plugins_async() and + * gst_install_plugins_sync(), and also the result code passed to the + * #GstInstallPluginsResultFunc specified with gst_install_plugin_async(). + * + * These codes indicate success or failure of starting an external installer + * program and to what extent the requested plugins could be installed. + * + * Since: 0.10.12 + */ +typedef enum { + /* Return codes from the installer. Returned by gst_install_plugins_sync(), + * or passed as result code to your #GstInstallPluginsResultFunc */ + GST_INSTALL_PLUGINS_SUCCESS = 0, + GST_INSTALL_PLUGINS_NOT_FOUND = 1, + GST_INSTALL_PLUGINS_ERROR = 2, + GST_INSTALL_PLUGINS_PARTIAL_SUCCESS = 3, + GST_INSTALL_PLUGINS_USER_ABORT = 4, + + /* Returned by gst_install_plugins_sync(), or passed as result code to your + * #GstInstallPluginsResultFunc */ + GST_INSTALL_PLUGINS_CRASHED = 100, + GST_INSTALL_PLUGINS_INVALID, + + /* Return codes from starting the external helper, may be returned by both + * gst_install_plugins_sync() and gst_install_plugins_async(), but should + * never be seen by a #GstInstallPluginsResultFunc */ + GST_INSTALL_PLUGINS_STARTED_OK = 200, + GST_INSTALL_PLUGINS_INTERNAL_FAILURE, + GST_INSTALL_PLUGINS_HELPER_MISSING, + GST_INSTALL_PLUGINS_INSTALL_IN_PROGRESS +} GstInstallPluginsReturn; + +/** + * GstInstallPluginsContext: + * + * Opaque context structure for the plugin installation. Use the provided + * API to set details on it. + * + * Since: 0.10.12 + */ +typedef struct _GstInstallPluginsContext GstInstallPluginsContext; + +GstInstallPluginsContext * gst_install_plugins_context_new (void); + +void gst_install_plugins_context_free (GstInstallPluginsContext * ctx); + +void gst_install_plugins_context_set_xid (GstInstallPluginsContext * ctx, + guint xid); + +/** + * 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. + * + * Since: 0.10.12 + */ +typedef void (*GstInstallPluginsResultFunc) (GstInstallPluginsReturn result, + gpointer user_data); + +GstInstallPluginsReturn gst_install_plugins_async (gchar ** details, + GstInstallPluginsContext * ctx, + GstInstallPluginsResultFunc func, + gpointer user_data); + +GstInstallPluginsReturn gst_install_plugins_sync (gchar ** details, + GstInstallPluginsContext * ctx); + +const gchar * gst_install_plugins_return_get_name (GstInstallPluginsReturn ret); + +gboolean gst_install_plugins_installation_in_progress (void); + +G_END_DECLS + +#endif /* __GST_BASE_UTILS_INSTALL_PLUGINS_H__ */ + diff --git a/gst-libs/gst/pbutils/pbutils.h b/gst-libs/gst/pbutils/pbutils.h index 10c62d38ab..cfa026c6eb 100644 --- a/gst-libs/gst/pbutils/pbutils.h +++ b/gst-libs/gst/pbutils/pbutils.h @@ -24,6 +24,7 @@ #include #include +#include G_BEGIN_DECLS diff --git a/gst-libs/gst/utils/Makefile.am b/gst-libs/gst/utils/Makefile.am index 2ba8f6b003..59dd18a0b0 100644 --- a/gst-libs/gst/utils/Makefile.am +++ b/gst-libs/gst/utils/Makefile.am @@ -5,6 +5,8 @@ libgstbaseutils_@GST_MAJORMINOR@_la_SOURCES = \ 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@includedir = $(includedir)/gstreamer-@GST_MAJORM libgstbaseutils_@GST_MAJORMINOR@include_HEADERS = \ base-utils.h \ descriptions.h \ + install-plugins.h \ missing-plugins.h libgstbaseutils_@GST_MAJORMINOR@_la_LIBADD = $(GST_LIBS) diff --git a/gst-libs/gst/utils/base-utils.h b/gst-libs/gst/utils/base-utils.h index 10c62d38ab..cfa026c6eb 100644 --- a/gst-libs/gst/utils/base-utils.h +++ b/gst-libs/gst/utils/base-utils.h @@ -24,6 +24,7 @@ #include #include +#include G_BEGIN_DECLS diff --git a/gst-libs/gst/utils/install-plugins.c b/gst-libs/gst/utils/install-plugins.c new file mode 100644 index 0000000000..ac23975de9 --- /dev/null +++ b/gst-libs/gst/utils/install-plugins.c @@ -0,0 +1,388 @@ +/* GStreamer base utils library plugin install support for applications + * Copyright (C) 2007 Tim-Philipp Müller + * Copyright (C) 2006 Ryan Lortie + * + * 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 + +#ifdef HAVE_SYS_TYPES_H +#include +#endif + +#ifdef HAVE_SYS_WAIT_H +#include +#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 */ + +/* private struct */ +struct _GstInstallPluginsContext +{ + guint xid; +}; + +/** + * 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 + * the XID of your main application window, so the installer can make its + * own window transient to your application windonw 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: + * + * ##include <gtk/gtk.h> + * ##ifdef GDK_WINDOWING_X11 + * ##include <gdk/gdkx.h> + * ##endif + * ... + * ##ifdef GDK_WINDOWING_X11 + * xid = GDK_WINDOW_XWINDOW (GTK_WIDGET (application_window)->window); + * ##endif + * ... + * + * + * Since: 0.10.12 + */ +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 + * + * Since: 0.10.12 + */ +GstInstallPluginsContext * +gst_install_plugins_context_new (void) +{ + return g_new0 (GstInstallPluginsContext, 1); +} + +/** + * gst_install_plugins_context_free: + * @ctx: a #GstInstallPluginsContext + * + * Frees a #GstInstallPluginsContext. + * + * Since: 0.10.12 + */ +void +gst_install_plugins_context_free (GstInstallPluginsContext * ctx) +{ + g_return_if_fail (ctx != NULL); + + g_free (ctx); +} + +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 +gst_install_plugins_spawn_child (gchar ** details, + GstInstallPluginsContext * ctx, GPid * child_pid, gint * exit_status) +{ + GPtrArray *arr; + gboolean ret; + GError *err = NULL; + gchar **argv, xid_str[64] = { 0, }; + + arr = g_ptr_array_new (); + + /* argv[0] = helper path */ + g_ptr_array_add (arr, (gchar *) gst_install_plugins_get_helper ()); + + /* add any additional command line args from the context */ + if (ctx != NULL && ctx->xid != 0) { + g_snprintf (xid_str, sizeof (xid_str), "--transient-for=%u", ctx->xid); + g_ptr_array_add (arr, xid_str); + } + + /* finally, add the detail strings */ + while (details != NULL && details[0] != NULL) { + g_ptr_array_add (arr, 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_assert_not_reached (); + } + + if (!ret) { + GST_WARNING ("Error spawning plugin install helper: %s", err->message); + g_error_free (err); + } + + g_ptr_array_free (arr, TRUE); + 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 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: NULL-terminated array of installer string details (see below) + * @ctx: a #GstInstallPluginsContext, or NULL + * @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. 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(). + * + * Returns: result code whether an external installer could be started + * + * Since: 0.10.12 + */ + +GstInstallPluginsReturn +gst_install_plugins_async (gchar ** 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: NULL-terminated array of installer string details + * @ctx: 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. + * + * Since: 0.10.12 + */ +GstInstallPluginsReturn +gst_install_plugins_sync (gchar ** 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 + * + * Since: 0.10.12 + */ +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 + * + * Since: 0.10.12 + */ +gboolean +gst_install_plugins_installation_in_progress (void) +{ + return install_in_progress; +} diff --git a/gst-libs/gst/utils/install-plugins.h b/gst-libs/gst/utils/install-plugins.h new file mode 100644 index 0000000000..835ffa8305 --- /dev/null +++ b/gst-libs/gst/utils/install-plugins.h @@ -0,0 +1,137 @@ +/* GStreamer base utils library plugin install support for applications + * Copyright (C) 2007 Tim-Philipp Müller + * Copyright (C) 2006 Ryan Lortie + * + * 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_PLUGINS_H__ +#define __GST_BASE_UTILS_INSTALL_PLUGINS_H__ + +#include + +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. Only return this if nothing + * has been installed. Return #GST_INSTALL_PLUGINS_PARTIAL_SUCCESS if + * some (but not all) of the requested plugins could be installed. + * @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_INVALID: the helper returned an invalid status code + * @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 + * + * Result codes returned by gst_install_plugins_async() and + * gst_install_plugins_sync(), and also the result code passed to the + * #GstInstallPluginsResultFunc specified with gst_install_plugin_async(). + * + * These codes indicate success or failure of starting an external installer + * program and to what extent the requested plugins could be installed. + * + * Since: 0.10.12 + */ +typedef enum { + /* Return codes from the installer. Returned by gst_install_plugins_sync(), + * or passed as result code to your #GstInstallPluginsResultFunc */ + GST_INSTALL_PLUGINS_SUCCESS = 0, + GST_INSTALL_PLUGINS_NOT_FOUND = 1, + GST_INSTALL_PLUGINS_ERROR = 2, + GST_INSTALL_PLUGINS_PARTIAL_SUCCESS = 3, + GST_INSTALL_PLUGINS_USER_ABORT = 4, + + /* Returned by gst_install_plugins_sync(), or passed as result code to your + * #GstInstallPluginsResultFunc */ + GST_INSTALL_PLUGINS_CRASHED = 100, + GST_INSTALL_PLUGINS_INVALID, + + /* Return codes from starting the external helper, may be returned by both + * gst_install_plugins_sync() and gst_install_plugins_async(), but should + * never be seen by a #GstInstallPluginsResultFunc */ + GST_INSTALL_PLUGINS_STARTED_OK = 200, + GST_INSTALL_PLUGINS_INTERNAL_FAILURE, + GST_INSTALL_PLUGINS_HELPER_MISSING, + GST_INSTALL_PLUGINS_INSTALL_IN_PROGRESS +} GstInstallPluginsReturn; + +/** + * GstInstallPluginsContext: + * + * Opaque context structure for the plugin installation. Use the provided + * API to set details on it. + * + * Since: 0.10.12 + */ +typedef struct _GstInstallPluginsContext GstInstallPluginsContext; + +GstInstallPluginsContext * gst_install_plugins_context_new (void); + +void gst_install_plugins_context_free (GstInstallPluginsContext * ctx); + +void gst_install_plugins_context_set_xid (GstInstallPluginsContext * ctx, + guint xid); + +/** + * 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. + * + * Since: 0.10.12 + */ +typedef void (*GstInstallPluginsResultFunc) (GstInstallPluginsReturn result, + gpointer user_data); + +GstInstallPluginsReturn gst_install_plugins_async (gchar ** details, + GstInstallPluginsContext * ctx, + GstInstallPluginsResultFunc func, + gpointer user_data); + +GstInstallPluginsReturn gst_install_plugins_sync (gchar ** details, + GstInstallPluginsContext * ctx); + +const gchar * gst_install_plugins_return_get_name (GstInstallPluginsReturn ret); + +gboolean gst_install_plugins_installation_in_progress (void); + +G_END_DECLS + +#endif /* __GST_BASE_UTILS_INSTALL_PLUGINS_H__ */ + diff --git a/tests/check/libs/pbutils.c b/tests/check/libs/pbutils.c index 342691ffdb..efa381ca29 100644 --- a/tests/check/libs/pbutils.c +++ b/tests/check/libs/pbutils.c @@ -24,7 +24,22 @@ #include #include -#include + +#include +#include +#include + +#ifdef HAVE_UNISTD_H +#include /* for unlink() */ +#endif + +#ifdef HAVE_SYS_TYPES_H +#include /* for fchmod() */ +#endif + +#ifdef HAVE_SYS_STAT_H +#include /* for fchmod() */ +#endif static void missing_msg_check_getters (GstMessage * msg) @@ -400,6 +415,8 @@ GST_START_TEST (test_base_utils_get_codec_description) } GST_END_TEST; + + GST_START_TEST (test_base_utils_taglist_add_codec_info) { GstTagList *list; @@ -430,6 +447,141 @@ GST_START_TEST (test_base_utils_taglist_add_codec_info) } 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; +} + +#define FAKE_INSTALL_PLUGINS_HELPER "/tmp/gst-plugins-base-unit-test-helper" +#define SCRIPT_NO_XID \ + "#!/bin/sh\n" \ + "if test x$1 != xdetail1; then exit 21; fi;\n" \ + "if test x$2 != xdetail2; then exit 22; fi;\n" \ + "exit 1\n" + +#define SCRIPT_WITH_XID \ + "#!/bin/sh\n" \ + "if test x$1 != 'x--transient-for=42'; 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 0\n" + +/* make sure our script gets called with the right parameters */ +static void +test_base_utils_install_plugins_do_callout (gchar ** details, + GstInstallPluginsContext * ctx, const gchar * script, + GstInstallPluginsReturn expected_result) +{ +#ifdef G_OS_UNIX + GstInstallPluginsReturn ret; + FILE *f; + + unlink (FAKE_INSTALL_PLUGINS_HELPER); + + f = g_fopen (FAKE_INSTALL_PLUGINS_HELPER, "w"); + if (f == NULL) + return; + if (g_fprintf (f, "%s", 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, ctx); + fail_unless (ret == GST_INSTALL_PLUGINS_HELPER_MISSING || + ret == expected_result, + "gst_install_plugins_sync() failed with unexpected ret %d, which is" + "neither HELPER_MISSING NOR %d", ret, expected_result); + + /* test async callout */ + marker = -333; + ret = gst_install_plugins_async (details, ctx, 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, expected_result); + } + } else { + fclose (f); + } + unlink (FAKE_INSTALL_PLUGINS_HELPER); +#endif /* G_OS_UNIX */ +} + +GST_START_TEST (test_base_utils_install_plugins) +{ + GstInstallPluginsContext *ctx; + GstInstallPluginsReturn ret; + gchar *details[] = { "detail1", "detail2" }; + + ctx = gst_install_plugins_context_new (); + + ASSERT_CRITICAL (ret = gst_install_plugins_sync (NULL, ctx); + ); + ASSERT_CRITICAL (ret = + gst_install_plugins_async (NULL, ctx, result_cb, (gpointer) & marker); + ); + ASSERT_CRITICAL (ret = + gst_install_plugins_async (details, ctx, 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, NULL); + fail_unless_equals_int (ret, GST_INSTALL_PLUGINS_HELPER_MISSING); + + marker = -333; + ret = + gst_install_plugins_async (details, NULL, 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); + + /* now make sure our scripts are actually called as expected (if possible) */ + test_base_utils_install_plugins_do_callout (details, NULL, SCRIPT_NO_XID, + GST_INSTALL_PLUGINS_NOT_FOUND); + + /* and again with context */ + gst_install_plugins_context_set_xid (ctx, 42); + test_base_utils_install_plugins_do_callout (details, ctx, SCRIPT_WITH_XID, + GST_INSTALL_PLUGINS_SUCCESS); + + /* and free the context now that we don't need it any longer */ + gst_install_plugins_context_free (ctx); + + /* 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 +593,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; } diff --git a/tests/check/libs/utils.c b/tests/check/libs/utils.c index 342691ffdb..efa381ca29 100644 --- a/tests/check/libs/utils.c +++ b/tests/check/libs/utils.c @@ -24,7 +24,22 @@ #include #include -#include + +#include +#include +#include + +#ifdef HAVE_UNISTD_H +#include /* for unlink() */ +#endif + +#ifdef HAVE_SYS_TYPES_H +#include /* for fchmod() */ +#endif + +#ifdef HAVE_SYS_STAT_H +#include /* for fchmod() */ +#endif static void missing_msg_check_getters (GstMessage * msg) @@ -400,6 +415,8 @@ GST_START_TEST (test_base_utils_get_codec_description) } GST_END_TEST; + + GST_START_TEST (test_base_utils_taglist_add_codec_info) { GstTagList *list; @@ -430,6 +447,141 @@ GST_START_TEST (test_base_utils_taglist_add_codec_info) } 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; +} + +#define FAKE_INSTALL_PLUGINS_HELPER "/tmp/gst-plugins-base-unit-test-helper" +#define SCRIPT_NO_XID \ + "#!/bin/sh\n" \ + "if test x$1 != xdetail1; then exit 21; fi;\n" \ + "if test x$2 != xdetail2; then exit 22; fi;\n" \ + "exit 1\n" + +#define SCRIPT_WITH_XID \ + "#!/bin/sh\n" \ + "if test x$1 != 'x--transient-for=42'; 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 0\n" + +/* make sure our script gets called with the right parameters */ +static void +test_base_utils_install_plugins_do_callout (gchar ** details, + GstInstallPluginsContext * ctx, const gchar * script, + GstInstallPluginsReturn expected_result) +{ +#ifdef G_OS_UNIX + GstInstallPluginsReturn ret; + FILE *f; + + unlink (FAKE_INSTALL_PLUGINS_HELPER); + + f = g_fopen (FAKE_INSTALL_PLUGINS_HELPER, "w"); + if (f == NULL) + return; + if (g_fprintf (f, "%s", 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, ctx); + fail_unless (ret == GST_INSTALL_PLUGINS_HELPER_MISSING || + ret == expected_result, + "gst_install_plugins_sync() failed with unexpected ret %d, which is" + "neither HELPER_MISSING NOR %d", ret, expected_result); + + /* test async callout */ + marker = -333; + ret = gst_install_plugins_async (details, ctx, 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, expected_result); + } + } else { + fclose (f); + } + unlink (FAKE_INSTALL_PLUGINS_HELPER); +#endif /* G_OS_UNIX */ +} + +GST_START_TEST (test_base_utils_install_plugins) +{ + GstInstallPluginsContext *ctx; + GstInstallPluginsReturn ret; + gchar *details[] = { "detail1", "detail2" }; + + ctx = gst_install_plugins_context_new (); + + ASSERT_CRITICAL (ret = gst_install_plugins_sync (NULL, ctx); + ); + ASSERT_CRITICAL (ret = + gst_install_plugins_async (NULL, ctx, result_cb, (gpointer) & marker); + ); + ASSERT_CRITICAL (ret = + gst_install_plugins_async (details, ctx, 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, NULL); + fail_unless_equals_int (ret, GST_INSTALL_PLUGINS_HELPER_MISSING); + + marker = -333; + ret = + gst_install_plugins_async (details, NULL, 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); + + /* now make sure our scripts are actually called as expected (if possible) */ + test_base_utils_install_plugins_do_callout (details, NULL, SCRIPT_NO_XID, + GST_INSTALL_PLUGINS_NOT_FOUND); + + /* and again with context */ + gst_install_plugins_context_set_xid (ctx, 42); + test_base_utils_install_plugins_do_callout (details, ctx, SCRIPT_WITH_XID, + GST_INSTALL_PLUGINS_SUCCESS); + + /* and free the context now that we don't need it any longer */ + gst_install_plugins_context_free (ctx); + + /* 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 +593,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; }