Audacious $Id:Doxyfile42802007-03-2104:39:00Znenolod$
plugin-registry.c
Go to the documentation of this file.
00001 /*
00002  * plugin-registry.c
00003  * Copyright 2009-2011 John Lindgren
00004  *
00005  * This file is part of Audacious.
00006  *
00007  * Audacious is free software: you can redistribute it and/or modify it under
00008  * the terms of the GNU General Public License as published by the Free Software
00009  * Foundation, version 2 or version 3 of the License.
00010  *
00011  * Audacious is distributed in the hope that it will be useful, but WITHOUT ANY
00012  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
00013  * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
00014  *
00015  * You should have received a copy of the GNU General Public License along with
00016  * Audacious. If not, see <http://www.gnu.org/licenses/>.
00017  *
00018  * The Audacious team does not consider modular code linking to Audacious or
00019  * using our public API to be a derived work.
00020  */
00021 
00022 /* While the registry is being built (during early startup) or destroyed (during
00023  * late shutdown), the registry_locked flag will be set.  Once this flag is
00024  * cleared, the registry will not be modified and can be read by concurrent
00025  * threads.  The one change that can happen during this time is that a plugin is
00026  * loaded; hence the mutex must be locked before checking that a plugin is
00027  * loaded and while loading it. */
00028 
00029 #include <glib.h>
00030 #include <limits.h>
00031 #include <stdio.h>
00032 #include <string.h>
00033 
00034 #include <libaudcore/audstrings.h>
00035 
00036 #include "debug.h"
00037 #include "interface.h"
00038 #include "misc.h"
00039 #include "plugin.h"
00040 #include "plugins.h"
00041 #include "util.h"
00042 
00043 #define FILENAME "plugin-registry"
00044 #define FORMAT 6
00045 
00046 typedef struct {
00047     GList * schemes;
00048 } TransportPluginData;
00049 
00050 typedef struct {
00051     GList * exts;
00052 } PlaylistPluginData;
00053 
00054 typedef struct {
00055     GList * keys[INPUT_KEYS];
00056     gboolean has_images, has_subtunes, can_write_tuple, has_infowin;
00057 } InputPluginData;
00058 
00059 struct PluginHandle {
00060     gchar * path;
00061     gboolean confirmed, loaded;
00062     gint timestamp, type;
00063     Plugin * header;
00064     gchar * name;
00065     gint priority;
00066     gboolean has_about, has_configure, enabled;
00067     GList * watches;
00068 
00069     union {
00070         TransportPluginData t;
00071         PlaylistPluginData p;
00072         InputPluginData i;
00073     } u;
00074 };
00075 
00076 typedef struct {
00077     PluginForEachFunc func;
00078     void * data;
00079 } PluginWatch;
00080 
00081 static const gchar * plugin_type_names[] = {
00082  [PLUGIN_TYPE_TRANSPORT] = "transport",
00083  [PLUGIN_TYPE_PLAYLIST] = "playlist",
00084  [PLUGIN_TYPE_INPUT] = "input",
00085  [PLUGIN_TYPE_EFFECT] = "effect",
00086  [PLUGIN_TYPE_OUTPUT] = "output",
00087  [PLUGIN_TYPE_VIS] = "vis",
00088  [PLUGIN_TYPE_GENERAL] = "general",
00089  [PLUGIN_TYPE_IFACE] = "iface"};
00090 
00091 static const gchar * input_key_names[] = {
00092  [INPUT_KEY_SCHEME] = "scheme",
00093  [INPUT_KEY_EXTENSION] = "ext",
00094  [INPUT_KEY_MIME] = "mime"};
00095 
00096 static GList * plugin_list = NULL;
00097 static gboolean registry_locked = TRUE;
00098 static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
00099 
00100 static PluginHandle * plugin_new (gchar * path, gboolean confirmed, gboolean
00101  loaded, gint timestamp, gint type, Plugin * header)
00102 {
00103     PluginHandle * plugin = g_malloc (sizeof (PluginHandle));
00104 
00105     plugin->path = path;
00106     plugin->confirmed = confirmed;
00107     plugin->loaded = loaded;
00108     plugin->timestamp = timestamp;
00109     plugin->type = type;
00110     plugin->header = header;
00111     plugin->name = NULL;
00112     plugin->priority = 0;
00113     plugin->has_about = FALSE;
00114     plugin->has_configure = FALSE;
00115     plugin->enabled = FALSE;
00116     plugin->watches = NULL;
00117 
00118     if (type == PLUGIN_TYPE_TRANSPORT)
00119     {
00120         plugin->enabled = TRUE;
00121         plugin->u.t.schemes = NULL;
00122     }
00123     else if (type == PLUGIN_TYPE_PLAYLIST)
00124     {
00125         plugin->enabled = TRUE;
00126         plugin->u.p.exts = NULL;
00127     }
00128     else if (type == PLUGIN_TYPE_INPUT)
00129     {
00130         plugin->enabled = TRUE;
00131         memset (plugin->u.i.keys, 0, sizeof plugin->u.i.keys);
00132         plugin->u.i.has_images = FALSE;
00133         plugin->u.i.has_subtunes = FALSE;
00134         plugin->u.i.can_write_tuple = FALSE;
00135         plugin->u.i.has_infowin = FALSE;
00136     }
00137 
00138     plugin_list = g_list_prepend (plugin_list, plugin);
00139     return plugin;
00140 }
00141 
00142 static void plugin_free (PluginHandle * plugin)
00143 {
00144     plugin_list = g_list_remove (plugin_list, plugin);
00145 
00146     g_list_foreach (plugin->watches, (GFunc) g_free, NULL);
00147     g_list_free (plugin->watches);
00148 
00149     if (plugin->type == PLUGIN_TYPE_TRANSPORT)
00150     {
00151         g_list_foreach (plugin->u.t.schemes, (GFunc) g_free, NULL);
00152         g_list_free (plugin->u.t.schemes);
00153     }
00154     else if (plugin->type == PLUGIN_TYPE_PLAYLIST)
00155     {
00156         g_list_foreach (plugin->u.p.exts, (GFunc) g_free, NULL);
00157         g_list_free (plugin->u.p.exts);
00158     }
00159     else if (plugin->type == PLUGIN_TYPE_INPUT)
00160     {
00161         for (gint key = 0; key < INPUT_KEYS; key ++)
00162         {
00163             g_list_foreach (plugin->u.i.keys[key], (GFunc) g_free, NULL);
00164             g_list_free (plugin->u.i.keys[key]);
00165         }
00166     }
00167 
00168     g_free (plugin->path);
00169     g_free (plugin->name);
00170     g_free (plugin);
00171 }
00172 
00173 static FILE * open_registry_file (const gchar * mode)
00174 {
00175     gchar path[PATH_MAX];
00176     snprintf (path, sizeof path, "%s/" FILENAME, get_path (AUD_PATH_USER_DIR));
00177     return fopen (path, mode);
00178 }
00179 
00180 static void transport_plugin_save (PluginHandle * plugin, FILE * handle)
00181 {
00182     for (GList * node = plugin->u.t.schemes; node; node = node->next)
00183         fprintf (handle, "scheme %s\n", (const gchar *) node->data);
00184 }
00185 
00186 static void playlist_plugin_save (PluginHandle * plugin, FILE * handle)
00187 {
00188     for (GList * node = plugin->u.p.exts; node; node = node->next)
00189         fprintf (handle, "ext %s\n", (const gchar *) node->data);
00190 }
00191 
00192 static void input_plugin_save (PluginHandle * plugin, FILE * handle)
00193 {
00194     for (gint key = 0; key < INPUT_KEYS; key ++)
00195     {
00196         for (GList * node = plugin->u.i.keys[key]; node; node = node->next)
00197             fprintf (handle, "%s %s\n", input_key_names[key], (const gchar *)
00198              node->data);
00199     }
00200 
00201     fprintf (handle, "images %d\n", plugin->u.i.has_images);
00202     fprintf (handle, "subtunes %d\n", plugin->u.i.has_subtunes);
00203     fprintf (handle, "writes %d\n", plugin->u.i.can_write_tuple);
00204     fprintf (handle, "infowin %d\n", plugin->u.i.has_infowin);
00205 }
00206 
00207 static void plugin_save (PluginHandle * plugin, FILE * handle)
00208 {
00209     fprintf (handle, "%s %s\n", plugin_type_names[plugin->type], plugin->path);
00210     fprintf (handle, "stamp %d\n", plugin->timestamp);
00211     fprintf (handle, "name %s\n", plugin->name);
00212     fprintf (handle, "priority %d\n", plugin->priority);
00213     fprintf (handle, "about %d\n", plugin->has_about);
00214     fprintf (handle, "config %d\n", plugin->has_configure);
00215     fprintf (handle, "enabled %d\n", plugin->enabled);
00216 
00217     if (plugin->type == PLUGIN_TYPE_TRANSPORT)
00218         transport_plugin_save (plugin, handle);
00219     else if (plugin->type == PLUGIN_TYPE_PLAYLIST)
00220         playlist_plugin_save (plugin, handle);
00221     else if (plugin->type == PLUGIN_TYPE_INPUT)
00222         input_plugin_save (plugin, handle);
00223 }
00224 
00225 void plugin_registry_save (void)
00226 {
00227     FILE * handle = open_registry_file ("w");
00228     g_return_if_fail (handle);
00229 
00230     fprintf (handle, "format %d\n", FORMAT);
00231 
00232     g_list_foreach (plugin_list, (GFunc) plugin_save, handle);
00233     fclose (handle);
00234 
00235     g_list_foreach (plugin_list, (GFunc) plugin_free, NULL);
00236     registry_locked = TRUE;
00237 }
00238 
00239 static gchar parse_key[512];
00240 static gchar * parse_value;
00241 
00242 static void parse_next (FILE * handle)
00243 {
00244     parse_value = NULL;
00245 
00246     if (! fgets (parse_key, sizeof parse_key, handle))
00247         return;
00248 
00249     gchar * space = strchr (parse_key, ' ');
00250     if (! space)
00251         return;
00252 
00253     * space = 0;
00254     parse_value = space + 1;
00255 
00256     gchar * newline = strchr (parse_value, '\n');
00257     if (newline)
00258         * newline = 0;
00259 }
00260 
00261 static gboolean parse_integer (const gchar * key, gint * value)
00262 {
00263     return (parse_value && ! strcmp (parse_key, key) && sscanf (parse_value,
00264      "%d", value) == 1);
00265 }
00266 
00267 static gchar * parse_string (const gchar * key)
00268 {
00269     return (parse_value && ! strcmp (parse_key, key)) ? g_strdup (parse_value) :
00270      NULL;
00271 }
00272 
00273 static void transport_plugin_parse (PluginHandle * plugin, FILE * handle)
00274 {
00275     gchar * value;
00276     while ((value = parse_string ("scheme")))
00277     {
00278         plugin->u.t.schemes = g_list_prepend (plugin->u.t.schemes, value);
00279         parse_next (handle);
00280     }
00281 }
00282 
00283 static void playlist_plugin_parse (PluginHandle * plugin, FILE * handle)
00284 {
00285     gchar * value;
00286     while ((value = parse_string ("ext")))
00287     {
00288         plugin->u.p.exts = g_list_prepend (plugin->u.p.exts, value);
00289         parse_next (handle);
00290     }
00291 }
00292 
00293 static void input_plugin_parse (PluginHandle * plugin, FILE * handle)
00294 {
00295     for (gint key = 0; key < INPUT_KEYS; key ++)
00296     {
00297         gchar * value;
00298         while ((value = parse_string (input_key_names[key])))
00299         {
00300             plugin->u.i.keys[key] = g_list_prepend (plugin->u.i.keys[key],
00301              value);
00302             parse_next (handle);
00303         }
00304     }
00305 
00306     if (parse_integer ("images", & plugin->u.i.has_images))
00307         parse_next (handle);
00308     if (parse_integer ("subtunes", & plugin->u.i.has_subtunes))
00309         parse_next (handle);
00310     if (parse_integer ("writes", & plugin->u.i.can_write_tuple))
00311         parse_next (handle);
00312     if (parse_integer ("infowin", & plugin->u.i.has_infowin))
00313         parse_next (handle);
00314 }
00315 
00316 static gboolean plugin_parse (FILE * handle)
00317 {
00318     gchar * path = NULL;
00319 
00320     gint type;
00321     for (type = 0; type < PLUGIN_TYPES; type ++)
00322     {
00323         if ((path = parse_string (plugin_type_names[type])))
00324             goto FOUND;
00325     }
00326 
00327     return FALSE;
00328 
00329 FOUND:
00330     parse_next (handle);
00331 
00332     gint timestamp;
00333     if (! parse_integer ("stamp", & timestamp))
00334     {
00335         g_free (path);
00336         return FALSE;
00337     }
00338 
00339     PluginHandle * plugin = plugin_new (path, FALSE, FALSE, timestamp, type,
00340      NULL);
00341     parse_next (handle);
00342 
00343     if ((plugin->name = parse_string ("name")))
00344         parse_next (handle);
00345     if (parse_integer ("priority", & plugin->priority))
00346         parse_next (handle);
00347     if (parse_integer ("about", & plugin->has_about))
00348         parse_next (handle);
00349     if (parse_integer ("config", & plugin->has_configure))
00350         parse_next (handle);
00351     if (parse_integer ("enabled", & plugin->enabled))
00352         parse_next (handle);
00353 
00354     if (type == PLUGIN_TYPE_TRANSPORT)
00355         transport_plugin_parse (plugin, handle);
00356     else if (type == PLUGIN_TYPE_PLAYLIST)
00357         playlist_plugin_parse (plugin, handle);
00358     else if (type == PLUGIN_TYPE_INPUT)
00359         input_plugin_parse (plugin, handle);
00360 
00361     return TRUE;
00362 }
00363 
00364 void plugin_registry_load (void)
00365 {
00366     FILE * handle = open_registry_file ("r");
00367     if (! handle)
00368         goto UNLOCK;
00369 
00370     parse_next (handle);
00371 
00372     gint format;
00373     if (! parse_integer ("format", & format) || format != FORMAT)
00374         goto ERR;
00375 
00376     parse_next (handle);
00377 
00378     while (plugin_parse (handle))
00379         ;
00380 
00381 ERR:
00382     fclose (handle);
00383 UNLOCK:
00384     registry_locked = FALSE;
00385 }
00386 
00387 static void plugin_prune (PluginHandle * plugin)
00388 {
00389     if (plugin->confirmed)
00390         return;
00391 
00392     AUDDBG ("Plugin not found: %s\n", plugin->path);
00393     plugin_free (plugin);
00394 }
00395 
00396 gint plugin_compare (PluginHandle * a, PluginHandle * b)
00397 {
00398     if (a->type < b->type)
00399         return -1;
00400     if (a->type > b->type)
00401         return 1;
00402     if (a->priority < b->priority)
00403         return -1;
00404     if (a->priority > b->priority)
00405         return 1;
00406 
00407     gint diff;
00408     if ((diff = string_compare (a->name, b->name)))
00409         return diff;
00410 
00411     return string_compare (a->path, b->path);
00412 }
00413 
00414 void plugin_registry_prune (void)
00415 {
00416     g_list_foreach (plugin_list, (GFunc) plugin_prune, NULL);
00417     plugin_list = g_list_sort (plugin_list, (GCompareFunc) plugin_compare);
00418     registry_locked = TRUE;
00419 }
00420 
00421 static gint plugin_lookup_cb (PluginHandle * plugin, const gchar * path)
00422 {
00423     return strcmp (plugin->path, path);
00424 }
00425 
00426 PluginHandle * plugin_lookup (const gchar * path)
00427 {
00428     GList * node = g_list_find_custom (plugin_list, path, (GCompareFunc)
00429      plugin_lookup_cb);
00430     return node ? node->data : NULL;
00431 }
00432 
00433 void plugin_register (const gchar * path)
00434 {
00435     PluginHandle * plugin = plugin_lookup (path);
00436     if (! plugin)
00437     {
00438         AUDDBG ("New plugin: %s\n", path);
00439         plugin_load (path);
00440         return;
00441     }
00442 
00443     gint timestamp = file_get_mtime (path);
00444     g_return_if_fail (timestamp >= 0);
00445 
00446     AUDDBG ("Register plugin: %s\n", path);
00447     plugin->confirmed = TRUE;
00448 
00449     if (plugin->timestamp == timestamp)
00450         return;
00451 
00452     AUDDBG ("Rescan plugin: %s\n", path);
00453     plugin->timestamp = timestamp;
00454     plugin_load (path);
00455 }
00456 
00457 void plugin_register_loaded (const gchar * path, Plugin * header)
00458 {
00459     AUDDBG ("Loaded plugin: %s\n", path);
00460     PluginHandle * plugin = plugin_lookup (path);
00461     gboolean new = FALSE;
00462 
00463     if (plugin)
00464     {
00465         g_return_if_fail (plugin->type == header->type);
00466 
00467         plugin->loaded = TRUE;
00468         plugin->header = header;
00469 
00470         if (registry_locked)
00471             return;
00472     }
00473     else
00474     {
00475         g_return_if_fail (! registry_locked);
00476 
00477         gint timestamp = file_get_mtime (path);
00478         g_return_if_fail (timestamp >= 0);
00479 
00480         plugin = plugin_new (g_strdup (path), TRUE, TRUE, timestamp,
00481          header->type, header);
00482         new = TRUE;
00483     }
00484 
00485     g_free (plugin->name);
00486     plugin->name = g_strdup (header->name);
00487     plugin->has_about = PLUGIN_HAS_FUNC (header, about);
00488     plugin->has_configure = PLUGIN_HAS_FUNC (header, configure) ||
00489      PLUGIN_HAS_FUNC (header, settings);
00490 
00491     if (header->type == PLUGIN_TYPE_TRANSPORT)
00492     {
00493         TransportPlugin * tp = (TransportPlugin *) header;
00494         for (gint i = 0; tp->schemes[i]; i ++)
00495             plugin->u.t.schemes = g_list_prepend (plugin->u.t.schemes, g_strdup
00496              (tp->schemes[i]));
00497     }
00498     else if (header->type == PLUGIN_TYPE_PLAYLIST)
00499     {
00500         PlaylistPlugin * pp = (PlaylistPlugin *) header;
00501         for (gint i = 0; pp->extensions[i]; i ++)
00502             plugin->u.p.exts = g_list_prepend (plugin->u.p.exts, g_strdup
00503              (pp->extensions[i]));
00504     }
00505     else if (header->type == PLUGIN_TYPE_INPUT)
00506     {
00507         InputPlugin * ip = (InputPlugin *) header;
00508         plugin->priority = ip->priority;
00509 
00510         for (gint key = 0; key < INPUT_KEYS; key ++)
00511         {
00512             g_list_foreach (plugin->u.i.keys[key], (GFunc) g_free, NULL);
00513             g_list_free (plugin->u.i.keys[key]);
00514             plugin->u.i.keys[key] = NULL;
00515         }
00516 
00517         if (PLUGIN_HAS_FUNC (ip, extensions))
00518         {
00519             for (gint i = 0; ip->extensions[i]; i ++)
00520                 plugin->u.i.keys[INPUT_KEY_EXTENSION] = g_list_prepend
00521                  (plugin->u.i.keys[INPUT_KEY_EXTENSION], g_strdup
00522                  (ip->extensions[i]));
00523         }
00524 
00525         if (PLUGIN_HAS_FUNC (ip, mimes))
00526         {
00527             for (gint i = 0; ip->mimes[i]; i ++)
00528                 plugin->u.i.keys[INPUT_KEY_MIME] = g_list_prepend
00529                  (plugin->u.i.keys[INPUT_KEY_MIME], g_strdup (ip->mimes[i]));
00530         }
00531 
00532         if (PLUGIN_HAS_FUNC (ip, schemes))
00533         {
00534             for (gint i = 0; ip->schemes[i]; i ++)
00535                 plugin->u.i.keys[INPUT_KEY_SCHEME] = g_list_prepend
00536                  (plugin->u.i.keys[INPUT_KEY_SCHEME], g_strdup (ip->schemes[i]));
00537         }
00538 
00539         plugin->u.i.has_images = PLUGIN_HAS_FUNC (ip, get_song_image);
00540         plugin->u.i.has_subtunes = ip->have_subtune;
00541         plugin->u.i.can_write_tuple = PLUGIN_HAS_FUNC (ip, update_song_tuple);
00542         plugin->u.i.has_infowin = PLUGIN_HAS_FUNC (ip, file_info_box);
00543     }
00544     else if (header->type == PLUGIN_TYPE_OUTPUT)
00545     {
00546         OutputPlugin * op = (OutputPlugin *) header;
00547         plugin->priority = 10 - op->probe_priority;
00548     }
00549     else if (header->type == PLUGIN_TYPE_EFFECT)
00550     {
00551         EffectPlugin * ep = (EffectPlugin *) header;
00552         plugin->priority = ep->order;
00553     }
00554     else if (header->type == PLUGIN_TYPE_GENERAL)
00555     {
00556         GeneralPlugin * gp = (GeneralPlugin *) header;
00557         if (new)
00558             plugin->enabled = gp->enabled_by_default;
00559     }
00560 }
00561 
00562 gint plugin_get_type (PluginHandle * plugin)
00563 {
00564     return plugin->type;
00565 }
00566 
00567 const gchar * plugin_get_filename (PluginHandle * plugin)
00568 {
00569     return plugin->path;
00570 }
00571 
00572 const void * plugin_get_header (PluginHandle * plugin)
00573 {
00574     g_static_mutex_lock (& mutex);
00575 
00576     if (! plugin->loaded)
00577     {
00578         plugin_load (plugin->path);
00579         plugin->loaded = TRUE;
00580     }
00581 
00582     g_static_mutex_unlock (& mutex);
00583     return plugin->header;
00584 }
00585 
00586 static gint plugin_by_header_cb (PluginHandle * plugin, const void * header)
00587 {
00588     return (plugin->header == header) ? 0 : -1;
00589 }
00590 
00591 PluginHandle * plugin_by_header (const void * header)
00592 {
00593     GList * node = g_list_find_custom (plugin_list, header, (GCompareFunc)
00594      plugin_by_header_cb);
00595     return node ? node->data : NULL;
00596 }
00597 
00598 void plugin_for_each (gint type, PluginForEachFunc func, void * data)
00599 {
00600     for (GList * node = plugin_list; node; node = node->next)
00601     {
00602         if (((PluginHandle *) node->data)->type != type)
00603             continue;
00604         if (! func (node->data, data))
00605             break;
00606     }
00607 }
00608 
00609 const gchar * plugin_get_name (PluginHandle * plugin)
00610 {
00611     return plugin->name;
00612 }
00613 
00614 gboolean plugin_has_about (PluginHandle * plugin)
00615 {
00616     return plugin->has_about;
00617 }
00618 
00619 gboolean plugin_has_configure (PluginHandle * plugin)
00620 {
00621     return plugin->has_configure;
00622 }
00623 
00624 gboolean plugin_get_enabled (PluginHandle * plugin)
00625 {
00626     return plugin->enabled;
00627 }
00628 
00629 static void plugin_call_watches (PluginHandle * plugin)
00630 {
00631     for (GList * node = plugin->watches; node; )
00632     {
00633         GList * next = node->next;
00634         PluginWatch * watch = node->data;
00635 
00636         if (! watch->func (plugin, watch->data))
00637         {
00638             g_free (watch);
00639             plugin->watches = g_list_delete_link (plugin->watches, node);
00640         }
00641 
00642         node = next;
00643     }
00644 }
00645 
00646 void plugin_set_enabled (PluginHandle * plugin, gboolean enabled)
00647 {
00648     plugin->enabled = enabled;
00649     plugin_call_watches (plugin);
00650 }
00651 
00652 typedef struct {
00653     PluginForEachFunc func;
00654     void * data;
00655 } PluginForEnabledState;
00656 
00657 static gboolean plugin_for_enabled_cb (PluginHandle * plugin,
00658  PluginForEnabledState * state)
00659 {
00660     if (! plugin->enabled)
00661         return TRUE;
00662     return state->func (plugin, state->data);
00663 }
00664 
00665 void plugin_for_enabled (gint type, PluginForEachFunc func, void * data)
00666 {
00667     PluginForEnabledState state = {func, data};
00668     plugin_for_each (type, (PluginForEachFunc) plugin_for_enabled_cb, & state);
00669 }
00670 
00671 void plugin_add_watch (PluginHandle * plugin, PluginForEachFunc func, void *
00672  data)
00673 {
00674     PluginWatch * watch = g_malloc (sizeof (PluginWatch));
00675     watch->func = func;
00676     watch->data = data;
00677     plugin->watches = g_list_prepend (plugin->watches, watch);
00678 }
00679 
00680 void plugin_remove_watch (PluginHandle * plugin, PluginForEachFunc func, void *
00681  data)
00682 {
00683     for (GList * node = plugin->watches; node; )
00684     {
00685         GList * next = node->next;
00686         PluginWatch * watch = node->data;
00687 
00688         if (watch->func == func && watch->data == data)
00689         {
00690             g_free (watch);
00691             plugin->watches = g_list_delete_link (plugin->watches, node);
00692         }
00693 
00694         node = next;
00695     }
00696 }
00697 
00698 typedef struct {
00699     const gchar * scheme;
00700     PluginHandle * plugin;
00701 } TransportPluginForSchemeState;
00702 
00703 static gboolean transport_plugin_for_scheme_cb (PluginHandle * plugin,
00704  TransportPluginForSchemeState * state)
00705 {
00706     if (! g_list_find_custom (plugin->u.t.schemes, state->scheme, (GCompareFunc)
00707      strcasecmp))
00708         return TRUE;
00709 
00710     state->plugin = plugin;
00711     return FALSE;
00712 }
00713 
00714 PluginHandle * transport_plugin_for_scheme (const gchar * scheme)
00715 {
00716     TransportPluginForSchemeState state = {scheme, NULL};
00717     plugin_for_enabled (PLUGIN_TYPE_TRANSPORT, (PluginForEachFunc)
00718      transport_plugin_for_scheme_cb, & state);
00719     return state.plugin;
00720 }
00721 
00722 typedef struct {
00723     const gchar * ext;
00724     PluginHandle * plugin;
00725 } PlaylistPluginForExtState;
00726 
00727 static gboolean playlist_plugin_for_ext_cb (PluginHandle * plugin,
00728  PlaylistPluginForExtState * state)
00729 {
00730     if (! g_list_find_custom (plugin->u.p.exts, state->ext, (GCompareFunc)
00731      strcasecmp))
00732         return TRUE;
00733 
00734     state->plugin = plugin;
00735     return FALSE;
00736 }
00737 
00738 PluginHandle * playlist_plugin_for_extension (const gchar * extension)
00739 {
00740     PlaylistPluginForExtState state = {extension, NULL};
00741     plugin_for_enabled (PLUGIN_TYPE_PLAYLIST, (PluginForEachFunc)
00742      playlist_plugin_for_ext_cb, & state);
00743     return state.plugin;
00744 }
00745 
00746 typedef struct {
00747     gint key;
00748     const gchar * value;
00749     PluginForEachFunc func;
00750     void * data;
00751 } InputPluginForKeyState;
00752 
00753 static gboolean input_plugin_for_key_cb (PluginHandle * plugin,
00754  InputPluginForKeyState * state)
00755 {
00756     if (! g_list_find_custom (plugin->u.i.keys[state->key], state->value,
00757      (GCompareFunc) strcasecmp))
00758         return TRUE;
00759 
00760     return state->func (plugin, state->data);
00761 }
00762 
00763 void input_plugin_for_key (gint key, const gchar * value, PluginForEachFunc
00764  func, void * data)
00765 {
00766     InputPluginForKeyState state = {key, value, func, data};
00767     plugin_for_enabled (PLUGIN_TYPE_INPUT, (PluginForEachFunc)
00768      input_plugin_for_key_cb, & state);
00769 }
00770 
00771 gboolean input_plugin_has_images (PluginHandle * plugin)
00772 {
00773     g_return_val_if_fail (plugin->type == PLUGIN_TYPE_INPUT, FALSE);
00774     return plugin->u.i.has_images;
00775 }
00776 
00777 gboolean input_plugin_has_subtunes (PluginHandle * plugin)
00778 {
00779     g_return_val_if_fail (plugin->type == PLUGIN_TYPE_INPUT, FALSE);
00780     return plugin->u.i.has_subtunes;
00781 }
00782 
00783 gboolean input_plugin_can_write_tuple (PluginHandle * plugin)
00784 {
00785     g_return_val_if_fail (plugin->type == PLUGIN_TYPE_INPUT, FALSE);
00786     return plugin->u.i.can_write_tuple;
00787 }
00788 
00789 gboolean input_plugin_has_infowin (PluginHandle * plugin)
00790 {
00791     g_return_val_if_fail (plugin->type == PLUGIN_TYPE_INPUT, FALSE);
00792     return plugin->u.i.has_infowin;
00793 }