avahi: Explicitly handle AVAHI_BROWSER_REMOVE events
[vlc.git] / modules / services_discovery / avahi.c
blob1d71cc82d3f7ac89d5ee0810ae8bbeb4b88cfd12
1 /*****************************************************************************
2 * avahi.c: Bonjour services discovery module
3 *****************************************************************************
4 * Copyright (C) 2005-2009, 2016 VideoLAN and VLC authors
6 * Authors: Jon Lech Johansen <jon@nanocrew.net>
7 * Jean-Baptiste Kempf <jb@videolan.org>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
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 General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 *****************************************************************************/
24 /*****************************************************************************
25 * Includes
26 *****************************************************************************/
28 #ifdef HAVE_CONFIG_H
29 # include "config.h"
30 #endif
32 #define VLC_MODULE_LICENSE VLC_LICENSE_GPL_2_PLUS
33 #include <vlc_common.h>
34 #include <vlc_plugin.h>
35 #include <vlc_services_discovery.h>
37 #include <avahi-client/client.h>
38 #include <avahi-client/publish.h>
39 #include <avahi-client/lookup.h>
40 #include <avahi-common/thread-watch.h>
41 #include <avahi-common/malloc.h>
42 #include <avahi-common/error.h>
44 /*****************************************************************************
45 * Module descriptor
46 *****************************************************************************/
48 /* Callbacks */
49 static int Open ( vlc_object_t * );
50 static void Close( vlc_object_t * );
52 VLC_SD_PROBE_HELPER("avahi", N_("Zeroconf network services"), SD_CAT_LAN)
54 vlc_module_begin ()
55 set_shortname( "Avahi" )
56 set_description( N_("Zeroconf services") )
57 set_category( CAT_PLAYLIST )
58 set_subcategory( SUBCAT_PLAYLIST_SD )
59 set_capability( "services_discovery", 0 )
60 set_callbacks( Open, Close )
61 add_shortcut( "mdns", "avahi" )
63 VLC_SD_PROBE_SUBMODULE
64 vlc_module_end ()
66 /*****************************************************************************
67 * Local structures
68 *****************************************************************************/
70 typedef struct
72 AvahiThreadedPoll *poll;
73 AvahiClient *client;
74 vlc_dictionary_t services_name_to_input_item;
75 } services_discovery_sys_t;
77 static const struct
79 const char *psz_protocol;
80 const char *psz_service_name;
81 } protocols[] = {
82 { "ftp", "_ftp._tcp" },
83 { "smb", "_smb._tcp" },
84 { "nfs", "_nfs._tcp" },
85 { "sftp", "_sftp-ssh._tcp" },
86 { "rtsp", "_rtsp._tcp" },
88 #define NB_PROTOCOLS (sizeof(protocols) / sizeof(*protocols))
90 /*****************************************************************************
91 * client_callback
92 *****************************************************************************/
93 static void client_callback( AvahiClient *c, AvahiClientState state,
94 void * userdata )
96 services_discovery_t *p_sd = ( services_discovery_t* )userdata;
97 services_discovery_sys_t *p_sys = p_sd->p_sys;
99 if( state == AVAHI_CLIENT_FAILURE &&
100 (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED) )
102 msg_Err( p_sd, "avahi client disconnected" );
103 avahi_threaded_poll_quit( p_sys->poll );
107 /*****************************************************************************
108 * resolve_callback
109 *****************************************************************************/
110 static void resolve_callback(
111 AvahiServiceResolver *r,
112 AvahiIfIndex interface,
113 AvahiProtocol protocol,
114 AvahiResolverEvent event,
115 const char *name,
116 const char *type,
117 const char *domain,
118 const char *host_name,
119 const AvahiAddress *address,
120 uint16_t port,
121 AvahiStringList *txt,
122 AvahiLookupResultFlags flags,
123 void* userdata )
125 services_discovery_t *p_sd = ( services_discovery_t* )userdata;
126 services_discovery_sys_t *p_sys = p_sd->p_sys;
128 VLC_UNUSED(interface); VLC_UNUSED(host_name);
129 VLC_UNUSED(flags);
131 if( event == AVAHI_RESOLVER_FAILURE )
133 msg_Err( p_sd,
134 "failed to resolve service '%s' of type '%s' in domain '%s'",
135 name, type, domain );
137 else if( event == AVAHI_RESOLVER_FOUND )
139 char a[128];
140 char *psz_uri = NULL;
141 char *psz_addr = NULL;
142 AvahiStringList *asl = NULL;
143 input_item_t *p_input = NULL;
145 msg_Info( p_sd, "service '%s' of type '%s' in domain '%s' port %i",
146 name, type, domain, port );
148 avahi_address_snprint(a, (sizeof(a)/sizeof(a[0]))-1, address);
149 if( protocol == AVAHI_PROTO_INET6 )
150 if( asprintf( &psz_addr, "[%s]", a ) == -1 )
151 return;
153 const char *psz_protocol = NULL;
154 for( unsigned int i = 0; i < NB_PROTOCOLS; i++ )
156 if( !strcmp(type, protocols[i].psz_service_name) )
157 psz_protocol = protocols[i].psz_protocol;
159 if( psz_protocol == NULL )
160 return;
162 if( txt != NULL )
163 asl = avahi_string_list_find( txt, "path" );
164 if( asl != NULL )
166 size_t size;
167 char *key = NULL;
168 char *value = NULL;
169 if( avahi_string_list_get_pair( asl, &key, &value, &size ) == 0 &&
170 value != NULL )
172 if( asprintf( &psz_uri, "%s://%s:%d%s",
173 psz_protocol,
174 psz_addr != NULL ? psz_addr : a,
175 port, value ) == -1 )
177 free( psz_addr );
178 return;
181 if( key != NULL )
182 avahi_free( (void *)key );
183 if( value != NULL )
184 avahi_free( (void *)value );
186 else
188 if( asprintf( &psz_uri, "%s://%s:%d",
189 psz_protocol,
190 psz_addr != NULL ? psz_addr : a, port ) == -1 )
192 free( psz_addr );
193 return;
197 free( psz_addr );
199 if( psz_uri != NULL )
201 p_input = input_item_New( psz_uri, name );
202 free( psz_uri );
204 if( p_input != NULL )
206 vlc_dictionary_insert( &p_sys->services_name_to_input_item,
207 name, p_input );
208 services_discovery_AddItem( p_sd, p_input );
209 input_item_Release( p_input );
213 avahi_service_resolver_free( r );
216 /*****************************************************************************
217 * browser_callback
218 *****************************************************************************/
219 static void browse_callback(
220 AvahiServiceBrowser *b,
221 AvahiIfIndex interface,
222 AvahiProtocol protocol,
223 AvahiBrowserEvent event,
224 const char *name,
225 const char *type,
226 const char *domain,
227 AvahiLookupResultFlags flags,
228 void* userdata )
230 VLC_UNUSED(b);
231 VLC_UNUSED(flags);
232 services_discovery_t *p_sd = ( services_discovery_t* )userdata;
233 services_discovery_sys_t *p_sys = p_sd->p_sys;
234 if( event == AVAHI_BROWSER_NEW )
236 if( avahi_service_resolver_new( p_sys->client, interface, protocol,
237 name, type, domain, AVAHI_PROTO_UNSPEC,
239 resolve_callback, userdata ) == NULL )
241 msg_Err( p_sd, "failed to resolve service '%s': %s", name,
242 avahi_strerror( avahi_client_errno( p_sys->client ) ) );
245 else if( event == AVAHI_BROWSER_REMOVE && name )
247 /** \todo Store the input id and search it, rather than searching the items */
248 input_item_t *p_item;
249 p_item = vlc_dictionary_value_for_key(
250 &p_sys->services_name_to_input_item,
251 name );
252 if( !p_item )
253 msg_Err( p_sd, "failed to find service '%s' in playlist", name );
254 else
256 services_discovery_RemoveItem( p_sd, p_item );
257 vlc_dictionary_remove_value_for_key(
258 &p_sys->services_name_to_input_item,
259 name, NULL, NULL );
264 /*****************************************************************************
265 * Open: initialize and create stuff
266 *****************************************************************************/
267 static int Open( vlc_object_t *p_this )
269 services_discovery_t *p_sd = ( services_discovery_t* )p_this;
270 services_discovery_sys_t *p_sys;
271 int err;
273 p_sd->p_sys = p_sys = calloc( 1, sizeof( services_discovery_sys_t ) );
274 if( !p_sys )
275 return VLC_ENOMEM;
277 p_sd->description = _("Zeroconf network services");
279 vlc_dictionary_init( &p_sys->services_name_to_input_item, 1 );
281 p_sys->poll = avahi_threaded_poll_new();
282 if( p_sys->poll == NULL )
284 msg_Err( p_sd, "failed to create Avahi threaded poll" );
285 goto error;
288 p_sys->client = avahi_client_new( avahi_threaded_poll_get(p_sys->poll),
289 0, client_callback, p_sd, &err );
290 if( p_sys->client == NULL )
292 msg_Err( p_sd, "failed to create avahi client: %s",
293 avahi_strerror( err ) );
294 goto error;
297 for( unsigned i = 0; i < NB_PROTOCOLS; i++ )
299 AvahiServiceBrowser *sb;
300 sb = avahi_service_browser_new( p_sys->client, AVAHI_IF_UNSPEC,
301 AVAHI_PROTO_UNSPEC,
302 protocols[i].psz_service_name, NULL,
303 0, browse_callback, p_sd );
304 if( sb == NULL )
306 msg_Err( p_sd, "failed to create avahi service browser %s", avahi_strerror( avahi_client_errno(p_sys->client) ) );
307 goto error;
311 avahi_threaded_poll_start( p_sys->poll );
313 return VLC_SUCCESS;
315 error:
316 if( p_sys->client != NULL )
317 avahi_client_free( p_sys->client );
318 if( p_sys->poll != NULL )
319 avahi_threaded_poll_free( p_sys->poll );
321 vlc_dictionary_clear( &p_sys->services_name_to_input_item, NULL, NULL );
322 free( p_sys );
324 return VLC_EGENERIC;
327 /*****************************************************************************
328 * Close: cleanup
329 *****************************************************************************/
330 static void Close( vlc_object_t *p_this )
332 services_discovery_t *p_sd = ( services_discovery_t* )p_this;
333 services_discovery_sys_t *p_sys = p_sd->p_sys;
334 avahi_threaded_poll_stop( p_sys->poll );
336 avahi_client_free( p_sys->client );
337 avahi_threaded_poll_free( p_sys->poll );
339 vlc_dictionary_clear( &p_sys->services_name_to_input_item, NULL, NULL );
340 free( p_sys );