vlm.c 90 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"
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
60
#include "../libvlc.h"
61

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

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

70 71
static void vlm_Destructor( vlm_t *p_vlm );

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

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

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

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

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

85
static int Manage( vlc_object_t * );
86

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

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

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

97 98 99 100 101 102
/*****************************************************************************
 * vlm_New:
 *****************************************************************************/
vlm_t *__vlm_New ( vlc_object_t *p_this )
{
    vlc_value_t lockval;
103
    vlm_t *p_vlm = NULL, **pp_vlm = &(libvlc_priv (p_this->p_libvlc)->p_vlm);
104
    char *psz_vlmconf;
105
    static const char vlm_object_name[] = "vlm daemon";
106

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

112 113
    vlc_mutex_lock( lockval.p_address );

114
    p_vlm = *pp_vlm;
115
    if( p_vlm )
116
    {   /* VLM already exists */
117 118 119 120
        vlc_object_yield( p_vlm );
        vlc_mutex_unlock( lockval.p_address );
        return p_vlm;
    }
121

122
    msg_Dbg( p_this, "creating VLM" );
123

124
    p_vlm = vlc_custom_create( p_this, sizeof( *p_vlm ), VLC_OBJECT_GENERIC,
125
                               vlm_object_name );
126 127 128 129
    if( !p_vlm )
    {
        vlc_mutex_unlock( lockval.p_address );
        return NULL;
130
    }
131

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

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

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

155
        msg_Dbg( p_this, "loading VLM configuration" );
156 157
        if( asprintf(&psz_buffer, "load %s", psz_vlmconf ) == -1 )
            psz_buffer = NULL;
158 159
        if( psz_buffer )
        {
160 161
            msg_Dbg( p_this, psz_buffer );
            if( vlm_ExecuteCommand( p_vlm, psz_buffer, &p_message ) )
162
                msg_Warn( p_this, "error while loading the configuration file" );
163

164
            vlm_MessageDelete(p_message);
165 166
            free(psz_buffer);
        }
167 168
    }
    free(psz_vlmconf);
169

170
    vlc_object_set_destructor( p_vlm, (vlc_destructor_t)vlm_Destructor );
171
    *pp_vlm = p_vlm; /* for future reference */
172 173 174
    vlc_mutex_unlock( lockval.p_address );

    return p_vlm;
175 176 177 178 179
}

/*****************************************************************************
 * vlm_Delete:
 *****************************************************************************/
Clément Stenac's avatar
Clément Stenac committed
180
void vlm_Delete( vlm_t *p_vlm )
181
{
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
182 183 184 185 186 187 188
    vlc_value_t lockval;

    /* vlm_Delete() is serialized against itself, and against vlm_New().
     * This way, vlm_Destructor () (called from vlc_objet_release() above)
     * is serialized against setting libvlc_priv->p_vlm from vlm_New(). */
    var_Get( p_vlm->p_libvlc, "vlm_mutex", &lockval );
    vlc_mutex_lock( lockval.p_address );
Clément Stenac's avatar
Clément Stenac committed
189
    vlc_object_release( p_vlm );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
190
    vlc_mutex_unlock( lockval.p_address );
191
}
192

193 194 195 196 197
/*****************************************************************************
 * vlm_Destructor:
 *****************************************************************************/
static void vlm_Destructor( vlm_t *p_vlm )
{
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
198 199
    libvlc_priv (p_vlm->p_libvlc)->p_vlm = NULL;

200
    vlm_ControlInternal( p_vlm, VLM_CLEAR_MEDIAS );
201
    TAB_CLEAN( p_vlm->i_media, p_vlm->media );
202

203
    vlm_ControlInternal( p_vlm, VLM_CLEAR_SCHEDULES );
204
    TAB_CLEAN( p_vlm->schedule, p_vlm->schedule );
205

206
    vlc_mutex_destroy( &p_vlm->lock );
207 208 209 210 211
}

/*****************************************************************************
 * vlm_ExecuteCommand:
 *****************************************************************************/
212
int vlm_ExecuteCommand( vlm_t *p_vlm, const char *psz_command,
Clément Stenac's avatar
Clément Stenac committed
213
                        vlm_message_t **pp_message)
