vlc.c 23.4 KB
Newer Older
1
/*****************************************************************************
2
 * vlc.c: Generic lua interface functions
3
 *****************************************************************************
4
 * Copyright (C) 2007-2008 the VideoLAN team
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
 * $Id$
 *
 * Authors: Antoine Cellerier <dionoea at videolan tod org>
 *          Pierre d'Herbemont <pdherbemont # videolan.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
28 29 30 31
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

32
#include <assert.h>
33
#include <sys/stat.h>
34

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
35 36
#define VLC_MODULE_LICENSE VLC_LICENSE_GPL_2_PLUS

François Cartegnie's avatar
François Cartegnie committed
37 38
#include "vlc.h"

39
#include <vlc_plugin.h>
40
#include <vlc_arrays.h>
41
#include <vlc_charset.h>
42
#include <vlc_fs.h>
43
#include <vlc_services_discovery.h>
44
#include <vlc_stream.h>
45 46 47 48 49 50 51

/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
#define INTF_TEXT N_("Lua interface")
#define INTF_LONGTEXT N_("Lua interface module to load")

52
#define CONFIG_TEXT N_("Lua interface configuration")
53
#define CONFIG_LONGTEXT N_("Lua interface configuration string. Format is: '[\"<interface module name>\"] = { <option> = <value>, ...}, ...'.")
54 55 56
#define PASS_TEXT N_( "Password" )
#define PASS_LONGTEXT N_( "A single password restricts access " \
    "to this interface." )
57 58 59 60
#define SRC_TEXT N_( "Source directory" )
#define SRC_LONGTEXT N_( "Source directory" )
#define INDEX_TEXT N_( "Directory index" )
#define INDEX_LONGTEXT N_( "Allow to build directory index" )
61

62 63 64 65 66 67 68 69 70
#define TELNETHOST_TEXT N_( "Host" )
#define TELNETHOST_LONGTEXT N_( "This is the host on which the " \
    "interface will listen. It defaults to all network interfaces (0.0.0.0)." \
    " If you want this interface to be available only on the local " \
    "machine, enter \"127.0.0.1\"." )
#define TELNETPORT_TEXT N_( "Port" )
#define TELNETPORT_LONGTEXT N_( "This is the TCP port on which this " \
    "interface will listen. It defaults to 4212." )
#define TELNETPWD_TEXT N_( "Password" )
71 72
#define TELNETPWD_LONGTEXT N_( "A single password restricts access " \
    "to this interface." )
73 74 75
#define RCHOST_TEXT N_("TCP command input")
#define RCHOST_LONGTEXT N_("Accept commands over a socket rather than stdin. " \
            "You can set the address and port the interface will bind to." )
76 77 78 79 80
#define CLIHOST_TEXT N_("CLI input")
#define CLIHOST_LONGTEXT N_( "Accept commands from this source. " \
    "The CLI defaults to stdin (\"*console\"), but can also bind to a " \
    "plain TCP socket (\"localhost:4212\") or use the telnet protocol " \
    "(\"telnet://0.0.0.0:4212\")" )
81

82 83
static int vlc_sd_probe_Open( vlc_object_t * );

84
vlc_module_begin ()
85 86
        set_shortname( N_("Lua") )
        set_description( N_("Lua interpreter") )
87
        set_category( CAT_INTERFACE )
88
        set_subcategory( SUBCAT_INTERFACE_MAIN )
89 90 91

        add_string( "lua-intf", "dummy", INTF_TEXT, INTF_LONGTEXT, false )
        add_string( "lua-config", "", CONFIG_TEXT, CONFIG_LONGTEXT, false )
92 93 94 95 96
        set_capability( "interface", 0 )
        set_callbacks( Open_LuaIntf, Close_LuaIntf )
        add_shortcut( "luaintf" )

    add_submodule ()
97
        set_section( N_("Lua HTTP"), 0 )
98
            add_password("http-password", NULL, PASS_TEXT, PASS_LONGTEXT)
99
            add_string ( "http-src",  NULL, SRC_TEXT,  SRC_LONGTEXT,  true )
100
            add_bool   ( "http-index", false, INDEX_TEXT, INDEX_LONGTEXT, true )
101 102
        set_capability( "interface", 0 )
        set_callbacks( Open_LuaHTTP, Close_LuaIntf )
103
        add_shortcut( "luahttp", "http" )
104
        set_description( N_("Lua HTTP") )
105 106

    add_submodule ()
107
        set_section( N_("Lua CLI"), 0 )
108
            add_string( "rc-host", NULL, RCHOST_TEXT, RCHOST_LONGTEXT, true )
109
            add_string( "cli-host", NULL, CLIHOST_TEXT, CLIHOST_LONGTEXT, true )
110
        set_capability( "interface", 25 )
111
        set_description( N_("Command-line interface") )
112
        set_callbacks( Open_LuaCLI, Close_LuaIntf )
113
#ifndef _WIN32
114 115 116
        add_shortcut( "luacli", "luarc", "cli", "rc" )
#else
        add_shortcut( "luacli", "luarc" )
117 118 119
#endif

    add_submodule ()
120
        set_section( N_("Lua Telnet"), 0 )
121
            add_string( "telnet-host", "localhost", TELNETHOST_TEXT,
122
                        TELNETHOST_LONGTEXT, true )
123
            add_integer( "telnet-port", TELNETPORT_DEFAULT, TELNETPORT_TEXT,
124
                         TELNETPORT_LONGTEXT, true )
Kaarlo Raiha's avatar
Kaarlo Raiha committed
125
                change_integer_range( 1, 65535 )
126 127
            add_password("telnet-password", NULL, TELNETPWD_TEXT,
                         TELNETPWD_LONGTEXT)
128 129
        set_capability( "interface", 0 )
        set_callbacks( Open_LuaTelnet, Close_LuaIntf )
130
        set_description( N_("Lua Telnet") )
131
        add_shortcut( "luatelnet", "telnet" )
132

133
    add_submodule ()
Pierre's avatar
Pierre committed
134
        set_shortname( N_( "Lua Meta Fetcher" ) )
135
        set_description( N_("Fetch meta data using lua scripts") )
Pierre's avatar
Pierre committed
136 137 138 139 140 141
        set_capability( "meta fetcher", 10 )
        set_callbacks( FetchMeta, NULL )

    add_submodule ()
        set_shortname( N_( "Lua Meta Reader" ) )
        set_description( N_("Read meta data using lua scripts") )
142 143 144
        set_capability( "meta reader", 10 )
        set_callbacks( ReadMeta, NULL )

145 146 147 148
    add_submodule ()
        add_shortcut( "luaplaylist" )
        set_shortname( N_("Lua Playlist") )
        set_description( N_("Lua Playlist Parser Interface") )
149
        set_capability( "stream_filter", 302 )
150
        set_callbacks( Import_LuaPlaylist, Close_LuaPlaylist )
151

152
    add_submodule ()
153 154 155 156
        set_shortname( N_( "Lua Art" ) )
        set_description( N_("Fetch artwork using lua scripts") )
        set_capability( "art finder", 10 )
        set_callbacks( FindArt, NULL )
157 158

    add_submodule ()
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
159
        set_shortname( N_("Lua Extension") )
160
        set_description( N_("Lua Extension") )
161 162 163
        add_shortcut( "luaextension" )
        set_capability( "extension", 1 )
        set_callbacks( Open_Extension, Close_Extension )
164 165 166 167 168

    add_submodule ()
        set_description( N_("Lua SD Module") )
        add_shortcut( "luasd" )
        set_capability( "services_discovery", 0 )
169
        add_string( "lua-sd", "", NULL, NULL, false )
170
            change_volatile()
171 172 173 174
        set_callbacks( Open_LuaSD, Close_LuaSD )

    VLC_SD_PROBE_SUBMODULE

175
vlc_module_end ()
176 177 178 179

/*****************************************************************************
 *
 *****************************************************************************/
