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 32
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

33 34 35
#include <vlc/vlc.h>

#include <stdio.h>
36
#include <ctype.h>                                              /* tolower() */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
37
#include <assert.h>
38

39 40
#include <vlc_vlm.h>

41 42
#ifdef ENABLE_VLM

Rafaël Carré's avatar
Rafaël Carré committed
43 44 45 46
#ifndef WIN32
#   include <sys/time.h>                                   /* gettimeofday() */
#endif

47 48
#ifdef HAVE_TIME_H
#   include <time.h>                                              /* ctime() */
49
#   include <sys/timeb.h>                                         /* ftime() */
50 51
#endif

Clément Stenac's avatar
Clément Stenac committed
52
#include <vlc_input.h>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
53
#include "input_internal.h"
Clément Stenac's avatar
Clément Stenac committed
54
#include <vlc_stream.h>
55
#include "vlm_internal.h"
56
#include <vlc_vod.h>
Clément Stenac's avatar
Clément Stenac committed
57
#include <vlc_charset.h>
58 59
#include <vlc_sout.h>
#include "../stream_output/stream_output.h"
60

61 62 63
/*****************************************************************************
 * Local prototypes.
 *****************************************************************************/
64 65 66 67 68

/* ugly kludge to avoid "null format string" warnings,
 * even if we handle NULL format string in vlm_MessageNew() */
static const char *vlm_NULL = NULL;

69 70
static void vlm_Destructor( vlm_t *p_vlm );

71 72
/* */
static int vlm_ControlInternal( vlm_t *, int, ... );
73

74 75
/* */
static vlm_message_t *vlm_Show( vlm_t *, vlm_media_sys_t *, vlm_schedule_sys_t *, const char * );
76

77
static vlm_schedule_sys_t *vlm_ScheduleSearch( vlm_t *, const char * );
78

79 80
static char *Save( vlm_t * );
static int Load( vlm_t *, char * );
81

82
static int ExecuteCommand( vlm_t *, const char *, vlm_message_t ** );
83

84
static int Manage( vlc_object_t * );
85

86
static vlm_schedule_sys_t *vlm_ScheduleNew( vlm_t *vlm, const char *psz_name );
87
static void vlm_ScheduleDelete( vlm_t *vlm, vlm_schedule_sys_t *sched );
88 89
static int vlm_ScheduleSetup( vlm_schedule_sys_t *schedule, const char *psz_cmd,
                              const char *psz_value );
90 91 92

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

93 94
/* */
static vlm_media_sys_t *vlm_MediaSearch( vlm_t *, const char *);
95

96 97 98 99 100 101
/*****************************************************************************
 * vlm_New:
 *****************************************************************************/
