vlm.c 89 KB
Newer Older
1
/*****************************************************************************
2
 * vlm.c: VLM interface plugin
3
 *****************************************************************************
Antoine Cellerier's avatar
Antoine Cellerier committed
4
 * Copyright (C) 2000-2005 the VideoLAN team
5
 * $Id$
6 7 8
 *
 * Authors: Simon Latapie <garf@videolan.org>
 *          Laurent Aimar <fenrir@videolan.org>
Gildas Bazin's avatar
Gildas Bazin committed
9
 *          Gildas Bazin <gbazin@videolan.org>
10 11 12 13 14 15 16 17 18 19 20 21 22
 *
 * 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
Antoine Cellerier's avatar
Antoine Cellerier committed
23
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24 25 26 27 28
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
29 30 31
#include <vlc/vlc.h>

#include <stdio.h>
32
#include <stdlib.h>                                      /* malloc(), free() */
33
#include <ctype.h>                                              /* tolower() */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
34
#include <assert.h>
35

36 37
#ifdef ENABLE_VLM

38

39 40
#ifdef HAVE_TIME_H
#   include <time.h>                                              /* ctime() */
41
#   include <sys/timeb.h>                                         /* ftime() */
42 43
#endif

Clément Stenac's avatar
Clément Stenac committed
44
#include <vlc_input.h>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
45
#include "input_internal.h"
Clément Stenac's avatar
Clément Stenac committed
46
#include <vlc_stream.h>
47
#include <vlc_vlm.h>
48
#include "vlm_internal.h"
49
#include <vlc_vod.h>
Clément Stenac's avatar
Clément Stenac committed
50
#include <vlc_charset.h>
51 52
#include <vlc_sout.h>
#include "../stream_output/stream_output.h"
53

54 55 56
/*****************************************************************************
 * Local prototypes.
 *****************************************************************************/
57 58
/* */
static int vlm_ControlInternal( vlm_t *, int, ... );
59

60 61 62
/* */
static vlm_message_t *vlm_Show( vlm_t *, vlm_media_sys_t *, vlm_schedule_sys_t *, const char * );
static vlm_message_t *vlm_Help( vlm_t *, char * );
63

64
static vlm_schedule_sys_t *vlm_ScheduleSearch( vlm_t *, const char * );
65

66 67
static char *Save( vlm_t * );
static int Load( vlm_t *, char * );
68

69
static int ExecuteCommand( vlm_t *, const char *, vlm_message_t ** );
70

71
static int Manage( vlc_object_t * );
72

73 74 75 76
static vlm_schedule_sys_t *vlm_ScheduleNew( vlm_t *vlm, const char *psz_name );
static void vlm_ScheduleDelete( vlm_t *vlm, vlm_schedule_sys_t *sched, const char *psz_name );
static int vlm_ScheduleSetup( vlm_schedule_sys_t *schedule, const char *psz_cmd,
                              const char *psz_value );
77 78 79

static int vlm_MediaVodControl( void *, vod_media_t *, const char *, int, va_list );

80 81
/* */
static vlm_media_sys_t *vlm_MediaSearch( vlm_t *, const char *);
82

83 84 85 86 87 88
/*****************************************************************************
 * vlm_New:
 *****************************************************************************/
vlm_t *__vlm_New ( vlc_object_t *p_this )
{
    vlc_value_t lockval;
Clément Stenac's avatar
Clément Stenac committed
89
    vlm_t *p_vlm = NULL;
90
    char *psz_vlmconf;
91

92 93 94 95 96
    /* Avoid multiple creation */
    if( var_Create( p_this->p_libvlc_global, "vlm_mutex", VLC_VAR_MUTEX ) ||
        var_Get( p_this->p_libvlc_global, "vlm_mutex", &lockval ) )
        return NULL;

97 98
    vlc_mutex_lock( lockval.p_address );

99 100
    p_vlm = vlc_object_find( p_this, VLC_OBJECT_VLM, FIND_ANYWHERE );
    if( p_vlm )
101
    {
102 103 104 105
        vlc_object_yield( p_vlm );
        vlc_mutex_unlock( lockval.p_address );
        return p_vlm;
    }
106

107
    msg_Dbg( p_this, "creating VLM" );
108

109 110 111 112 113
    p_vlm = vlc_object_create( p_this, VLC_OBJECT_VLM );
    if( !p_vlm )
    {
        vlc_mutex_unlock( lockval.p_address );
        return NULL;
114
    }
115 116

    vlc_mutex_init( p_this->p_libvlc, &p_vlm->lock );
117
    p_vlm->i_id = 1;
118 119 120
    TAB_INIT( p_vlm->i_media, p_vlm->media );
    TAB_INIT( p_vlm->i_schedule, p_vlm->schedule );
    p_vlm->i_vod = 0;
121
    p_vlm->p_vod = NULL;
122 123
    vlc_object_yield( p_vlm );
    vlc_object_attach( p_vlm, p_this->p_libvlc );
124

Clément Stenac's avatar
Clément Stenac committed
125
    if( vlc_thread_create( p_vlm, "vlm thread",
126 127
                           Manage, VLC_THREAD_PRIORITY_LOW, VLC_FALSE ) )
    {
Clément Stenac's avatar
Clément Stenac committed
128 129
        vlc_mutex_destroy( &p_vlm->lock );
        vlc_object_destroy( p_vlm );
130 131
        return NULL;
    }
132

133 134
    /* Load our configuration file */
    psz_vlmconf = var_CreateGetString( p_vlm, "vlm-conf" );
135 136 137 138 139
    if( psz_vlmconf && *psz_vlmconf )
    {
        vlm_message_t *p_message = NULL;
        char *psz_buffer = NULL;

140
        msg_Dbg( p_this, "loading VLM configuration" );
141 142 143
        asprintf(&psz_buffer, "load %s", psz_vlmconf );
        if( psz_buffer )
        {
144 145
            msg_Dbg( p_this, psz_buffer );
            if( vlm_ExecuteCommand( p_vlm, psz_buffer, &p_message ) )
146
                msg_Warn( p_this, "error while loading the configuration file" );
147

148
            vlm_MessageDelete(p_message);
149 150
            free(psz_buffer);
        }
151 152
    }
    free(psz_vlmconf);
153

154 155 156
    vlc_mutex_unlock( lockval.p_address );

    return p_vlm;
157 158 159 160 161
}

