/* * Copyright (C) 2011-2013 Karlsruhe Institute of Technology * * This file is part of Ufo. * * This library is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "config.h" #include #include #include #include #include #include #include "compat.h" /** * SECTION:ufo-plugin-manager * @Short_description: Load a task from a shared object * @Title: UfoPluginManager * * The plugin manager opens and loads #UfoTaskNode objects using * ufo_plugin_manager_get_task() from shared objects. The libraries are * searched for in the path configured at build time and in paths provided by * the `UFO_PLUGIN_PATH` environment variable. */ static gchar *ufo_transform_string (const gchar *pattern, const gchar *s, const gchar *separator); static gchar *ufo_transform_string_package (const gchar *pattern, const gchar *s1,const gchar *s2, const gchar *separator); G_DEFINE_TYPE (UfoPluginManager, ufo_plugin_manager, G_TYPE_OBJECT) #define UFO_PLUGIN_MANAGER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), UFO_TYPE_PLUGIN_MANAGER, UfoPluginManagerPrivate)) typedef UfoNode* (* NewFunc) (void); struct _UfoPluginManagerPrivate { GList *paths; GSList *modules; GHashTable *new_funcs; /* maps from gchar* to NewFunc* */ }; enum { PROP_0, N_PROPERTIES }; /** * UfoPluginManagerError: * @UFO_PLUGIN_MANAGER_ERROR_MODULE_NOT_FOUND: The module could not be found * @UFO_PLUGIN_MANAGER_ERROR_MODULE_OPEN: Module could not be opened * @UFO_PLUGIN_MANAGER_ERROR_SYMBOL_NOT_FOUND: Necessary entry symbol was not * found * * Possible errors that ufo_plugin_manager_get_task() can return. */ GQuark ufo_plugin_manager_error_quark (void) { return g_quark_from_static_string ("ufo-plugin-manager-error-quark"); } static gchar * plugin_manager_get_path (UfoPluginManagerPrivate *priv, const gchar *name) { GList *it; /* Check first if filename is already a path */ if (g_path_is_absolute (name)) { if (g_file_test (name, G_FILE_TEST_EXISTS)) return g_strdup (name); else return NULL; } /* If it is not a path, search in all known paths */ g_list_for (priv->paths, it) { gchar *path = g_build_filename ((gchar *) it->data, name, NULL); if (g_file_test (path, G_FILE_TEST_EXISTS)) return path; g_free (path); } return NULL; } /** * ufo_plugin_manager_new: * * Create a plugin manager object to instantiate filter objects. * * Return value: A new #UfoPluginManager object. */ UfoPluginManager * ufo_plugin_manager_new (void) { return UFO_PLUGIN_MANAGER (g_object_new (UFO_TYPE_PLUGIN_MANAGER, NULL)); } /** * ufo_plugin_manager_get_plugin: * @manager: A #UfoPluginManager * @func_name: Name of the constructor function. * @module_name: Filename of the shared object. * @error: return location for a GError or %NULL * * Load a module and return an instance. * * Returns: (transfer full): (allow-none): A loaded plugin or %NULL if module * cannot be found. */ GObject * ufo_plugin_manager_get_plugin (UfoPluginManager *manager, const gchar *func_name, const gchar *module_name, GError **error) { g_return_val_if_fail (UFO_IS_PLUGIN_MANAGER (manager) && func_name != NULL && module_name != NULL, NULL); UfoPluginManagerPrivate *priv = UFO_PLUGIN_MANAGER_GET_PRIVATE (manager); gpointer plugin = NULL; NewFunc *func = NULL; GModule *module = NULL; gchar *key = g_strjoin("@", func_name, module_name, NULL); func = g_hash_table_lookup (priv->new_funcs, key); if (func == NULL) { gchar *path = plugin_manager_get_path (priv, module_name); if (path == NULL) { GString *search_paths; GList *it; search_paths = g_string_new (NULL); g_list_for (priv->paths, it) { g_string_append (search_paths, (const gchar *) it->data); if (it->next != NULL) g_string_append (search_paths, ", "); } g_set_error (error, UFO_PLUGIN_MANAGER_ERROR, UFO_PLUGIN_MANAGER_ERROR_MODULE_NOT_FOUND, "Module %s not found in %s", module_name, search_paths->str); g_string_free (search_paths, TRUE); goto handle_error; } module = g_module_open (path, G_MODULE_BIND_LAZY); g_debug ("OPEN %s", path); g_free (path); if (!module) { g_set_error (error, UFO_PLUGIN_MANAGER_ERROR, UFO_PLUGIN_MANAGER_ERROR_MODULE_OPEN, "Module %s could not be opened: %s", module_name, g_module_error ()); goto handle_error; } func = g_malloc0 (sizeof (NewFunc)); if (!g_module_symbol (module, func_name, (gpointer *) func)) { g_set_error (error, UFO_PLUGIN_MANAGER_ERROR, UFO_PLUGIN_MANAGER_ERROR_SYMBOL_NOT_FOUND, "%s is not exported by module %s: %s", func_name, module_name, g_module_error ()); g_free (func); if (!g_module_close (module)) g_warning ("%s", g_module_error ()); goto handle_error; } priv->modules = g_slist_append (priv->modules, module); g_hash_table_insert (priv->new_funcs, g_strdup (key), func); g_free (key); } plugin = (*func) (); return plugin; handle_error: g_free (key); return NULL; } /** * ufo_plugin_get_all_plugin_names: * @manager: A #UfoPluginManager * @filename_regex: Regex for filenames * @filename_pattern: Pattern according with the files will be searched * * Return a list with potential plugin names that match shared objects in all * search paths. * * Return value: (element-type utf8) (transfer full): List of strings with filter names */ GList* ufo_plugin_get_all_plugin_names (UfoPluginManager *manager, const GRegex *filename_regex, const gchar *filename_pattern) { UfoPluginManagerPrivate *priv; GList *it; GList *result = NULL; GMatchInfo *match_info = NULL; g_return_val_if_fail (UFO_IS_PLUGIN_MANAGER (manager), NULL); priv = manager->priv; g_list_for (priv->paths, it) { glob_t glob_vector; gchar *pattern; pattern = g_build_filename ((gchar *) it->data, filename_pattern, NULL); glob (pattern, GLOB_MARK | GLOB_TILDE, NULL, &glob_vector); g_free (pattern); gsize i = 0; while (i < glob_vector.gl_pathc) { g_regex_match (filename_regex, glob_vector.gl_pathv[i], 0, &match_info); if (g_match_info_matches (match_info)) { gchar *word = g_match_info_fetch (match_info, 1); result = g_list_append (result, word); } i++; } } g_match_info_free (match_info); return result; } static void ufo_plugin_manager_finalize (GObject *gobject) { UfoPluginManager *manager = UFO_PLUGIN_MANAGER (gobject); UfoPluginManagerPrivate *priv = UFO_PLUGIN_MANAGER_GET_PRIVATE (manager); /* XXX: This is a necessary hack! We return a full reference for * ufo_plugin_manager_get_task() so that the Python run-time can cleanup * the tasks that are assigned. However, there is no relationship between * graphs, tasks and the plugin manager and it might happen, that the plugin * manager is destroy before the graph which in turn would unref invalid * objects. So, we just don't close the modules and hope for the best. */ g_list_free_full (priv->paths, g_free); g_hash_table_destroy (priv->new_funcs); G_OBJECT_CLASS (ufo_plugin_manager_parent_class)->finalize (gobject); } static void ufo_plugin_manager_class_init (UfoPluginManagerClass *klass) { GObjectClass *oclass; oclass = G_OBJECT_CLASS (klass); oclass->finalize = ufo_plugin_manager_finalize; g_type_class_add_private (klass, sizeof (UfoPluginManagerPrivate)); } static void add_environment_paths (UfoPluginManagerPrivate *priv, const gchar *env) { gchar **paths; if (env == NULL) return; paths = g_strsplit(env, ":", -1); for (unsigned idx = 0; paths[idx]; ++idx) { /* Ignore empty paths */ if (*paths[idx]) priv->paths = g_list_append (priv->paths, paths[idx]); } g_free(paths); } static void ufo_plugin_manager_init (UfoPluginManager *manager) { static const gchar *PATH_VAR = "UFO_PLUGIN_PATH"; UfoPluginManagerPrivate *priv; manager->priv = priv = UFO_PLUGIN_MANAGER_GET_PRIVATE (manager); priv->modules = NULL; priv->new_funcs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); add_environment_paths (priv, g_getenv (PATH_VAR)); priv->paths = g_list_append (priv->paths, g_strdup (UFO_PLUGIN_DIR)); } /** * ufo_plugin_manager_get_task: * @manager: A #UfoPluginManager * @name: Name of the plugin. * @error: return location for a GError or %NULL * * Load a #UfoFilter module and return an instance. The shared object name must * be * constructed as "libfilter@name.so". * * Since: 0.2, the error parameter is available * * Returns: (transfer full): (allow-none): #UfoFilter or %NULL if module cannot be found */ UfoTaskNode * ufo_plugin_manager_get_task (UfoPluginManager *manager, const gchar *name, GError **error) { g_return_val_if_fail (UFO_IS_PLUGIN_MANAGER (manager) && name != NULL, NULL); UfoTaskNode *node; if (!g_strcmp0 (name, "[dummy]")) return UFO_TASK_NODE (ufo_dummy_task_new ()); if (!g_strcmp0 (name, "[copy]")) return UFO_TASK_NODE (ufo_copy_task_new ()); gchar *module_name = ufo_transform_string ("libufofilter%s.so", name, NULL); gchar *func_name = ufo_transform_string ("ufo_%s_task_new", name, "_"); node = UFO_TASK_NODE (ufo_plugin_manager_get_plugin (manager, func_name, module_name, error)); if (node != NULL) { ufo_task_node_set_plugin_name (node, name); g_debug ("NEW %s", ufo_task_node_get_identifier (node)); } g_free (func_name); g_free (module_name); return node; } /** * ufo_plugin_manager_get_task_from_package: * @manager: A #UfoPluginManager * @package_name: Name of library package * @name: Name of the plugin. * @error: return location for a GError or %NULL * * Load a #UfoTaskNode module and return an instance. The shared object name must * be in @package_name subfolder and constructed as "lib@name.so". * * Since: 0.2, the error parameter is available * * Returns: (transfer full): (allow-none): #UfoTaskNode or %NULL if module cannot be found */ UfoTaskNode* ufo_plugin_manager_get_task_from_package (UfoPluginManager *manager, const gchar *package_name, const gchar *name, GError **error) { g_return_val_if_fail (UFO_IS_PLUGIN_MANAGER (manager) && name != NULL, NULL); gchar *so_name = ufo_transform_string_package ("%s/libufo%s.so", package_name, name, NULL); gchar *func_name = ufo_transform_string_package ("ufo_%s_%s_task_new", package_name, name, "_"); UfoTaskNode *node = UFO_TASK_NODE (ufo_plugin_manager_get_plugin (manager, func_name, so_name, error)); if (node != NULL) ufo_task_node_set_plugin_name (node, name); g_free (func_name); g_free (so_name); g_debug ("NEW %s", ufo_task_node_get_identifier (node)); return node; } /** * ufo_plugin_manager_get_all_task_names: * @manager: A #UfoPluginManager * * Return a list with potential filter names that match shared objects in all * search paths. * * Return value: (element-type utf8) (transfer full): List of strings with filter names */ GList * ufo_plugin_manager_get_all_task_names (UfoPluginManager *manager) { g_return_val_if_fail (UFO_IS_PLUGIN_MANAGER (manager), NULL); GRegex *regex = g_regex_new ("libufofilter(\\w+).so", 0, 0, NULL); GList *result = ufo_plugin_get_all_plugin_names(manager, regex, "libufofilter*.so"); g_regex_unref (regex); return result; } /** * ufo_transform_string: * @pattern: A pattern to place the result string in it. * @s: A string, which should be placed in the @pattern. * @separator: A string containing separator symbols in the @s that should be removed * * Returns: A string there in @pattern was placed @s. */ gchar * ufo_transform_string (const gchar *pattern, const gchar *s, const gchar *separator) { gchar **sv; gchar *transformed; gchar *result; sv = g_strsplit_set (s, "-_ ", -1); transformed = g_strjoinv (separator, sv); result = g_strdup_printf (pattern, transformed); g_strfreev (sv); g_free (transformed); return result; } /** * ufo_transform_string_package: * @pattern: A pattern to place the result string in it. * @s: A string, which should be placed in the @pattern. * @separator: A string containing separator symbols in the @s that should be removed * * Returns: A string there in @pattern was placed @s. */ gchar * ufo_transform_string_package (const gchar *pattern, const gchar *s1, const gchar *s2, const gchar *separator) { gchar **sv; gchar *transformed1; gchar *transformed2; gchar *result; sv = g_strsplit_set (s1, "-_ ", -1); transformed1 = g_strjoinv (separator, sv); g_strfreev (sv); sv = g_strsplit_set (s2, "-_ ", -1); transformed2 = g_strjoinv (separator, sv); g_strfreev (sv); result = g_strdup_printf (pattern, transformed1, transformed2); g_free (transformed1); g_free (transformed2); return result; }