214
{
Clément Stenac's avatar
Clément Stenac committed
215
    int i_result;
216

Clément Stenac's avatar
Clément Stenac committed
217 218 219
    vlc_mutex_lock( &p_vlm->lock );
    i_result = ExecuteCommand( p_vlm, psz_command, pp_message );
    vlc_mutex_unlock( &p_vlm->lock );
220

Clément Stenac's avatar
Clément Stenac committed
221
    return i_result;
222 223
}

224 225

static const char quotes[] = "\"'";
226 227 228
/**
 * FindCommandEnd: look for the end of a possibly quoted string
 * @return NULL on mal-formatted string,
229
 * pointer past the last character otherwise.
230
 */
231
static const char *FindCommandEnd( const char *psz_sent )
232
{
233
    char c, quote = 0;
234

235
    while( (c = *psz_sent) != '\0' )
236
    {
237
        if( !quote )
Gildas Bazin's avatar
Gildas Bazin committed
238
        {
239
            if( strchr(quotes,c) )   // opening quote
240
                quote = c;
241
            else if( isspace(c) )         // non-escaped space
242
                return psz_sent;
243
            else if( c == '\\' )
244 245
            {
                psz_sent++;         // skip escaped character
246
                if( *psz_sent == '\0' )
247 248
                    return psz_sent;
            }
Gildas Bazin's avatar
Gildas Bazin committed
249
        }
250
        else
251
        {
252
            if( c == quote )         // non-escaped matching quote
253
                quote = 0;
254
            else if( (quote == '"') && (c == '\\') )
255 256 257 258 259 260
            {
                psz_sent++;         // skip escaped character
                if (*psz_sent == '\0')
                    return NULL;    // error, closing quote missing
            }
        }
Gildas Bazin's avatar
Gildas Bazin committed
261
        psz_sent++;
262
    }
263

264 265 266
    // error (NULL) if we could not find a matching quote
    return quote ? NULL : psz_sent;
}
267

Gildas Bazin's avatar
Gildas Bazin committed
268

269
/**
270
 * Unescape a nul-terminated string.
271 272 273 274 275 276 277
 * 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.
 */
278
static int Unescape( char *out, const char *in )
279
{
280
    char c, quote = 0;
281

282
    while( (c = *in++) != '\0' )
283
    {
284
        if( !quote )
Gildas Bazin's avatar
Gildas Bazin committed
285
        {
286
            if (strchr(quotes,c))   // opening quote
287
            {
288 289 290
                quote = c;
                continue;
            }
291
            else if( c == '\\' )
292 293 294 295 296 297 298 299 300 301 302 303 304
            {
                switch (c = *in++)
                {
                    case '"':
                    case '\'':
                    case '\\':
                        *out++ = c;
                        continue;

                    case '\0':
                        *out = '\0';
                        return 0;
                }
305
                if( isspace(c) )
306 307
                {
                    *out++ = c;
308
                    continue;
309 310 311 312 313 314 315
                }
                /* None of the special cases - copy the backslash */
                *out++ = '\\';
            }
        }
        else
        {
316
            if( c == quote )         // non-escaped matching quote
317 318 319 320
            {
                quote = 0;
                continue;
            }
321
            if( (quote == '"') && (c == '\\') )
322
            {
323
                switch( c = *in++ )
324 325 326 327 328 329 330 331 332 333 334 335
                {
                    case '"':
                    case '\\':
                        *out++ = c;
                        continue;

                    case '\0':   // should never happen
                        *out = '\0';
                        return -1;
                }
                /* None of the special cases - copy the backslash */
                *out++ = '\\';
336 337
            }
        }
338
        *out++ = c;
339
    }
340 341 342

    *out = '\0';
    return 0;
343 344
}

345

Gildas Bazin's avatar
Gildas Bazin committed
346 347 348 349 350
/*****************************************************************************
 * ExecuteCommand: The main state machine
 *****************************************************************************
 * Execute a command which ends with '\0' (string)
 *****************************************************************************/
