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 <assert.h>
29 #include <vlc_common.h>
30 #include <vlc_services_discovery.h>
31 
32 #include "vlc.h"
33 #include "libs.h"
34 
35 /*****************************************************************************
36  * Local prototypes
37  *****************************************************************************/
38 static void *Run( void * );
39 static int DoSearch( services_discovery_t *p_sd, const char *psz_query );
40 static int FillDescriptor( services_discovery_t *, services_discovery_descriptor_t * );
41 static int Control( services_discovery_t *p_sd, int i_command, va_list args );
42 
43 // When successful, the returned string is stored on top of the lua
44 // stack and remains valid as long as it is kept in the stack.
vlclua_sd_description(vlc_object_t * obj,lua_State * L,const char * filename)45 static const char *vlclua_sd_description( vlc_object_t *obj, lua_State *L,
46                                           const char *filename )
47 {
48     lua_getglobal( L, "descriptor" );
49     if( !lua_isfunction( L, -1 ) )
50     {
51         msg_Warn( obj, "No 'descriptor' function in '%s'", filename );
52         lua_pop( L, 1 );
53         return NULL;
54     }
55 
56     if( lua_pcall( L, 0, 1, 0 ) )
57     {
58         msg_Warn( obj, "Error while running script %s, "
59                   "function descriptor(): %s", filename,
60                   lua_tostring( L, -1 ) );
61         lua_pop( L, 1 );
62         return NULL;
63     }
64 
65     lua_getfield( L, -1, "title" );
66     if ( !lua_isstring( L, -1 ) )
67     {
68         msg_Warn( obj, "'descriptor' function in '%s' returned no title",
69                   filename );
70         lua_pop( L, 2 );
71         return NULL;
72     }
73 
74     return lua_tostring( L, -1 );
75 }
76 
vlclua_probe_sd(vlc_object_t * obj,const char * name)77 int vlclua_probe_sd( vlc_object_t *obj, const char *name )
78 {
79     vlc_probe_t *probe = (vlc_probe_t *)obj;
80 
81     char *filename = vlclua_find_file( "sd", name );
82     if( filename == NULL )
83     {
84         // File suddenly disappeared - maybe a race condition, no problem
85         msg_Err( probe, "Couldn't probe lua services discovery script \"%s\".",
86                  name );
87         return VLC_PROBE_CONTINUE;
88     }
89 
90     lua_State *L = luaL_newstate();
91     if( !L )
92     {
93         msg_Err( probe, "Could not create new Lua State" );
94         free( filename );
95         return VLC_ENOMEM;
96     }
97     luaL_openlibs( L );
98     if( vlclua_add_modules_path( L, filename ) )
99     {
100         msg_Err( probe, "Error while setting the module search path for %s",
101                  filename );
102         lua_close( L );
103         free( filename );
104         return VLC_ENOMEM;
105     }
106     if( vlclua_dofile( obj, L, filename ) )
107     {
108         msg_Err( probe, "Error loading script %s: %s", filename,
109                  lua_tostring( L, -1 ) );
110         lua_close( L );
111         free( filename );
112         return VLC_PROBE_CONTINUE;
113     }
114     const char *description = vlclua_sd_description( obj, L, filename );
115     if( description == NULL )
116         description = name;
117 
118     int r = VLC_ENOMEM;
119     char *name_esc = config_StringEscape( name );
120     char *chain;
121     if( asprintf( &chain, "lua{sd='%s'}", name_esc ) != -1 )
122     {
123         r = vlc_sd_probe_Add( probe, chain, description, SD_CAT_INTERNET );
124         free( chain );
125     }
126     free( name_esc );
127 
128     lua_close( L );
129     free( filename );
130     return r;
131 }
132 
133 static const char * const ppsz_sd_options[] = { "sd", NULL };
134 
135 /*****************************************************************************
136  * Local structures
137  *****************************************************************************/
138 struct services_discovery_sys_t
139 {
140     lua_State *L;
141     char *psz_filename;
142 
143     vlc_thread_t thread;
144     vlc_mutex_t lock;
145     vlc_cond_t cond;
146 
147     char **ppsz_query;
148     int i_query;
149 };
150 static const luaL_Reg p_reg[] = { { NULL, NULL } };
151 
152 /*****************************************************************************
153  * Open: initialize and create stuff
154  *****************************************************************************/
Open_LuaSD(vlc_object_t * p_this)155 int Open_LuaSD( vlc_object_t *p_this )
156 {
157     if( lua_Disabled( p_this ) )
158         return VLC_EGENERIC;
159 
160     services_discovery_t *p_sd = ( services_discovery_t * )p_this;
161     services_discovery_sys_t *p_sys;
162     lua_State *L = NULL;
163     char *psz_name;
164 
165     if( !( p_sys = malloc( sizeof( services_discovery_sys_t ) ) ) )
166         return VLC_ENOMEM;
167 
168     if( !strcmp( p_sd->psz_name, "lua" ) ||
169         !strcmp( p_sd->psz_name, "luasd" ) )
170     {
171         // We want to load the module name "lua"
172         // This module can be used to load lua script not registered
173         // as builtin lua SD modules.
174         config_ChainParse( p_sd, "lua-", ppsz_sd_options, p_sd->p_cfg );
175         psz_name = var_GetString( p_sd, "lua-sd" );
176     }
177     else
178     {
179         // We are loading a builtin lua sd module.
180         psz_name = strdup(p_sd->psz_name);
181     }
182 
183     p_sd->p_sys = p_sys;
184     p_sd->pf_control = Control;
185     p_sys->psz_filename = vlclua_find_file( "sd", psz_name );
186     if( !p_sys->psz_filename )
187     {
188         msg_Err( p_sd, "Couldn't find lua services discovery script \"%s\".",
189                  psz_name );
190         free( psz_name );
191         goto error;
192     }
193     free( psz_name );
194     L = luaL_newstate();
195     if( !L )
196     {
197         msg_Err( p_sd, "Could not create new Lua State" );
198         goto error;
199     }
200     vlclua_set_this( L, p_sd );
201     luaL_openlibs( L );
202     luaL_register_namespace( L, "vlc", p_reg );
203     luaopen_input( L );
204     luaopen_msg( L );
205     luaopen_object( L );
206     luaopen_sd_sd( L );
207     luaopen_strings( L );
208     luaopen_variables( L );
209     luaopen_stream( L );
210     luaopen_gettext( L );
211     luaopen_xml( L );
212     lua_pop( L, 1 );
213 
214     if( vlclua_add_modules_path( L, p_sys->psz_filename ) )
215     {
216         msg_Warn( p_sd, "Error while setting the module search path for %s",
217                   p_sys->psz_filename );
218         goto error;
219     }
220     if( vlclua_dofile( VLC_OBJECT(p_sd), L, p_sys->psz_filename ) )
221     {
222         msg_Err( p_sd, "Error loading script %s: %s", p_sys->psz_filename,
223                   lua_tostring( L, lua_gettop( L ) ) );
224         lua_pop( L, 1 );
225         goto error;
226     }
227 
228     // No strdup(), just don't remove the string from the lua stack
229     p_sd->description = vlclua_sd_description( VLC_OBJECT(p_sd), L,
230                                                p_sys->psz_filename );
231     if( p_sd->description == NULL )
232         p_sd->description = p_sd->psz_name;
233 
234     p_sys->L = L;
235     vlc_mutex_init( &p_sys->lock );
236     vlc_cond_init( &p_sys->cond );
237     TAB_INIT( p_sys->i_query, p_sys->ppsz_query );
238 
239     if( vlc_clone( &p_sys->thread, Run, p_sd, VLC_THREAD_PRIORITY_LOW ) )
240     {
241         TAB_CLEAN( p_sys->i_query, p_sys->ppsz_query );
242         vlc_cond_destroy( &p_sys->cond );
243         vlc_mutex_destroy( &p_sys->lock );
244         goto error;
245     }
246     return VLC_SUCCESS;
247 
248 error:
249     if( L )
250         lua_close( L );
251     free( p_sys->psz_filename );
252     free( p_sys );
253     return VLC_EGENERIC;
254 }
255 
256 /*****************************************************************************
257  * Close: cleanup
258  *****************************************************************************/
Close_LuaSD(vlc_object_t * p_this)259 void Close_LuaSD( vlc_object_t *p_this )
260 {
261     services_discovery_t *p_sd = ( services_discovery_t * )p_this;
262     services_discovery_sys_t *p_sys = p_sd->p_sys;
263 
264     vlc_cancel( p_sys->thread );
265     vlc_join( p_sys->thread, NULL );
266 
267     for( int i = 0; i < p_sys->i_query; i++ )
268         free( p_sys->ppsz_query[i] );
269     TAB_CLEAN( p_sys->i_query, p_sys->ppsz_query );
270 
271     vlc_cond_destroy( &p_sys->cond );
272     vlc_mutex_destroy( &p_sys->lock );
273     free( p_sys->psz_filename );
274     lua_close( p_sys->L );
275     free( p_sys );
276 }
277 
278 /*****************************************************************************
279  * Run: Thread entry-point
280  ****************************************************************************/
Run(void * data)281 static void* Run( void *data )
282 {
283     services_discovery_t *p_sd = ( services_discovery_t * )data;
284     services_discovery_sys_t *p_sys = p_sd->p_sys;
285     lua_State *L = p_sys->L;
286 
287     int cancel = vlc_savecancel();
288 
289     lua_getglobal( L, "main" );
290     if( !lua_isfunction( L, lua_gettop( L ) ) || lua_pcall( L, 0, 1, 0 ) )
291     {
292         msg_Err( p_sd, "Error while running script %s, "
293                   "function main(): %s", p_sys->psz_filename,
294                   lua_tostring( L, lua_gettop( L ) ) );
295         lua_pop( L, 1 );
296         vlc_restorecancel( cancel );
297         return NULL;
298     }
299     msg_Dbg( p_sd, "LuaSD script loaded: %s", p_sys->psz_filename );
300 
301     /* Force garbage collection, because the core will keep the SD
302      * open, but lua will never gc until lua_close(). */
303     lua_gc( L, LUA_GCCOLLECT, 0 );
304 
305     vlc_restorecancel( cancel );
306 
307     /* Main loop to handle search requests */
308     vlc_mutex_lock( &p_sys->lock );
309     mutex_cleanup_push( &p_sys->lock );
310     for( ;; )
311     {
312         /* Wait for a request */
313         if( !p_sys->i_query )
314         {
315             vlc_cond_wait( &p_sys->cond, &p_sys->lock );
316             continue;
317         }
318 
319         /* Execute one query (protected against cancellation) */
320         char *psz_query = p_sys->ppsz_query[p_sys->i_query - 1];
321         TAB_ERASE(p_sys->i_query, p_sys->ppsz_query, p_sys->i_query - 1);
322         vlc_mutex_unlock( &p_sys->lock );
323 
324         cancel = vlc_savecancel();
325         DoSearch( p_sd, psz_query );
326         free( psz_query );
327         /* Force garbage collection, because the core will keep the SD
328          * open, but lua will never gc until lua_close(). */
329         lua_gc( L, LUA_GCCOLLECT, 0 );
330         vlc_restorecancel( cancel );
331 
332         vlc_mutex_lock( &p_sys->lock );
333     }
334     vlc_cleanup_pop();
335     vlc_assert_unreachable();
336 }
337 
338 /*****************************************************************************
339  * Control: services discrovery control
340  ****************************************************************************/
Control(services_discovery_t * p_sd,int i_command,va_list args)341 static int Control( services_discovery_t *p_sd, int i_command, va_list args )
342 {
343     services_discovery_sys_t *p_sys = p_sd->p_sys;
344 
345     switch( i_command )
346     {
347     case SD_CMD_SEARCH:
348     {
349         const char *psz_query = va_arg( args, const char * );
350         vlc_mutex_lock( &p_sys->lock );
351         TAB_APPEND( p_sys->i_query, p_sys->ppsz_query, strdup( psz_query ) );
352         vlc_cond_signal( &p_sys->cond );
353         vlc_mutex_unlock( &p_sys->lock );
354         break;
355     }
356 
357     case SD_CMD_DESCRIPTOR:
358     {
359         services_discovery_descriptor_t *p_desc = va_arg( args,
360                                 services_discovery_descriptor_t * );
361         return FillDescriptor( p_sd, p_desc );
362     }
363     }
364 
365     return VLC_SUCCESS;
366 }
367 
368 /*****************************************************************************
369  * DoSearch: search for a given query
370  ****************************************************************************/
DoSearch(services_discovery_t * p_sd,const char * psz_query)371 static int DoSearch( services_discovery_t *p_sd, const char *psz_query )
372 {
373     services_discovery_sys_t *p_sys = p_sd->p_sys;
374     lua_State *L = p_sys->L;
375 
376     /* Lookup for the 'search' function */
377     lua_getglobal( L, "search" );
378     if( !lua_isfunction( L, lua_gettop( L ) ) )
379     {
380         msg_Err( p_sd, "The script '%s' does not define any 'search' function",
381                  p_sys->psz_filename );
382         lua_pop( L, 1 );
383         return VLC_EGENERIC;
384     }
385 
386     /* Push the query */
387     lua_pushstring( L, psz_query );
388 
389     /* Call the 'search' function */
390     if( lua_pcall( L, 1, 0, 0 ) )
391     {
392         msg_Err( p_sd, "Error while running the script '%s': %s",
393                  p_sys->psz_filename, lua_tostring( L, lua_gettop( L ) ) );
394         lua_pop( L, 1 );
395         return VLC_EGENERIC;
396     }
397 
398     return VLC_SUCCESS;
399 }
400 
401 /** List of capabilities */
402 static const char *const ppsz_capabilities[] = {
403     "search",
404     NULL
405 };
406 
407 /*****************************************************************************
408  * FillDescriptor: call the descriptor function and fill the structure
409  ****************************************************************************/
FillDescriptor(services_discovery_t * p_sd,services_discovery_descriptor_t * p_desc)410 static int FillDescriptor( services_discovery_t *p_sd,
411                            services_discovery_descriptor_t *p_desc )
412 {
413     services_discovery_sys_t *p_sys = p_sd->p_sys;
414     int i_ret = VLC_EGENERIC;
415 
416     /* Create a new lua thread */
417     lua_State *L = luaL_newstate();
418 
419     if( vlclua_dofile( VLC_OBJECT(p_sd), L, p_sys->psz_filename ) )
420     {
421         msg_Err( p_sd, "Error loading script %s: %s", p_sys->psz_filename,
422                  lua_tostring( L, -1 ) );
423         goto end;
424     }
425 
426     /* Call the "descriptor" function */
427     lua_getglobal( L, "descriptor" );
428     if( !lua_isfunction( L, -1 ) || lua_pcall( L, 0, 1, 0 ) )
429     {
430         msg_Warn( p_sd, "Error getting the descriptor in '%s': %s",
431                   p_sys->psz_filename, lua_tostring( L, -1 ) );
432         goto end;
433     }
434 
435     /* Get the different fields of the returned table */
436     lua_getfield( L, -1, "short_description" );
437     p_desc->psz_short_desc = luaL_strdupornull( L, -1 );
438     lua_pop( L, 1 );
439 
440     lua_getfield( L, -1, "icon" );
441     p_desc->psz_icon_url = luaL_strdupornull( L, -1 );
442     lua_pop( L, 1 );
443 
444     lua_getfield( L, -1, "url" );
445     p_desc->psz_url = luaL_strdupornull( L, -1 );
446     lua_pop( L, 1 );
447 
448     lua_getfield( L, -1, "capabilities" );
449     p_desc->i_capabilities = 0;
450     if( lua_istable( L, -1 ) )
451     {
452         /* List all table entries */
453         lua_pushnil( L );
454         while( lua_next( L, -2 ) != 0 )
455         {
456             /* Key is at index -2 and value at index -1 */
457             const char *psz_cap = luaL_checkstring( L, -1 );
458             int i_cap = 0;
459             const char *psz_iter;
460             for( psz_iter = *ppsz_capabilities; psz_iter;
461                  psz_iter = ppsz_capabilities[ ++i_cap ] )
462             {
463                 if( !strcmp( psz_iter, psz_cap ) )
464                 {
465                     p_desc->i_capabilities |= 1 << i_cap;
466                     break;
467                 }
468             }
469 
470             lua_pop( L, 1 );
471 
472             if( !psz_iter )
473                 msg_Warn( p_sd, "Services discovery capability '%s' unknown in "
474                                 "script '%s'", psz_cap, p_sys->psz_filename );
475         }
476     }
477 
478     lua_pop( L, 1 );
479     i_ret = VLC_SUCCESS;
480 
481 end:
482     lua_close( L );
483     return i_ret;
484 
485 }
486