180
static const char *ppsz_lua_exts[] = { ".luac", ".lua", ".vle", NULL };
181 182 183
static int file_select( const char *file )
{
    int i = strlen( file );
Antoine Cellerier's avatar
Antoine Cellerier committed
184 185 186 187
    int j;
    for( j = 0; ppsz_lua_exts[j]; j++ )
    {
        int l = strlen( ppsz_lua_exts[j] );
188
        if( i >= l && !strcmp( file+i-l, ppsz_lua_exts[j] ) )
Antoine Cellerier's avatar
Antoine Cellerier committed
189 190 191
            return 1;
    }
    return 0;
192 193 194 195 196 197 198
}

static int file_compare( const char **a, const char **b )
{
    return strcmp( *a, *b );
}

199 200
static char **vlclua_dir_list_append( char **restrict list, char *basedir,
                                      const char *luadirname )
201
{
202 203
    if (unlikely(basedir == NULL))
        return list;
204

205 206 207
    if (likely(asprintf(list, "%s"DIR_SEP"lua"DIR_SEP"%s",
                        basedir, luadirname) != -1))
        list++;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
208

209 210 211
    free(basedir);
    return list;
}
212

213 214 215 216 217 218 219 220 221
int vlclua_dir_list(const char *luadirname, char ***restrict listp)
{
    char **list = malloc(4 * sizeof(char *));
    if (unlikely(list == NULL))
        return VLC_EGENERIC;

    *listp = list;

    /* Lua scripts in user-specific data directory */
222
    list = vlclua_dir_list_append(list, config_GetUserDir(VLC_USERDATA_DIR),
223
                                  luadirname);
224

225
    char *libdir = config_GetSysPath(VLC_PKG_LIBEXEC_DIR, NULL);
226
    char *datadir = config_GetSysPath(VLC_PKG_DATA_DIR, NULL);
227
    bool both = libdir != NULL && datadir != NULL && strcmp(libdir, datadir);
228

229
    /* Tokenized Lua scripts in architecture-specific data directory */
230
    list = vlclua_dir_list_append(list, libdir, luadirname);
231

232
    /* Source Lua Scripts in architecture-independent data directory */
233
    if (both || libdir == NULL)
234
        list = vlclua_dir_list_append(list, datadir, luadirname);
235

236
    *list = NULL;
237 238 239
    return VLC_SUCCESS;
}

