]> git.sesse.net Git - vlc/blob - modules/lua/services_discovery.c
8ae108d25ccfec01e67db4d7269a5593db812837
[vlc] / modules / lua / services_discovery.c
1 /*****************************************************************************
2  * services_discovery.c : Services discovery using lua scripts
3  *****************************************************************************
4  * Copyright (C) 2010 VideoLAN and AUTHORS
5  *
6  * Authors: Fabio Ritrovato <sephiroth87 at videolan dot org>
7  *          RĂ©mi Duraffort <ivoire at videolan -dot- org>
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., 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 <vlc_common.h>
29 #include <vlc_services_discovery.h>
30
31 #include "vlc.h"
32 #include "libs.h"
33
34 /*****************************************************************************
35  * Local prototypes
36  *****************************************************************************/
37 static void *Run( void * );
38 static int DoSearch( services_discovery_t *p_sd, const char *psz_query );
39 static int FillDescriptor( services_discovery_t *, services_discovery_descriptor_t * );
40 static int Control( services_discovery_t *p_sd, int i_command, va_list args );
41
42 static const char * const ppsz_sd_options[] = { "sd", "longname", NULL };
43
44 /*****************************************************************************
45  * Local structures
46  *****************************************************************************/
47 struct services_discovery_sys_t
48 {
49     lua_State *L;
50     char *psz_filename;
51
52     vlc_thread_t thread;
53     vlc_mutex_t lock;
54     vlc_cond_t cond;
55     bool b_exiting;
56
57     char **ppsz_query;
58     int i_query;
59 };
60 static const luaL_Reg p_reg[] = { { NULL, NULL } };
61
62 /*****************************************************************************
63  * Open: initialize and create stuff
64  *****************************************************************************/
65 int Open_LuaSD( vlc_object_t *p_this )
66 {
67     services_discovery_t *p_sd = ( services_discovery_t * )p_this;
68     services_discovery_sys_t *p_sys;
69     lua_State *L = NULL;
70     char *psz_name;
71
72     if( !strcmp( p_sd->psz_name, "lua" ) )
73     {
74         // We want to load the module name "lua"
75         // This module can be used to load lua script not registered
76         // as builtin lua SD modules.
77         config_ChainParse( p_sd, "lua-", ppsz_sd_options, p_sd->p_cfg );
78         psz_name = var_GetString( p_sd, "lua-sd" );
79     }
80     else
81     {
82         // We are loading a builtin lua sd module.
83         psz_name = strdup(p_sd->psz_name);
84     }
85
86     if( !( p_sys = malloc( sizeof( services_discovery_sys_t ) ) ) )
87     {
88         free( psz_name );
89         return VLC_ENOMEM;
90     }
91     p_sd->p_sys = p_sys;
92     p_sd->pf_control = Control;
93     p_sys->psz_filename = vlclua_find_file( p_this, "sd", psz_name );
94     if( !p_sys->psz_filename )
95     {
96         msg_Err( p_sd, "Couldn't find lua services discovery script \"%s\".",
97                  psz_name );
98         free( psz_name );
99         goto error;
100     }
101     free( psz_name );
102     L = luaL_newstate();
103     if( !L )
104     {
105         msg_Err( p_sd, "Could not create new Lua State" );
106         goto error;
107     }
108     vlclua_set_this( L, p_sd );
109     luaL_openlibs( L );
110     luaL_register( L, "vlc", p_reg );
111     luaopen_input( L );
112     luaopen_msg( L );
113     luaopen_net( L );
114     luaopen_object( L );
115     luaopen_sd( L );
116     luaopen_strings( L );
117     luaopen_variables( L );
118     luaopen_stream( L );
119     luaopen_gettext( L );
120     luaopen_xml( L );
121     luaopen_md5( L );
122     lua_pop( L, 1 );
123
124     if( vlclua_add_modules_path( p_sd, L, p_sys->psz_filename ) )
125     {
126         msg_Warn( p_sd, "Error while setting the module search path for %s",
127                   p_sys->psz_filename );
128         goto error;
129     }
130     if( luaL_dofile( L, p_sys->psz_filename ) )
131     {
132         msg_Err( p_sd, "Error loading script %s: %s", p_sys->psz_filename,
133                   lua_tostring( L, lua_gettop( L ) ) );
134         lua_pop( L, 1 );
135         goto error;
136     }
137     p_sys->L = L;
138     vlc_mutex_init( &p_sys->lock );
139     vlc_cond_init( &p_sys->cond );
140     p_sys->b_exiting = false;
141     TAB_INIT( p_sys->i_query, p_sys->ppsz_query );
142
143     if( vlc_clone( &p_sys->thread, Run, p_sd, VLC_THREAD_PRIORITY_LOW ) )
144     {
145         TAB_CLEAN( p_sys->i_query, p_sys->ppsz_query );
146         vlc_cond_destroy( &p_sys->cond );
147         vlc_mutex_destroy( &p_sys->lock );
148         goto error;
149     }
150     return VLC_SUCCESS;
151
152 error:
153     if( L )
154         lua_close( L );
155     free( p_sys->psz_filename );
156     free( p_sys );
157     return VLC_EGENERIC;
158 }
159
160 /*****************************************************************************
161  * Close: cleanup
162  *****************************************************************************/
163 void Close_LuaSD( vlc_object_t *p_this )
164 {
165     services_discovery_t *p_sd = ( services_discovery_t * )p_this;
166     services_discovery_sys_t *p_sys = p_sd->p_sys;
167
168     vlc_mutex_lock( &p_sys->lock );
169     p_sys->b_exiting = true;
170     vlc_mutex_unlock( &p_sys->lock );
171
172     vlc_cancel( p_sys->thread );
173     vlc_join( p_sys->thread, NULL );
174
175     for( int i = 0; i < p_sys->i_query; i++ )
176         free( p_sys->ppsz_query[i] );
177     TAB_CLEAN( p_sys->i_query, p_sys->ppsz_query );
178
179     vlc_cond_destroy( &p_sys->cond );
180     vlc_mutex_destroy( &p_sys->lock );
181     free( p_sys->psz_filename );
182     lua_close( p_sys->L );
183     free( p_sys );
184 }
185
186 /*****************************************************************************
187  * Run: Thread entry-point
188  ****************************************************************************/
189 static void* Run( void *data )
190 {
191     services_discovery_t *p_sd = ( services_discovery_t * )data;
192     services_discovery_sys_t *p_sys = p_sd->p_sys;
193     lua_State *L = p_sys->L;
194
195     int cancel = vlc_savecancel();
196
197     lua_getglobal( L, "main" );
198     if( !lua_isfunction( L, lua_gettop( L ) ) || lua_pcall( L, 0, 1, 0 ) )
199     {
200         msg_Err( p_sd, "Error while running script %s, "
201                   "function main(): %s", p_sys->psz_filename,
202                   lua_tostring( L, lua_gettop( L ) ) );
203         lua_pop( L, 1 );
204         vlc_restorecancel( cancel );
205         return NULL;
206     }
207     msg_Dbg( p_sd, "LuaSD script loaded: %s", p_sys->psz_filename );
208
209     /* Force garbage collection, because the core will keep the SD
210      * open, but lua will never gc until lua_close(). */
211     lua_gc( L, LUA_GCCOLLECT, 0 );
212
213     vlc_restorecancel( cancel );
214
215     /* Main loop to handle search requests */
216     vlc_mutex_lock( &p_sys->lock );
217     mutex_cleanup_push( &p_sys->lock );
218     while( !p_sys->b_exiting )
219     {
220         /* Wait for a request */
221         while( !p_sys->i_query )
222             vlc_cond_wait( &p_sys->cond, &p_sys->lock );
223
224         /* Execute every query each one protected against cancelation */
225         cancel = vlc_savecancel();
226         while( !p_sys->b_exiting && p_sys->i_query )
227         {
228             char *psz_query = p_sys->ppsz_query[p_sys->i_query - 1];
229             REMOVE_ELEM( p_sys->ppsz_query, p_sys->i_query, p_sys->i_query - 1 );
230
231             vlc_mutex_unlock( &p_sys->lock );
232             DoSearch( p_sd, psz_query );
233             free( psz_query );
234             vlc_mutex_lock( &p_sys->lock );
235         }
236         /* Force garbage collection, because the core will keep the SD
237          * open, but lua will never gc until lua_close(). */
238         lua_gc( L, LUA_GCCOLLECT, 0 );
239
240         vlc_restorecancel( cancel );
241     }
242     vlc_cleanup_run();
243
244     return NULL;
245 }
246
247 /*****************************************************************************
248  * Control: services discrovery control
249  ****************************************************************************/
250 static int Control( services_discovery_t *p_sd, int i_command, va_list args )
251 {
252     services_discovery_sys_t *p_sys = p_sd->p_sys;
253
254     switch( i_command )
255     {
256     case SD_CMD_SEARCH:
257     {
258         const char *psz_query = va_arg( args, const char * );
259         vlc_mutex_lock( &p_sys->lock );
260         TAB_APPEND( p_sys->i_query, p_sys->ppsz_query, strdup( psz_query ) );
261         vlc_cond_signal( &p_sys->cond );
262         vlc_mutex_unlock( &p_sys->lock );
263         break;
264     }
265
266     case SD_CMD_DESCRIPTOR:
267     {
268         services_discovery_descriptor_t *p_desc = va_arg( args,
269                                 services_discovery_descriptor_t * );
270         return FillDescriptor( p_sd, p_desc );
271     }
272     }
273
274     return VLC_SUCCESS;
275 }
276
277 /*****************************************************************************
278  * DoSearch: search for a given query
279  ****************************************************************************/
280 static int DoSearch( services_discovery_t *p_sd, const char *psz_query )
281 {
282     services_discovery_sys_t *p_sys = p_sd->p_sys;
283     lua_State *L = p_sys->L;
284
285     /* Lookup for the 'search' function */
286     lua_getglobal( L, "search" );
287     if( !lua_isfunction( L, lua_gettop( L ) ) )
288     {
289         msg_Err( p_sd, "The script '%s' does not define any 'search' function",
290                  p_sys->psz_filename );
291         lua_pop( L, 1 );
292         return VLC_EGENERIC;
293     }
294
295     /* Push the query */
296     lua_pushstring( L, psz_query );
297
298     /* Call the 'search' function */
299     if( lua_pcall( L, 1, 0, 0 ) )
300     {
301         msg_Err( p_sd, "Error while running the script '%s': %s",
302                  p_sys->psz_filename, lua_tostring( L, lua_gettop( L ) ) );
303         lua_pop( L, 1 );
304         return VLC_EGENERIC;
305     }
306
307     return VLC_SUCCESS;
308 }
309
310 /** List of capabilities */
311 static const char *const ppsz_capabilities[] = {
312     "search",
313     NULL
314 };
315
316 /*****************************************************************************
317  * FillDescriptor: call the descriptor function and fill the structure
318  ****************************************************************************/
319 static int FillDescriptor( services_discovery_t *p_sd,
320                            services_discovery_descriptor_t *p_desc )
321 {
322     services_discovery_sys_t *p_sys = p_sd->p_sys;
323     int i_ret = VLC_EGENERIC;
324
325     /* Create a new lua thread */
326     lua_State *L = luaL_newstate();
327     if( luaL_dofile( L, p_sys->psz_filename ) )
328     {
329         msg_Err( p_sd, "Error loading script %s: %s", p_sys->psz_filename,
330                  lua_tostring( L, -1 ) );
331         goto end;
332     }
333
334     /* Call the "descriptor" function */
335     lua_getglobal( L, "descriptor" );
336     if( !lua_isfunction( L, -1 ) || lua_pcall( L, 0, 1, 0 ) )
337     {
338         msg_Warn( p_sd, "Error getting the descriptor in '%s': %s",
339                   p_sys->psz_filename, lua_tostring( L, -1 ) );
340         goto end;
341     }
342
343     /* Get the different fields of the returned table */
344     lua_getfield( L, -1, "short_description" );
345     p_desc->psz_short_desc = luaL_strdupornull( L, -1 );
346     lua_pop( L, 1 );
347
348     lua_getfield( L, -1, "icon" );
349     p_desc->psz_icon_url = luaL_strdupornull( L, -1 );
350     lua_pop( L, 1 );
351
352     lua_getfield( L, -1, "url" );
353     p_desc->psz_url = luaL_strdupornull( L, -1 );
354     lua_pop( L, 1 );
355
356     lua_getfield( L, -1, "capabilities" );
357     p_desc->i_capabilities = 0;
358     if( lua_istable( L, -1 ) )
359     {
360         /* List all table entries */
361         lua_pushnil( L );
362         while( lua_next( L, -2 ) != 0 )
363         {
364             /* Key is at index -2 and value at index -1 */
365             const char *psz_cap = luaL_checkstring( L, -1 );
366             int i_cap = 0;
367             const char *psz_iter;
368             for( psz_iter = *ppsz_capabilities; psz_iter;
369                  psz_iter = ppsz_capabilities[ ++i_cap ] )
370             {
371                 if( !strcmp( psz_iter, psz_cap ) )
372                 {
373                     p_desc->i_capabilities |= 1 << i_cap;
374                     break;
375                 }
376             }
377             if( !psz_iter )
378                 msg_Warn( p_sd, "Services discovery capability '%s' unknown in "
379                                 "script '%s'", psz_cap, p_sys->psz_filename );
380         }
381     }
382     lua_pop( L, 1 );
383     i_ret = VLC_SUCCESS;
384
385 end:
386     lua_close( L );
387     return i_ret;
388
389 }