XMMS2
plugin.c
Go to the documentation of this file.
1 /* XMMS2 - X Music Multiplexer System
2  * Copyright (C) 2003-2011 XMMS2 Team
3  *
4  * PLUGINS ARE NOT CONSIDERED TO BE DERIVED WORK !!!
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  * Lesser General Public License for more details.
15  */
16 
17 #include "xmms_configuration.h"
18 #include "xmmspriv/xmms_plugin.h"
19 #include "xmms/xmms_config.h"
20 #include "xmmspriv/xmms_config.h"
21 #include "xmms/xmms_object.h"
22 #include "xmms/xmms_log.h"
23 #include "xmmspriv/xmms_playlist.h"
25 #include "xmmspriv/xmms_xform.h"
26 
27 #include <gmodule.h>
28 #include <string.h>
29 #include <stdarg.h>
30 
31 #ifdef HAVE_VALGRIND
32 # include <memcheck.h>
33 #endif
34 
35 /* OSX uses the .bundle extension, but g_module_build_path returns .so. */
36 #ifdef USE_BUNDLES
37 #define get_module_ext(dir) g_build_filename (dir, "*.bundle", NULL)
38 #else
39 #define get_module_ext(dir) g_module_build_path (dir, "*")
40 #endif
41 
42 
43 /*
44  * Global variables
45  */
46 static GList *xmms_plugin_list;
47 
48 /*
49  * Function prototypes
50  */
51 static gboolean xmms_plugin_setup (xmms_plugin_t *plugin, const xmms_plugin_desc_t *desc);
52 static gboolean xmms_plugin_load (const xmms_plugin_desc_t *desc, GModule *module);
53 static gboolean xmms_plugin_scan_directory (const gchar *dir);
54 
55 /*
56  * Public functions
57  */
58 
59 
60 /**
61  * @if internal
62  * -- internal documentation section --
63  * @addtogroup XMMSPlugin
64  * @{
65  */
66 
67 /**
68  * @internal
69  * Lookup the value of a plugin's config property, given the property key.
70  * @param[in] plugin The plugin
71  * @param[in] key The property key (config path)
72  * @return A config value
73  * @todo config value <-> property fixup
74  */
77  const gchar *key)
78 {
79  gchar path[XMMS_PLUGIN_SHORTNAME_MAX_LEN + 256];
81 
82  g_return_val_if_fail (plugin, NULL);
83  g_return_val_if_fail (key, NULL);
84 
85  g_snprintf (path, sizeof (path), "%s.%s",
86  xmms_plugin_shortname_get (plugin), key);
87  prop = xmms_config_lookup (path);
88 
89  return prop;
90 }
91 
92 /**
93  * @internal
94  * Register a config property for a plugin.
95  * @param[in] plugin The plugin
96  * @param[in] name The property name
97  * @param[in] default_value The default value for the property
98  * @param[in] cb A callback function to be executed when the property value
99  * changes
100  * @param[in] userdata Pointer to data to be passed to the callback
101  * @todo config value <-> property fixup
102  */
105  const gchar *name,
106  const gchar *default_value,
108  gpointer userdata)
109 {
110  gchar fullpath[XMMS_PLUGIN_SHORTNAME_MAX_LEN + 256];
112 
113  g_return_val_if_fail (plugin, NULL);
114  g_return_val_if_fail (name, NULL);
115  g_return_val_if_fail (default_value, NULL);
116 
117  g_snprintf (fullpath, sizeof (fullpath), "%s.%s",
118  xmms_plugin_shortname_get (plugin), name);
119 
120  prop = xmms_config_property_register (fullpath, default_value, cb,
121  userdata);
122 
123  return prop;
124 }
125 
126 /**
127  * @internal Get the type of this plugin
128  * @param[in] plugin The plugin
129  * @return The plugin type (#xmms_plugin_type_t)
130  */
133 {
134  g_return_val_if_fail (plugin, 0);
135 
136  return plugin->type;
137 }
138 
139 /**
140  * @internal Get the plugin's name. This is just an accessor method.
141  * @param[in] plugin The plugin
142  * @return A string containing the plugin's name
143  */
144 const char *
146 {
147  g_return_val_if_fail (plugin, NULL);
148 
149  return plugin->name;
150 }
151 
152 /**
153  * @internal Get the plugin's short name. This is just an accessor method.
154  * @param[in] plugin The plugin
155  * @return A string containing the plugin's short name
156  */
157 const gchar *
159 {
160  g_return_val_if_fail (plugin, NULL);
161 
162  return plugin->shortname;
163 }
164 
165 /**
166  * @internal Get the plugin's version. This is just an accessor method.
167  * @param[in] plugin The plugin
168  * @return A string containing the plugin's version
169  */
170 const gchar *
172 {
173  g_return_val_if_fail (plugin, NULL);
174 
175  return plugin->version;
176 }
177 
178 /**
179  * @internal Get the plugin's description. This is just an accessor method.
180  * @param[in] plugin The plugin
181  * @return A string containing the plugin's description
182  */
183 const char *
185 {
186  g_return_val_if_fail (plugin, NULL);
187 
188  return plugin->description;
189 }
190 
191 /*
192  * Private functions
193  */
194 
195 
196 static void
197 xmms_plugin_add_builtin_plugins (void)
198 {
199  extern const xmms_plugin_desc_t xmms_builtin_ringbuf;
200  extern const xmms_plugin_desc_t xmms_builtin_magic;
201  extern const xmms_plugin_desc_t xmms_builtin_converter;
202  extern const xmms_plugin_desc_t xmms_builtin_segment;
203  extern const xmms_plugin_desc_t xmms_builtin_visualization;
204 
205  xmms_plugin_load (&xmms_builtin_ringbuf, NULL);
206  xmms_plugin_load (&xmms_builtin_magic, NULL);
207  xmms_plugin_load (&xmms_builtin_converter, NULL);
208  xmms_plugin_load (&xmms_builtin_segment, NULL);
209  xmms_plugin_load (&xmms_builtin_visualization, NULL);
210 }
211 
212 
213 /**
214  * @internal Initialise the plugin system
215  * @param[in] path Absolute path to the plugins directory.
216  * @return Whether the initialisation was successful or not.
217  */
218 gboolean
219 xmms_plugin_init (const gchar *path)
220 {
221  if (!path)
222  path = PKGLIBDIR;
223 
224  xmms_plugin_scan_directory (path);
225 
226  xmms_plugin_add_builtin_plugins ();
227  return TRUE;
228 }
229 
230 /**
231  * @internal Shut down the plugin system. This function unrefs all the plugins
232  * loaded.
233  */
234 void
236 {
237 #ifdef HAVE_VALGRIND
238  /* print out a leak summary at this point, because the final leak
239  * summary won't include proper backtraces of leaks found in
240  * plugins, since we close the so's here.
241  *
242  * note: the following call doesn't do anything if we're not run
243  * in valgrind
244  */
245  VALGRIND_DO_LEAK_CHECK
246  ;
247 #endif
248 
249  while (xmms_plugin_list) {
250  xmms_plugin_t *p = xmms_plugin_list->data;
251 
252  /* if this plugin's refcount is > 1, then there's a bug
253  * in one of the other subsystems
254  */
255  if (p->object.ref > 1) {
256  XMMS_DBG ("%s's refcount is %i",
257  p->name, p->object.ref);
258  }
259 
260  xmms_object_unref (p);
261 
262  xmms_plugin_list = g_list_delete_link (xmms_plugin_list,
263  xmms_plugin_list);
264  }
265 }
266 
267 
268 static gboolean
269 xmms_plugin_load (const xmms_plugin_desc_t *desc, GModule *module)
270 {
271  xmms_plugin_t *plugin;
272  xmms_plugin_t *(*allocer) (void);
273  gboolean (*verifier) (xmms_plugin_t *);
274  gint expected_ver;
275 
276  XMMS_DBG ("Loading plugin '%s'", desc->name);
277 
278  switch (desc->type) {
280  expected_ver = XMMS_OUTPUT_API_VERSION;
281  allocer = xmms_output_plugin_new;
282  verifier = xmms_output_plugin_verify;
283  break;
285  expected_ver = XMMS_XFORM_API_VERSION;
286  allocer = xmms_xform_plugin_new;
287  verifier = xmms_xform_plugin_verify;
288  break;
289  default:
290  XMMS_DBG ("Unknown plugin type!");
291  return FALSE;
292  }
293 
294  if (desc->api_version != expected_ver) {
295  XMMS_DBG ("Bad api version!");
296  return FALSE;
297  }
298 
299  plugin = allocer ();
300  if (!plugin) {
301  XMMS_DBG ("Alloc failed!");
302  return FALSE;
303  }
304 
305  if (!xmms_plugin_setup (plugin, desc)) {
306  xmms_log_error ("Setup failed for plugin '%s'!", desc->name);
307  xmms_object_unref (plugin);
308  return FALSE;
309  }
310 
311  if (!desc->setup_func (plugin)) {
312  xmms_log_error ("Setup function failed for plugin '%s'!",
313  desc->name);
314  xmms_object_unref (plugin);
315  return FALSE;
316  }
317 
318  if (!verifier (plugin)) {
319  xmms_log_error ("Verify failed for plugin '%s'!", desc->name);
320  xmms_object_unref (plugin);
321  return FALSE;
322  }
323 
324  plugin->module = module;
325 
326  xmms_plugin_list = g_list_prepend (xmms_plugin_list, plugin);
327  return TRUE;
328 }
329 
330 /**
331  * @internal Scan a particular directory for plugins to load
332  * @param[in] dir Absolute path to plugins directory
333  * @return TRUE if directory successfully scanned for plugins
334  */
335 static gboolean
336 xmms_plugin_scan_directory (const gchar *dir)
337 {
338  GDir *d;
339  const char *name;
340  gchar *path;
341  gchar *temp;
342  gchar *pattern;
343  GModule *module;
344  gpointer sym;
345 
346  temp = get_module_ext (dir);
347 
348  XMMS_DBG ("Scanning directory for plugins (%s)", temp);
349 
350  pattern = g_path_get_basename (temp);
351 
352  g_free (temp);
353 
354  d = g_dir_open (dir, 0, NULL);
355  if (!d) {
356  xmms_log_error ("Failed to open plugin directory (%s)", dir);
357  return FALSE;
358  }
359 
360  while ((name = g_dir_read_name (d))) {
361 
362  if (!g_pattern_match_simple (pattern, name))
363  continue;
364 
365  path = g_build_filename (dir, name, NULL);
366  if (!g_file_test (path, G_FILE_TEST_IS_REGULAR)) {
367  g_free (path);
368  continue;
369  }
370 
371  XMMS_DBG ("Trying to load file: %s", path);
372  module = g_module_open (path, G_MODULE_BIND_LOCAL);
373  if (!module) {
374  xmms_log_error ("Failed to open plugin %s: %s",
375  path, g_module_error ());
376  g_free (path);
377  continue;
378  }
379 
380  if (!g_module_symbol (module, "XMMS_PLUGIN_DESC", &sym)) {
381  xmms_log_error ("Failed to find plugin header in %s", path);
382  g_module_close (module);
383  g_free (path);
384  continue;
385  }
386  g_free (path);
387 
388  if (!xmms_plugin_load ((const xmms_plugin_desc_t *) sym, module)) {
389  g_module_close (module);
390  }
391  }
392 
393  g_dir_close (d);
394  g_free (pattern);
395 
396  return TRUE;
397 }
398 
399 /**
400  * @internal Apply a function to all plugins of specified type.
401  * @param[in] type The type of plugin to look for.
402  * @param[in] func function to apply.
403  * @param[in] user_data Userspecified data passed to function.
404  */
405 void
407 {
408  GList *node;
409 
410  for (node = xmms_plugin_list; node; node = g_list_next (node)) {
411  xmms_plugin_t *plugin = node->data;
412 
413  if (plugin->type == type || type == XMMS_PLUGIN_TYPE_ALL) {
414  if (!func (plugin, user_data))
415  break;
416  }
417  }
418 }
419 
420 typedef struct {
421  const gchar *name;
422  xmms_plugin_t *plugin;
423 } xmms_plugin_find_foreach_data_t;
424 
425 static gboolean
426 xmms_plugin_find_foreach (xmms_plugin_t *plugin, gpointer udata)
427 {
428  xmms_plugin_find_foreach_data_t *data = udata;
429 
430  if (!g_ascii_strcasecmp (plugin->shortname, data->name)) {
431  xmms_object_ref (plugin);
432  data->plugin = plugin;
433  return FALSE;
434  }
435  return TRUE;
436 }
437 
438 /**
439  * @internal Find a plugin that's been loaded, by a particular type and name
440  * @param[in] type The type of plugin to look for
441  * @param[in] name The name of the plugin to look for
442  * @return The plugin instance, if found. NULL otherwise.
443  */
445 xmms_plugin_find (xmms_plugin_type_t type, const gchar *name)
446 {
447  xmms_plugin_find_foreach_data_t data = {name, NULL};
448  xmms_plugin_foreach (type, xmms_plugin_find_foreach, &data);
449  return data.plugin;
450 }
451 
452 
453 static gboolean
454 xmms_plugin_setup (xmms_plugin_t *plugin, const xmms_plugin_desc_t *desc)
455 {
456  plugin->type = desc->type;
457  plugin->shortname = desc->shortname;
458  plugin->name = desc->name;
459  plugin->version = desc->version;
460  plugin->description = desc->description;
461 
462  return TRUE;
463 }
464 
465 void
467 {
468  if (plugin->module)
469  g_module_close (plugin->module);
470 }
xmms_plugin_type_t
#define xmms_object_unref(obj)
Definition: xmms_object.h:109
xmms_plugin_type_t xmms_plugin_type_get(const xmms_plugin_t *plugin)
Definition: plugin.c:132
xmms_config_property_t * xmms_plugin_config_property_register(xmms_plugin_t *plugin, const gchar *name, const gchar *default_value, xmms_object_handler_t cb, gpointer userdata)
Definition: plugin.c:104
#define get_module_ext(dir)
Definition: plugin.c:39
xmms_config_property_t * xmms_config_lookup(const gchar *path)
Look up a config key from the global config.
Definition: config.c:171
gboolean xmms_plugin_init(const gchar *path)
Definition: plugin.c:219
const gchar * xmms_plugin_shortname_get(const xmms_plugin_t *plugin)
Definition: plugin.c:158
xmms_object_t object
Definition: xmms_plugin.h:30
xmms_plugin_t * xmms_xform_plugin_new(void)
Definition: xform_plugin.c:43
G_BEGIN_DECLS struct xmms_plugin_desc_St xmms_plugin_desc_t
gboolean xmms_output_plugin_verify(xmms_plugin_t *_plugin)
Definition: outputplugin.c:101
const gchar * description
Definition: xmms_plugin.h:36
#define xmms_log_error(fmt,...)
Definition: xmms_log.h:35
#define XMMS_PLUGIN_SHORTNAME_MAX_LEN
Definition: xmms_plugin.h:27
const gchar * xmms_plugin_version_get(const xmms_plugin_t *plugin)
Definition: plugin.c:171
const gchar * version
Definition: xmms_plugin.h:37
GModule * module
Definition: xmms_plugin.h:31
const char * xmms_plugin_description_get(const xmms_plugin_t *plugin)
Definition: plugin.c:184
void xmms_plugin_foreach(xmms_plugin_type_t type, xmms_plugin_foreach_func_t func, gpointer user_data)
Definition: plugin.c:406
#define XMMS_XFORM_API_VERSION
void xmms_plugin_destroy(xmms_plugin_t *plugin)
Definition: plugin.c:466
#define xmms_object_ref(obj)
Definition: xmms_object.h:103
const gchar * shortname
Definition: xmms_plugin.h:35
#define XMMS_DBG(fmt,...)
Definition: xmms_log.h:32
xmms_config_property_t * xmms_config_property_register(const gchar *path, const gchar *default_value, xmms_object_handler_t cb, gpointer userdata)
Register a new config property.
Definition: config.c:334
xmms_config_property_t * xmms_plugin_config_lookup(xmms_plugin_t *plugin, const gchar *key)
Definition: plugin.c:76
struct xmms_config_property_St xmms_config_property_t
Definition: xmms_config.h:26
#define XMMS_OUTPUT_API_VERSION
The current API version.
xmms_plugin_t * xmms_plugin_find(xmms_plugin_type_t type, const gchar *name)
Definition: plugin.c:445
const char * xmms_plugin_name_get(const xmms_plugin_t *plugin)
Definition: plugin.c:145
void xmms_plugin_shutdown()
Definition: plugin.c:235
const gchar * name
Definition: xmms_plugin.h:34
xmms_plugin_t * xmms_output_plugin_new(void)
Definition: outputplugin.c:70
gboolean(* xmms_plugin_foreach_func_t)(xmms_plugin_t *, gpointer)
Definition: xmms_plugin.h:48
void(* xmms_object_handler_t)(xmms_object_t *object, xmmsv_t *data, gpointer userdata)
Definition: xmms_object.h:66
gboolean xmms_xform_plugin_verify(xmms_plugin_t *_plugin)
Definition: xform_plugin.c:66
xmms_plugin_type_t type
Definition: xmms_plugin.h:33