vlm_t *__vlm_New ( vlc_object_t *p_this )
{
    vlc_value_t lockval;
Clément Stenac's avatar
Clément Stenac committed
102
    vlm_t *p_vlm = NULL;
103
    char *psz_vlmconf;
104

105
    /* Avoid multiple creation */
106 107
    if( var_Create( p_this->p_libvlc, "vlm_mutex", VLC_VAR_MUTEX ) ||
        var_Get( p_this->p_libvlc, "vlm_mutex", &lockval ) )
108 109
        return NULL;

110 111
    vlc_mutex_lock( lockval.p_address );

112 113
    p_vlm = vlc_object_find( p_this, VLC_OBJECT_VLM, FIND_ANYWHERE );
    if( p_vlm )
114
    {
115 116 117 118
        vlc_object_yield( p_vlm );
        vlc_mutex_unlock( lockval.p_address );
        return p_vlm;
    }
119

120
    msg_Dbg( p_this, "creating VLM" );
121

122 123 124 125 126
    p_vlm = vlc_object_create( p_this, VLC_OBJECT_VLM );
    if( !p_vlm )
    {
        vlc_mutex_unlock( lockval.p_address );
        return NULL;
127
    }
128 129

    vlc_mutex_init( p_this->p_libvlc, &p_vlm->lock );
130
    p_vlm->i_id = 1;
131 132 133
    TAB_INIT( p_vlm->i_media, p_vlm->media );
    TAB_INIT( p_vlm->i_schedule, p_vlm->schedule );
    p_vlm->i_vod = 0;
134
    p_vlm->p_vod = NULL;
135
    vlc_object_attach( p_vlm, p_this->p_libvlc );
136

Clément Stenac's avatar
Clément Stenac committed
137
    if( vlc_thread_create( p_vlm, "vlm thread",
138 139
                           Manage, VLC_THREAD_PRIORITY_LOW, VLC_FALSE ) )
    {
Clément Stenac's avatar
Clément Stenac committed
140
        vlc_mutex_destroy( &p_vlm->lock );
141
        vlc_object_release( p_vlm );
142 143
        return NULL;
    }
144

145 146
    /* Load our configuration file */
    psz_vlmconf = var_CreateGetString( p_vlm, "vlm-conf" );
147 148 149 150 151
    if( psz_vlmconf && *psz_vlmconf )
    {
        vlm_message_t *p_message = NULL;
        char *psz_buffer = NULL;

152
        msg_Dbg( p_this, "loading VLM configuration" );
153 154 155
        asprintf(&psz_buffer, "load %s", psz_vlmconf );
        if( psz_buffer )
        {
156 157
            msg_Dbg( p_this, psz_buffer );
            if( vlm_ExecuteCommand( p_vlm, psz_buffer, &p_message ) )
158
                msg_Warn( p_this, "error while loading the configuration file" );
159

160
            vlm_MessageDelete(p_message);
161 162
            free(psz_buffer);
        }
163 164
    }
    free(psz_vlmconf);
165

166
    vlc_object_set_destructor( p_vlm, (vlc_destructor_t)vlm_Destructor );
167 168 169
    vlc_mutex_unlock( lockval.p_address );

    return p_vlm;
170 171 172 173 174
}

/*****************************************************************************
 * vlm_Delete:
 *****************************************************************************/
Clément Stenac's avatar
Clément Stenac committed
175
void vlm_Delete( vlm_t *p_vlm )
176
{
Clément Stenac's avatar
Clément Stenac committed
177
    vlc_object_release( p_vlm );
178
}
179

180 181 182 183 184
/*****************************************************************************
 * vlm_Destructor:
 *****************************************************************************/
static void vlm_Destructor( vlm_t *p_vlm )
{
185
    vlm_ControlInternal( p_vlm, VLM_CLEAR_MEDIAS );
186
    TAB_CLEAN( p_vlm->i_media, p_vlm->media );
187

188
    vlm_ControlInternal( p_vlm, VLM_CLEAR_SCHEDULES );
189
    TAB_CLEAN( p_vlm->schedule, p_vlm->schedule );
190

191
    vlc_mutex_destroy( &p_vlm->lock );
192 193 194 195 196
}

