XMMS2
|
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 }