/*****************************************************************************
 * vlm_Delete:
 *****************************************************************************/
Clément Stenac's avatar
Clément Stenac committed
162
void vlm_Delete( vlm_t *p_vlm )
163 164 165
{
    vlc_value_t lockval;

166
    var_Get( p_vlm->p_libvlc_global, "vlm_mutex", &lockval );
167 168
    vlc_mutex_lock( lockval.p_address );

Clément Stenac's avatar
Clément Stenac committed
169
    vlc_object_release( p_vlm );
170

Clément Stenac's avatar
Clément Stenac committed
171
    if( p_vlm->i_refcount > 0 )
172 173 174 175 176
    {
        vlc_mutex_unlock( lockval.p_address );
        return;
    }

Clément Stenac's avatar
Clément Stenac committed
177 178
    p_vlm->b_die = VLC_TRUE;
    vlc_thread_join( p_vlm );
179

180 181
    vlc_object_detach( p_vlm );

182
    vlm_ControlInternal( p_vlm, VLM_CLEAR_MEDIAS );
183
    TAB_CLEAN( p_vlm->i_media, p_vlm->media );
184

185
    vlm_ControlInternal( p_vlm, VLM_CLEAR_SCHEDULES );
186
    TAB_CLEAN( p_vlm->schedule, p_vlm->schedule );
187

188 189
    vlc_mutex_destroy( &p_vlm->lock );

Clément Stenac's avatar
Clément Stenac committed
190
    vlc_object_destroy( p_vlm );
191 192 193 194 195 196
    vlc_mutex_unlock( lockval.p_address );
}

/*****************************************************************************
 * vlm_ExecuteCommand:
 *****************************************************************************/
197
int vlm_ExecuteCommand( vlm_t *p_vlm, const char *psz_command,
Clément Stenac's avatar
Clément Stenac committed
198
                        vlm_message_t **pp_message)
199
{
Clément Stenac's avatar
Clément Stenac committed
200
    int i_result;
201

Clément Stenac's avatar
Clément Stenac committed
202 203 204
    vlc_mutex_lock( &p_vlm->lock );
    i_result = ExecuteCommand( p_vlm, psz_command, pp_message );
    vlc_mutex_unlock( &p_vlm->lock );
205

Clément Stenac's avatar
Clément Stenac committed
206
    return i_result;
207 208
}

209 210

static const char quotes[] = "\"'";
211 212 213
/**
 * FindCommandEnd: look for the end of a possibly quoted string
 * @return NULL on mal-formatted string,
214
 * pointer past the last character otherwise.
215
 */
216
static const char *FindCommandEnd( const char *psz_sent )
217
{
218
    char c, quote = 0;
219

220
    while( (c = *psz_sent) != '\0' )
221
    {
222
        if( !quote )
Gildas Bazin's avatar
Gildas Bazin committed
223
        {
224
            if( strchr(quotes,c) )   // opening quote
225
                quote = c;
226
            else if( isspace(c) )         // non-escaped space
227
                return psz_sent;
228
            else if( c == '\\' )
229 230
            {
                psz_sent++;         // skip escaped character
231
                if( *psz_sent == '\0' )
232 233
                    return psz_sent;
            }
Gildas Bazin's avatar
Gildas Bazin committed
234
        }
235
        else
236
        {
237
            if( c == quote )         // non-escaped matching quote
238
                quote = 0;
239
            else if( (quote == '"') && (c == '\\') )
240 241 242 243 244 245
            {
                psz_sent++;         // skip escaped character
                if (*psz_sent == '\0')
                    return NULL;    // error, closing quote missing
            }
        }
Gildas Bazin's avatar
Gildas Bazin committed
246
        psz_sent++;
247
    }
248

249 250 251
    // error (NULL) if we could not find a matching quote
    return quote ? NULL : psz_sent;
}
252