351
static int ExecuteSyntaxError( const char *psz_cmd, vlm_message_t **pp_status )
352
{
353 354 355
    *pp_status = vlm_MessageNew( psz_cmd, "Wrong command syntax" );
    return VLC_EGENERIC;
}
356

357
static bool ExecuteIsMedia( vlm_t *p_vlm, const char *psz_name )
358 359
{
    int64_t id;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
360

361
    if( !psz_name || vlm_ControlInternal( p_vlm, VLM_GET_MEDIA_ID, psz_name, &id ) )
362 363
        return false;
    return true;
364
}
365
static bool ExecuteIsSchedule( vlm_t *p_vlm, const char *psz_name )
366 367
{
    if( !psz_name || !vlm_ScheduleSearch( p_vlm, psz_name ) )
368 369
        return false;
    return true;
370
}
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
371

372 373 374 375
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;
376

377 378
    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
379

380 381
    if( p_schedule != NULL )
    {
382
        vlm_ScheduleDelete( p_vlm, p_schedule );
383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405
    }
    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;
    }
406

407
    *pp_status = vlm_MessageNew( "del", vlm_NULL );
408 409
    return VLC_SUCCESS;
}
410

411 412 413 414
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;
415

416 417 418 419 420
    if( !psz_name )
    {
        *pp_status = vlm_Show( p_vlm, NULL, NULL, NULL );
        return VLC_SUCCESS;
    }
Antoine Cellerier's avatar
Antoine Cellerier committed
421

422 423
    p_media = vlm_MediaSearch( p_vlm, psz_name );
    p_schedule = vlm_ScheduleSearch( p_vlm, psz_name );
424

425 426 427 428 429 430
    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 );
431

432 433
    return VLC_SUCCESS;
}
434

435
static int ExecuteHelp( vlm_message_t **pp_status )
436
{
437 438 439
    vlm_message_t *message_child;

#define MessageAdd( a ) \
440
        vlm_MessageAdd( *pp_status, vlm_MessageNew( a, vlm_NULL ) );
441 442 443
#define MessageAddChild( a ) \
        vlm_MessageAdd( message_child, vlm_MessageNew( a, vlm_NULL ) );

444
    *pp_status = vlm_MessageNew( "help", vlm_NULL );
445 446 447 448 449 450 451 452 453 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

    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" );

481 482 483 484 485 486 487 488 489 490 491 492 493
    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 ) )
494
    {
495 496
        *pp_status = vlm_MessageNew( "control", "%s: media unknown", psz_name );
        return VLC_EGENERIC;
497 498
    }

499 500 501 502 503
    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") )
504
    {
505 506
        i_index = 1;
        psz_instance = ppsz_arg[0];
507

508 509 510 511 512
        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];
513

514 515
    if( i_index+1 < i_arg )
        psz_argument = ppsz_arg[i_index+1];
516

517 518
    p_media = vlm_MediaSearch( p_vlm, psz_name );
    assert( p_media );
519

520 521 522 523
    if( !strcmp( psz_control, "play" ) )
    {
        int i_input_index = 0;
        int i;
524

525
        if( ( psz_argument && sscanf(psz_argument, "%d", &i) == 1 ) && i > 0 && i-1 < p_media->cfg.i_input )
526
        {
527
            i_input_index = i-1;
528 529 530 531 532 533 534 535 536 537 538 539 540 541
        }
        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;
                }
            }
        }
542

543 544
        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
545
        else
546
            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
547
    }
548
    else if( !strcmp( psz_control, "seek" ) )
