XMMS2

src/xmms/plugin.c

Go to the documentation of this file.
00001 /*  XMMS2 - X Music Multiplexer System
00002  *  Copyright (C) 2003-2009 XMMS2 Team
00003  *
00004  *  PLUGINS ARE NOT CONSIDERED TO BE DERIVED WORK !!!
00005  *
00006  *  This library is free software; you can redistribute it and/or
00007  *  modify it under the terms of the GNU Lesser General Public
00008  *  License as published by the Free Software Foundation; either
00009  *  version 2.1 of the License, or (at your option) any later version.
00010  *
00011  *  This library is distributed in the hope that it will be useful,
00012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014  *  Lesser General Public License for more details.
00015  */
00016 
00017 #include "xmms_configuration.h"
00018 #include "xmmspriv/xmms_plugin.h"
00019 #include "xmms/xmms_config.h"
00020 #include "xmmspriv/xmms_config.h"
00021 #include "xmms/xmms_object.h"
00022 #include "xmms/xmms_log.h"
00023 #include "xmmspriv/xmms_playlist.h"
00024 #include "xmmspriv/xmms_outputplugin.h"
00025 #include "xmmspriv/xmms_xform.h"
00026 
00027 #include <gmodule.h>
00028 #include <string.h>
00029 #include <stdarg.h>
00030 
00031 #ifdef HAVE_VALGRIND
00032 # include <memcheck.h>
00033 #endif
00034 
00035 /* OSX uses the .bundle extension, but g_module_build_path returns .so. */
00036 #ifdef USE_BUNDLES
00037 #define get_module_ext(dir) g_build_filename (dir, "*.bundle", NULL)
00038 #else
00039 #define get_module_ext(dir) g_module_build_path (dir, "*")
00040 #endif
00041 
00042 
00043 /*
00044  * Global variables
00045  */
00046 static GList *xmms_plugin_list;
00047 
00048 /*
00049  * Function prototypes
00050  */
00051 static gboolean xmms_plugin_setup (xmms_plugin_t *plugin, const xmms_plugin_desc_t *desc);
00052 static gboolean xmms_plugin_load (const xmms_plugin_desc_t *desc, GModule *module);
00053 static gboolean xmms_plugin_scan_directory (const gchar *dir);
00054 
00055 /*
00056  * Public functions
00057  */
00058 
00059 
00060 /**
00061  * @if internal
00062  * -- internal documentation section --
00063  * @addtogroup XMMSPlugin
00064  * @{
00065  */
00066 
00067 /**
00068  * @internal 
00069  * Lookup the value of a plugin's config property, given the property key.
00070  * @param[in] plugin The plugin
00071  * @param[in] key The property key (config path)
00072  * @return A config value
00073  * @todo config value <-> property fixup
00074  */
00075 xmms_config_property_t *
00076 xmms_plugin_config_lookup (xmms_plugin_t *plugin,
00077                            const gchar *key)
00078 {
00079     gchar path[XMMS_PLUGIN_SHORTNAME_MAX_LEN + 256];
00080     xmms_config_property_t *prop;
00081 
00082     g_return_val_if_fail (plugin, NULL);
00083     g_return_val_if_fail (key, NULL);
00084 
00085     g_snprintf (path, sizeof (path), "%s.%s",
00086                 xmms_plugin_shortname_get (plugin), key);
00087     prop = xmms_config_lookup (path);
00088 
00089     return prop;
00090 }
00091 
00092 /**
00093  * @internal 
00094  * Register a config property for a plugin.
00095  * @param[in] plugin The plugin
00096  * @param[in] name The property name
00097  * @param[in] default_value The default value for the property
00098  * @param[in] cb A callback function to be executed when the property value
00099  * changes
00100  * @param[in] userdata Pointer to data to be passed to the callback
00101  * @todo config value <-> property fixup
00102  */
00103 xmms_config_property_t *
00104 xmms_plugin_config_property_register (xmms_plugin_t *plugin,
00105                                       const gchar *name,
00106                                       const gchar *default_value,
00107                                       xmms_object_handler_t cb,
00108                                       gpointer userdata)
00109 {
00110     gchar fullpath[XMMS_PLUGIN_SHORTNAME_MAX_LEN + 256];
00111     xmms_config_property_t *prop;
00112 
00113     g_return_val_if_fail (plugin, NULL);
00114     g_return_val_if_fail (name, NULL);
00115     g_return_val_if_fail (default_value, NULL);
00116 
00117     g_snprintf (fullpath, sizeof (fullpath), "%s.%s",
00118                 xmms_plugin_shortname_get (plugin), name);
00119 
00120     prop = xmms_config_property_register (fullpath, default_value, cb,
00121                                           userdata);
00122 
00123     return prop;
00124 }
00125 
00126 /**
00127  * @internal Get the type of this plugin
00128  * @param[in] plugin The plugin
00129  * @return The plugin type (#xmms_plugin_type_t)
00130  */
00131 xmms_plugin_type_t
00132 xmms_plugin_type_get (const xmms_plugin_t *plugin)
00133 {
00134     g_return_val_if_fail (plugin, 0);
00135 
00136     return plugin->type;
00137 }
00138 
00139 /**
00140  * @internal Get the plugin's name. This is just an accessor method.
00141  * @param[in] plugin The plugin
00142  * @return A string containing the plugin's name
00143  */
00144 const char *
00145 xmms_plugin_name_get (const xmms_plugin_t *plugin)
00146 {
00147     g_return_val_if_fail (plugin, NULL);
00148 
00149     return plugin->name;
00150 }
00151 
00152 /**
00153  * @internal Get the plugin's short name. This is just an accessor method.
00154  * @param[in] plugin The plugin
00155  * @return A string containing the plugin's short name
00156  */
00157 const gchar *
00158 xmms_plugin_shortname_get (const xmms_plugin_t *plugin)
00159 {
00160     g_return_val_if_fail (plugin, NULL);
00161 
00162     return plugin->shortname;
00163 }
00164 
00165 /**
00166  * @internal Get the plugin's version. This is just an accessor method.
00167  * @param[in] plugin The plugin
00168  * @return A string containing the plugin's version
00169  */
00170 const gchar *
00171 xmms_plugin_version_get (const xmms_plugin_t *plugin)
00172 {
00173     g_return_val_if_fail (plugin, NULL);
00174 
00175     return plugin->version;
00176 }
00177 
00178 /**
00179  * @internal Get the plugin's description. This is just an accessor method.
00180  * @param[in] plugin The plugin
00181  * @return A string containing the plugin's description
00182  */
00183 const char *
00184 xmms_plugin_description_get (const xmms_plugin_t *plugin)
00185 {
00186     g_return_val_if_fail (plugin, NULL);
00187 
00188     return plugin->description;
00189 }
00190 
00191 /*
00192  * Private functions
00193  */
00194 
00195 
00196 static void
00197 xmms_plugin_add_builtin_plugins (void)
00198 {
00199     extern const xmms_plugin_desc_t xmms_builtin_ringbuf;
00200     extern const xmms_plugin_desc_t xmms_builtin_magic;
00201     extern const xmms_plugin_desc_t xmms_builtin_converter;
00202     extern const xmms_plugin_desc_t xmms_builtin_segment;
00203     extern const xmms_plugin_desc_t xmms_builtin_visualization;
00204 
00205     xmms_plugin_load (&xmms_builtin_ringbuf, NULL);
00206     xmms_plugin_load (&xmms_builtin_magic, NULL);
00207     xmms_plugin_load (&xmms_builtin_converter, NULL);
00208     xmms_plugin_load (&xmms_builtin_segment, NULL);
00209     xmms_plugin_load (&xmms_builtin_visualization, NULL);
00210 }
00211 
00212 
00213 /**
00214  * @internal Initialise the plugin system
00215  * @param[in] path Absolute path to the plugins directory.
00216  * @return Whether the initialisation was successful or not.
00217  */
00218 gboolean
00219 xmms_plugin_init (const gchar *path)
00220 {
00221     if (!path)
00222         path = PKGLIBDIR;
00223 
00224     xmms_plugin_scan_directory (path);
00225 
00226     xmms_plugin_add_builtin_plugins ();
00227     return TRUE;
00228 }
00229 
00230 /**
00231  * @internal Shut down the plugin system. This function unrefs all the plugins
00232  * loaded.
00233  */
00234 void
00235 xmms_plugin_shutdown ()
00236 {
00237 #ifdef HAVE_VALGRIND
00238     /* print out a leak summary at this point, because the final leak
00239      * summary won't include proper backtraces of leaks found in
00240      * plugins, since we close the so's here.
00241      *
00242      * note: the following call doesn't do anything if we're not run
00243      * in valgrind
00244      */
00245     VALGRIND_DO_LEAK_CHECK
00246         ;
00247 #endif
00248 
00249     while (xmms_plugin_list) {
00250         xmms_plugin_t *p = xmms_plugin_list->data;
00251 
00252         /* if this plugin's refcount is > 1, then there's a bug
00253          * in one of the other subsystems
00254          */
00255         if (p->object.ref > 1) {
00256             XMMS_DBG ("%s's refcount is %i",
00257                       p->name, p->object.ref);
00258         }
00259 
00260         xmms_object_unref (p);
00261 
00262         xmms_plugin_list = g_list_delete_link (xmms_plugin_list,
00263                                                xmms_plugin_list);
00264     }
00265 }
00266 
00267 
00268 static gboolean
00269 xmms_plugin_load (const xmms_plugin_desc_t *desc, GModule *module)
00270 {
00271     xmms_plugin_t *plugin;
00272     xmms_plugin_t *(*allocer) (void);
00273     gboolean (*verifier) (xmms_plugin_t *);
00274     gint expected_ver;
00275 
00276     XMMS_DBG ("Loading plugin '%s'", desc->name);
00277 
00278     switch (desc->type) {
00279     case XMMS_PLUGIN_TYPE_OUTPUT:
00280         expected_ver = XMMS_OUTPUT_API_VERSION;
00281         allocer = xmms_output_plugin_new;
00282         verifier = xmms_output_plugin_verify;
00283         break;
00284     case XMMS_PLUGIN_TYPE_XFORM:
00285         expected_ver = XMMS_XFORM_API_VERSION;
00286         allocer = xmms_xform_plugin_new;
00287         verifier = xmms_xform_plugin_verify;
00288         break;
00289     default:
00290         XMMS_DBG ("Unknown plugin type!");
00291         return FALSE;
00292     }
00293 
00294     if (desc->api_version != expected_ver) {
00295         XMMS_DBG ("Bad api version!");
00296         return FALSE;
00297     }
00298 
00299     plugin = allocer ();
00300     if (!plugin) {
00301         XMMS_DBG ("Alloc failed!");
00302         return FALSE;
00303     }
00304 
00305     if (!xmms_plugin_setup (plugin, desc)) {
00306         xmms_log_error ("Setup failed for plugin '%s'!", desc->name);
00307         xmms_object_unref (plugin);
00308         return FALSE;
00309     }
00310 
00311     if (!desc->setup_func (plugin)) {
00312         xmms_log_error ("Setup function failed for plugin '%s'!",
00313                         desc->name);
00314         xmms_object_unref (plugin);
00315         return FALSE;
00316     }
00317 
00318     if (!verifier (plugin)) {
00319         xmms_log_error ("Verify failed for plugin '%s'!", desc->name);
00320         xmms_object_unref (plugin);
00321         return FALSE;
00322     }
00323 
00324     plugin->module = module;
00325 
00326     xmms_plugin_list = g_list_prepend (xmms_plugin_list, plugin);
00327     return TRUE;
00328 }
00329 
00330 /**
00331  * @internal Scan a particular directory for plugins to load
00332  * @param[in] dir Absolute path to plugins directory
00333  * @return TRUE if directory successfully scanned for plugins
00334  */
00335 static gboolean
00336 xmms_plugin_scan_directory (const gchar *dir)
00337 {
00338     GDir *d;
00339     const char *name;
00340     gchar *path;
00341     gchar *temp;
00342     gchar *pattern;
00343     GModule *module;
00344     gpointer sym;
00345 
00346     temp = get_module_ext (dir);
00347 
00348     XMMS_DBG ("Scanning directory for plugins (%s)", temp);
00349 
00350     pattern = g_path_get_basename (temp);
00351 
00352     g_free (temp);
00353 
00354     d = g_dir_open (dir, 0, NULL);
00355     if (!d) {
00356         xmms_log_error ("Failed to open plugin directory (%s)", dir);
00357         return FALSE;
00358     }
00359 
00360     while ((name = g_dir_read_name (d))) {
00361 
00362         if (!g_pattern_match_simple (pattern, name))
00363             continue;
00364 
00365         path = g_build_filename (dir, name, NULL);
00366         if (!g_file_test (path, G_FILE_TEST_IS_REGULAR)) {
00367             g_free (path);
00368             continue;
00369         }
00370 
00371         XMMS_DBG ("Trying to load file: %s", path);
00372         module = g_module_open (path, G_MODULE_BIND_LOCAL);
00373         if (!module) {
00374             xmms_log_error ("Failed to open plugin %s: %s",
00375                             path, g_module_error ());
00376             g_free (path);
00377             continue;
00378         }
00379 
00380         if (!g_module_symbol (module, "XMMS_PLUGIN_DESC", &sym)) {
00381             xmms_log_error ("Failed to find plugin header in %s", path);
00382             g_module_close (module);
00383             g_free (path);
00384             continue;
00385         }
00386         g_free (path);
00387 
00388         if (!xmms_plugin_load ((const xmms_plugin_desc_t *) sym, module)) {
00389             g_module_close (module);
00390         }
00391     }
00392 
00393     g_dir_close (d);
00394     g_free (pattern);
00395 
00396     return TRUE;
00397 }
00398 
00399 /**
00400  * @internal Apply a function to all plugins of specified type.
00401  * @param[in] type The type of plugin to look for.
00402  * @param[in] func function to apply.
00403  * @param[in] user_data Userspecified data passed to function.
00404  */
00405 void
00406 xmms_plugin_foreach (xmms_plugin_type_t type, xmms_plugin_foreach_func_t func, gpointer user_data)
00407 {
00408     GList *node;
00409 
00410     for (node = xmms_plugin_list; node; node = g_list_next (node)) {
00411         xmms_plugin_t *plugin = node->data;
00412 
00413         if (plugin->type == type || type == XMMS_PLUGIN_TYPE_ALL) {
00414             if (!func (plugin, user_data))
00415                 break;
00416         }
00417     }
00418 }
00419 
00420 typedef struct {
00421     const gchar *name;
00422     xmms_plugin_t *plugin;
00423 } xmms_plugin_find_foreach_data_t;
00424 
00425 static gboolean
00426 xmms_plugin_find_foreach (xmms_plugin_t *plugin, gpointer udata)
00427 {
00428     xmms_plugin_find_foreach_data_t *data = udata;
00429 
00430     if (!g_ascii_strcasecmp (plugin->shortname, data->name)) {
00431         xmms_object_ref (plugin);
00432         data->plugin = plugin;
00433         return FALSE;
00434     }
00435     return TRUE;
00436 }
00437 
00438 /**
00439  * @internal Find a plugin that's been loaded, by a particular type and name
00440  * @param[in] type The type of plugin to look for
00441  * @param[in] name The name of the plugin to look for
00442  * @return The plugin instance, if found. NULL otherwise.
00443  */
00444 xmms_plugin_t *
00445 xmms_plugin_find (xmms_plugin_type_t type, const gchar *name)
00446 {
00447     xmms_plugin_find_foreach_data_t data = {name, NULL};
00448     xmms_plugin_foreach (type, xmms_plugin_find_foreach, &data);
00449     return data.plugin;
00450 }
00451 
00452 
00453 static gboolean
00454 xmms_plugin_setup (xmms_plugin_t *plugin, const xmms_plugin_desc_t *desc)
00455 {
00456     plugin->type = desc->type;
00457     plugin->shortname = desc->shortname;
00458     plugin->name = desc->name;
00459     plugin->version = desc->version;
00460     plugin->description = desc->description;
00461 
00462     return TRUE;
00463 }
00464 
00465 void
00466 xmms_plugin_destroy (xmms_plugin_t *plugin)
00467 {
00468     if (plugin->module)
00469         g_module_close (plugin->module);
00470 }