240 241
void vlclua_dir_list_free( char **ppsz_dir_list )
{
Rémi Duraffort's avatar
Rémi Duraffort committed
242
    for( char **ppsz_dir = ppsz_dir_list; *ppsz_dir; ppsz_dir++ )
243
        free( *ppsz_dir );
244
    free( ppsz_dir_list );
245 246
}

247 248 249 250 251 252
/*****************************************************************************
 * Will execute func on all scripts in luadirname, and stop if func returns
 * success.
 *****************************************************************************/
int vlclua_scripts_batch_execute( vlc_object_t *p_this,
                                  const char * luadirname,
253
                                  int (*func)(vlc_object_t *, const char *, const luabatch_context_t *),
254 255
                                  void * user_data)
{
256
    char **ppsz_dir_list = NULL;
Rémi Duraffort's avatar
Rémi Duraffort committed
257
    int i_ret;
258

259
    if( (i_ret = vlclua_dir_list( luadirname, &ppsz_dir_list )) != VLC_SUCCESS)
260 261
        return i_ret;

Rémi Duraffort's avatar
Rémi Duraffort committed
262
    i_ret = VLC_EGENERIC;
263
    for( char **ppsz_dir = ppsz_dir_list; *ppsz_dir; ppsz_dir++ )
264
    {
265
        char **ppsz_filelist;
266 267

        msg_Dbg( p_this, "Trying Lua scripts in %s", *ppsz_dir );
Rémi Duraffort's avatar
Rémi Duraffort committed
268 269
        int i_files = vlc_scandir( *ppsz_dir, &ppsz_filelist, file_select,
                                   file_compare );
270 271 272 273 274
        if( i_files < 0 )
            continue;

        char **ppsz_file = ppsz_filelist;
        char **ppsz_fileend = ppsz_filelist + i_files;
275

276
        while( ppsz_file < ppsz_fileend )
277
        {
278 279
            char *psz_filename;

280
            if( asprintf( &psz_filename,
281 282 283 284 285
                          "%s" DIR_SEP "%s", *ppsz_dir, *ppsz_file ) == -1 )
                psz_filename = NULL;
            free( *(ppsz_file++) );

            if( likely(psz_filename != NULL) )
286
            {
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
287
                msg_Dbg( p_this, "Trying Lua playlist script %s", psz_filename );
288
                i_ret = func( p_this, psz_filename, user_data );
289 290 291
                free( psz_filename );
                if( i_ret == VLC_SUCCESS )
                    break;
292
            }
293 294
        }

295 296
        while( ppsz_file < ppsz_fileend )
            free( *(ppsz_file++) );
297
        free( ppsz_filelist );
298 299 300

        if( i_ret == VLC_SUCCESS )
            break;
301
    }
302
    vlclua_dir_list_free( ppsz_dir_list );
303 304 305
    return i_ret;
}