Gildas Bazin's avatar
Gildas Bazin committed
549
    {
550 551
        if( psz_argument )
        {
552
            bool b_relative;
553
            if( psz_argument[0] == '+' || psz_argument[0] == '-' )
554
                b_relative = true;
555
            else
556
                b_relative = false;
Gildas Bazin's avatar
Gildas Bazin committed
557

558 559 560 561
            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
562

563 564 565 566
                if( strstr( psz_argument, "ms" ) )
                    i_new_time =  1000 * (int64_t)atoi( psz_argument );
                else
                    i_new_time = 1000000 * (int64_t)atoi( psz_argument );
567

568 569 570 571 572 573 574 575 576 577 578 579 580 581
                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;
582

583 584 585
                if( b_relative )
                {
                    double d_position = 0.0;
586

587 588 589 590 591 592 593 594 595
                    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
596
        }
597
        else
Gildas Bazin's avatar
Gildas Bazin committed
598
        {
599
            i_result = VLC_EGENERIC;
Gildas Bazin's avatar
Gildas Bazin committed
600
        }
601 602 603 604
    }
    else if( !strcmp( psz_control, "rewind" ) )
    {
        if( psz_argument )
Gildas Bazin's avatar
Gildas Bazin committed
605
        {
606 607
            const double d_scale = i18n_atof( psz_argument );
            double d_position;
Gildas Bazin's avatar
Gildas Bazin committed
608

609 610 611 612 613
            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 );
614 615 616
        }
        else
        {
617
            i_result = VLC_EGENERIC;
618 619
        }
    }
620
    else if( !strcmp( psz_control, "forward" ) )
621
    {
622
        if( psz_argument )
623
        {
624 625
            const double d_scale = i18n_atof( psz_argument );
            double d_position;
626

627 628 629 630 631
            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 );
632 633 634 635

        }
        else
        {
636
            i_result = VLC_EGENERIC;
637 638
        }
    }
639
    else if( !strcmp( psz_control, "stop" ) )
Gildas Bazin's avatar
Gildas Bazin committed
640
    {
641 642 643 644 645 646 647 648 649
        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
650
    }
651

652
    if( i_result )
Gildas Bazin's avatar
Gildas Bazin committed
653
    {
654 655 656
        *pp_status = vlm_MessageNew( "control", "unknown error" );
        return VLC_SUCCESS;
    }
657
    *pp_status = vlm_MessageNew( "control", vlm_NULL );
658 659
    return VLC_SUCCESS;
}
660

661 662 663
static int ExecuteExport( vlm_t *p_vlm, vlm_message_t **pp_status )
{
    char *psz_export = Save( p_vlm );
664

665 666 667 668
    *pp_status = vlm_MessageNew( "export", psz_export );
    free( psz_export );
    return VLC_SUCCESS;
}
669

670 671 672
static int ExecuteSave( vlm_t *p_vlm, const char *psz_file, vlm_message_t **pp_status )
{
    FILE *f = utf8_fopen( psz_file, "wt" );
673
    char *psz_save = NULL;
Gildas Bazin's avatar
Gildas Bazin committed
674

675 676
    if( !f )
        goto error;
677

678 679
    psz_save = Save( p_vlm );
    if( psz_save == NULL )
680 681 682 683
        goto error;
    if( fputs( psz_save, f ) == EOF )
        goto error;;
    if( fclose( f ) )
684
    {
685
        f = NULL;
686 687
        goto error;
    }
688

689
    free( psz_save );
690

691
    *pp_status = vlm_MessageNew( "save", vlm_NULL );
692 693 694
    return VLC_SUCCESS;

error:
695 696 697 698
    free( psz_save );
    if( f )
         fclose( f );
    *pp_status = vlm_MessageNew( "save", "Unable to save to file");
699 700
    return VLC_EGENERIC;
}
701

702 703 704 705 706
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
707

708 709 710 711
    if( !p_stream )
    {
        *pp_status = vlm_MessageNew( "load", "Unable to load from file" );
        return VLC_EGENERIC;
712
    }
Gildas Bazin's avatar
Gildas Bazin committed
713

714 715
    /* FIXME needed ? */
    if( stream_Seek( p_stream, 0 ) != 0 )
716
    {
717
        stream_Delete( p_stream );
718

719 720
        *pp_status = vlm_MessageNew( "load", "Read file error" );
        return VLC_EGENERIC;
721
    }
Gildas Bazin's avatar
Gildas Bazin committed
722

723
    i_size = stream_Size( p_stream );
724

725 726 727 728
    psz_buffer = malloc( i_size + 1 );
    if( !psz_buffer )
    {
        stream_Delete( p_stream );
729

730 731
        *pp_status = vlm_MessageNew( "load", "Read file error" );
        return VLC_EGENERIC;
732 733
    }

734 735
    stream_Read( p_stream, psz_buffer, i_size );
    psz_buffer[i_size] = '\0';