Gildas Bazin's avatar
Gildas Bazin committed
253

254
/**
255
 * Unescape a nul-terminated string.
256 257 258 259 260 261 262
 * Note that in and out can be identical.
 *
 * @param out output buffer (at least <strlen (in) + 1> characters long)
 * @param in nul-terminated string to be unescaped
 *
 * @return 0 on success, -1 on error.
 */
263
static int Unescape( char *out, const char *in )
264
{
265
    char c, quote = 0;
266

267
    while( (c = *in++) != '\0' )
268
    {
269
        if( !quote )
Gildas Bazin's avatar
Gildas Bazin committed
270
        {
271
            if (strchr(quotes,c))   // opening quote
272
            {
273 274 275
                quote = c;
                continue;
            }
276
            else if( c == '\\' )
277 278 279 280 281 282 283 284 285 286 287 288 289
            {
                switch (c = *in++)
                {
                    case '"':
                    case '\'':
                    case '\\':
                        *out++ = c;
                        continue;

                    case '\0':
                        *out = '\0';
                        return 0;
                }
290
                if( isspace(c) )
291 292
                {
                    *out++ = c;
293
                    continue;
294 295 296 297 298 299 300
                }
                /* None of the special cases - copy the backslash */
                *out++ = '\\';
            }
        }
        else
        {
301
            if( c == quote )         // non-escaped matching quote
302 303 304 305
            {
                quote = 0;
                continue;
            }
306
            if( (quote == '"') && (c == '\\') )
307
            {
308
                switch( c = *in++ )
309 310 311 312 313 314 315 316 317 318 319 320
                {
                    case '"':
                    case '\\':
                        *out++ = c;
                        continue;

                    case '\0':   // should never happen
                        *out = '\0';
                        return -1;
                }
                /* None of the special cases - copy the backslash */
                *out++ = '\\';
321 322
            }
        }
323
        *out++ = c;
324
    }
325 326 327

    *out = '\0';
    return 0;
328 329
}

330

Gildas Bazin's avatar
Gildas Bazin committed
331 332 333 334 335
/*****************************************************************************
 * ExecuteCommand: The main state machine
 *****************************************************************************
 * Execute a command which ends with '\0' (string)
 *****************************************************************************/
336
static int ExecuteSyntaxError( const char *psz_cmd, vlm_message_t **pp_status )
337
{
338 339 340
    *pp_status = vlm_MessageNew( psz_cmd, "Wrong command syntax" );
    return VLC_EGENERIC;
}
341

342 343 344
static vlc_bool_t ExecuteIsMedia( vlm_t *p_vlm, const char *psz_name )
{
    int64_t id;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
345

346 347 348 349 350 351 352 353 354 355
    if( !psz_name || vlm_ControlInternal( p_vlm, VLM_GET_MEDIA_ID, psz_name, &id ) )
        return VLC_FALSE;
    return VLC_TRUE;
}
static vlc_bool_t ExecuteIsSchedule( vlm_t *p_vlm, const char *psz_name )
{
    if( !psz_name || !vlm_ScheduleSearch( p_vlm, psz_name ) )
        return VLC_FALSE;
    return VLC_TRUE;
}
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
356

357 358 359 360
static int ExecuteDel( vlm_t *p_vlm, const char *psz_name, vlm_message_t **pp_status )
{
    vlm_media_sys_t *p_media;
    vlm_schedule_sys_t *p_schedule;
361

362 363
    p_media = vlm_MediaSearch( p_vlm, psz_name );
    p_schedule = vlm_ScheduleSearch( p_vlm, psz_name );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
364

365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390
    if( p_schedule != NULL )
    {
        vlm_ScheduleDelete( p_vlm, p_schedule, NULL );
    }
    else if( p_media != NULL )
    {
        vlm_ControlInternal( p_vlm, VLM_DEL_MEDIA, p_media->cfg.id );
    }
    else if( !strcmp(psz_name, "media") )
    {
        vlm_ControlInternal( p_vlm, VLM_CLEAR_MEDIAS );
    }
    else if( !strcmp(psz_name, "schedule") )
    {
        vlm_ControlInternal( p_vlm, VLM_CLEAR_SCHEDULES );
    }
    else if( !strcmp(psz_name, "all") )
    {
        vlm_ControlInternal( p_vlm, VLM_CLEAR_MEDIAS );
        vlm_ControlInternal( p_vlm, VLM_CLEAR_SCHEDULES );
    }
    else
    {
        *pp_status = vlm_MessageNew( "del", "%s: media unknown", psz_name );
        return VLC_EGENERIC;
    }
391

392 393 394
    *pp_status = vlm_MessageNew( "del", NULL );
    return VLC_SUCCESS;
}
395

