From 2ae03ba72fa81379c0f059ca4229f70202e0b9bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Tue, 6 Jan 2009 17:58:59 +0000 Subject: [PATCH] Add API for making a GStreamer plugin 'dependent' on external files, directories or environment variables, so that GS... Original commit message from CVS: * docs/gst/gstreamer-sections.txt:: * gst/gst_private.h: (GstPluginDep), (_GstPluginPrivate): * gst/gstplugin.c: (gst_plugin_init), (gst_plugin_finalize), (gst_plugin_class_init), (gst_plugin_list_free), (gst_plugin_ext_dep_get_env_vars_hash), (_priv_plugin_deps_env_vars_changed), (gst_plugin_ext_dep_extract_env_vars_paths), (gst_plugin_ext_dep_get_hash_from_stat_entry), (gst_plugin_ext_dep_direntry_matches), (gst_plugin_ext_dep_scan_dir_and_match_names), (gst_plugin_ext_dep_scan_path_with_filenames), (gst_plugin_ext_dep_get_stat_hash), (_priv_plugin_deps_files_changed), (gst_plugin_ext_dep_free), (gst_plugin_ext_dep_strv_equal), (gst_plugin_ext_dep_equals), (gst_plugin_add_dependency), (gst_plugin_add_dependency_simple): * gst/gstplugin.h: (GstPluginPrivate), (GstPluginFlags), (GST_PLUGIN_DEPENDENCY_FLAG_NONE), (GST_PLUGIN_DEPENDENCY_FLAG_RECURSE), (GST_PLUGIN_DEPENDENCY_FLAG_PATHS_ARE_DEFAULT_ONLY), (GST_PLUGIN_DEPENDENCY_FLAG_FILE_NAME_IS_SUFFIX), (GstPluginDependencyFlags), (GstPluginFilter): * gst/gstregistry.c: (gst_registry_scan_path_level): * gst/gstregistrybinary.c: (gst_registry_binary_save_feature), (gst_registry_binary_save_plugin_dep), (gst_registry_binary_save_plugin), (gst_registry_binary_load_feature), (gst_registry_binary_load_plugin_dep_strv), (gst_registry_binary_load_plugin_dep), (gst_registry_binary_load_plugin): * gst/gstregistrybinary.h: (GST_MAGIC_BINARY_VERSION_STR), (GstBinaryPluginElement), (_GstBinaryDep), (GstBinaryDep): * gst/gstregistryxml.c: (gst_registry_xml_save_plugin): Add API for making a GStreamer plugin 'dependent' on external files, directories or environment variables, so that GStreamer knows when it needs to re-load GStreamer plugins that wrap other plugin systems. Fixes bug #350477. API: add gst_plugin_add_dependency() API: add gst_plugin_add_dependency_simple() --- ChangeLog | 41 +++ docs/gst/gstreamer-sections.txt | 7 + gst/gst_private.h | 23 ++ gst/gstplugin.c | 527 +++++++++++++++++++++++++++++++- gst/gstplugin.h | 37 ++- gst/gstregistry.c | 16 +- gst/gstregistrybinary.c | 103 +++++++ gst/gstregistrybinary.h | 16 +- gst/gstregistryxml.c | 4 + 9 files changed, 767 insertions(+), 7 deletions(-) diff --git a/ChangeLog b/ChangeLog index b1d01e3cf5..05f5ade05d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,44 @@ +2009-01-06 Tim-Philipp Müller + + * docs/gst/gstreamer-sections.txt:: + * gst/gst_private.h: (GstPluginDep), (_GstPluginPrivate): + * gst/gstplugin.c: (gst_plugin_init), (gst_plugin_finalize), + (gst_plugin_class_init), (gst_plugin_list_free), + (gst_plugin_ext_dep_get_env_vars_hash), + (_priv_plugin_deps_env_vars_changed), + (gst_plugin_ext_dep_extract_env_vars_paths), + (gst_plugin_ext_dep_get_hash_from_stat_entry), + (gst_plugin_ext_dep_direntry_matches), + (gst_plugin_ext_dep_scan_dir_and_match_names), + (gst_plugin_ext_dep_scan_path_with_filenames), + (gst_plugin_ext_dep_get_stat_hash), + (_priv_plugin_deps_files_changed), (gst_plugin_ext_dep_free), + (gst_plugin_ext_dep_strv_equal), (gst_plugin_ext_dep_equals), + (gst_plugin_add_dependency), (gst_plugin_add_dependency_simple): + * gst/gstplugin.h: (GstPluginPrivate), (GstPluginFlags), + (GST_PLUGIN_DEPENDENCY_FLAG_NONE), + (GST_PLUGIN_DEPENDENCY_FLAG_RECURSE), + (GST_PLUGIN_DEPENDENCY_FLAG_PATHS_ARE_DEFAULT_ONLY), + (GST_PLUGIN_DEPENDENCY_FLAG_FILE_NAME_IS_SUFFIX), + (GstPluginDependencyFlags), (GstPluginFilter): + * gst/gstregistry.c: (gst_registry_scan_path_level): + * gst/gstregistrybinary.c: (gst_registry_binary_save_feature), + (gst_registry_binary_save_plugin_dep), + (gst_registry_binary_save_plugin), + (gst_registry_binary_load_feature), + (gst_registry_binary_load_plugin_dep_strv), + (gst_registry_binary_load_plugin_dep), + (gst_registry_binary_load_plugin): + * gst/gstregistrybinary.h: (GST_MAGIC_BINARY_VERSION_STR), + (GstBinaryPluginElement), (_GstBinaryDep), (GstBinaryDep): + * gst/gstregistryxml.c: (gst_registry_xml_save_plugin): + Add API for making a GStreamer plugin 'dependent' on external files, + directories or environment variables, so that GStreamer knows when + it needs to re-load GStreamer plugins that wrap other plugin systems. + Fixes bug #350477. + API: add gst_plugin_add_dependency() + API: add gst_plugin_add_dependency_simple() + 2009-01-06 Tim-Philipp Müller * docs/faq/gst-uninstalled: diff --git a/docs/gst/gstreamer-sections.txt b/docs/gst/gstreamer-sections.txt index 5dc1b4750c..5da67ce36a 100644 --- a/docs/gst/gstreamer-sections.txt +++ b/docs/gst/gstreamer-sections.txt @@ -1641,6 +1641,10 @@ gst_plugin_load gst_plugin_load_by_name gst_plugin_list_free gst_plugin_register_static + +GstPluginDependencyFlags +gst_plugin_add_dependency +gst_plugin_add_dependency_simple GstPluginClass GST_PLUGIN @@ -1653,6 +1657,9 @@ GST_IS_PLUGIN GST_IS_PLUGIN_CLASS GstPluginFlags GST_TYPE_PLUGIN_FLAGS +GST_TYPE_PLUGIN_DEPENDENCY_FLAGS +GstPluginPrivate +gst_plugin_dependency_flags_get_type gst_plugin_get_type diff --git a/gst/gst_private.h b/gst/gst_private.h index 49e6371ff4..cfdd440aa9 100644 --- a/gst/gst_private.h +++ b/gst/gst_private.h @@ -44,6 +44,9 @@ extern const char g_log_domain_gstreamer[]; /* we need this in pretty much all files */ #include "gstinfo.h" +/* for the flags in the GstPluginDep structure below */ +#include "gstplugin.h" + G_BEGIN_DECLS /* used by gstparse.c and grammar.y */ @@ -51,6 +54,26 @@ struct _GstParseContext { GList * missing_elements; }; +/* used by gstplugin.c and gstregistrybinary.c */ +typedef struct { + /* details registered via gst_plugin_add_dependency() */ + GstPluginDependencyFlags flags; + gchar **env_vars; + gchar **paths; + gchar **names; + + /* information saved from the last time the plugin was loaded (-1 = unset) */ + guint env_hash; /* hash of content of environment variables in env_vars */ + guint stat_hash; /* hash of stat() on all relevant files and directories */ +} GstPluginDep; + +struct _GstPluginPrivate { + GList *deps; /* list of GstPluginDep structures */ +}; + +gboolean _priv_plugin_deps_env_vars_changed (GstPlugin * plugin); +gboolean _priv_plugin_deps_files_changed (GstPlugin * plugin); + gboolean _priv_gst_in_valgrind (void); /* Initialize GStreamer private quark storage */ diff --git a/gst/gstplugin.c b/gst/gstplugin.c index 11fe18114d..a7cd8671e2 100644 --- a/gst/gstplugin.c +++ b/gst/gstplugin.c @@ -109,13 +109,15 @@ static void gst_plugin_desc_copy (GstPluginDesc * dest, const GstPluginDesc * src); static void gst_plugin_desc_free (GstPluginDesc * desc); +static void gst_plugin_ext_dep_free (GstPluginDep * dep); G_DEFINE_TYPE (GstPlugin, gst_plugin, GST_TYPE_OBJECT); static void gst_plugin_init (GstPlugin * plugin) { - /* do nothing, needed because of G_DEFINE_TYPE */ + plugin->priv = + G_TYPE_INSTANCE_GET_PRIVATE (plugin, GST_TYPE_PLUGIN, GstPluginPrivate); } static void @@ -135,6 +137,10 @@ gst_plugin_finalize (GObject * object) g_free (plugin->basename); gst_plugin_desc_free (&plugin->desc); + g_list_foreach (plugin->priv->deps, (GFunc) gst_plugin_ext_dep_free, NULL); + g_list_free (plugin->priv->deps); + plugin->priv->deps = NULL; + G_OBJECT_CLASS (gst_plugin_parent_class)->finalize (object); } @@ -142,6 +148,8 @@ static void gst_plugin_class_init (GstPluginClass * klass) { G_OBJECT_CLASS (klass)->finalize = GST_DEBUG_FUNCPTR (gst_plugin_finalize); + + g_type_class_add_private (klass, sizeof (GstPluginPrivate)); } GQuark @@ -1032,3 +1040,520 @@ gst_plugin_list_free (GList * list) } g_list_free (list); } + +/* ===== plugin dependencies ===== */ + +/* Scenarios: + * ENV + xyz where ENV can contain multiple values separated by SEPARATOR + * xyz may be "" (if ENV contains path to file rather than dir) + * ENV + *xyz same as above, but xyz acts as suffix filter + * ENV + xyz* same as above, but xyz acts as prefix filter (is this needed?) + * ENV + *xyz* same as above, but xyz acts as strstr filter (is this needed?) + * + * same as above, with additional paths hard-coded at compile-time: + * - only check paths + ... if ENV is not set or yields not paths + * - always check paths + ... in addition to ENV + * + * When user specifies set of environment variables, he/she may also use e.g. + * "HOME/.mystuff/plugins", and we'll expand the content of $HOME with the + * remainder + */ + +/* we store in registry: + * sets of: + * { + * - environment variables (array of strings) + * - last hash of env variable contents (uint) (so we can avoid doing stats + * if one of the env vars has changed; premature optimisation galore) + * - hard-coded paths (array of strings) + * - xyz filename/suffix/prefix strings (array of strings) + * - flags (int) + * - last hash of file/dir stats (int) + * } + * (= struct GstPluginDep) + */ + +static guint +gst_plugin_ext_dep_get_env_vars_hash (GstPlugin * plugin, GstPluginDep * dep) +{ + gchar **e; + guint hash; + + /* there's no deeper logic to what we do here; all we want to know (when + * checking if the plugin needs to be rescanned) is whether the content of + * one of the environment variables in the list is different from when it + * was last scanned */ + hash = 0; + for (e = dep->env_vars; e != NULL && *e != NULL; ++e) { + const gchar *val; + gchar env_var[256]; + + /* order matters: "val",NULL needs to yield a different hash than + * NULL,"val", so do a shift here whether the var is set or not */ + hash = hash << 5; + + /* want environment variable at beginning of string */ + if (!g_ascii_isalnum (**e)) { + GST_WARNING_OBJECT (plugin, "string prefix is not a valid environment " + "variable string: %s", *e); + continue; + } + + /* user is allowed to specify e.g. "HOME/.pitivi/plugins" */ + g_strlcpy (env_var, *e, sizeof (env_var)); + g_strdelimit (env_var, "/\\", '\0'); + + if ((val = g_getenv (env_var))) + hash += g_str_hash (val); + } + + return hash; +} + +gboolean +_priv_plugin_deps_env_vars_changed (GstPlugin * plugin) +{ + GList *l; + + for (l = plugin->priv->deps; l != NULL; l = l->next) { + GstPluginDep *dep = l->data; + + if (dep->env_hash != gst_plugin_ext_dep_get_env_vars_hash (plugin, dep)) + return TRUE; + } + + return FALSE; +} + +static GList * +gst_plugin_ext_dep_extract_env_vars_paths (GstPlugin * plugin, + GstPluginDep * dep) +{ + gchar **evars; + GList *paths = NULL; + + for (evars = dep->env_vars; evars != NULL && *evars != NULL; ++evars) { + const gchar *e; + gchar **components; + + /* want environment variable at beginning of string */ + if (!g_ascii_isalnum (**evars)) { + GST_WARNING_OBJECT (plugin, "string prefix is not a valid environment " + "variable string: %s", *evars); + continue; + } + + /* user is allowed to specify e.g. "HOME/.pitivi/plugins", which we want to + * split into the env_var name component and the path component */ + components = g_strsplit_set (*evars, "/\\", 2); + g_assert (components != NULL); + + e = g_getenv (components[0]); + GST_LOG_OBJECT (plugin, "expanding %s = '%s' (path suffix: %s)", + components[0], GST_STR_NULL (e), GST_STR_NULL (components[1])); + + if (components[1] != NULL) { + g_strdelimit (components[1], "/\\", G_DIR_SEPARATOR); + } + + if (e != NULL && *e != '\0') { + gchar **arr; + guint i; + + arr = g_strsplit (e, G_SEARCHPATH_SEPARATOR_S, -1); + + for (i = 0; arr != NULL && arr[i] != NULL; ++i) { + gchar *full_path; + + if (!g_path_is_absolute (arr[i])) { + GST_INFO_OBJECT (plugin, "ignoring environment variable content '%s'" + ": either not an absolute path or not a path at all", arr[i]); + continue; + } + + if (components[1] != NULL) { + full_path = g_build_filename (arr[i], components[1], NULL); + } else { + full_path = g_strdup (arr[i]); + } + + if (!g_list_find_custom (paths, full_path, (GCompareFunc) strcmp)) { + GST_LOG_OBJECT (plugin, "path: '%s'", full_path); + paths = g_list_prepend (paths, full_path); + full_path = NULL; + } else { + GST_LOG_OBJECT (plugin, "path: '%s' (duplicate,ignoring)", full_path); + g_free (full_path); + } + } + + g_strfreev (arr); + } + + g_strfreev (components); + } + + GST_LOG_OBJECT (plugin, "Extracted %d paths from environment", + g_list_length (paths)); + + return paths; +} + +static guint +gst_plugin_ext_dep_get_hash_from_stat_entry (struct stat *s) +{ + if (!(s->st_mode & (S_IFDIR | S_IFREG))) + return (guint) - 1; + + /* completely random formula */ + return ((s->st_size << 3) + (s->st_mtime << 5)) ^ s->st_ctime; +} + +static gboolean +gst_plugin_ext_dep_direntry_matches (GstPlugin * plugin, const gchar * entry, + const gchar ** filenames, GstPluginDependencyFlags flags) +{ + /* no filenames specified, match all entries for now (could probably + * optimise by just taking the dir stat hash or so) */ + if (filenames == NULL || *filenames == NULL || **filenames == '\0') + return TRUE; + + while (*filenames != NULL) { + /* suffix match? */ + if (((flags & GST_PLUGIN_DEPENDENCY_FLAG_FILE_NAME_IS_SUFFIX)) && + g_str_has_suffix (entry, *filenames)) { + return TRUE; + /* else it's an exact match that's needed */ + } else if (strcmp (entry, *filenames) == 0) { + return TRUE; + } + GST_LOG ("%s does not match %s, flags=0x%04x", entry, *filenames, flags); + ++filenames; + } + return FALSE; +} + +static guint +gst_plugin_ext_dep_scan_dir_and_match_names (GstPlugin * plugin, + const gchar * path, const gchar ** filenames, + GstPluginDependencyFlags flags, int depth) +{ + const gchar *entry; + gboolean recurse_dirs; + GError *err = NULL; + GDir *dir; + guint hash = 0; + + recurse_dirs = !!(flags & GST_PLUGIN_DEPENDENCY_FLAG_RECURSE); + + dir = g_dir_open (path, 0, &err); + if (dir == NULL) { + GST_DEBUG_OBJECT (plugin, "g_dir_open(%s) failed: %s", path, err->message); + g_error_free (err); + return (guint) - 1; + } + + /* FIXME: we're assuming here that we always get the directory entries in + * the same order, and not in a random order */ + while ((entry = g_dir_read_name (dir))) { + gboolean have_match; + struct stat s; + gchar *full_path; + guint fhash; + + have_match = + gst_plugin_ext_dep_direntry_matches (plugin, entry, filenames, flags); + + /* avoid the stat if possible */ + if (!have_match && !recurse_dirs) + continue; + + full_path = g_build_filename (path, entry, NULL); + if (g_stat (full_path, &s) < 0) { + fhash = (guint) - 1; + GST_LOG_OBJECT (plugin, "stat: %s (error: %s)", full_path, + g_strerror (errno)); + } else if (have_match) { + fhash = gst_plugin_ext_dep_get_hash_from_stat_entry (&s); + GST_LOG_OBJECT (plugin, "stat: %s (result: %u)", full_path, fhash); + } else if ((s.st_mode & (S_IFDIR))) { + fhash = gst_plugin_ext_dep_scan_dir_and_match_names (plugin, full_path, + filenames, flags, depth + 1); + } else { + /* it's not a name match, we want to recurse, but it's not a directory */ + g_free (full_path); + continue; + } + + hash = (hash + fhash) << 1; + g_free (full_path); + } + + g_dir_close (dir); + return hash; +} + +static guint +gst_plugin_ext_dep_scan_path_with_filenames (GstPlugin * plugin, + const gchar * path, const gchar ** filenames, + GstPluginDependencyFlags flags) +{ + const gchar *empty_filenames[] = { "", NULL }; + gboolean recurse_into_dirs, partial_names; + guint i, hash = 0; + + /* to avoid special-casing below (FIXME?) */ + if (filenames == NULL || *filenames == NULL) + filenames = empty_filenames; + + recurse_into_dirs = !!(flags & GST_PLUGIN_DEPENDENCY_FLAG_RECURSE); + partial_names = !!(flags & GST_PLUGIN_DEPENDENCY_FLAG_FILE_NAME_IS_SUFFIX); + + /* if we can construct the exact paths to check with the data we have, just + * stat them one by one; this is more efficient than opening the directory + * and going through each entry to see if it matches one of our filenames. */ + if (!recurse_into_dirs && !partial_names) { + for (i = 0; filenames[i] != NULL; ++i) { + struct stat s; + gchar *full_path; + guint fhash; + + full_path = g_build_filename (path, filenames[i], NULL); + if (g_stat (full_path, &s) < 0) { + fhash = (guint) - 1; + GST_LOG_OBJECT (plugin, "stat: %s (error: %s)", full_path, + g_strerror (errno)); + } else { + fhash = gst_plugin_ext_dep_get_hash_from_stat_entry (&s); + GST_LOG_OBJECT (plugin, "stat: %s (result: %08x)", full_path, fhash); + } + hash = (hash + fhash) << 1; + g_free (full_path); + } + } else { + hash = gst_plugin_ext_dep_scan_dir_and_match_names (plugin, path, + filenames, flags, 0); + } + + return hash; +} + +static guint +gst_plugin_ext_dep_get_stat_hash (GstPlugin * plugin, GstPluginDep * dep) +{ + gboolean paths_are_default_only; + GList *scan_paths; + guint scan_hash = 0; + + GST_LOG_OBJECT (plugin, "start"); + + paths_are_default_only = + dep->flags & GST_PLUGIN_DEPENDENCY_FLAG_PATHS_ARE_DEFAULT_ONLY; + + scan_paths = gst_plugin_ext_dep_extract_env_vars_paths (plugin, dep); + + if (scan_paths == NULL || !paths_are_default_only) { + gchar **paths; + + for (paths = dep->paths; paths != NULL && *paths != NULL; ++paths) { + const gchar *path = *paths; + + if (!g_list_find_custom (scan_paths, path, (GCompareFunc) strcmp)) { + GST_LOG_OBJECT (plugin, "path: '%s'", path); + scan_paths = g_list_prepend (scan_paths, g_strdup (path)); + } else { + GST_LOG_OBJECT (plugin, "path: '%s' (duplicate, ignoring)", path); + } + } + } + + /* not that the order really matters, but it makes debugging easier */ + scan_paths = g_list_reverse (scan_paths); + + while (scan_paths != NULL) { + const gchar *path = scan_paths->data; + + scan_hash += gst_plugin_ext_dep_scan_path_with_filenames (plugin, path, + (const gchar **) dep->names, dep->flags); + scan_hash = scan_hash << 1; + + g_free (scan_paths->data); + scan_paths = g_list_delete_link (scan_paths, scan_paths); + } + + GST_LOG_OBJECT (plugin, "done, scan_hash: %08x", scan_hash); + return scan_hash; +} + +gboolean +_priv_plugin_deps_files_changed (GstPlugin * plugin) +{ + GList *l; + + for (l = plugin->priv->deps; l != NULL; l = l->next) { + GstPluginDep *dep = l->data; + + if (dep->stat_hash != gst_plugin_ext_dep_get_stat_hash (plugin, dep)) + return TRUE; + } + + return FALSE; +} + +static void +gst_plugin_ext_dep_free (GstPluginDep * dep) +{ + g_strfreev (dep->env_vars); + g_strfreev (dep->paths); + g_strfreev (dep->names); + g_free (dep); +} + +static gboolean +gst_plugin_ext_dep_strv_equal (gchar ** arr1, gchar ** arr2) +{ + if (arr1 == arr2) + return TRUE; + if (arr1 == NULL || arr2 == NULL) + return FALSE; + for (; *arr1 != NULL && *arr2 != NULL; ++arr1, ++arr2) { + if (strcmp (*arr1, *arr2) != 0) + return FALSE; + } + return (*arr1 == *arr2); +} + +static gboolean +gst_plugin_ext_dep_equals (GstPluginDep * dep, const gchar ** env_vars, + const gchar ** paths, const gchar ** names, GstPluginDependencyFlags flags) +{ + if (dep->flags != flags) + return FALSE; + + return gst_plugin_ext_dep_strv_equal (dep->env_vars, (gchar **) env_vars) && + gst_plugin_ext_dep_strv_equal (dep->paths, (gchar **) paths) && + gst_plugin_ext_dep_strv_equal (dep->names, (gchar **) names); +} + +/** + * gst_plugin_add_dependency: + * @plugin: a #GstPlugin + * @env_vars: NULL-terminated array of environent variables affecting the + * feature set of the plugin (e.g. an environment variable containing + * paths where to look for additional modules/plugins of a library), + * or NULL. Environment variable names may be followed by a path component + * which will be added to the content of the environment variable, e.g. + * "HOME/.mystuff/plugins". + * @paths: NULL-terminated array of directories/paths where dependent files + * may be. + * @names: NULL-terminated array of file names (or file name suffixes, + * depending on @flags) to be used in combination with the paths from + * @paths and/or the paths extracted from the environment variables in + * @env_vars, or NULL. + * @flags: optional flags, or #GST_PLUGIN_DEPENDENCY_FLAG_NONE + * + * Make GStreamer aware of external dependencies which affect the feature + * set of this plugin (ie. the elements or typefinders associated with it). + * + * GStreamer will re-inspect plugins with external dependencies whenever any + * of the external dependencies change. This is useful for plugins which wrap + * other plugin systems, e.g. a plugin which wraps a plugin-based visualisation + * library and makes visualisations available as GStreamer elements, or a + * codec loader which exposes elements and/or caps dependent on what external + * codec libraries are currently installed. + * + * Since: 0.10.22 + */ +void +gst_plugin_add_dependency (GstPlugin * plugin, const gchar ** env_vars, + const gchar ** paths, const gchar ** names, GstPluginDependencyFlags flags) +{ + GstPluginDep *dep; + GList *l; + + g_return_if_fail (GST_IS_PLUGIN (plugin)); + g_return_if_fail (env_vars != NULL || paths != NULL); + + for (l = plugin->priv->deps; l != NULL; l = l->next) { + if (gst_plugin_ext_dep_equals (l->data, env_vars, paths, names, flags)) { + GST_LOG_OBJECT (plugin, "dependency already registered"); + return; + } + } + + dep = g_new0 (GstPluginDep, 1); + + dep->env_vars = g_strdupv ((gchar **) env_vars); + dep->paths = g_strdupv ((gchar **) paths); + dep->names = g_strdupv ((gchar **) names); + dep->flags = flags; + + dep->env_hash = gst_plugin_ext_dep_get_env_vars_hash (plugin, dep); + dep->stat_hash = gst_plugin_ext_dep_get_stat_hash (plugin, dep); + + plugin->priv->deps = g_list_append (plugin->priv->deps, dep); + + GST_DEBUG_OBJECT (plugin, "added dependency:"); + for (; env_vars != NULL && *env_vars != NULL; ++env_vars) + GST_DEBUG_OBJECT (plugin, " evar: %s", *env_vars); + for (; paths != NULL && *paths != NULL; ++paths) + GST_DEBUG_OBJECT (plugin, " path: %s", *paths); + for (; names != NULL && *names != NULL; ++names) + GST_DEBUG_OBJECT (plugin, " name: %s", *names); +} + +/** + * gst_plugin_add_dependency_simple: + * @plugin: the #GstPlugin + * @env_vars: one or more environent variables (separated by ':', ';' or ','), + * or NULL. Environment variable names may be followed by a path component + * which will be added to the content of the environment variable, e.g. + * "HOME/.mystuff/plugins:MYSTUFF_PLUGINS_PATH" + * @paths: one ore more directory paths (separated by ':' or ';' or ','), + * or NULL. Example: "/usr/lib/mystuff/plugins" + * @names: one or more file names or file name suffixes (separated by commas), + * or NULL + * @flags: optional flags, or #GST_PLUGIN_DEPENDENCY_FLAG_NONE + * + * Make GStreamer aware of external dependencies which affect the feature + * set of this plugin (ie. the elements or typefinders associated with it). + * + * GStreamer will re-inspect plugins with external dependencies whenever any + * of the external dependencies change. This is useful for plugins which wrap + * other plugin systems, e.g. a plugin which wraps a plugin-based visualisation + * library and makes visualisations available as GStreamer elements, or a + * codec loader which exposes elements and/or caps dependent on what external + * codec libraries are currently installed. + * + * Convenience wrapper function for gst_plugin_add_dependency() which + * takes simple strings as arguments instead of string arrays, with multiple + * arguments separated by predefined delimiters (see above). + * + * Since: 0.10.22 + */ +void +gst_plugin_add_dependency_simple (GstPlugin * plugin, + const gchar * env_vars, const gchar * paths, const gchar * names, + GstPluginDependencyFlags flags) +{ + gchar **a_evars = NULL; + gchar **a_paths = NULL; + gchar **a_names = NULL; + + if (env_vars) + a_evars = g_strsplit_set (env_vars, ":;,", -1); + if (paths) + a_paths = g_strsplit_set (paths, ":;,", -1); + if (names) + a_names = g_strsplit_set (names, ",", -1); + + gst_plugin_add_dependency (plugin, (const gchar **) a_evars, + (const gchar **) a_paths, (const gchar **) a_names, flags); + + if (a_evars) + g_strfreev (a_evars); + if (a_paths) + g_strfreev (a_paths); + if (a_names) + g_strfreev (a_names); +} diff --git a/gst/gstplugin.h b/gst/gstplugin.h index 9e61d1646d..1ddae94821 100644 --- a/gst/gstplugin.h +++ b/gst/gstplugin.h @@ -37,6 +37,7 @@ G_BEGIN_DECLS typedef struct _GstPlugin GstPlugin; typedef struct _GstPluginClass GstPluginClass; +typedef struct _GstPluginPrivate GstPluginPrivate; typedef struct _GstPluginDesc GstPluginDesc; /** @@ -75,6 +76,27 @@ typedef enum GST_PLUGIN_FLAG_CACHED = (1<<0) } GstPluginFlags; +/** + * GstPluginDependencyFlags: + * @GST_PLUGIN_DEPENDENCY_FLAG_NONE : no special flags + * @GST_PLUGIN_DEPENDENCY_FLAG_RECURSE : recurse into subdirectories + * @GST_PLUGIN_DEPENDENCY_FLAG_PATHS_ARE_DEFAULT_ONLY : use paths + * argument only if none of the environment variables is set + * @GST_PLUGIN_DEPENDENCY_FLAG_FILE_NAME_IS_SUFFIX : interpret + * filename argument as filter suffix and check all matching files in + * the directory + * + * Flags used in connection with gst_plugin_add_dependency(). + * + * Since: 0.10.22 + */ +typedef enum { + GST_PLUGIN_DEPENDENCY_FLAG_NONE = 0, + GST_PLUGIN_DEPENDENCY_FLAG_RECURSE = (1 << 0), + GST_PLUGIN_DEPENDENCY_FLAG_PATHS_ARE_DEFAULT_ONLY = (1 << 1), + GST_PLUGIN_DEPENDENCY_FLAG_FILE_NAME_IS_SUFFIX = (1 << 2) +} GstPluginDependencyFlags; + /** * GstPluginInitFunc: * @plugin: The plugin object that can be used to register #GstPluginFeatures for this plugin. @@ -156,7 +178,8 @@ struct _GstPlugin { gboolean registered; /* TRUE when the registry has seen a filename * that matches the plugin's basename */ - gpointer _gst_reserved[GST_PADDING]; + GstPluginPrivate *priv; + gpointer _gst_reserved[GST_PADDING - 1]; }; struct _GstPluginClass { @@ -298,6 +321,18 @@ GstPlugin * gst_plugin_load_file (const gchar *filename, GError** error); GstPlugin * gst_plugin_load (GstPlugin *plugin); GstPlugin * gst_plugin_load_by_name (const gchar *name); +void gst_plugin_add_dependency (GstPlugin * plugin, + const gchar ** env_vars, + const gchar ** paths, + const gchar ** names, + GstPluginDependencyFlags flags); + +void gst_plugin_add_dependency_simple (GstPlugin * plugin, + const gchar * env_vars, + const gchar * paths, + const gchar * names, + GstPluginDependencyFlags flags); + void gst_plugin_list_free (GList *list); G_END_DECLS diff --git a/gst/gstregistry.c b/gst/gstregistry.c index d3e8271eab..bfb099d3ad 100644 --- a/gst/gstregistry.c +++ b/gst/gstregistry.c @@ -863,6 +863,8 @@ gst_registry_scan_path_level (GstRegistry * registry, const gchar * path, * was already seen by the registry, we ignore it */ plugin = gst_registry_lookup (registry, filename); if (plugin) { + gboolean env_vars_changed, deps_changed = FALSE; + if (plugin->registered) { GST_DEBUG_OBJECT (registry, "plugin already registered from path \"%s\"", @@ -871,8 +873,12 @@ gst_registry_scan_path_level (GstRegistry * registry, const gchar * path, gst_object_unref (plugin); continue; } + + env_vars_changed = _priv_plugin_deps_env_vars_changed (plugin); + if (plugin->file_mtime == file_status.st_mtime && - plugin->file_size == file_status.st_size) { + plugin->file_size == file_status.st_size && !env_vars_changed && + !(deps_changed = _priv_plugin_deps_files_changed (plugin))) { GST_LOG_OBJECT (registry, "file %s cached", filename); plugin->flags &= ~GST_PLUGIN_FLAG_CACHED; GST_LOG_OBJECT (registry, "marking plugin %p as registered as %s", @@ -888,9 +894,11 @@ gst_registry_scan_path_level (GstRegistry * registry, const gchar * path, } else { GST_INFO_OBJECT (registry, "cached info for %s is stale", filename); GST_DEBUG_OBJECT (registry, "mtime %ld != %ld or size %" - G_GINT64_FORMAT " != %" - G_GINT64_FORMAT, plugin->file_mtime, file_status.st_mtime, - (gint64) plugin->file_size, (gint64) file_status.st_size); + G_GINT64_FORMAT " != %" G_GINT64_FORMAT " or external dependency " + "env_vars changed: %d or external dependencies changed: %d", + plugin->file_mtime, file_status.st_mtime, + (gint64) plugin->file_size, (gint64) file_status.st_size, + env_vars_changed, deps_changed); gst_registry_remove_plugin (gst_registry_get_default (), plugin); /* We don't use a GError here because a failure to load some shared * objects as plugins is normal (particularly in the uninstalled case) diff --git a/gst/gstregistrybinary.c b/gst/gstregistrybinary.c index a1a21ef2a6..7c2ecfcf26 100644 --- a/gst/gstregistrybinary.c +++ b/gst/gstregistrybinary.c @@ -554,6 +554,38 @@ fail: return FALSE; } +static gboolean +gst_registry_binary_save_plugin_dep (GList ** list, GstPluginDep * dep) +{ + GstBinaryDep *ed; + GstBinaryChunk *chk; + gchar **s; + + ed = g_new0 (GstBinaryDep, 1); + chk = gst_registry_binary_make_data (ed, sizeof (GstBinaryDep)); + + ed->flags = dep->flags; + ed->n_env_vars = 0; + ed->n_paths = 0; + ed->n_names = 0; + + ed->env_hash = dep->env_hash; + ed->stat_hash = dep->stat_hash; + + for (s = dep->env_vars; s != NULL && *s != NULL; ++s, ++ed->n_env_vars) + gst_registry_binary_save_string (list, *s); + + for (s = dep->paths; s != NULL && *s != NULL; ++s, ++ed->n_paths) + gst_registry_binary_save_string (list, *s); + + for (s = dep->names; s != NULL && *s != NULL; ++s, ++ed->n_names) + gst_registry_binary_save_string (list, *s); + + *list = g_list_prepend (*list, chk); + + GST_LOG ("Saved external plugin dependency"); + return TRUE; +} /* * gst_registry_binary_save_plugin: @@ -575,8 +607,18 @@ gst_registry_binary_save_plugin (GList ** list, GstRegistry * registry, pe->file_size = plugin->file_size; pe->file_mtime = plugin->file_mtime; + pe->n_deps = 0; pe->nfeatures = 0; + /* pack external deps */ + for (walk = plugin->priv->deps; walk != NULL; walk = walk->next) { + if (!gst_registry_binary_save_plugin_dep (list, walk->data)) { + GST_ERROR ("Could not save external plugin dependency, aborting."); + goto fail; + } + ++pe->n_deps; + } + /* pack plugin features */ plugin_features = gst_registry_get_feature_list_by_plugin (registry, plugin->desc.name); @@ -954,6 +996,57 @@ fail: return FALSE; } +static gchar ** +gst_registry_binary_load_plugin_dep_strv (gchar ** in, guint n) +{ + gchar **arr; + + if (n == 0) + return NULL; + + arr = g_new0 (gchar *, n + 1); + while (n > 0) { + unpack_string (*in, arr[n - 1]); + --n; + } + return arr; +} + +static gboolean +gst_registry_binary_load_plugin_dep (GstPlugin * plugin, gchar ** in) +{ + GstPluginDep *dep; + GstBinaryDep *d; + gchar **s; + + align (*in); + GST_LOG_OBJECT (plugin, "Unpacking GstBinaryDep from %p", *in); + unpack_element (*in, d, GstBinaryDep); + + dep = g_new0 (GstPluginDep, 1); + + dep->env_hash = d->env_hash; + dep->stat_hash = d->stat_hash; + + dep->flags = d->flags; + + dep->names = gst_registry_binary_load_plugin_dep_strv (in, d->n_names); + dep->paths = gst_registry_binary_load_plugin_dep_strv (in, d->n_paths); + dep->env_vars = gst_registry_binary_load_plugin_dep_strv (in, d->n_env_vars); + + plugin->priv->deps = g_list_append (plugin->priv->deps, dep); + + GST_DEBUG_OBJECT (plugin, "Loaded external plugin dependency from registry: " + "env_hash: %08x, stat_hash: %08x", dep->env_hash, dep->stat_hash); + for (s = dep->env_vars; s != NULL && *s != NULL; ++s) + GST_LOG_OBJECT (plugin, " evar: %s", *s); + for (s = dep->paths; s != NULL && *s != NULL; ++s) + GST_LOG_OBJECT (plugin, " path: %s", *s); + for (s = dep->names; s != NULL && *s != NULL; ++s) + GST_LOG_OBJECT (plugin, " name: %s", *s); + + return TRUE; +} /* * gst_registry_binary_load_plugin: @@ -1004,6 +1097,8 @@ gst_registry_binary_load_plugin (GstRegistry * registry, gchar ** in) gst_registry_add_plugin (registry, plugin); GST_DEBUG ("Added plugin '%s' plugin with %d features from binary registry", plugin->desc.name, pe->nfeatures); + + /* Load plugin features */ for (i = 0; i < pe->nfeatures; i++) { if (!gst_registry_binary_load_feature (registry, in, plugin->desc.name)) { GST_ERROR ("Error while loading binary feature"); @@ -1011,6 +1106,14 @@ gst_registry_binary_load_plugin (GstRegistry * registry, gchar ** in) } } + /* Load external plugin dependencies */ + for (i = 0; i < pe->n_deps; ++i) { + if (!gst_registry_binary_load_plugin_dep (plugin, in)) { + GST_ERROR_OBJECT (plugin, "Could not read external plugin dependency"); + goto fail; + } + } + return TRUE; /* Errors */ diff --git a/gst/gstregistrybinary.h b/gst/gstregistrybinary.h index 5ce3e96d89..bcdb8a65e9 100644 --- a/gst/gstregistrybinary.h +++ b/gst/gstregistrybinary.h @@ -57,7 +57,7 @@ * This _must_ be updated whenever the registry format changes, * we currently use the core version where this change happened. */ -#define GST_MAGIC_BINARY_VERSION_STR ("0.10.21.1") +#define GST_MAGIC_BINARY_VERSION_STR ("0.10.21.2") /* * GST_MAGIC_BINARY_VERSION_LEN: @@ -108,9 +108,23 @@ typedef struct _GstBinaryPluginElement gulong file_size; gulong file_mtime; + guint n_deps; + guint nfeatures; } GstBinaryPluginElement; +/* GstBinaryDep: + */ +typedef struct _GstBinaryDep +{ + guint flags; + guint n_env_vars; + guint n_paths; + guint n_names; + + guint env_hash; + guint stat_hash; +} GstBinaryDep; /* * GstBinaryPluginFeature: diff --git a/gst/gstregistryxml.c b/gst/gstregistryxml.c index c2eb3ced36..f402b773bb 100644 --- a/gst/gstregistryxml.c +++ b/gst/gstregistryxml.c @@ -785,6 +785,10 @@ gst_registry_xml_save_plugin (GstRegistry * registry, GstPlugin * plugin) GList *walk; char s[100]; + if (plugin->priv->deps != NULL) { + GST_WARNING ("XML registry does not support external plugin dependencies"); + } + if (!gst_registry_save_escaped (registry, " ", "name", plugin->desc.name)) return FALSE; if (!gst_registry_save_escaped (registry, " ", "description",