/*****************************************************************************
 * 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
    if( p_schedule != NULL )
    {
367
        vlm_ScheduleDelete( p_vlm, p_schedule );
368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390
    }
    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
    *pp_status = vlm_MessageNew( "del", vlm_NULL );
393 394
    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
static int ExecuteHelp( vlm_message_t *p_status )
421
{
422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465
    vlm_message_t *message_child;

#define MessageAdd( a ) \
        vlm_MessageAdd( p_status, vlm_MessageNew( a, vlm_NULL ) );
#define MessageAddChild( a ) \
        vlm_MessageAdd( message_child, vlm_MessageNew( a, vlm_NULL ) );

    p_status = vlm_MessageNew( "help", vlm_NULL );

    message_child = MessageAdd( "Commands Syntax:" );
    MessageAddChild( "new (name) vod|broadcast|schedule [properties]" );
    MessageAddChild( "setup (name) (properties)" );
    MessageAddChild( "show [(name)|media|schedule]" );
    MessageAddChild( "del (name)|all|media|schedule" );
    MessageAddChild( "control (name) [instance_name] (command)" );
    MessageAddChild( "save (config_file)" );
    MessageAddChild( "export" );
    MessageAddChild( "load (config_file)" );

    message_child = MessageAdd( "Media Proprieties Syntax:" );
    MessageAddChild( "input (input_name)" );
    MessageAddChild( "inputdel (input_name)|all" );
    MessageAddChild( "inputdeln input_number" );
    MessageAddChild( "output (output_name)" );
    MessageAddChild( "option (option_name)[=value]" );
    MessageAddChild( "enabled|disabled" );
    MessageAddChild( "loop|unloop (broadcast only)" );
    MessageAddChild( "mux (mux_name)" );

    message_child = MessageAdd( "Schedule Proprieties Syntax:" );
    MessageAddChild( "enabled|disabled" );
    MessageAddChild( "append (command_until_rest_of_the_line)" );
    MessageAddChild( "date (year)/(month)/(day)-(hour):(minutes):"
                     "(seconds)|now" );
    MessageAddChild( "period (years_aka_12_months)/(months_aka_30_days)/"
                     "(days)-(hours):(minutes):(seconds)" );
    MessageAddChild( "repeat (number_of_repetitions)" );

    message_child = MessageAdd( "Control Commands Syntax:" );
    MessageAddChild( "play [input_number]" );
    MessageAddChild( "pause" );
    MessageAddChild( "stop" );
    MessageAddChild( "seek [+-](percentage) | [+-](seconds)s | [+-](miliseconds)ms" );

466 467 468 469 470 471 472 473 474 475 476 477 478
    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 ) )
479
    {
480 481
        *pp_status = vlm_MessageNew( "control", "%s: media unknown", psz_name );
        return VLC_EGENERIC;
482 483
    }

484 485 486 487 488
    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") )
489
    {
490 491
        i_index = 1;
        psz_instance = ppsz_arg[0];
492

493 494 495 496 497
        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];
498

499 500
    if( i_index+1 < i_arg )
        psz_argument = ppsz_arg[i_index+1];
501

502 503
    p_media = vlm_MediaSearch( p_vlm, psz_name );
    assert( p_media );
504

505 506 507 508
    if( !strcmp( psz_control, "play" ) )
    {
        int i_input_index = 0;
        int i;
509

510
        if( ( psz_argument && sscanf(psz_argument, "%d", &i) == 1 ) && i > 0 && i-1 < p_media->cfg.i_input )
511
        {
512
            i_input_index = i-1;
513 514 515 516 517 518 519 520 521 522 523 524 525 526
        }
        else if( psz_argument )
        {
            int j;
            vlm_media_t *p_cfg = &p_media->cfg;
            for ( j=0; j < p_cfg->i_input; j++)
            {
                if( !strcmp( p_cfg->ppsz_input[j], psz_argument ) )
                {
                    i_input_index = j;
                    break;
                }
            }
        }
527

528 529
        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
530
        else
531
            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
532
    }
533
    else if( !strcmp( psz_control, "seek" ) )
Gildas Bazin's avatar
Gildas Bazin committed
534
    {
535 536 537 538 539 540 541
        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
542

543 544 545 546
            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
547

548 549 550 551
                if( strstr( psz_argument, "ms" ) )
                    i_new_time =  1000 * (int64_t)atoi( psz_argument );
                else
                    i_new_time = 1000000 * (int64_t)atoi( psz_argument );
552

553 554 555 556 557 558 559 560 561 562 563 564 565 566
                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;
567

568 569 570
                if( b_relative )
                {
                    double d_position = 0.0;
571

572 573 574 575 576 577 578 579 580
                    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
581
        }
582
        else
Gildas Bazin's avatar
Gildas Bazin committed
583
        {
584
            i_result = VLC_EGENERIC;
Gildas Bazin's avatar
Gildas Bazin committed
585
        }
586 587 588 589
    }
    else if( !strcmp( psz_control, "rewind" ) )
    {
        if( psz_argument )
Gildas Bazin's avatar
Gildas Bazin committed
590
        {
591 592
            const double d_scale = i18n_atof( psz_argument );
            double d_position;
Gildas Bazin's avatar
Gildas Bazin committed
593

594 595 596 597 598
            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 );
599 600 601
        }
        else
        {
602
            i_result = VLC_EGENERIC;
603 604
        }
    }
605
    else if( !strcmp( psz_control, "forward" ) )
606
    {
607
        if( psz_argument )
608
        {
609 610
            const double d_scale = i18n_atof( psz_argument );
            double d_position;
611

612 613 614 615 616
            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 );
617 618 619 620

        }
        else
        {
621
            i_result = VLC_EGENERIC;
622 623
        }
    }
624
    else if( !strcmp( psz_control, "stop" ) )
Gildas Bazin's avatar
Gildas Bazin committed
625
    {
626 627 628 629 630 631 632 633 634
        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
635
    }
636

637
    if( i_result )
Gildas Bazin's avatar
Gildas Bazin committed
638
    {
639 640 641
        *pp_status = vlm_MessageNew( "control", "unknown error" );
        return VLC_SUCCESS;
    }
642
    *pp_status = vlm_MessageNew( "control", vlm_NULL );
643 644
    return VLC_SUCCESS;
}
645

646 647 648
static int ExecuteExport( vlm_t *p_vlm, vlm_message_t **pp_status )
{
    char *psz_export = Save( p_vlm );
649

650 651 652 653
    *pp_status = vlm_MessageNew( "export", psz_export );
    free( psz_export );
    return VLC_SUCCESS;
}
654

655 656 657 658
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
659

660 661
    if( !f )
        goto error;
662

663 664 665 666 667 668 669 670 671
    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 );
672

673
    *pp_status = vlm_MessageNew( "save", vlm_NULL );
674 675 676 677 678 679
    return VLC_SUCCESS;

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

681 682 683 684 685
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
686

687 688 689 690
    if( !p_stream )
    {
        *pp_status = vlm_MessageNew( "load", "Unable to load from file" );
        return VLC_EGENERIC;
691
    }
Gildas Bazin's avatar
Gildas Bazin committed
692

693 694
    /* FIXME needed ? */
    if( stream_Seek( p_stream, 0 ) != 0 )