396 397 398 399
static int ExecuteShow( vlm_t *p_vlm, const char *psz_name, vlm_message_t **pp_status )
{
    vlm_media_sys_t *p_media;
    vlm_schedule_sys_t *p_schedule;
400

401 402 403 404 405
    if( !psz_name )
    {
        *pp_status = vlm_Show( p_vlm, NULL, NULL, NULL );
        return VLC_SUCCESS;
    }
Antoine Cellerier's avatar
Antoine Cellerier committed
406

407 408
    p_media = vlm_MediaSearch( p_vlm, psz_name );
    p_schedule = vlm_ScheduleSearch( p_vlm, psz_name );
409

410 411 412 413 414 415
    if( p_schedule != NULL )
        *pp_status = vlm_Show( p_vlm, NULL, p_schedule, NULL );
    else if( p_media != NULL )
        *pp_status = vlm_Show( p_vlm, p_media, NULL, NULL );
    else
        *pp_status = vlm_Show( p_vlm, NULL, NULL, psz_name );
416

417 418
    return VLC_SUCCESS;
}
419

420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435
static int ExecuteHelp( vlm_t *p_vlm, vlm_message_t **pp_status )
{
    *pp_status = vlm_Help( p_vlm, NULL );
    return VLC_SUCCESS;
}

static int ExecuteControl( vlm_t *p_vlm, const char *psz_name, const int i_arg, char ** ppsz_arg, vlm_message_t **pp_status )
{
    vlm_media_sys_t *p_media;
    const char *psz_control = NULL;
    const char *psz_instance = NULL;
    const char *psz_argument = NULL;
    int i_index;
    int i_result;

    if( !ExecuteIsMedia( p_vlm, psz_name ) )
436
    {
437 438
        *pp_status = vlm_MessageNew( "control", "%s: media unknown", psz_name );
        return VLC_EGENERIC;
439 440
    }

441 442 443 444 445
    assert( i_arg > 0 );

#define IS(txt) ( !strcmp( ppsz_arg[i_index], (txt) ) )
    i_index = 0;
    if( !IS("play") && !IS("stop") && !IS("pause") && !IS("seek") )
446
    {
447 448
        i_index = 1;
        psz_instance = ppsz_arg[0];
449

450 451 452 453 454
        if( i_index >= i_arg || ( !IS("play") && !IS("stop") && !IS("pause") && !IS("seek") ) )
            return ExecuteSyntaxError( "control", pp_status );
    }
#undef IS
    psz_control = ppsz_arg[i_index];
455

456 457
    if( i_index+1 < i_arg )
        psz_argument = ppsz_arg[i_index+1];
458

459 460
    p_media = vlm_MediaSearch( p_vlm, psz_name );
    assert( p_media );
461

462 463 464 465
    if( !strcmp( psz_control, "play" ) )
    {
        int i_input_index = 0;
        int i;
466

467 468
        if( ( psz_argument && sscanf(psz_argument, "%d", &i) == 1 ) && i > 0 && i-1 < p_media->cfg.i_input )
            i_input_index = i-1;
469

470 471
        if( p_media->cfg.b_vod )
            i_result = vlm_ControlInternal( p_vlm, VLM_START_MEDIA_VOD_INSTANCE, p_media->cfg.id, psz_instance, i_input_index, NULL );    // we should get here now
Gildas Bazin's avatar
Gildas Bazin committed
472
        else
473
            i_result = vlm_ControlInternal( p_vlm, VLM_START_MEDIA_BROADCAST_INSTANCE, p_media->cfg.id, psz_instance, i_input_index );
Gildas Bazin's avatar
Gildas Bazin committed
474
    }
475
    else if( !strcmp( psz_control, "seek" ) )
Gildas Bazin's avatar
Gildas Bazin committed
476
    {
477 478 479 480 481 482 483
        if( psz_argument )
        {
            vlc_bool_t b_relative;
            if( psz_argument[0] == '+' || psz_argument[0] == '-' )
                b_relative = VLC_TRUE;
            else
                b_relative = VLC_FALSE;
Gildas Bazin's avatar
Gildas Bazin committed
484

485 486 487 488
            if( strstr( psz_argument, "ms" ) || strstr( psz_argument, "s" ) )
            {
                /* Time (ms or s) */
                int64_t i_new_time;
Clément Stenac's avatar
Clément Stenac committed
489

490 491 492 493
                if( strstr( psz_argument, "ms" ) )
                    i_new_time =  1000 * (int64_t)atoi( psz_argument );
                else
                    i_new_time = 1000000 * (int64_t)atoi( psz_argument );
494

495 496 497 498 499 500 501 502 503 504 505 506 507 508
                if( b_relative )
                {
                    int64_t i_time = 0;
                    vlm_ControlInternal( p_vlm, VLM_GET_MEDIA_INSTANCE_TIME, p_media->cfg.id, psz_instance, &i_time );
                    i_new_time += i_time;
                }
                if( i_new_time < 0 )
                    i_new_time = 0;
                i_result = vlm_ControlInternal( p_vlm, VLM_SET_MEDIA_INSTANCE_TIME, p_media->cfg.id, psz_instance, i_new_time );
            }
            else
            {
                /* Percent */
                double d_new_position = i18n_atof( psz_argument ) / 100.0;
509

510 511 512
                if( b_relative )
                {
                    double d_position = 0.0;
513

514 515 516 517 518 519 520 521 522
                    vlm_ControlInternal( p_vlm, VLM_GET_MEDIA_INSTANCE_POSITION, p_media->cfg.id, psz_instance, &d_position );
                    d_new_position += d_position;
                }
                if( d_new_position < 0.0 )
                    d_new_position = 0.0;
                else if( d_new_position > 1.0 )
                    d_new_position = 1.0;
                i_result = vlm_ControlInternal( p_vlm, VLM_SET_MEDIA_INSTANCE_POSITION, p_media->cfg.id, psz_instance, d_new_position );
            }
Gildas Bazin's avatar
Gildas Bazin committed
523
        }
524
        else
Gildas Bazin's avatar
Gildas Bazin committed
525
        {
526
            i_result = VLC_EGENERIC;
Gildas Bazin's avatar
Gildas Bazin committed
527
        }
528 529 530 531
    }
    else if( !strcmp( psz_control, "rewind" ) )
    {
        if( psz_argument )
Gildas Bazin's avatar
Gildas Bazin committed
532
        {
533 534
            const double d_scale = i18n_atof( psz_argument );
            double d_position;
Gildas Bazin's avatar
Gildas Bazin committed
535

536 537 538 539 540
            vlm_ControlInternal( p_vlm, VLM_GET_MEDIA_INSTANCE_POSITION, p_media->cfg.id, psz_instance, &d_position );
            d_position -= (d_scale / 1000.0);
            if( d_position < 0.0 )
                d_position = 0.0;
            i_result = vlm_ControlInternal( p_vlm, VLM_SET_MEDIA_INSTANCE_POSITION, p_media->cfg.id, psz_instance, d_position );
541 542 543
        }
        else
        {
544
            i_result = VLC_EGENERIC;
545 546
        }
    }
547
    else if( !strcmp( psz_control, "forward" ) )
548
    {
549
        if( psz_argument )
550
        {
551 552
            const double d_scale = i18n_atof( psz_argument );
            double d_position;
553

554 555 556 557 558
            vlm_ControlInternal( p_vlm, VLM_GET_MEDIA_INSTANCE_POSITION, p_media->cfg.id, psz_instance, &d_position );
            d_position += (d_scale / 1000.0);
            if( d_position > 1.0 )
                d_position = 1.0;
            i_result = vlm_ControlInternal( p_vlm, VLM_SET_MEDIA_INSTANCE_POSITION, p_media->cfg.id, psz_instance, d_position );
559 560 561 562

        }
        else
        {
563
            i_result = VLC_EGENERIC;
564 565
        }
    }
566
    else if( !strcmp( psz_control, "stop" ) )
Gildas Bazin's avatar
Gildas Bazin committed
567
    {
568 569 570 571 572 573 574 575 576
        i_result = vlm_ControlInternal( p_vlm, VLM_STOP_MEDIA_INSTANCE, p_media->cfg.id, psz_instance );
    }
    else if( !strcmp( psz_control, "pause" ) )
    {
        i_result = vlm_ControlInternal( p_vlm, VLM_PAUSE_MEDIA_INSTANCE, p_media->cfg.id, psz_instance );
    }
    else
    {
        i_result = VLC_EGENERIC;
Gildas Bazin's avatar
Gildas Bazin committed
577
    }
578

579
    if( i_result )
Gildas Bazin's avatar
Gildas Bazin committed
580
    {
581 582 583 584 585 586
        *pp_status = vlm_MessageNew( "control", "unknown error" );
        return VLC_SUCCESS;
    }
    *pp_status = vlm_MessageNew( "control", NULL );
    return VLC_SUCCESS;
}
587

