* Bonjour services discovery module using avahi.
[vlc.git] / modules / access_output / bonjour.c
1 /*****************************************************************************
2  * bonjour.c
3  *****************************************************************************
4  * Copyright (C) 2005 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Jon Lech Johansen <jon@nanocrew.net>
8  *
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.
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 General Public License for more details.
18  *
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., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27 #include <stdlib.h>
28
29 #include <vlc/vlc.h>
30
31 #ifdef HAVE_AVAHI_CLIENT
32 #include <vlc/intf.h>
33 #include <vlc/sout.h>
34
35 #include <avahi-client/client.h>
36 #include <avahi-common/alternative.h>
37 #include <avahi-common/simple-watch.h>
38 #include <avahi-common/malloc.h>
39 #include <avahi-common/error.h>
40
41 /*****************************************************************************
42  * Structures
43  *****************************************************************************/
44 typedef struct poll_thread_t
45 {
46     VLC_COMMON_MEMBERS
47
48     AvahiSimplePoll     *simple_poll;
49 } poll_thread_t;
50
51 typedef struct bonjour_t
52 {
53     vlc_object_t        *p_log;
54
55     poll_thread_t       *poll_thread;
56     AvahiSimplePoll     *simple_poll;
57     AvahiEntryGroup     *group;
58     AvahiClient         *client;
59     char                *psz_stype;
60     char                *psz_name;
61     int                 i_port;
62     char                *psz_txt;
63 } bonjour_t;
64
65 /*****************************************************************************
66  * Prototypes
67  *****************************************************************************/
68 static int create_service( bonjour_t * );
69
70 /*****************************************************************************
71  * entry_group_callback
72  *****************************************************************************/
73 static void entry_group_callback( AvahiEntryGroup *g,
74                                   AvahiEntryGroupState state,
75                                   void *userdata )
76 {
77     bonjour_t *p_sys = (bonjour_t *)userdata;
78
79     if( state == AVAHI_ENTRY_GROUP_ESTABLISHED )
80     {
81         msg_Dbg( p_sys->p_log, "service '%s' successfully established",
82                  p_sys->psz_name );
83     }
84     else if( state == AVAHI_ENTRY_GROUP_COLLISION )
85     {
86         char *n;
87
88         n = avahi_alternative_service_name( p_sys->psz_name );
89         avahi_free( p_sys->psz_name );
90         p_sys->psz_name = n;
91
92         create_service( p_sys );
93     }
94 }
95
96 /*****************************************************************************
97  * create_service
98  *****************************************************************************/
99 static int create_service( bonjour_t *p_sys )
100 {
101     int error;
102
103     if( p_sys->group == NULL )
104     {
105         p_sys->group = avahi_entry_group_new( p_sys->client,
106                                               entry_group_callback,
107                                               p_sys );
108         if( p_sys->group == NULL )
109         {
110             msg_Err( p_sys->p_log, "failed to create avahi entry group: %s",
111                      avahi_strerror( avahi_client_errno( p_sys->client ) ) );
112             return VLC_EGENERIC;
113         }
114     }
115
116     error = avahi_entry_group_add_service( p_sys->group, AVAHI_IF_UNSPEC,
117                                            AVAHI_PROTO_UNSPEC, p_sys->psz_name,
118                                            p_sys->psz_stype, NULL, NULL,
119                                            p_sys->i_port,
120                                            p_sys->psz_txt, NULL );
121     if( error < 0 )
122     {
123         msg_Err( p_sys->p_log, "failed to add %s service: %s",
124                  p_sys->psz_stype, avahi_strerror( error ) );
125         return VLC_EGENERIC;
126     }
127
128     error = avahi_entry_group_commit( p_sys->group );
129     if( error < 0 )
130     {
131         msg_Err( p_sys->p_log, "failed to commit entry group: %s",
132                  avahi_strerror( error ) );
133         return VLC_EGENERIC;
134     }
135
136     return VLC_SUCCESS;
137 }
138
139 /*****************************************************************************
140  * client_callback
141  *****************************************************************************/
142 static void client_callback( AvahiClient *c,
143                              AvahiClientState state,
144                              void * userdata )
145 {
146     bonjour_t *p_sys = (bonjour_t *)userdata;
147
148     if( state == AVAHI_CLIENT_S_RUNNING )
149     {
150         p_sys->client = c;
151         create_service( p_sys );
152     }
153     else if( state == AVAHI_CLIENT_S_COLLISION )
154     {
155         if( p_sys->group != NULL )
156             avahi_entry_group_reset( p_sys->group );
157     }
158     else if( state == AVAHI_CLIENT_DISCONNECTED )
159     {
160         msg_Err( p_sys->p_log, "avahi client disconnected" );
161         avahi_simple_poll_quit( p_sys->simple_poll );
162     }
163 }
164
165 /*****************************************************************************
166  * poll_iterate_thread
167  *****************************************************************************/
168 static void poll_iterate_thread( poll_thread_t *p_pt )
169 {
170     vlc_thread_ready( p_pt );
171
172     while( !p_pt->b_die )
173         if( avahi_simple_poll_iterate( p_pt->simple_poll, 100 ) != 0 )
174             break;
175 }
176
177 /*****************************************************************************
178  * bonjour_start_service
179  *****************************************************************************/
180 void *bonjour_start_service( vlc_object_t *p_log, char *psz_stype,
181                             char *psz_name, int i_port, char *psz_txt )
182 {
183     int err;
184     bonjour_t *p_sys;
185
186     p_sys = (bonjour_t *)malloc( sizeof(*p_sys) );
187     if( p_sys == NULL )
188     {
189         msg_Err( p_log, "out of memory" );
190         return NULL;
191     }
192
193     memset( p_sys, 0, sizeof(*p_sys) );
194
195     p_sys->p_log = p_log;
196
197     p_sys->i_port = i_port;
198     p_sys->psz_name = avahi_strdup( psz_name );
199     p_sys->psz_stype = avahi_strdup( psz_stype );
200     if( p_sys->psz_name == NULL || p_sys->psz_stype == NULL )
201     {
202         msg_Err( p_sys->p_log, "out of memory" );
203         goto error;
204     }
205
206     if( psz_txt != NULL )
207     {
208         p_sys->psz_txt = avahi_strdup( psz_txt );
209         if( p_sys->psz_txt == NULL )
210         {
211             msg_Err( p_sys->p_log, "out of memory" );
212             goto error;
213         }
214     }
215
216     p_sys->simple_poll = avahi_simple_poll_new();
217     if( p_sys->simple_poll == NULL )
218     {
219         msg_Err( p_sys->p_log, "failed to create avahi simple pool" );
220         goto error;
221     }
222
223     p_sys->client = avahi_client_new( avahi_simple_poll_get(p_sys->simple_poll),
224                                       client_callback, p_sys, &err );
225     if( p_sys->client == NULL )
226     {
227         msg_Err( p_sys->p_log, "failed to create avahi client: %s",
228                  avahi_strerror( err ) );
229         goto error;
230     }
231
232     p_sys->poll_thread = vlc_object_create( p_sys->p_log,
233                                             sizeof(poll_thread_t) );
234     if( p_sys->poll_thread == NULL )
235     {
236         msg_Err( p_sys->p_log, "out of memory" );
237         goto error;
238     }
239     p_sys->poll_thread->simple_poll = p_sys->simple_poll;
240
241     if( vlc_thread_create( p_sys->poll_thread, "Avahi Poll Iterate Thread",
242                            poll_iterate_thread,
243                            VLC_THREAD_PRIORITY_HIGHEST, VLC_FALSE ) )
244     {
245         msg_Err( p_sys->p_log, "failed to create poll iterate thread" );
246         goto error;
247     }
248
249     return (void *)p_sys;
250
251 error:
252     if( p_sys->poll_thread != NULL )
253         vlc_object_destroy( p_sys->poll_thread );
254     if( p_sys->client != NULL )
255         avahi_client_free( p_sys->client );
256     if( p_sys->simple_poll != NULL )
257         avahi_simple_poll_free( p_sys->simple_poll );
258     if( p_sys->psz_stype != NULL )
259         avahi_free( p_sys->psz_stype );
260     if( p_sys->psz_name != NULL )
261         avahi_free( p_sys->psz_name );
262     if( p_sys->psz_txt != NULL )
263         avahi_free( p_sys->psz_txt );
264
265     free( (void *)p_sys );
266
267     return NULL;
268 }
269
270 /*****************************************************************************
271  * bonjour_stop_service
272  *****************************************************************************/
273 void bonjour_stop_service( void *_p_sys )
274 {
275     bonjour_t *p_sys = (bonjour_t *)_p_sys;
276
277     if( p_sys->poll_thread->b_thread )
278     {
279         p_sys->poll_thread->b_die = 1;
280         vlc_thread_join( p_sys->poll_thread );
281     }
282
283     vlc_object_destroy( p_sys->poll_thread );
284
285     if( p_sys->group != NULL )
286         avahi_entry_group_free( p_sys->group );
287
288     avahi_client_free( p_sys->client );
289     avahi_simple_poll_free( p_sys->simple_poll );
290
291     if( p_sys->psz_name != NULL )
292         avahi_free( p_sys->psz_name );
293
294     if( p_sys->psz_txt != NULL )
295         avahi_free( p_sys->psz_txt );
296
297     avahi_free( p_sys->psz_stype );
298
299     free( _p_sys );
300 }
301
302 #endif /* HAVE_AVAHI_CLIENT */