695
    {
696
        stream_Delete( p_stream );
697

698 699
        *pp_status = vlm_MessageNew( "load", "Read file error" );
        return VLC_EGENERIC;
700
    }
Gildas Bazin's avatar
Gildas Bazin committed
701

702
    i_size = stream_Size( p_stream );
703

704 705 706 707
    psz_buffer = malloc( i_size + 1 );
    if( !psz_buffer )
    {
        stream_Delete( p_stream );
708

709 710
        *pp_status = vlm_MessageNew( "load", "Read file error" );
        return VLC_EGENERIC;
711 712
    }

713 714
    stream_Read( p_stream, psz_buffer, i_size );
    psz_buffer[i_size] = '\0';
715

716
    stream_Delete( p_stream );
Gildas Bazin's avatar
Gildas Bazin committed
717

718
    if( Load( p_vlm, psz_buffer ) )
Gildas Bazin's avatar
Gildas Bazin committed
719
    {
720 721 722 723
        free( psz_buffer );

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

726
    free( psz_buffer );
Gildas Bazin's avatar
Gildas Bazin committed
727

728
    *pp_status = vlm_MessageNew( "load", vlm_NULL );
729 730
    return VLC_SUCCESS;
}
Gildas Bazin's avatar
Gildas Bazin committed
731

732 733 734 735 736
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
737

738 739 740 741
    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
