Audacious  $Id:Doxyfile42802007-03-2104:39:00Znenolod$
plugin-registry.c
Go to the documentation of this file.
1 /*
2  * plugin-registry.c
3  * Copyright 2009-2011 John Lindgren
4  *
5  * This file is part of Audacious.
6  *
7  * Audacious is free software: you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License as published by the Free Software
9  * Foundation, version 2 or version 3 of the License.
10  *
11  * Audacious is distributed in the hope that it will be useful, but WITHOUT ANY
12  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
13  * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along with
16  * Audacious. If not, see <http://www.gnu.org/licenses/>.
17  *
18  * The Audacious team does not consider modular code linking to Audacious or
19  * using our public API to be a derived work.
20  */
21 
22 /* While the registry is being built (during early startup) or destroyed (during
23  * late shutdown), the registry_locked flag will be set. Once this flag is
24  * cleared, the registry will not be modified and can be read by concurrent
25  * threads. The one change that can happen during this time is that a plugin is
26  * loaded; hence the mutex must be locked before checking that a plugin is
27  * loaded and while loading it. */
28 
29 #include <glib.h>
30 #include <pthread.h>
31 #include <stdio.h>
32 #include <string.h>
33 
34 #include <libaudcore/audstrings.h>
35 
36 #include "debug.h"
37 #include "interface.h"
38 #include "misc.h"
39 #include "plugin.h"
40 #include "plugins.h"
41 #include "util.h"
42 
43 #define FILENAME "plugin-registry"
44 #define FORMAT 6
45 
46 typedef struct {
47  GList * schemes;
49 
50 typedef struct {
51  GList * exts;
53 
54 typedef struct {
55  GList * keys[INPUT_KEYS];
56  bool_t has_images, has_subtunes, can_write_tuple, has_infowin;
58 
59 struct PluginHandle {
60  char * path;
63  Plugin * header;
64  char * name;
65  int priority;
67  GList * watches;
68 
69  union {
73  } u;
74 };
75 
76 typedef struct {
78  void * data;
79 } PluginWatch;
80 
81 static const char * plugin_type_names[] = {
82  [PLUGIN_TYPE_TRANSPORT] = "transport",
83  [PLUGIN_TYPE_PLAYLIST] = "playlist",
84  [PLUGIN_TYPE_INPUT] = "input",
85  [PLUGIN_TYPE_EFFECT] = "effect",
86  [PLUGIN_TYPE_OUTPUT] = "output",
87  [PLUGIN_TYPE_VIS] = "vis",
88  [PLUGIN_TYPE_GENERAL] = "general",
89  [PLUGIN_TYPE_IFACE] = "iface"};
90 
91 static const char * input_key_names[] = {
92  [INPUT_KEY_SCHEME] = "scheme",
93  [INPUT_KEY_EXTENSION] = "ext",
94  [INPUT_KEY_MIME] = "mime"};
95 
96 static GList * plugin_list = NULL;
98 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
99 
100 static PluginHandle * plugin_new (char * path, bool_t confirmed, bool_t
101  loaded, int timestamp, int type, Plugin * header)
102 {
103  PluginHandle * plugin = g_malloc (sizeof (PluginHandle));
104 
105  plugin->path = path;
106  plugin->confirmed = confirmed;
107  plugin->loaded = loaded;
108  plugin->timestamp = timestamp;
109  plugin->type = type;
110  plugin->header = header;
111  plugin->name = NULL;
112  plugin->priority = 0;
113  plugin->has_about = FALSE;
114  plugin->has_configure = FALSE;
115  plugin->enabled = FALSE;
116  plugin->watches = NULL;
117 
118  if (type == PLUGIN_TYPE_TRANSPORT)
119  {
120  plugin->enabled = TRUE;
121  plugin->u.t.schemes = NULL;
122  }
123  else if (type == PLUGIN_TYPE_PLAYLIST)
124  {
125  plugin->enabled = TRUE;
126  plugin->u.p.exts = NULL;
127  }
128  else if (type == PLUGIN_TYPE_INPUT)
129  {
130  plugin->enabled = TRUE;
131  memset (plugin->u.i.keys, 0, sizeof plugin->u.i.keys);
132  plugin->u.i.has_images = FALSE;
133  plugin->u.i.has_subtunes = FALSE;
134  plugin->u.i.can_write_tuple = FALSE;
135  plugin->u.i.has_infowin = FALSE;
136  }
137 
138  plugin_list = g_list_prepend (plugin_list, plugin);
139  return plugin;
140 }
141 
143 {
144  plugin_list = g_list_remove (plugin_list, plugin);
145 
146  g_list_foreach (plugin->watches, (GFunc) g_free, NULL);
147  g_list_free (plugin->watches);
148 
149  if (plugin->type == PLUGIN_TYPE_TRANSPORT)
150  {
151  g_list_foreach (plugin->u.t.schemes, (GFunc) g_free, NULL);
152  g_list_free (plugin->u.t.schemes);
153  }
154  else if (plugin->type == PLUGIN_TYPE_PLAYLIST)
155  {
156  g_list_foreach (plugin->u.p.exts, (GFunc) g_free, NULL);
157  g_list_free (plugin->u.p.exts);
158  }
159  else if (plugin->type == PLUGIN_TYPE_INPUT)
160  {
161  for (int key = 0; key < INPUT_KEYS; key ++)
162  {
163  g_list_foreach (plugin->u.i.keys[key], (GFunc) g_free, NULL);
164  g_list_free (plugin->u.i.keys[key]);
165  }
166  }
167 
168  g_free (plugin->path);
169  g_free (plugin->name);
170  g_free (plugin);
171 }
172 
173 static FILE * open_registry_file (const char * mode)
174 {
175  char * path = g_strdup_printf ("%s/" FILENAME, get_path (AUD_PATH_USER_DIR));
176  FILE * file = fopen (path, mode);
177  g_free (path);
178  return file;
179 }
180 
181 static void transport_plugin_save (PluginHandle * plugin, FILE * handle)
182 {
183  for (GList * node = plugin->u.t.schemes; node; node = node->next)
184  fprintf (handle, "scheme %s\n", (const char *) node->data);
185 }
186 
187 static void playlist_plugin_save (PluginHandle * plugin, FILE * handle)
188 {
189  for (GList * node = plugin->u.p.exts; node; node = node->next)
190  fprintf (handle, "ext %s\n", (const char *) node->data);
191 }
192 
193 static void input_plugin_save (PluginHandle * plugin, FILE * handle)
194 {
195  for (int key = 0; key < INPUT_KEYS; key ++)
196  {
197  for (GList * node = plugin->u.i.keys[key]; node; node = node->next)
198  fprintf (handle, "%s %s\n", input_key_names[key], (const char *)
199  node->data);
200  }
201 
202  fprintf (handle, "images %d\n", plugin->u.i.has_images);
203  fprintf (handle, "subtunes %d\n", plugin->u.i.has_subtunes);
204  fprintf (handle, "writes %d\n", plugin->u.i.can_write_tuple);
205  fprintf (handle, "infowin %d\n", plugin->u.i.has_infowin);
206 }
207 
208 static void plugin_save (PluginHandle * plugin, FILE * handle)
209 {
210  fprintf (handle, "%s %s\n", plugin_type_names[plugin->type], plugin->path);
211  fprintf (handle, "stamp %d\n", plugin->timestamp);
212  fprintf (handle, "name %s\n", plugin->name);
213  fprintf (handle, "priority %d\n", plugin->priority);
214  fprintf (handle, "about %d\n", plugin->has_about);
215  fprintf (handle, "config %d\n", plugin->has_configure);
216  fprintf (handle, "enabled %d\n", plugin->enabled);
217 
218  if (plugin->type == PLUGIN_TYPE_TRANSPORT)
219  transport_plugin_save (plugin, handle);
220  else if (plugin->type == PLUGIN_TYPE_PLAYLIST)
221  playlist_plugin_save (plugin, handle);
222  else if (plugin->type == PLUGIN_TYPE_INPUT)
223  input_plugin_save (plugin, handle);
224 }
225 
227 {
228  FILE * handle = open_registry_file ("w");
229  g_return_if_fail (handle);
230 
231  fprintf (handle, "format %d\n", FORMAT);
232 
233  g_list_foreach (plugin_list, (GFunc) plugin_save, handle);
234  fclose (handle);
235 
236  g_list_foreach (plugin_list, (GFunc) plugin_free, NULL);
238 }
239 
240 static char parse_key[512];
241 static char * parse_value;
242 
243 static void parse_next (FILE * handle)
244 {
245  parse_value = NULL;
246 
247  if (! fgets (parse_key, sizeof parse_key, handle))
248  return;
249 
250  char * space = strchr (parse_key, ' ');
251  if (! space)
252  return;
253 
254  * space = 0;
255  parse_value = space + 1;
256 
257  char * newline = strchr (parse_value, '\n');
258  if (newline)
259  * newline = 0;
260 }
261 
262 static bool_t parse_integer (const char * key, int * value)
263 {
264  return (parse_value && ! strcmp (parse_key, key) && sscanf (parse_value,
265  "%d", value) == 1);
266 }
267 
268 static char * parse_string (const char * key)
269 {
270  return (parse_value && ! strcmp (parse_key, key)) ? g_strdup (parse_value) :
271  NULL;
272 }
273 
274 static void transport_plugin_parse (PluginHandle * plugin, FILE * handle)
275 {
276  char * value;
277  while ((value = parse_string ("scheme")))
278  {
279  plugin->u.t.schemes = g_list_prepend (plugin->u.t.schemes, value);
280  parse_next (handle);
281  }
282 }
283 
284 static void playlist_plugin_parse (PluginHandle * plugin, FILE * handle)
285 {
286  char * value;
287  while ((value = parse_string ("ext")))
288  {
289  plugin->u.p.exts = g_list_prepend (plugin->u.p.exts, value);
290  parse_next (handle);
291  }
292 }
293 
294 static void input_plugin_parse (PluginHandle * plugin, FILE * handle)
295 {
296  for (int key = 0; key < INPUT_KEYS; key ++)
297  {
298  char * value;
299  while ((value = parse_string (input_key_names[key])))
300  {
301  plugin->u.i.keys[key] = g_list_prepend (plugin->u.i.keys[key],
302  value);
303  parse_next (handle);
304  }
305  }
306 
307  if (parse_integer ("images", & plugin->u.i.has_images))
308  parse_next (handle);
309  if (parse_integer ("subtunes", & plugin->u.i.has_subtunes))
310  parse_next (handle);
311  if (parse_integer ("writes", & plugin->u.i.can_write_tuple))
312  parse_next (handle);
313  if (parse_integer ("infowin", & plugin->u.i.has_infowin))
314  parse_next (handle);
315 }
316 
317 static bool_t plugin_parse (FILE * handle)
318 {
319  char * path = NULL;
320 
321  int type;
322  for (type = 0; type < PLUGIN_TYPES; type ++)
323  {
324  if ((path = parse_string (plugin_type_names[type])))
325  goto FOUND;
326  }
327 
328  return FALSE;
329 
330 FOUND:
331  parse_next (handle);
332 
333  int timestamp;
334  if (! parse_integer ("stamp", & timestamp))
335  {
336  g_free (path);
337  return FALSE;
338  }
339 
340  PluginHandle * plugin = plugin_new (path, FALSE, FALSE, timestamp, type,
341  NULL);
342  parse_next (handle);
343 
344  if ((plugin->name = parse_string ("name")))
345  parse_next (handle);
346  if (parse_integer ("priority", & plugin->priority))
347  parse_next (handle);
348  if (parse_integer ("about", & plugin->has_about))
349  parse_next (handle);
350  if (parse_integer ("config", & plugin->has_configure))
351  parse_next (handle);
352  if (parse_integer ("enabled", & plugin->enabled))
353  parse_next (handle);
354 
355  if (type == PLUGIN_TYPE_TRANSPORT)
356  transport_plugin_parse (plugin, handle);
357  else if (type == PLUGIN_TYPE_PLAYLIST)
358  playlist_plugin_parse (plugin, handle);
359  else if (type == PLUGIN_TYPE_INPUT)
360  input_plugin_parse (plugin, handle);
361 
362  return TRUE;
363 }
364 
366 {
367  FILE * handle = open_registry_file ("r");
368  if (! handle)
369  goto UNLOCK;
370 
371  parse_next (handle);
372 
373  int format;
374  if (! parse_integer ("format", & format) || format != FORMAT)
375  goto ERR;
376 
377  parse_next (handle);
378 
379  while (plugin_parse (handle))
380  ;
381 
382 ERR:
383  fclose (handle);
384 UNLOCK:
386 }
387 
389 {
390  if (plugin->confirmed)
391  return;
392 
393  AUDDBG ("Plugin not found: %s\n", plugin->path);
394  plugin_free (plugin);
395 }
396 
398 {
399  if (a->type < b->type)
400  return -1;
401  if (a->type > b->type)
402  return 1;
403  if (a->priority < b->priority)
404  return -1;
405  if (a->priority > b->priority)
406  return 1;
407 
408  int diff;
409  if ((diff = string_compare (a->name, b->name)))
410  return diff;
411 
412  return string_compare (a->path, b->path);
413 }
414 
416 {
417  g_list_foreach (plugin_list, (GFunc) plugin_prune, NULL);
418  plugin_list = g_list_sort (plugin_list, (GCompareFunc) plugin_compare);
420 }
421 
422 static int plugin_lookup_cb (PluginHandle * plugin, const char * path)
423 {
424  return strcmp (plugin->path, path);
425 }
426 
427 PluginHandle * plugin_lookup (const char * path)
428 {
429  GList * node = g_list_find_custom (plugin_list, path, (GCompareFunc)
431  return node ? node->data : NULL;
432 }
433 
434 static int plugin_lookup_basename_cb (PluginHandle * plugin, const char * basename)
435 {
436  char * test = g_path_get_basename (plugin->path);
437 
438  char * dot = strrchr (test, '.');
439  if (dot)
440  * dot = 0;
441 
442  int ret = strcmp (test, basename);
443 
444  g_free (test);
445  return ret;
446 }
447 
448 /* Note: If there are multiple plugins with the same basename, this returns only
449  * one of them. So give different plugins different basenames. --jlindgren */
450 PluginHandle * plugin_lookup_basename (const char * basename)
451 {
452  GList * node = g_list_find_custom (plugin_list, basename, (GCompareFunc)
454  return node ? node->data : NULL;
455 }
456 
457 void plugin_register (const char * path)
458 {
459  PluginHandle * plugin = plugin_lookup (path);
460  if (! plugin)
461  {
462  AUDDBG ("New plugin: %s\n", path);
463  plugin_load (path);
464  return;
465  }
466 
467  int timestamp = file_get_mtime (path);
468  g_return_if_fail (timestamp >= 0);
469 
470  AUDDBG ("Register plugin: %s\n", path);
471  plugin->confirmed = TRUE;
472 
473  if (plugin->timestamp == timestamp)
474  return;
475 
476  AUDDBG ("Rescan plugin: %s\n", path);
477  plugin->timestamp = timestamp;
478  plugin_load (path);
479 }
480 
481 void plugin_register_loaded (const char * path, Plugin * header)
482 {
483  AUDDBG ("Loaded plugin: %s\n", path);
484  PluginHandle * plugin = plugin_lookup (path);
485  bool_t new = FALSE;
486 
487  if (plugin)
488  {
489  g_return_if_fail (plugin->type == header->type);
490 
491  plugin->loaded = TRUE;
492  plugin->header = header;
493 
494  if (registry_locked)
495  return;
496  }
497  else
498  {
499  g_return_if_fail (! registry_locked);
500 
501  int timestamp = file_get_mtime (path);
502  g_return_if_fail (timestamp >= 0);
503 
504  plugin = plugin_new (g_strdup (path), TRUE, TRUE, timestamp,
505  header->type, header);
506  new = TRUE;
507  }
508 
509  g_free (plugin->name);
510  plugin->name = g_strdup (header->name);
511  plugin->has_about = PLUGIN_HAS_FUNC (header, about);
512  plugin->has_configure = PLUGIN_HAS_FUNC (header, configure) ||
513  PLUGIN_HAS_FUNC (header, settings);
514 
515  if (header->type == PLUGIN_TYPE_TRANSPORT)
516  {
517  TransportPlugin * tp = (TransportPlugin *) header;
518  for (int i = 0; tp->schemes[i]; i ++)
519  plugin->u.t.schemes = g_list_prepend (plugin->u.t.schemes, g_strdup
520  (tp->schemes[i]));
521  }
522  else if (header->type == PLUGIN_TYPE_PLAYLIST)
523  {
524  PlaylistPlugin * pp = (PlaylistPlugin *) header;
525  for (int i = 0; pp->extensions[i]; i ++)
526  plugin->u.p.exts = g_list_prepend (plugin->u.p.exts, g_strdup
527  (pp->extensions[i]));
528  }
529  else if (header->type == PLUGIN_TYPE_INPUT)
530  {
531  InputPlugin * ip = (InputPlugin *) header;
532  plugin->priority = ip->priority;
533 
534  for (int key = 0; key < INPUT_KEYS; key ++)
535  {
536  g_list_foreach (plugin->u.i.keys[key], (GFunc) g_free, NULL);
537  g_list_free (plugin->u.i.keys[key]);
538  plugin->u.i.keys[key] = NULL;
539  }
540 
541  if (PLUGIN_HAS_FUNC (ip, extensions))
542  {
543  for (int i = 0; ip->extensions[i]; i ++)
544  plugin->u.i.keys[INPUT_KEY_EXTENSION] = g_list_prepend
545  (plugin->u.i.keys[INPUT_KEY_EXTENSION], g_strdup
546  (ip->extensions[i]));
547  }
548 
549  if (PLUGIN_HAS_FUNC (ip, mimes))
550  {
551  for (int i = 0; ip->mimes[i]; i ++)
552  plugin->u.i.keys[INPUT_KEY_MIME] = g_list_prepend
553  (plugin->u.i.keys[INPUT_KEY_MIME], g_strdup (ip->mimes[i]));
554  }
555 
556  if (PLUGIN_HAS_FUNC (ip, schemes))
557  {
558  for (int i = 0; ip->schemes[i]; i ++)
559  plugin->u.i.keys[INPUT_KEY_SCHEME] = g_list_prepend
560  (plugin->u.i.keys[INPUT_KEY_SCHEME], g_strdup (ip->schemes[i]));
561  }
562 
563  plugin->u.i.has_images = PLUGIN_HAS_FUNC (ip, get_song_image);
564  plugin->u.i.has_subtunes = ip->have_subtune;
565  plugin->u.i.can_write_tuple = PLUGIN_HAS_FUNC (ip, update_song_tuple);
566  plugin->u.i.has_infowin = PLUGIN_HAS_FUNC (ip, file_info_box);
567  }
568  else if (header->type == PLUGIN_TYPE_OUTPUT)
569  {
570  OutputPlugin * op = (OutputPlugin *) header;
571  plugin->priority = 10 - op->probe_priority;
572  }
573  else if (header->type == PLUGIN_TYPE_EFFECT)
574  {
575  EffectPlugin * ep = (EffectPlugin *) header;
576  plugin->priority = ep->order;
577  }
578  else if (header->type == PLUGIN_TYPE_GENERAL)
579  {
580  GeneralPlugin * gp = (GeneralPlugin *) header;
581  if (new)
582  plugin->enabled = gp->enabled_by_default;
583  }
584 }
585 
587 {
588  return plugin->type;
589 }
590 
592 {
593  return plugin->path;
594 }
595 
597 {
598  pthread_mutex_lock (& mutex);
599 
600  if (! plugin->loaded)
601  {
602  plugin_load (plugin->path);
603  plugin->loaded = TRUE;
604  }
605 
606  pthread_mutex_unlock (& mutex);
607  return plugin->header;
608 }
609 
610 static int plugin_by_header_cb (PluginHandle * plugin, const void * header)
611 {
612  return (plugin->header == header) ? 0 : -1;
613 }
614 
615 PluginHandle * plugin_by_header (const void * header)
616 {
617  GList * node = g_list_find_custom (plugin_list, header, (GCompareFunc)
619  return node ? node->data : NULL;
620 }
621 
623 {
624  for (GList * node = plugin_list; node; node = node->next)
625  {
626  if (((PluginHandle *) node->data)->type != type)
627  continue;
628  if (! func (node->data, data))
629  break;
630  }
631 }
632 
634 {
635  return plugin->name;
636 }
637 
639 {
640  return plugin->has_about;
641 }
642 
644 {
645  return plugin->has_configure;
646 }
647 
649 {
650  return plugin->enabled;
651 }
652 
654 {
655  for (GList * node = plugin->watches; node; )
656  {
657  GList * next = node->next;
658  PluginWatch * watch = node->data;
659 
660  if (! watch->func (plugin, watch->data))
661  {
662  g_free (watch);
663  plugin->watches = g_list_delete_link (plugin->watches, node);
664  }
665 
666  node = next;
667  }
668 }
669 
671 {
672  plugin->enabled = enabled;
673  plugin_call_watches (plugin);
674 }
675 
676 typedef struct {
678  void * data;
680 
682  PluginForEnabledState * state)
683 {
684  if (! plugin->enabled)
685  return TRUE;
686  return state->func (plugin, state->data);
687 }
688 
690 {
691  PluginForEnabledState state = {func, data};
693 }
694 
696  data)
697 {
698  PluginWatch * watch = g_malloc (sizeof (PluginWatch));
699  watch->func = func;
700  watch->data = data;
701  plugin->watches = g_list_prepend (plugin->watches, watch);
702 }
703 
705  data)
706 {
707  for (GList * node = plugin->watches; node; )
708  {
709  GList * next = node->next;
710  PluginWatch * watch = node->data;
711 
712  if (watch->func == func && watch->data == data)
713  {
714  g_free (watch);
715  plugin->watches = g_list_delete_link (plugin->watches, node);
716  }
717 
718  node = next;
719  }
720 }
721 
722 typedef struct {
723  const char * scheme;
726 
729 {
730  if (! g_list_find_custom (plugin->u.t.schemes, state->scheme,
731  (GCompareFunc) g_ascii_strcasecmp))
732  return TRUE;
733 
734  state->plugin = plugin;
735  return FALSE;
736 }
737 
739 {
740  TransportPluginForSchemeState state = {scheme, NULL};
743  return state.plugin;
744 }
745 
746 typedef struct {
747  const char * ext;
750 
753 {
754  if (! g_list_find_custom (plugin->u.p.exts, state->ext,
755  (GCompareFunc) g_ascii_strcasecmp))
756  return TRUE;
757 
758  state->plugin = plugin;
759  return FALSE;
760 }
761 
762 PluginHandle * playlist_plugin_for_extension (const char * extension)
763 {
764  PlaylistPluginForExtState state = {extension, NULL};
766  playlist_plugin_for_ext_cb, & state);
767  return state.plugin;
768 }
769 
770 typedef struct {
771  int key;
772  const char * value;
774  void * data;
776 
778  InputPluginForKeyState * state)
779 {
780  if (! g_list_find_custom (plugin->u.i.keys[state->key], state->value,
781  (GCompareFunc) g_ascii_strcasecmp))
782  return TRUE;
783 
784  return state->func (plugin, state->data);
785 }
786 
787 void input_plugin_for_key (int key, const char * value, PluginForEachFunc
788  func, void * data)
789 {
790  InputPluginForKeyState state = {key, value, func, data};
792  input_plugin_for_key_cb, & state);
793 }
794 
796 {
797  g_return_val_if_fail (plugin->type == PLUGIN_TYPE_INPUT, FALSE);
798  return plugin->u.i.has_images;
799 }
800 
802 {
803  g_return_val_if_fail (plugin->type == PLUGIN_TYPE_INPUT, FALSE);
804  return plugin->u.i.has_subtunes;
805 }
806 
808 {
809  g_return_val_if_fail (plugin->type == PLUGIN_TYPE_INPUT, FALSE);
810  return plugin->u.i.can_write_tuple;
811 }
812 
814 {
815  g_return_val_if_fail (plugin->type == PLUGIN_TYPE_INPUT, FALSE);
816  return plugin->u.i.has_infowin;
817 }