588 589 590
static int ExecuteExport( vlm_t *p_vlm, vlm_message_t **pp_status )
{
    char *psz_export = Save( p_vlm );
591

592 593 594 595
    *pp_status = vlm_MessageNew( "export", psz_export );
    free( psz_export );
    return VLC_SUCCESS;
}
596

597 598 599 600
static int ExecuteSave( vlm_t *p_vlm, const char *psz_file, vlm_message_t **pp_status )
{
    FILE *f = utf8_fopen( psz_file, "wt" );
    char *psz_save;
Gildas Bazin's avatar
Gildas Bazin committed
601

602 603
    if( !f )
        goto error;
604

605 606 607 608 609 610 611 612 613
    psz_save = Save( p_vlm );
    if( psz_save == NULL )
    {
        fclose( f );
        goto error;
    }
    fwrite( psz_save, strlen( psz_save ), 1, f );
    free( psz_save );
    fclose( f );
614

615 616 617 618 619 620 621
    *pp_status = vlm_MessageNew( "save", NULL );
    return VLC_SUCCESS;

error:
    *pp_status = vlm_MessageNew( "save", "Unable to save to file" );
    return VLC_EGENERIC;
}
622

623 624 625 626 627
static int ExecuteLoad( vlm_t *p_vlm, const char *psz_url, vlm_message_t **pp_status )
{
    stream_t *p_stream = stream_UrlNew( p_vlm, psz_url );
    int64_t i_size;
    char *psz_buffer;
Gildas Bazin's avatar
Gildas Bazin committed
628

629 630 631 632
    if( !p_stream )
    {
        *pp_status = vlm_MessageNew( "load", "Unable to load from file" );
        return VLC_EGENERIC;
633
    }
Gildas Bazin's avatar
Gildas Bazin committed
634

635 636
    /* FIXME needed ? */
    if( stream_Seek( p_stream, 0 ) != 0 )
637
    {
638
        stream_Delete( p_stream );
639

640 641
        *pp_status = vlm_MessageNew( "load", "Read file error" );
        return VLC_EGENERIC;
642
    }
Gildas Bazin's avatar
Gildas Bazin committed
643

644
    i_size = stream_Size( p_stream );
645

646 647 648 649
    psz_buffer = malloc( i_size + 1 );
    if( !psz_buffer )
    {
        stream_Delete( p_stream );
650

651 652
        *pp_status = vlm_MessageNew( "load", "Read file error" );
        return VLC_EGENERIC;
653 654
    }

655 656
    stream_Read( p_stream, psz_buffer, i_size );
    psz_buffer[i_size] = '\0';
657

658
    stream_Delete( p_stream );
Gildas Bazin's avatar
Gildas Bazin committed
659

660
    if( Load( p_vlm, psz_buffer ) )
Gildas Bazin's avatar
Gildas Bazin committed
661
    {
662 663 664 665
        free( psz_buffer );

        *pp_status = vlm_MessageNew( "load", "Error while loading file" );
        return VLC_EGENERIC;
Gildas Bazin's avatar
Gildas Bazin committed
666 667
    }

668
    free( psz_buffer );
Gildas Bazin's avatar
Gildas Bazin committed
669

670 671 672
    *pp_status = vlm_MessageNew( "load", NULL );
    return VLC_SUCCESS;
}
Gildas Bazin's avatar
Gildas Bazin committed
673

