1 /*****************************************************************************
2  * media_discoverer.c: libvlc new API media discoverer functions
3  *****************************************************************************
4  * Copyright (C) 2007 VLC authors and VideoLAN
5  * $Id: 367d7ea8e8b3584a6e41265c0d63043170c8e341 $
6  *
7  * Authors: Pierre d'Herbemont <pdherbemont # 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 
24 #ifdef HAVE_CONFIG_H
25 # include "config.h"
26 #endif
27 
28 #include <assert.h>
29 
30 #include <vlc/libvlc.h>
31 #include <vlc/libvlc_media.h>
32 #include <vlc/libvlc_media_list.h>
33 #include <vlc/libvlc_media_discoverer.h>
34 #include <vlc/libvlc_events.h>
35 
36 #include <vlc_services_discovery.h>
37 
38 #include "libvlc_internal.h"
39 #include "media_internal.h" // libvlc_media_new_from_input_item()
40 #include "media_list_internal.h" // libvlc_media_list_internal_add_media()
41 
42 struct libvlc_media_discoverer_t
43 {
44     libvlc_event_manager_t   event_manager;
45     libvlc_instance_t *      p_libvlc_instance;
46     services_discovery_t *   p_sd;
47     libvlc_media_list_t *    p_mlist;
48     vlc_dictionary_t         catname_to_submedialist;
49     char                     name[];
50 };
51 
52 /*
53  * Private functions
54  */
55 
56 /**************************************************************************
57  *       services_discovery_item_added (Private) (VLC event callback)
58  **************************************************************************/
59 
services_discovery_item_added(services_discovery_t * sd,input_item_t * parent,input_item_t * p_item,const char * psz_cat)60 static void services_discovery_item_added( services_discovery_t *sd,
61                                            input_item_t *parent,
62                                            input_item_t *p_item,
63                                            const char *psz_cat )
64 {
65     libvlc_media_t * p_md;
66     libvlc_media_discoverer_t *p_mdis = sd->owner.sys;
67     libvlc_media_list_t * p_mlist = p_mdis->p_mlist;
68 
69     p_md = libvlc_media_new_from_input_item( p_mdis->p_libvlc_instance,
70                                              p_item );
71 
72     if( parent != NULL )
73     {
74         /* Flatten items list for now. TODO: tree support. */
75     }
76     else
77     /* If we have a category, that mean we have to group the items having
78      * that category in a media_list. */
79     if( psz_cat )
80     {
81         p_mlist = vlc_dictionary_value_for_key( &p_mdis->catname_to_submedialist, psz_cat );
82 
83         if( p_mlist == kVLCDictionaryNotFound )
84         {
85             libvlc_media_t * p_catmd;
86             p_catmd = libvlc_media_new_as_node( p_mdis->p_libvlc_instance, psz_cat );
87             p_mlist = libvlc_media_subitems( p_catmd );
88             p_mlist->b_read_only = true;
89 
90             /* Insert the newly created mlist in our dictionary */
91             vlc_dictionary_insert( &p_mdis->catname_to_submedialist, psz_cat, p_mlist );
92 
93             /* Insert the md into the root list */
94             libvlc_media_list_lock( p_mdis->p_mlist );
95             libvlc_media_list_internal_add_media( p_mdis->p_mlist, p_catmd );
96             libvlc_media_list_unlock( p_mdis->p_mlist );
97 
98             /* We don't release the mlist cause the dictionary
99              * doesn't retain the object. But we release the md. */
100             libvlc_media_release( p_catmd );
101         }
102     }
103 
104     libvlc_media_list_lock( p_mlist );
105     libvlc_media_list_internal_add_media( p_mlist, p_md );
106     libvlc_media_list_unlock( p_mlist );
107 
108     libvlc_media_release( p_md );
109 }
110 
111 /**************************************************************************
112  *       services_discovery_item_removed (Private) (VLC event callback)
113  **************************************************************************/
114 
services_discovery_item_removed(services_discovery_t * sd,input_item_t * p_item)115 static void services_discovery_item_removed( services_discovery_t *sd,
116                                              input_item_t *p_item )
117 {
118     libvlc_media_t * p_md;
119     libvlc_media_discoverer_t *p_mdis = sd->owner.sys;
120 
121     int i, count = libvlc_media_list_count( p_mdis->p_mlist );
122     libvlc_media_list_lock( p_mdis->p_mlist );
123     for( i = 0; i < count; i++ )
124     {
125         p_md = libvlc_media_list_item_at_index( p_mdis->p_mlist, i );
126         assert(p_md != NULL);
127         if( p_md->p_input_item == p_item )
128         {
129             libvlc_media_list_internal_remove_index( p_mdis->p_mlist, i );
130             libvlc_media_release( p_md );
131             break;
132         }
133         libvlc_media_release( p_md );
134     }
135     libvlc_media_list_unlock( p_mdis->p_mlist );
136 }
137 
138 /*
139  * Public libvlc functions
140  */
141 
142 /**************************************************************************
143  *       new (Public)
144  **************************************************************************/
145 libvlc_media_discoverer_t *
libvlc_media_discoverer_new(libvlc_instance_t * p_inst,const char * psz_name)146 libvlc_media_discoverer_new( libvlc_instance_t * p_inst, const char * psz_name )
147 {
148     /* podcast SD is a hack and only works with custom playlist callbacks. */
149     if( !strncasecmp( psz_name, "podcast", 7 ) )
150         return NULL;
151 
152     libvlc_media_discoverer_t *p_mdis;
153 
154     p_mdis = malloc(sizeof(*p_mdis) + strlen(psz_name) + 1);
155     if( unlikely(p_mdis == NULL) )
156     {
157         libvlc_printerr( "Not enough memory" );
158         return NULL;
159     }
160 
161     p_mdis->p_libvlc_instance = p_inst;
162     p_mdis->p_mlist = libvlc_media_list_new( p_inst );
163     p_mdis->p_mlist->b_read_only = true;
164     p_mdis->p_sd = NULL;
165 
166     vlc_dictionary_init( &p_mdis->catname_to_submedialist, 0 );
167     libvlc_event_manager_init( &p_mdis->event_manager, p_mdis );
168 
169     libvlc_retain( p_inst );
170     strcpy( p_mdis->name, psz_name );
171     return p_mdis;
172 }
173 
174 /**************************************************************************
175  *       start (Public)
176  **************************************************************************/
177 LIBVLC_API int
libvlc_media_discoverer_start(libvlc_media_discoverer_t * p_mdis)178 libvlc_media_discoverer_start( libvlc_media_discoverer_t * p_mdis )
179 {
180     struct services_discovery_owner_t owner = {
181         p_mdis,
182         services_discovery_item_added,
183         services_discovery_item_removed,
184     };
185 
186     /* Here we go */
187     p_mdis->p_sd = vlc_sd_Create( (vlc_object_t *)p_mdis->p_libvlc_instance->p_libvlc_int,
188                                   p_mdis->name, &owner );
189     if( p_mdis->p_sd == NULL )
190     {
191         libvlc_printerr( "%s: no such discovery module found", p_mdis->name );
192         return -1;
193     }
194 
195     libvlc_event_t event;
196     event.type = libvlc_MediaDiscovererStarted;
197     libvlc_event_send( &p_mdis->event_manager, &event );
198     return 0;
199 }
200 
201 /**************************************************************************
202  *       stop (Public)
203  **************************************************************************/
204 LIBVLC_API void
libvlc_media_discoverer_stop(libvlc_media_discoverer_t * p_mdis)205 libvlc_media_discoverer_stop( libvlc_media_discoverer_t * p_mdis )
206 {
207     libvlc_media_list_t * p_mlist = p_mdis->p_mlist;
208     libvlc_media_list_lock( p_mlist );
209     libvlc_media_list_internal_end_reached( p_mlist );
210     libvlc_media_list_unlock( p_mlist );
211 
212     libvlc_event_t event;
213     event.type = libvlc_MediaDiscovererEnded;
214     libvlc_event_send( &p_mdis->event_manager, &event );
215 
216     vlc_sd_Destroy( p_mdis->p_sd );
217     p_mdis->p_sd = NULL;
218 }
219 
220 /**************************************************************************
221  *       new_from_name (Public)
222  *
223  * \deprecated Use libvlc_media_discoverer_new and libvlc_media_discoverer_start
224  **************************************************************************/
225 libvlc_media_discoverer_t *
libvlc_media_discoverer_new_from_name(libvlc_instance_t * p_inst,const char * psz_name)226 libvlc_media_discoverer_new_from_name( libvlc_instance_t * p_inst,
227                                        const char * psz_name )
228 {
229     libvlc_media_discoverer_t *p_mdis = libvlc_media_discoverer_new( p_inst, psz_name );
230 
231     if( !p_mdis )
232         return NULL;
233 
234     if( libvlc_media_discoverer_start( p_mdis ) != 0)
235     {
236         libvlc_media_discoverer_release( p_mdis );
237         return NULL;
238     }
239 
240     return p_mdis;
241 }
242 
243 /**************************************************************************
244  * release (Public)
245  **************************************************************************/
246 static void
MediaListDictValueRelease(void * mlist,void * obj)247 MediaListDictValueRelease( void* mlist, void* obj )
248 {
249     libvlc_media_list_release( mlist );
250     (void)obj;
251 }
252 
253 void
libvlc_media_discoverer_release(libvlc_media_discoverer_t * p_mdis)254 libvlc_media_discoverer_release( libvlc_media_discoverer_t * p_mdis )
255 {
256     if( p_mdis->p_sd != NULL )
257         libvlc_media_discoverer_stop( p_mdis );
258 
259     libvlc_media_list_release( p_mdis->p_mlist );
260 
261     vlc_dictionary_clear( &p_mdis->catname_to_submedialist,
262         MediaListDictValueRelease, NULL );
263 
264     libvlc_event_manager_destroy( &p_mdis->event_manager );
265     libvlc_release( p_mdis->p_libvlc_instance );
266 
267     free( p_mdis );
268 }
269 
270 /**************************************************************************
271  * localized_name (Public)
272  **************************************************************************/
273 char *
libvlc_media_discoverer_localized_name(libvlc_media_discoverer_t * p_mdis)274 libvlc_media_discoverer_localized_name( libvlc_media_discoverer_t * p_mdis )
275 {
276     if( p_mdis->p_sd == NULL || p_mdis->p_sd->description == NULL )
277         return NULL;
278     return strdup( p_mdis->p_sd->description );
279 }
280 
281 /**************************************************************************
282  * media_list (Public)
283  **************************************************************************/
284 libvlc_media_list_t *
libvlc_media_discoverer_media_list(libvlc_media_discoverer_t * p_mdis)285 libvlc_media_discoverer_media_list( libvlc_media_discoverer_t * p_mdis )
286 {
287     libvlc_media_list_retain( p_mdis->p_mlist );
288     return p_mdis->p_mlist;
289 }
290 
291 /**************************************************************************
292  * event_manager (Public)
293  **************************************************************************/
294 libvlc_event_manager_t *
libvlc_media_discoverer_event_manager(libvlc_media_discoverer_t * p_mdis)295 libvlc_media_discoverer_event_manager( libvlc_media_discoverer_t * p_mdis )
296 {
297     return &p_mdis->event_manager;
298 }
299 
300 
301 /**************************************************************************
302  * running (Public)
303  **************************************************************************/
304 int
libvlc_media_discoverer_is_running(libvlc_media_discoverer_t * p_mdis)305 libvlc_media_discoverer_is_running( libvlc_media_discoverer_t * p_mdis )
306 {
307     return p_mdis->p_sd != NULL;
308 }
309 
310 void
libvlc_media_discoverer_list_release(libvlc_media_discoverer_description_t ** pp_services,size_t i_count)311 libvlc_media_discoverer_list_release( libvlc_media_discoverer_description_t **pp_services,
312                                       size_t i_count )
313 {
314     if( i_count > 0 )
315     {
316         for( size_t i = 0; i < i_count; ++i )
317         {
318             free( pp_services[i]->psz_name );
319             free( pp_services[i]->psz_longname );
320         }
321         free( *pp_services );
322         free( pp_services );
323     }
324 }
325 
326 size_t
libvlc_media_discoverer_list_get(libvlc_instance_t * p_inst,libvlc_media_discoverer_category_t i_cat,libvlc_media_discoverer_description_t *** ppp_services)327 libvlc_media_discoverer_list_get( libvlc_instance_t *p_inst,
328                                   libvlc_media_discoverer_category_t i_cat,
329                                   libvlc_media_discoverer_description_t ***ppp_services )
330 {
331     assert( p_inst != NULL && ppp_services != NULL );
332 
333     int i_core_cat;
334     switch( i_cat )
335     {
336     case libvlc_media_discoverer_devices:
337         i_core_cat = SD_CAT_DEVICES;
338         break;
339     case libvlc_media_discoverer_lan:
340         i_core_cat = SD_CAT_LAN;
341         break;
342     case libvlc_media_discoverer_podcasts:
343         i_core_cat = SD_CAT_INTERNET;
344         break;
345     case libvlc_media_discoverer_localdirs:
346         i_core_cat = SD_CAT_MYCOMPUTER;
347         break;
348     default:
349         vlc_assert_unreachable();
350         *ppp_services = NULL;
351         return 0;
352     }
353 
354     /* Fetch all sd names, longnames and categories */
355     char **ppsz_names, **ppsz_longnames;
356     int *p_categories;
357     ppsz_names = vlc_sd_GetNames( p_inst->p_libvlc_int, &ppsz_longnames,
358                                   &p_categories );
359 
360     if( ppsz_names == NULL )
361     {
362         *ppp_services = NULL;
363         return 0;
364     }
365 
366     /* Count the number of sd matching our category (i_cat/i_core_cat) */
367     size_t i_nb_services = 0;
368     char **ppsz_name = ppsz_names;
369     int *p_category = p_categories;
370     for( ; *ppsz_name != NULL; ppsz_name++, p_category++ )
371     {
372         if( *p_category == i_core_cat )
373             i_nb_services++;
374     }
375 
376     libvlc_media_discoverer_description_t **pp_services = NULL, *p_services = NULL;
377     if( i_nb_services > 0 )
378     {
379         /* Double alloc here, so that the caller iterates through pointers of
380          * struct instead of structs. This allows us to modify the struct
381          * without breaking the API. */
382 
383         pp_services = malloc( i_nb_services
384                               * sizeof(libvlc_media_discoverer_description_t *) );
385         p_services = malloc( i_nb_services
386                              * sizeof(libvlc_media_discoverer_description_t) );
387         if( pp_services == NULL || p_services == NULL )
388         {
389             free( pp_services );
390             free( p_services );
391             pp_services = NULL;
392             p_services = NULL;
393             i_nb_services = 0;
394             /* Even if alloc fails, the next loop must be run in order to free
395              * names returned by vlc_sd_GetNames */
396         }
397     }
398 
399     /* Fill output pp_services or free unused name, longname */
400     char **ppsz_longname = ppsz_longnames;
401     ppsz_name = ppsz_names;
402     p_category = p_categories;
403     unsigned int i_service_idx = 0;
404     libvlc_media_discoverer_description_t *p_service = p_services;
405     for( ; *ppsz_name != NULL; ppsz_name++, ppsz_longname++, p_category++ )
406     {
407         if( pp_services != NULL && *p_category == i_core_cat )
408         {
409             p_service->psz_name = *ppsz_name;
410             p_service->psz_longname = *ppsz_longname;
411             p_service->i_cat = i_cat;
412             pp_services[i_service_idx++] = p_service++;
413         }
414         else
415         {
416             free( *ppsz_name );
417             free( *ppsz_longname );
418         }
419     }
420     free( ppsz_names );
421     free( ppsz_longnames );
422     free( p_categories );
423 
424     *ppp_services = pp_services;
425     return i_nb_services;
426 }
427