742
        {
743
            vlm_ScheduleSetup( p_schedule, ppsz_property[i], NULL );
Gildas Bazin's avatar
Gildas Bazin committed
744
        }
745
        else if( !strcmp( ppsz_property[i], "append" ) )
Gildas Bazin's avatar
Gildas Bazin committed
746
        {
747 748 749 750
            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
751

752 753
            if( ++i >= i_property )
                break;
754

755 756 757 758 759 760 761
            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
762

763 764 765 766 767 768 769 770
            vlm_ScheduleSetup( p_schedule, "append", psz_line );
            break;
        }
        else
        {
            if( i + 1 >= i_property )
            {
                if( b_new )
771
                    vlm_ScheduleDelete( p_vlm, p_schedule );
772 773
                return ExecuteSyntaxError( psz_cmd, pp_status );
            }
Gildas Bazin's avatar
Gildas Bazin committed
774

775 776 777 778
            vlm_ScheduleSetup( p_schedule, ppsz_property[i], ppsz_property[i+1] );
            i++;
        }
    }
779
    *pp_status = vlm_MessageNew( psz_cmd, vlm_NULL );
780 781
    return VLC_SUCCESS;
}
Gildas Bazin's avatar
Gildas Bazin committed
782

783 784 785 786 787 788 789
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
790

791 792
#undef ERROR
#undef MISSING
793 794 795
#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
796

797 798 799 800 801 802 803 804 805 806 807
#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" ) )
808
        {
809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825
            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
826

827 828 829
            MISSING( "inputdel" );

            for( j = 0; j < p_cfg->i_input; j++ )
Gildas Bazin's avatar
Gildas Bazin committed
830
            {
831
                if( !strcmp( p_cfg->ppsz_input[j], psz_value ) )
Gildas Bazin's avatar
Gildas Bazin committed
832
                {
833 834
                    TAB_REMOVE( p_cfg->i_input, p_cfg->ppsz_input, p_cfg->ppsz_input[j] );
                    break;
Gildas Bazin's avatar
Gildas Bazin committed
835 836
                }
            }
837
            i++;
838
        }
839 840 841
        else if( !strcmp( psz_option, "inputdeln" ) )
        {
            int i_index;
Gildas Bazin's avatar
Gildas Bazin committed
842

843
            MISSING( "inputdeln" );
844
 
845 846 847 848 849 850 851 852 853
            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" );

854
            free( p_cfg->psz_output );
855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882
            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" );

883
            free( p_cfg->vod.psz_mux );
884 885 886 887 888 889 890 891
            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" );
        }
892
    }
893 894
#undef MISSING
#undef ERROR
895

896 897 898
    /* */
    i_result = vlm_ControlInternal( p_vlm, VLM_CHANGE_MEDIA, p_cfg );
    vlm_media_Delete( p_cfg );
Gildas Bazin's avatar
Gildas Bazin committed
899

900
    *pp_status = vlm_MessageNew( psz_cmd, vlm_NULL );
901
    return i_result;
902

Gildas Bazin's avatar
Gildas Bazin committed
903
error:
904 905 906 907 908 909
    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
910
    return VLC_EGENERIC;
911
}
Jean-Paul Saman's avatar
Jean-Paul Saman committed
912

913
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 )
914
{
915 916
    /* Check name */
    if( !strcmp( psz_name, "all" ) || !strcmp( psz_name, "media" ) || !strcmp( psz_name, "schedule" ) )
917
    {
918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935
        *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 );
936
    }
937 938 939 940
    else if( !strcmp( psz_type, "vod" ) || !strcmp( psz_type, "broadcast" ) )
    {
        vlm_media_t cfg;
        int64_t id;
941

942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959
        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;
    }
960 961
}

962
static int ExecuteSetup( vlm_t *p_vlm, const char *psz_name, const int i_property, char *ppsz_property[], vlm_message_t **pp_status )
963
{
964
    if( ExecuteIsSchedule( p_vlm, psz_name ) )