674 675 676 677 678
static int ExecuteScheduleProperty( vlm_t *p_vlm, vlm_schedule_sys_t *p_schedule, vlc_bool_t b_new,
                                    const int i_property, char *ppsz_property[], vlm_message_t **pp_status )
{
    const char *psz_cmd = b_new ? "new" : "setup";
    int i;
Gildas Bazin's avatar
Gildas Bazin committed
679

680 681 682 683
    for( i = 0; i < i_property; i++ )
    {
        if( !strcmp( ppsz_property[i], "enabled" ) ||
            !strcmp( ppsz_property[i], "disabled" ) )
Gildas Bazin's avatar
Gildas Bazin committed
684
        {
685
            vlm_ScheduleSetup( p_schedule, ppsz_property[i], NULL );
Gildas Bazin's avatar
Gildas Bazin committed
686
        }
687
        else if( !strcmp( ppsz_property[i], "append" ) )
Gildas Bazin's avatar
Gildas Bazin committed
688
        {
689 690 691 692
            char *psz_line;
            int j;
            /* Beware: everything behind append is considered as
             * command line */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
693

694 695
            if( ++i >= i_property )
                break;
696

697 698 699 700 701 702 703
            psz_line = strdup( ppsz_property[i] );
            for( j = i+1; j < i_property; j++ )
            {
                psz_line = realloc( psz_line, strlen(psz_line) + strlen(ppsz_property[j]) + 1 + 1 );
                strcat( psz_line, " " );
                strcat( psz_line, ppsz_property[j] );
            }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
704

705 706 707 708 709 710 711 712 713 714 715
            vlm_ScheduleSetup( p_schedule, "append", psz_line );
            break;
        }
        else
        {
            if( i + 1 >= i_property )
            {
                if( b_new )
                    vlm_ScheduleDelete( p_vlm, p_schedule, NULL );
                return ExecuteSyntaxError( psz_cmd, pp_status );
            }
Gildas Bazin's avatar
Gildas Bazin committed
716

717 718 719 720 721 722 723
            vlm_ScheduleSetup( p_schedule, ppsz_property[i], ppsz_property[i+1] );
            i++;
        }
    }
    *pp_status = vlm_MessageNew( psz_cmd, NULL );
    return VLC_SUCCESS;
}
Gildas Bazin's avatar
Gildas Bazin committed
724