736

737
    stream_Delete( p_stream );
Gildas Bazin's avatar
Gildas Bazin committed
738

739
    if( Load( p_vlm, psz_buffer ) )
Gildas Bazin's avatar
Gildas Bazin committed
740
    {
741 742 743 744
        free( psz_buffer );

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

747
    free( psz_buffer );
Gildas Bazin's avatar
Gildas Bazin committed
748

749
    *pp_status = vlm_MessageNew( "load", vlm_NULL );
750 751
    return VLC_SUCCESS;
}
Gildas Bazin's avatar
Gildas Bazin committed
752

753
static int ExecuteScheduleProperty( vlm_t *p_vlm, vlm_schedule_sys_t *p_schedule, bool b_new,
754 755 756 757
                                    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
758

759 760 761 762
    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
763
        {
764
            vlm_ScheduleSetup( p_schedule, ppsz_property[i], NULL );
Gildas Bazin's avatar
Gildas Bazin committed
765
        }
766
        else if( !strcmp( ppsz_property[i], "append" ) )
Gildas Bazin's avatar
Gildas Bazin committed
767
        {
768 769 770 771
            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
772

773 774
            if( ++i >= i_property )
                break;
775

776 777 778 779 780 781 782
            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
783

784 785 786 787 788 789 790 791
            vlm_ScheduleSetup( p_schedule, "append", psz_line );
            break;
        }
        else
        {
            if( i + 1 >= i_property )
            {
                if( b_new )
792
                    vlm_ScheduleDelete( p_vlm, p_schedule );
793 794
                return ExecuteSyntaxError( psz_cmd, pp_status );
            }
Gildas Bazin's avatar
Gildas Bazin committed
795

796 797 798 799
            vlm_ScheduleSetup( p_schedule, ppsz_property[i], ppsz_property[i+1] );
            i++;
        }
    }
800
    *pp_status = vlm_MessageNew( psz_cmd, vlm_NULL );
801 802
    return VLC_SUCCESS;
}
Gildas Bazin's avatar
Gildas Bazin committed
803

804
static int ExecuteMediaProperty( vlm_t *p_vlm, int64_t id, bool b_new,
805 806 807 808 809 810
                                 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
811

812 813
#undef ERROR
#undef MISSING
814 815 816
#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
817

818 819 820 821 822 823 824 825
#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" ) )
        {
826
            p_cfg->b_enabled = true;
827 828
        }
        else if( !strcmp( psz_option, "disabled" ) )
829
        {
830
            p_cfg->b_enabled = false;
831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846
        }
        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
847

848 849 850
            MISSING( "inputdel" );

            for( j = 0; j < p_cfg->i_input; j++ )
Gildas Bazin's avatar
Gildas Bazin committed
851
            {
852
                if( !strcmp( p_cfg->ppsz_input[j], psz_value ) )
Gildas Bazin's avatar
Gildas Bazin committed
853
                {
854 855
                    TAB_REMOVE( p_cfg->i_input, p_cfg->ppsz_input, p_cfg->ppsz_input[j] );
                    break;
Gildas Bazin's avatar
Gildas Bazin committed
856 857
                }
            }
858
            i++;
859
        }
860 861 862
        else if( !strcmp( psz_option, "inputdeln" ) )
        {
            int i_index;
Gildas Bazin's avatar
Gildas Bazin committed
863

864
            MISSING( "inputdeln" );
865
 
866 867 868 869 870 871 872 873 874
            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" );

875
            free( p_cfg->psz_output );
876 877 878 879 880 881 882 883 884 885 886 887 888 889
            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" );
890
            p_cfg->broadcast.b_loop = true;
891 892 893 894 895
        }
        else if( !strcmp( psz_option, "unloop" ) )
        {
            if( p_cfg->b_vod )
                ERROR( "invalid unloop option for vod" );
896
            p_cfg->broadcast.b_loop = false;
897 898 899 900 901 902 903
        }
        else if( !strcmp( psz_option, "mux" ) )
        {
            MISSING( "mux" );
            if( !p_cfg->b_vod )
                ERROR( "invalid mux option for broadcast" );