306
char *vlclua_find_file( const char *psz_luadirname, const char *psz_name )
Antoine Cellerier's avatar
Antoine Cellerier committed
307
{
308
    char **ppsz_dir_list = NULL;
309
    vlclua_dir_list( psz_luadirname, &ppsz_dir_list );
Rémi Duraffort's avatar
Rémi Duraffort committed
310 311

    for( char **ppsz_dir = ppsz_dir_list; *ppsz_dir; ppsz_dir++ )
Antoine Cellerier's avatar
Antoine Cellerier committed
312 313 314 315 316 317 318 319 320 321 322 323 324
    {
        for( const char **ppsz_ext = ppsz_lua_exts; *ppsz_ext; ppsz_ext++ )
        {
            char *psz_filename;
            struct stat st;

            if( asprintf( &psz_filename, "%s"DIR_SEP"%s%s", *ppsz_dir,
                          psz_name, *ppsz_ext ) < 0 )
            {
                vlclua_dir_list_free( ppsz_dir_list );
                return NULL;
            }

325
            if( vlc_stat( psz_filename, &st ) == 0
Antoine Cellerier's avatar
Antoine Cellerier committed
326 327 328 329 330 331 332 333 334 335 336
                && S_ISREG( st.st_mode ) )
            {
                vlclua_dir_list_free( ppsz_dir_list );
                return psz_filename;
            }
            free( psz_filename );
        }
    }
    vlclua_dir_list_free( ppsz_dir_list );
    return NULL;
}
337 338 339 340 341

/*****************************************************************************
 * Meta data setters utility.
 * Playlist item table should be on top of the stack when these are called
 *****************************************************************************/
342 343 344
#undef vlclua_read_meta_data
void vlclua_read_meta_data( vlc_object_t *p_this, lua_State *L,
                            input_item_t *p_input )