725 726 727 728 729 730 731
static int ExecuteMediaProperty( vlm_t *p_vlm, int64_t id, vlc_bool_t b_new,
                                 const int i_property, char *ppsz_property[], vlm_message_t **pp_status )
{
    const char *psz_cmd = b_new ? "new" : "setup";
    vlm_media_t *p_cfg = NULL;
    int i_result;
    int i;
Gildas Bazin's avatar
Gildas Bazin committed
732

733 734 735
#define ERROR( txt ) do { *pp_status = vlm_MessageNew( psz_cmd, txt); goto error; } while(0)
    if( vlm_ControlInternal( p_vlm, VLM_GET_MEDIA, id, &p_cfg ) )
        ERROR( "unknown media" );
Gildas Bazin's avatar
Gildas Bazin committed
736

737 738 739 740 741 742 743 744 745 746 747
#define MISSING(cmd) do { if( !psz_value ) ERROR( "missing argument for " cmd ); } while(0)
    for( i = 0; i < i_property; i++ )
    {
        const char *psz_option = ppsz_property[i];
        const char *psz_value = i+1 < i_property ? ppsz_property[i+1] :  NULL;

        if( !strcmp( psz_option, "enabled" ) )
        {
            p_cfg->b_enabled = VLC_TRUE;
        }
        else if( !strcmp( psz_option, "disabled" ) )
748
        {
749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765
            p_cfg->b_enabled = VLC_FALSE;
        }
        else if( !strcmp( psz_option, "input" ) )
        {
            MISSING( "input" );
            TAB_APPEND( p_cfg->i_input, p_cfg->ppsz_input, strdup(psz_value) );
            i++;
        }
        else if( !strcmp( psz_option, "inputdel" ) && psz_value && !strcmp( psz_value, "all" ) )
        {
            while( p_cfg->i_input > 0 )
                TAB_REMOVE( p_cfg->i_input, p_cfg->ppsz_input, p_cfg->ppsz_input[0] );
            i++;
        }
        else if( !strcmp( psz_option, "inputdel" ) )
        {
            int j;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
766

767 768 769
            MISSING( "inputdel" );

            for( j = 0; j < p_cfg->i_input; j++ )
Gildas Bazin's avatar
Gildas Bazin committed
770
            {
771
                if( !strcmp( p_cfg->ppsz_input[j], psz_value ) )
Gildas Bazin's avatar
Gildas Bazin committed
772
                {
773 774
                    TAB_REMOVE( p_cfg->i_input, p_cfg->ppsz_input, p_cfg->ppsz_input[j] );
                    break;
Gildas Bazin's avatar
Gildas Bazin committed
775 776
                }
            }
777
            i++;
778
        }
779 780 781
        else if( !strcmp( psz_option, "inputdeln" ) )
        {
            int i_index;
Gildas Bazin's avatar
Gildas Bazin committed
782

783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833
            MISSING( "inputdeln" );
            
            i_index = atoi( psz_value );
            if( i_index > 0 && i_index <= p_cfg->i_input )
                TAB_REMOVE( p_cfg->i_input, p_cfg->ppsz_input, p_cfg->ppsz_input[i_index-1] );
            i++;
        }
        else if( !strcmp( psz_option, "output" ) )
        {
            MISSING( "output" );

            if( p_cfg->psz_output != NULL )
                free( p_cfg->psz_output );
            p_cfg->psz_output = *psz_value ? strdup( psz_value ) : NULL;
            i++;
        }
        else if( !strcmp( psz_option, "option" ) )
        {
            MISSING( "option" );

            TAB_APPEND( p_cfg->i_option, p_cfg->ppsz_option, strdup( psz_value ) );
            i++;
        }
        else if( !strcmp( psz_option, "loop" ) )
        {
            if( p_cfg->b_vod )
                ERROR( "invalid loop option for vod" );
            p_cfg->broadcast.b_loop = VLC_TRUE;
        }
        else if( !strcmp( psz_option, "unloop" ) )
        {
            if( p_cfg->b_vod )
                ERROR( "invalid unloop option for vod" );
            p_cfg->broadcast.b_loop = VLC_FALSE;
        }
        else if( !strcmp( psz_option, "mux" ) )
        {
            MISSING( "mux" );
            if( !p_cfg->b_vod )
                ERROR( "invalid mux option for broadcast" );

            if( p_cfg->vod.psz_mux )
                free( p_cfg->vod.psz_mux );
            p_cfg->vod.psz_mux = *psz_value ? strdup( psz_value ) : NULL;
            i++;
        }
        else
        {
            fprintf( stderr, "PROP: name=%s unknown\n", psz_option );
            ERROR( "Wrong command syntax" );
        }
834
    }
835 836
#undef MISSING
#undef ERROR
837

838 839 840
    /* */
    i_result = vlm_ControlInternal( p_vlm, VLM_CHANGE_MEDIA, p_cfg );
    vlm_media_Delete( p_cfg );
Gildas Bazin's avatar
Gildas Bazin committed
841

842 843
    *pp_status = vlm_MessageNew( psz_cmd, NULL );
    return i_result;
844

Gildas Bazin's avatar
Gildas Bazin committed
845
error:
846 847 848 849 850 851
    if( p_cfg )
    {
        if( b_new )
            vlm_ControlInternal( p_vlm, VLM_DEL_MEDIA, p_cfg->id );
        vlm_media_Delete( p_cfg );
    }
Gildas Bazin's avatar
Gildas Bazin committed
852
    return VLC_EGENERIC;
853
}
854
static int ExecuteNew( vlm_t *p_vlm, const char *psz_name, const char *psz_type, const int i_property, char *ppsz_property[], vlm_message_t **pp_status )
855
{
856 857
    /* Check name */
    if( !strcmp( psz_name, "all" ) || !strcmp( psz_name, "media" ) || !strcmp( psz_name, "schedule" ) )
858
    {
859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876
        *pp_status = vlm_MessageNew( "new", "\"all\", \"media\" and \"schedule\" are reserved names" );
        return VLC_EGENERIC;
    }
    if( ExecuteIsMedia( p_vlm, psz_name ) || ExecuteIsSchedule( p_vlm, psz_name ) )
    {
        *pp_status = vlm_MessageNew( "new", "%s: Name already in use", psz_name );
        return VLC_EGENERIC;
    }
    /* */
    if( !strcmp( psz_type, "schedule" ) )
    {
        vlm_schedule_sys_t *p_schedule = vlm_ScheduleNew( p_vlm, psz_name );
        if( !p_schedule )
        {
            *pp_status = vlm_MessageNew( "new", "could not create schedule" );
            return VLC_EGENERIC;
        }
        return ExecuteScheduleProperty( p_vlm, p_schedule, VLC_TRUE, i_property, ppsz_property, pp_status );
877
    }
878 879 880 881
    else if( !strcmp( psz_type, "vod" ) || !strcmp( psz_type, "broadcast" ) )
    {
        vlm_media_t cfg;
        int64_t id;
882

883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900
        vlm_media_Init( &cfg );
        cfg.psz_name = strdup( psz_name );
        cfg.b_vod = !strcmp( psz_type, "vod" );

        if( vlm_ControlInternal( p_vlm, VLM_ADD_MEDIA, &cfg, &id ) )
        {
            vlm_media_Clean( &cfg );
            *pp_status = vlm_MessageNew( "new", "could not create media" );
            return VLC_EGENERIC;
        }
        vlm_media_Clean( &cfg );
        return ExecuteMediaProperty( p_vlm, id, VLC_TRUE, i_property, ppsz_property, pp_status );
    }
    else
    {
        *pp_status = vlm_MessageNew( "new", "%s: Choose between vod, broadcast or schedule", psz_type );
        return VLC_EGENERIC;
    }
901 902
}

