sd: use owner structure instead of legacy event manager
[vlc.git] / src / playlist / services_discovery.c
1 /*****************************************************************************
2  * services_discovery.c : Manage playlist services_discovery modules
3  *****************************************************************************
4  * Copyright (C) 1999-2004 VLC authors and VideoLAN
5  * $Id$
6  *
7  * Authors: ClĂ©ment Stenac <zorglub@videolan.org>
8  *
9  * This program is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this program; if not, write to the Free Software Foundation,
21  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23 #ifdef HAVE_CONFIG_H
24 # include "config.h"
25 #endif
26 #include <assert.h>
27
28 #include <vlc_common.h>
29 #include "vlc_playlist.h"
30 #include <vlc_services_discovery.h>
31 #include <vlc_probe.h>
32 #include <vlc_modules.h>
33 #include "playlist_internal.h"
34 #include "../libvlc.h"
35
36 typedef struct
37 {
38     char *name;
39     char *longname;
40     int category;
41 } vlc_sd_probe_t;
42
43 int vlc_sd_probe_Add (vlc_probe_t *probe, const char *name,
44                       const char *longname, int category)
45 {
46     vlc_sd_probe_t names = { strdup(name), strdup(longname), category };
47
48     if (unlikely (names.name == NULL || names.longname == NULL
49                || vlc_probe_add (probe, &names, sizeof (names))))
50     {
51         free (names.name);
52         free (names.longname);
53         return VLC_ENOMEM;
54     }
55     return VLC_PROBE_CONTINUE;
56 }
57
58 #undef vlc_sd_GetNames
59
60 /**
61  * Gets the list of available services discovery plugins.
62  */
63 char **vlc_sd_GetNames (vlc_object_t *obj, char ***pppsz_longnames, int **pp_categories)
64 {
65     size_t count;
66     vlc_sd_probe_t *tab = vlc_probe (obj, "services probe", &count);
67
68     if (count == 0)
69     {
70         free (tab);
71         return NULL;
72     }
73
74     char **names = malloc (sizeof(char *) * (count + 1));
75     char **longnames = malloc (sizeof(char *) * (count + 1));
76     int *categories = malloc(sizeof(int) * (count + 1));
77
78     if (unlikely (names == NULL || longnames == NULL || categories == NULL))
79     {
80         free(names);
81         free(longnames);
82         free(categories);
83         free(tab);
84         return NULL;
85     }
86     for( size_t i = 0; i < count; i++ )
87     {
88         names[i] = tab[i].name;
89         longnames[i] = tab[i].longname;
90         categories[i] = tab[i].category;
91     }
92     free (tab);
93     names[count] = longnames[count] = NULL;
94     categories[count] = 0;
95     *pppsz_longnames = longnames;
96     if( pp_categories ) *pp_categories = categories;
97     else free( categories );
98     return names;
99 }
100
101 /*
102  * Services discovery
103  * Basically you just listen to Service discovery event through the
104  * sd's event manager.
105  * That's how the playlist get's Service Discovery information
106  */
107
108 services_discovery_t *vlc_sd_Create(vlc_object_t *parent, const char *cfg,
109     const struct services_discovery_owner_t *restrict owner)
110 {
111     services_discovery_t *sd = vlc_custom_create(parent, sizeof (*sd),
112                                                  "services discovery");
113     if (unlikely(sd == NULL))
114         return NULL;
115
116     free(config_ChainCreate(&sd->psz_name, &sd->p_cfg, cfg));
117     sd->owner = *owner;
118
119     return sd;
120 }
121
122 /*******************************************************************//**
123  * Start a Service Discovery
124  ***********************************************************************/
125 bool vlc_sd_Start ( services_discovery_t * p_sd )
126 {
127     assert(!p_sd->p_module);
128
129     p_sd->p_module = module_need( p_sd, "services_discovery",
130                                   p_sd->psz_name, true );
131     if( p_sd->p_module == NULL )
132     {
133         msg_Err( p_sd, "no suitable services discovery module" );
134         return false;
135     }
136
137     return true;
138 }
139
140 /*******************************************************************//**
141  * Stop a Service Discovery
142  ***********************************************************************/
143 void vlc_sd_Stop ( services_discovery_t * p_sd )
144 {
145     module_unneed( p_sd, p_sd->p_module );
146     p_sd->p_module = NULL;
147 }
148
149 /*******************************************************************//**
150  * Destroy a Service Discovery
151  ***********************************************************************/
152 void vlc_sd_Destroy( services_discovery_t *p_sd )
153 {
154     config_ChainDestroy( p_sd->p_cfg );
155     free( p_sd->psz_name );
156     vlc_object_release( p_sd );
157 }
158
159 /*******************************************************************//**
160  * Get the Localized Name
161  *
162  * This is useful for interfaces and libVLC
163  ***********************************************************************/
164 char *
165 services_discovery_GetLocalizedName ( services_discovery_t * p_sd )
166 {
167     if (p_sd->p_module == NULL)
168         return NULL;
169     return strdup( module_get_name( p_sd->p_module, true ) );
170 }
171
172 /*
173  * Playlist - Services discovery bridge
174  */
175
176 struct vlc_sd_internal_t
177 {
178     /* the playlist items for category and onelevel */
179     playlist_item_t      *node;
180     services_discovery_t *sd; /**< Loaded service discovery modules */
181     char name[];
182 };
183
184  /* A new item has been added to a certain sd */
185 static void playlist_sd_item_added(services_discovery_t *sd,
186                                    input_item_t *p_input, const char *psz_cat)
187 {
188     vlc_sd_internal_t *sds = sd->owner.sys;
189     playlist_item_t *p_parent = sds->node;
190     playlist_t * p_playlist = p_parent->p_playlist;
191
192     msg_Dbg( p_playlist, "Adding %s in %s",
193                 p_input->psz_name ? p_input->psz_name : "(null)",
194                 psz_cat ? psz_cat : "(null)" );
195
196     PL_LOCK;
197     /* If p_parent is in root category (this is clearly a hack) and we have a cat */
198     if( !EMPTY_STR(psz_cat) )
199     {
200         /* */
201         playlist_item_t * p_cat;
202         p_cat = playlist_ChildSearchName( p_parent, psz_cat );
203         if( !p_cat )
204         {
205             p_cat = playlist_NodeCreate( p_playlist, psz_cat,
206                                          p_parent, PLAYLIST_END, 0, NULL );
207             p_cat->i_flags |= PLAYLIST_RO_FLAG | PLAYLIST_SKIP_FLAG;
208         }
209         p_parent = p_cat;
210     }
211
212     playlist_NodeAddInput( p_playlist, p_input, p_parent,
213                            PLAYLIST_APPEND, PLAYLIST_END,
214                            pl_Locked );
215     PL_UNLOCK;
216 }
217
218  /* A new item has been removed from a certain sd */
219 static void playlist_sd_item_removed(services_discovery_t *sd,
220                                      input_item_t *p_input)
221 {
222     vlc_sd_internal_t *sds = sd->owner.sys;
223     playlist_item_t *p_sd_node = sds->node;
224     playlist_t *p_playlist = p_sd_node->p_playlist;
225
226     PL_LOCK;
227     playlist_item_t *p_item =
228         playlist_ItemFindFromInputAndRoot( p_playlist, p_input,
229                                            p_sd_node, false );
230     if( !p_item )
231     {
232         PL_UNLOCK; return;
233     }
234     playlist_item_t *p_parent = p_item->p_parent;
235     /* if the item was added under a category and the category node
236        becomes empty, delete that node as well */
237     if( p_parent->i_children > 1 || p_parent == p_sd_node )
238         playlist_DeleteItem( p_playlist, p_item, true );
239     else
240         playlist_NodeDelete( p_playlist, p_parent, true, true );
241     PL_UNLOCK;
242 }
243
244 int playlist_ServicesDiscoveryAdd(playlist_t *playlist, const char *chain)
245 {
246     vlc_sd_internal_t *sds = malloc(sizeof (*sds) + strlen(chain) + 1);
247     if (unlikely(sds == NULL))
248         return VLC_ENOMEM;
249
250     /* Look for configuration chain "longname" */
251     const char *longname = "?";
252     config_chain_t *cfg;
253     char *name;
254
255     free(config_ChainCreate(&name, &cfg, chain));
256     msg_Dbg(playlist, "adding services_discovery %s...", name);
257
258     for (config_chain_t *p = cfg; p != NULL; p = p->p_next)
259         if (cfg->psz_name != NULL && !strcmp(cfg->psz_name, "longname"))
260         {
261             if (cfg->psz_value != NULL)
262                 longname = cfg->psz_value;
263             break;
264         }
265
266     playlist_Lock(playlist);
267     sds->node = playlist_NodeCreate(playlist, longname, playlist->p_root,
268                                     PLAYLIST_END,
269                                     PLAYLIST_RO_FLAG|PLAYLIST_SKIP_FLAG, NULL);
270     playlist_Unlock(playlist);
271
272     config_ChainDestroy(cfg);
273     free(name);
274
275     if (unlikely(sds->node == NULL))
276     {
277         free(sds);
278         return VLC_ENOMEM;
279     }
280
281     struct services_discovery_owner_t owner = {
282         sds,
283         playlist_sd_item_added,
284         playlist_sd_item_removed,
285     };
286
287     /* Perform the addition */
288     sds->sd = vlc_sd_Create(VLC_OBJECT(playlist), chain, &owner);
289     if (unlikely(sds->sd == NULL))
290     {
291         playlist_Lock(playlist);
292         playlist_NodeDelete(playlist, sds->node, true, false);
293         playlist_Unlock(playlist);
294         free(sds);
295         return VLC_ENOMEM;
296     }
297
298     if (!vlc_sd_Start(sds->sd))
299     {
300         vlc_sd_Destroy(sds->sd);
301         playlist_Lock(playlist);
302         playlist_NodeDelete(playlist, sds->node, true, false);
303         playlist_Unlock(playlist);
304         free(sds);
305         return VLC_EGENERIC;
306     }
307
308     strcpy(sds->name, chain);
309
310     playlist_Lock(playlist);
311     TAB_APPEND(pl_priv(playlist)->i_sds, pl_priv(playlist)->pp_sds, sds);
312     playlist_Unlock(playlist);
313     return VLC_SUCCESS;
314 }
315
316 static void playlist_ServicesDiscoveryInternalRemove(playlist_t *playlist,
317                                                      vlc_sd_internal_t *sds)
318 {
319     assert(sds->sd != NULL);
320     vlc_sd_Stop(sds->sd);
321     vlc_sd_Destroy(sds->sd);
322
323     /* Remove the sd playlist node if it exists */
324     playlist_Lock(playlist);
325     playlist_NodeDelete(playlist, sds->node, true, false);
326     playlist_Unlock(playlist);
327
328     free(sds);
329 }
330
331
332 int playlist_ServicesDiscoveryRemove(playlist_t *playlist, const char *name)
333 {
334     playlist_private_t *priv = pl_priv(playlist);
335     vlc_sd_internal_t *sds = NULL;
336
337     playlist_Lock(playlist);
338     for (int i = 0; i < priv->i_sds; i++)
339     {
340         vlc_sd_internal_t *entry = priv->pp_sds[i];
341
342         if (!strcmp(name, entry->name))
343         {
344             REMOVE_ELEM(priv->pp_sds, priv->i_sds, i);
345             sds = entry;
346             break;
347         }
348     }
349     playlist_Unlock(playlist);
350
351     if (sds == NULL)
352     {
353         msg_Warn(playlist, "discovery %s is not loaded", name);
354         return VLC_EGENERIC;
355     }
356
357     playlist_ServicesDiscoveryInternalRemove(playlist, sds);
358     return VLC_SUCCESS;
359 }
360
361 bool playlist_IsServicesDiscoveryLoaded( playlist_t * p_playlist,
362                                          const char *psz_name )
363 {
364     playlist_private_t *priv = pl_priv( p_playlist );
365     bool found = false;
366     PL_LOCK;
367
368     for( int i = 0; i < priv->i_sds; i++ )
369     {
370         vlc_sd_internal_t *sds = priv->pp_sds[i];
371
372         if (!strcmp(psz_name, sds->name))
373         {
374             found = true;
375             break;
376         }
377     }
378     PL_UNLOCK;
379     return found;
380 }
381
382 int playlist_ServicesDiscoveryControl( playlist_t *p_playlist, const char *psz_name, int i_control, ... )
383 {
384     playlist_private_t *priv = pl_priv( p_playlist );
385     int i_ret = VLC_EGENERIC;
386     int i;
387
388     PL_LOCK;
389     for( i = 0; i < priv->i_sds; i++ )
390     {
391         vlc_sd_internal_t *sds = priv->pp_sds[i];
392         if (!strcmp(psz_name, sds->name))
393         {
394             va_list args;
395             va_start( args, i_control );
396             i_ret = vlc_sd_control(sds->sd, i_control, args );
397             va_end( args );
398             break;
399         }
400     }
401
402     assert( i != priv->i_sds );
403     PL_UNLOCK;
404
405     return i_ret;
406 }
407
408 void playlist_ServicesDiscoveryKillAll(playlist_t *playlist)
409 {
410     playlist_private_t *priv = pl_priv(playlist);
411
412     for (int i = 0; i < priv->i_sds; i++)
413         playlist_ServicesDiscoveryInternalRemove(playlist, priv->pp_sds[i]);
414
415     TAB_CLEAN(priv->i_sds, priv->pp_sds);
416 }