345 346 347
{
#define TRY_META( a, b )                                        \
    lua_getfield( L, -1, a );                                   \
348 349
    if( lua_isstring( L, -1 ) &&                                \
        strcmp( lua_tostring( L, -1 ), "" ) )                   \
350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368
    {                                                           \
        char *psz_value = strdup( lua_tostring( L, -1 ) );      \
        EnsureUTF8( psz_value );                                \
        msg_Dbg( p_this, #b ": %s", psz_value );                \
        input_item_Set ## b ( p_input, psz_value );             \
        free( psz_value );                                      \
    }                                                           \
    lua_pop( L, 1 ); /* pop a */
    TRY_META( "title", Title );
    TRY_META( "artist", Artist );
    TRY_META( "genre", Genre );
    TRY_META( "copyright", Copyright );
    TRY_META( "album", Album );
    TRY_META( "tracknum", TrackNum );
    TRY_META( "description", Description );
    TRY_META( "rating", Rating );
    TRY_META( "date", Date );
    TRY_META( "setting", Setting );
    TRY_META( "url", URL );
369
    TRY_META( "language",  Language );
370
    TRY_META( "nowplaying", NowPlaying );
371 372 373 374 375 376 377 378 379
    TRY_META( "publisher",  Publisher );
    TRY_META( "encodedby",  EncodedBy );
    TRY_META( "arturl",     ArtURL );
    TRY_META( "trackid",    TrackID );
    TRY_META( "director",   Director );
    TRY_META( "season",     Season );
    TRY_META( "episode",    Episode );
    TRY_META( "show_name",  ShowName );
    TRY_META( "actors",     Actors );
380 381
}

382 383
#undef vlclua_read_custom_meta_data
void vlclua_read_custom_meta_data( vlc_object_t *p_this, lua_State *L,
384 385
                                     input_item_t *p_input )
{
386 387 388 389 390 391
    /* Lock the input item and create the meta table if needed */
    vlc_mutex_lock( &p_input->lock );

    if( !p_input->p_meta )
        p_input->p_meta = vlc_meta_New();

392 393 394 395 396 397 398 399 400 401
    /* ... item */
    lua_getfield( L, -1, "meta" );
    /* ... item meta */
    if( lua_istable( L, -1 ) )
    {
        lua_pushnil( L );
        /* ... item meta nil */
        while( lua_next( L, -2 ) )
        {
            /* ... item meta key value */
402
            if( !lua_isstring( L, -2 ) || !lua_isstring( L, -1 ) )
403
            {
404 405 406
                msg_Err( p_this, "'meta' keys and values must be strings");
                lua_pop( L, 1 ); /* pop "value" */
                continue;
407
            }
408 409 410 411 412 413
            const char *psz_key = lua_tostring( L, -2 );
            const char *psz_value = lua_tostring( L, -1 );

            vlc_meta_AddExtra( p_input->p_meta, psz_key, psz_value );

            lua_pop( L, 1 ); /* pop "value" */
414 415 416 417
        }
    }
    lua_pop( L, 1 ); /* pop "meta" */
    /* ... item -> back to original stack */
418 419

    vlc_mutex_unlock( &p_input->lock );
420 421 422 423 424 425 426 427
}

/*****************************************************************************
 * Playlist utilities
 ****************************************************************************/
/**
 * Playlist item table should be on top of the stack when this is called
 */
428 429
#undef vlclua_read_options
void vlclua_read_options( vlc_object_t *p_this, lua_State *L,
430 431 432 433 434 435 436 437 438 439 440 441
                            int *pi_options, char ***pppsz_options )
{
    lua_getfield( L, -1, "options" );
    if( lua_istable( L, -1 ) )
    {
        lua_pushnil( L );
        while( lua_next( L, -2 ) )
        {
            if( lua_isstring( L, -1 ) )
            {
                char *psz_option = strdup( lua_tostring( L, -1 ) );
                msg_Dbg( p_this, "Option: %s", psz_option );
442
                TAB_APPEND( *pi_options, *pppsz_options, psz_option );
443 444 445 446 447 448 449 450 451 452 453
            }
            else
            {
                msg_Warn( p_this, "Option should be a string" );
            }
            lua_pop( L, 1 ); /* pop option */
        }
    }
    lua_pop( L, 1 ); /* pop "options" */
}

454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533
input_item_t *vlclua_read_input_item(vlc_object_t *obj, lua_State *L)
{
    if (!lua_istable(L, -1))
    {
        msg_Warn(obj, "Playlist item should be a table" );
        return NULL;
    }

    lua_getfield(L, -1, "path");

    /* playlist key item path */
    if (!lua_isstring(L, -1))
    {
        lua_pop(L, 1); /* pop "path" */
        msg_Warn(obj, "Playlist item's path should be a string");
        return NULL;
    }

    /* Read path and name */
    const char *path = lua_tostring(L, -1);
    msg_Dbg(obj, "Path: %s", path);

    const char *name = NULL;
    lua_getfield(L, -2, "name");
    if (lua_isstring(L, -1))
    {
        name = lua_tostring(L, -1);
        msg_Dbg(obj, "Name: %s", name);
    }
    else if (!lua_isnil(L, -1))
        msg_Warn(obj, "Playlist item name should be a string" );

    /* Read duration */
    mtime_t duration = -1;

    lua_getfield( L, -3, "duration" );
    if (lua_isnumber(L, -1))
        duration = (mtime_t)(lua_tonumber(L, -1) * (CLOCK_FREQ * 1.));
    else if (!lua_isnil(L, -1))
        msg_Warn(obj, "Playlist item duration should be a number (seconds)");
    lua_pop( L, 1 ); /* pop "duration" */

    /* Read options: item must be on top of stack */
    char **optv = NULL;
    int optc = 0;

    lua_pushvalue(L, -3);
    vlclua_read_options(obj, L, &optc, &optv);

    /* Create input item */
    input_item_t *item = input_item_NewExt(path, name, duration,
                                           ITEM_TYPE_UNKNOWN,
                                           ITEM_NET_UNKNOWN);
    if (unlikely(item == NULL))
        goto out;

    input_item_AddOptions(item, optc, (const char **)optv,
                          VLC_INPUT_OPTION_TRUSTED);
    lua_pop(L, 3); /* pop "path name item" */
    /* playlist key item */

    /* Read meta data: item must be on top of stack */
    vlclua_read_meta_data(obj, L, item);

    /* copy the psz_name to the meta data, if "Title" is still empty */
    char* title = input_item_GetTitle(item);
    if (title == NULL)
        input_item_SetTitle(item, name);
    free(title);

    /* Read custom meta data: item must be on top of stack*/
    vlclua_read_custom_meta_data(obj, L, item);

out:
    while (optc > 0)
           free(optv[--optc]);
    free(optv);
    return item;
}

534 535
static int vlc_sd_probe_Open( vlc_object_t *obj )
{
536 537 538
    if( lua_Disabled( obj ) )
        return VLC_EGENERIC;

539 540
    vlc_dictionary_t name_d;

541
    char **ppsz_dir_list;
542 543 544 545
    if( vlclua_dir_list( "sd", &ppsz_dir_list ) )
        return VLC_ENOMEM;

    vlc_dictionary_init( &name_d, 32 );
546
    for( char **ppsz_dir = ppsz_dir_list; *ppsz_dir; ppsz_dir++ )
547
    {
548 549 550
        char **ppsz_filelist;
        int i_files = vlc_scandir( *ppsz_dir, &ppsz_filelist, file_select,
                                    file_compare );
551
        if( i_files < 1 ) continue;
552

553 554 555
        for( char **ppsz_file = ppsz_filelist;
             ppsz_file < ppsz_filelist + i_files; ppsz_file++ )
        {
556 557 558
            char *temp = strchr( *ppsz_file, '.' );
            if( temp )
                *temp = '\0';
559

560
            if( !vlc_dictionary_has_key( &name_d, *ppsz_file ) )
561
                vlc_dictionary_insert( &name_d, *ppsz_file, &name_d );
562
            free( *ppsz_file );
563
        }
564 565 566
        free( ppsz_filelist );
    }
    vlclua_dir_list_free( ppsz_dir_list );
567 568 569 570 571

    int r = VLC_PROBE_CONTINUE;
    char **names = vlc_dictionary_all_keys( &name_d );
    if( names != NULL )
    {
572
        for( char **name = names; *name; ++name )
573 574 575 576 577
        {
            r = vlclua_probe_sd( obj, *name );
            if( r != VLC_PROBE_CONTINUE )
                break;
        }
578 579 580 581

        for( char **name = names; *name; ++name )
            free( *name );

582 583 584 585
        free( names );
    }
    vlc_dictionary_clear( &name_d, NULL, NULL );

586
    return r;
587
}
588 589 590

static int vlclua_add_modules_path_inner( lua_State *L, const char *psz_path )
{
591
    int count = 0;
592
    for( const char **ppsz_ext = ppsz_lua_exts; *ppsz_ext; ppsz_ext++ )
593
    {
594 595 596
        lua_pushfstring( L, "%s"DIR_SEP"modules"DIR_SEP"?%s;",
                         psz_path, *ppsz_ext );
        count ++;
597 598
    }

599
    return count;
600 601
}

602
int vlclua_add_modules_path( lua_State *L, const char *psz_filename )
603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629
{
    /* Setup the module search path:
     *   * "The script's directory"/modules
     *   * "The script's parent directory"/modules
     *   * and so on for all the next directories in the directory list
     */
    char *psz_path = strdup( psz_filename );
    if( !psz_path )
        return 1;

    char *psz_char = strrchr( psz_path, DIR_SEP_CHAR );
    if( !psz_char )
    {
        free( psz_path );
        return 1;
    }
    *psz_char = '\0';

    /* psz_path now holds the file's directory */
    psz_char = strrchr( psz_path, DIR_SEP_CHAR );
    if( !psz_char )
    {
        free( psz_path );
        return 1;
    }
    *psz_char = '\0';

630 631 632 633
    /* Push package on stack */
    int count = 0;
    lua_getglobal( L, "package" );

634
    /* psz_path now holds the file's parent directory */
635
    count += vlclua_add_modules_path_inner( L, psz_path );
636 637 638
    *psz_char = DIR_SEP_CHAR;

    /* psz_path now holds the file's directory */
639
    count += vlclua_add_modules_path_inner( L, psz_path );
640

641
    char **ppsz_dir_list = NULL;
642
    vlclua_dir_list( psz_char+1/* gruik? */, &ppsz_dir_list );
643 644 645 646 647 648 649 650
    char **ppsz_dir = ppsz_dir_list;

    for( ; *ppsz_dir && strcmp( *ppsz_dir, psz_path ); ppsz_dir++ );
    free( psz_path );

    for( ; *ppsz_dir; ppsz_dir++ )
    {
        psz_path = *ppsz_dir;
651 652
        /* FIXME: doesn't work well with meta/... modules due to the double
         * directory depth */
653 654
        psz_char = strrchr( psz_path, DIR_SEP_CHAR );
        if( !psz_char )
655 656 657 658
        {
            vlclua_dir_list_free( ppsz_dir_list );
            return 1;
        }
659 660

        *psz_char = '\0';
661
        count += vlclua_add_modules_path_inner( L, psz_path );
662
        *psz_char = DIR_SEP_CHAR;
663
        count += vlclua_add_modules_path_inner( L, psz_path );
664 665
    }

666 667 668 669
    lua_getfield( L, -(count+1), "path" ); /* Get package.path */
    lua_concat( L, count+1 ); /* Concat vlc module paths and package.path */
    lua_setfield( L, -2, "path"); /* Set package.path */
    lua_pop( L, 1 ); /* Pop the package module */
670 671

    vlclua_dir_list_free( ppsz_dir_list );
672
    return 0;
673 674
}

675
/** Replacement for luaL_dofile, using VLC's input capabilities */
676
int vlclua_dofile( vlc_object_t *p_this, lua_State *L, const char *curi )
677
{
678 679 680 681 682 683 684 685 686 687 688
    char *uri = ToLocaleDup( curi );
    if( !strstr( uri, "://" ) ) {
        int ret = luaL_dofile( L, uri );
        free( uri );
        return ret;
    }
    if( !strncasecmp( uri, "file://", 7 ) ) {
        int ret = luaL_dofile( L, uri + 7 );
        free( uri );
        return ret;
    }
689
    stream_t *s = vlc_stream_NewURL( p_this, uri );
690
    if( !s )
691
    {
692
        free( uri );
693
        return 1;
694
    }
695 696 697 698 699
    int64_t i_size = stream_Size( s );
    char *p_buffer = ( i_size > 0 ) ? malloc( i_size ) : NULL;
    if( !p_buffer )
    {
        // FIXME: read the whole stream until we reach the end (if no size)
700
        vlc_stream_Delete( s );
701
        free( uri );
702 703
        return 1;
    }
704
    int64_t i_read = vlc_stream_Read( s, p_buffer, (int) i_size );
705 706 707 708 709
    int i_ret = ( i_read == i_size ) ? 0 : 1;
    if( !i_ret )
        i_ret = luaL_loadbuffer( L, p_buffer, (size_t) i_size, uri );
    if( !i_ret )
        i_ret = lua_pcall( L, 0, LUA_MULTRET, 0 );
710
    vlc_stream_Delete( s );
711
    free( p_buffer );
712
    free( uri );
713 714
    return i_ret;
}