903
static int ExecuteSetup( vlm_t *p_vlm, const char *psz_name, const int i_property, char *ppsz_property[], vlm_message_t **pp_status )
904
{
905
    if( ExecuteIsSchedule( p_vlm, psz_name ) )
906
    {
907 908 909 910 911 912 913 914 915
        vlm_schedule_sys_t *p_schedule = vlm_ScheduleSearch( p_vlm, psz_name );
        return ExecuteScheduleProperty( p_vlm, p_schedule, VLC_FALSE, i_property, ppsz_property, pp_status );
    }
    else if( ExecuteIsMedia( p_vlm, psz_name ) )
    {
        int64_t id;
        if( vlm_ControlInternal( p_vlm, VLM_GET_MEDIA_ID, psz_name, &id ) )
            goto error;
        return ExecuteMediaProperty( p_vlm, id, VLC_FALSE, i_property, ppsz_property, pp_status );
916 917
    }

918 919 920
error:
    *pp_status = vlm_MessageNew( "setup", "%s unknown", psz_name );
    return VLC_EGENERIC;
921 922
}

923 924
static int ExecuteCommand( vlm_t *p_vlm, const char *psz_command,
                           vlm_message_t **pp_message )
925
{
926 927 928 929
    size_t i_command = 0;
    char buf[strlen (psz_command) + 1], *psz_buf = buf;
    char *ppsz_command[3+sizeof (buf) / 2];
    vlm_message_t *p_message = NULL;
930

931 932 933 934
    /* First, parse the line and cut it */
    while( *psz_command != '\0' )
    {
        const char *psz_temp;
935

936 937 938 939 940
        if(isspace (*psz_command))
        {
            psz_command++;
            continue;
        }
941

942 943 944 945 946 947
        /* support for comments */
        if( i_command == 0 && *psz_command == '#')
        {
            p_message = vlm_MessageNew( "", NULL );
            goto success;
        }
948

949
        psz_temp = FindCommandEnd( psz_command );
950

951 952 953 954 955
        if( psz_temp == NULL )
        {
            p_message = vlm_MessageNew( "Incomplete command", psz_command );
            goto error;
        }
956

957
        assert (i_command < (sizeof (ppsz_command) / sizeof (ppsz_command[0])));
958

959 960 961
        ppsz_command[i_command] = psz_buf;
        memcpy (psz_buf, psz_command, psz_temp - psz_command);
        psz_buf[psz_temp - psz_command] = '\0';
962

963 964 965 966 967 968 969 970 971 972 973 974 975 976 977
        Unescape (psz_buf, psz_buf);

        i_command++;
        psz_buf += psz_temp - psz_command + 1;
        psz_command = psz_temp;

        assert (buf + sizeof (buf) >= psz_buf);
    }

    /*
     * And then Interpret it
     */

#define IF_EXECUTE( name, check, cmd ) if( !strcmp(ppsz_command[0], name ) ) { if( (check) ) goto syntax_error;  if( (cmd) ) goto error; goto success; }
    if( i_command == 0 )