implement PROBE command
[melted] / src / melted / melted_local.c
1 /*
2 * melted_local.c -- Local Melted Parser
3 * Copyright (C) 2002-2009 Ushodaya Enterprises Limited
4 * Author: Charles Yates <charles.yates@pandora.be>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 */
20
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24
25 /* System header files */
26 #include <stdlib.h>
27 #include <string.h>
28 #include <signal.h>
29
30 /* Needed for backtrace on linux */
31 #ifdef linux
32 #include <execinfo.h>
33 #endif
34
35 /* mvcp header files */
36 #include <mvcp/mvcp_util.h>
37
38 /* MLT header files. */
39 #include <framework/mlt_factory.h>
40
41 /* Application header files */
42 #include "melted_local.h"
43 #include "melted_connection.h"
44 #include "melted_commands.h"
45 #include "melted_unit_commands.h"
46 #include "melted_log.h"
47
48 /** Private melted_local structure.
49 */
50
51 typedef struct
52 {
53 mvcp_parser parser;
54 char root_dir[1024];
55 }
56 *melted_local, melted_local_t;
57
58 /** Forward declarations.
59 */
60
61 static mvcp_response melted_local_connect( melted_local );
62 static mvcp_response melted_local_execute( melted_local, char * );
63 static mvcp_response melted_local_push( melted_local, char *, mlt_service );
64 static mvcp_response melted_local_receive( melted_local, char *, char * );
65 static void melted_local_close( melted_local );
66 response_codes melted_help( command_argument arg );
67 response_codes melted_run( command_argument arg );
68 response_codes melted_shutdown( command_argument arg );
69
70 /** MVCP Parser constructor.
71 */
72
73 mvcp_parser melted_parser_init_local( )
74 {
75 mvcp_parser parser = malloc( sizeof( mvcp_parser_t ) );
76 melted_local local = malloc( sizeof( melted_local_t ) );
77
78 if ( parser != NULL )
79 {
80 memset( parser, 0, sizeof( mvcp_parser_t ) );
81
82 parser->connect = (parser_connect)melted_local_connect;
83 parser->execute = (parser_execute)melted_local_execute;
84 parser->push = (parser_push)melted_local_push;
85 parser->received = (parser_received)melted_local_receive;
86 parser->close = (parser_close)melted_local_close;
87 parser->real = local;
88
89 if ( local != NULL )
90 {
91 memset( local, 0, sizeof( melted_local_t ) );
92 local->parser = parser;
93 local->root_dir[0] = '/';
94 }
95
96 // Construct the factory
97 mlt_factory_init( getenv( "MLT_REPOSITORY" ) );
98 }
99 return parser;
100 }
101
102 /** response status code/message pair
103 */
104
105 typedef struct
106 {
107 int code;
108 const char *message;
109 }
110 responses_t;
111
112 /** response messages
113 */
114
115 static responses_t responses [] =
116 {
117 {RESPONSE_SUCCESS, "OK"},
118 {RESPONSE_SUCCESS_N, "OK"},
119 {RESPONSE_SUCCESS_1, "OK"},
120 {RESPONSE_UNKNOWN_COMMAND, "Unknown command"},
121 {RESPONSE_TIMEOUT, "Operation timed out"},
122 {RESPONSE_MISSING_ARG, "Argument missing"},
123 {RESPONSE_INVALID_UNIT, "Unit not found"},
124 {RESPONSE_BAD_FILE, "Failed to locate or open clip"},
125 {RESPONSE_OUT_OF_RANGE, "Argument value out of range"},
126 {RESPONSE_TOO_MANY_FILES, "Too many files open"},
127 {RESPONSE_ERROR, "Server Error"}
128 };
129
130 /** Argument types.
131 */
132
133 typedef enum
134 {
135 ATYPE_NONE,
136 ATYPE_FLOAT,
137 ATYPE_STRING,
138 ATYPE_INT,
139 ATYPE_PAIR
140 }
141 arguments_types;
142
143 /** A command definition.
144 */
145
146 typedef struct
147 {
148 /* The command string corresponding to this operation (e.g. "play") */
149 const char *command;
150 /* The function associated with it */
151 response_codes (*operation) ( command_argument );
152 /* a boolean to indicate if this is a unit or global command
153 unit commands require a unit identifier as first argument */
154 int is_unit;
155 /* What type is the argument (RTTI :-) ATYPE_whatever */
156 int type;
157 /* online help information */
158 const char *help;
159 }
160 command_t;
161
162 /* The following define the queue of commands available to the user. The
163 first entry is the name of the command (the string which must be typed),
164 the second command is the function associated with it, the third argument
165 is for the type of the argument, and the last argument specifies whether
166 this is something which should be handled immediately or whether it
167 should be queued (only robot motion commands need to be queued). */
168
169 static command_t vocabulary[] =
170 {
171 {"BYE", NULL, 0, ATYPE_NONE, "Terminates the session. Units are not removed and task queue is not flushed."},
172 {"HELP", melted_help, 0, ATYPE_NONE, "Display this information!"},
173 {"NLS", melted_list_nodes, 0, ATYPE_NONE, "List the AV/C nodes on the 1394 bus."},
174 {"UADD", melted_add_unit, 0, ATYPE_STRING, "Create a new playout unit (virtual VTR) to transmit to receiver specified in GUID argument."},
175 {"ULS", melted_list_units, 0, ATYPE_NONE, "Lists the units that have already been added to the server."},
176 {"CLS", melted_list_clips, 0, ATYPE_STRING, "Lists the clips at directory name argument."},
177 {"PROBE", melted_probe_clip, 0, ATYPE_STRING, "Probe clip for playback. Output clip parameters."},
178 {"SET", melted_set_global_property, 0, ATYPE_PAIR, "Set a server configuration property."},
179 {"GET", melted_get_global_property, 0, ATYPE_STRING, "Get a server configuration property."},
180 {"RUN", melted_run, 0, ATYPE_STRING, "Run a batch file." },
181 {"LIST", melted_list, 1, ATYPE_NONE, "List the playlist associated to a unit."},
182 {"LOAD", melted_load, 1, ATYPE_STRING, "Load clip specified in absolute filename argument."},
183 {"INSERT", melted_insert, 1, ATYPE_STRING, "Insert a clip at the given clip index."},
184 {"REMOVE", melted_remove, 1, ATYPE_NONE, "Remove a clip at the given clip index."},
185 {"CLEAN", melted_clean, 1, ATYPE_NONE, "Clean a unit by removing all but the currently playing clip."},
186 {"WIPE", melted_wipe, 1, ATYPE_NONE, "Clean a unit by removing everything before the currently playing clip."},
187 {"CLEAR", melted_clear, 1, ATYPE_NONE, "Clear a unit by removing all clips."},
188 {"MOVE", melted_move, 1, ATYPE_INT, "Move a clip to another clip index."},
189 {"APND", melted_append, 1, ATYPE_STRING, "Append a clip specified in absolute filename argument."},
190 {"PLAY", melted_play, 1, ATYPE_NONE, "Play a loaded clip at speed -2000 to 2000 where 1000 = normal forward speed."},
191 {"STOP", melted_stop, 1, ATYPE_NONE, "Stop a loaded and playing clip."},
192 {"PAUSE", melted_pause, 1, ATYPE_NONE, "Pause a playing clip."},
193 {"REW", melted_rewind, 1, ATYPE_NONE, "Rewind a unit. If stopped, seek to beginning of clip. If playing, play fast backwards."},
194 {"FF", melted_ff, 1, ATYPE_NONE, "Fast forward a unit. If stopped, seek to beginning of clip. If playing, play fast forwards."},
195 {"STEP", melted_step, 1, ATYPE_INT, "Step argument number of frames forward or backward."},
196 {"GOTO", melted_goto, 1, ATYPE_INT, "Jump to frame number supplied as argument."},
197 {"SIN", melted_set_in_point, 1, ATYPE_INT, "Set the IN point of the loaded clip to frame number argument. -1 = reset in point to 0"},
198 {"SOUT", melted_set_out_point, 1, ATYPE_INT, "Set the OUT point of the loaded clip to frame number argument. -1 = reset out point to maximum."},
199 {"USTA", melted_get_unit_status, 1, ATYPE_NONE, "Report information about the unit."},
200 {"USET", melted_set_unit_property, 1, ATYPE_PAIR, "Set a unit configuration property."},
201 {"UGET", melted_get_unit_property, 1, ATYPE_STRING, "Get a unit configuration property."},
202 {"XFER", melted_transfer, 1, ATYPE_STRING, "Transfer the unit's clip to another unit specified as argument."},
203 {"SHUTDOWN", melted_shutdown, 0, ATYPE_NONE, "Shutdown the server."},
204 {NULL, NULL, 0, ATYPE_NONE, NULL}
205 };
206
207 /** Usage message
208 */
209
210 static char helpstr [] =
211 "melted -- A Multimedia Playout Server\n"
212 " Copyright (C) 2002-2003 Ushodaya Enterprises Limited\n"
213 " Authors:\n"
214 " Dan Dennedy <dan@dennedy.org>\n"
215 " Charles Yates <charles.yates@pandora.be>\n"
216 "Available commands:\n";
217
218 /** Lookup the response message for a status code.
219 */
220
221 inline const char *get_response_msg( int code )
222 {
223 int i = 0;
224 for ( i = 0; responses[ i ].message != NULL && code != responses[ i ].code; i ++ ) ;
225 return responses[ i ].message;
226 }
227
228 /** Tell the user the melted command set
229 */
230
231 response_codes melted_help( command_argument cmd_arg )
232 {
233 int i = 0;
234
235 mvcp_response_printf( cmd_arg->response, 10240, "%s", helpstr );
236
237 for ( i = 0; vocabulary[ i ].command != NULL; i ++ )
238 mvcp_response_printf( cmd_arg->response, 1024,
239 "%-10.10s%s\n",
240 vocabulary[ i ].command,
241 vocabulary[ i ].help );
242
243 mvcp_response_printf( cmd_arg->response, 2, "\n" );
244
245 return RESPONSE_SUCCESS_N;
246 }
247
248 /** Execute a batch file.
249 */
250
251 response_codes melted_run( command_argument cmd_arg )
252 {
253 mvcp_response temp = mvcp_parser_run( cmd_arg->parser, (char *)cmd_arg->argument );
254
255 if ( temp != NULL )
256 {
257 int index = 0;
258
259 mvcp_response_set_error( cmd_arg->response,
260 mvcp_response_get_error_code( temp ),
261 mvcp_response_get_error_string( temp ) );
262
263 for ( index = 1; index < mvcp_response_count( temp ); index ++ )
264 mvcp_response_printf( cmd_arg->response, 10240, "%s\n", mvcp_response_get_line( temp, index ) );
265
266 mvcp_response_close( temp );
267 }
268
269 return mvcp_response_get_error_code( cmd_arg->response );
270 }
271
272 response_codes melted_shutdown( command_argument cmd_arg )
273 {
274 exit( 0 );
275 return RESPONSE_SUCCESS;
276 }
277
278 /** Processes 'thread' id
279 */
280
281 static pthread_t self;
282
283 /* Signal handler to deal with various shutdown signals. Basically this
284 should clean up and power down the motor. Note that the death of any
285 child thread will kill all thrads. */
286
287 void signal_handler( int sig )
288 {
289 if ( pthread_equal( self, pthread_self( ) ) )
290 {
291
292 #ifdef _GNU_SOURCE
293 melted_log( LOG_DEBUG, "Received %s - shutting down.", strsignal(sig) );
294 #else
295 melted_log( LOG_DEBUG, "Received signal %i - shutting down.", sig );
296 #endif
297
298 exit(EXIT_SUCCESS);
299 }
300 }
301
302 static void sigsegv_handler()
303 {
304 #ifdef linux
305 void *array[ 10 ];
306 size_t size;
307 char **strings;
308 size_t i;
309
310 melted_log( LOG_CRIT, "\a\nmelted experienced a segmentation fault.\n"
311 "Dumping stack from the offending thread\n\n" );
312 size = backtrace( array, 10 );
313 strings = backtrace_symbols( array, size );
314
315 melted_log( LOG_CRIT, "Obtained %zd stack frames.\n", size );
316
317 for ( i = 0; i < size; i++ )
318 melted_log( LOG_CRIT, "%s", strings[ i ] );
319
320 free( strings );
321
322 melted_log( LOG_CRIT, "\nDone dumping - exiting.\n" );
323 #else
324 melted_log( LOG_CRIT, "\a\nmelted experienced a segmentation fault.\n" );
325 #endif
326 exit( EXIT_FAILURE );
327 }
328
329
330
331 /** Local 'connect' function.
332 */
333
334 static mvcp_response melted_local_connect( melted_local local )
335 {
336 mvcp_response response = mvcp_response_init( );
337
338 self = pthread_self( );
339
340 mvcp_response_set_error( response, 100, "VTR Ready" );
341
342 signal( SIGHUP, signal_handler );
343 signal( SIGINT, signal_handler );
344 signal( SIGTERM, SIG_DFL );
345 signal( SIGSTOP, signal_handler );
346 signal( SIGPIPE, signal_handler );
347 signal( SIGALRM, signal_handler );
348 signal( SIGCHLD, SIG_IGN );
349 if ( getenv( "MLT_SIGSEGV" ) )
350 signal( SIGSEGV, sigsegv_handler );
351
352 return response;
353 }
354
355 /** Set the error and determine the message associated to this command.
356 */
357
358 void melted_command_set_error( command_argument cmd, response_codes code )
359 {
360 mvcp_response_set_error( cmd->response, code, get_response_msg( code ) );
361 }
362
363 /** Parse the unit argument.
364 */
365
366 int melted_command_parse_unit( command_argument cmd, int argument )
367 {
368 int unit = -1;
369 char *string = mvcp_tokeniser_get_string( cmd->tokeniser, argument );
370 if ( string != NULL && ( string[ 0 ] == 'U' || string[ 0 ] == 'u' ) && strlen( string ) > 1 )
371 unit = atoi( string + 1 );
372 return unit;
373 }
374
375 /** Parse a normal argument.
376 */
377
378 void *melted_command_parse_argument( command_argument cmd, int argument, arguments_types type, char *command )
379 {
380 void *ret = NULL;
381 char *value = mvcp_tokeniser_get_string( cmd->tokeniser, argument );
382
383 if ( value != NULL )
384 {
385 switch( type )
386 {
387 case ATYPE_NONE:
388 break;
389
390 case ATYPE_FLOAT:
391 ret = malloc( sizeof( float ) );
392 if ( ret != NULL )
393 *( float * )ret = atof( value );
394 break;
395
396 case ATYPE_STRING:
397 ret = strdup( value );
398 break;
399
400 case ATYPE_PAIR:
401 if ( strchr( command, '=' ) )
402 {
403 char *ptr = strchr( command, '=' );
404 while ( *( ptr - 1 ) != ' ' )
405 ptr --;
406 ret = strdup( ptr );
407 ptr = ret;
408 while( ptr[ strlen( ptr ) - 1 ] == ' ' )
409 ptr[ strlen( ptr ) - 1 ] = '\0';
410 }
411 break;
412
413 case ATYPE_INT:
414 ret = malloc( sizeof( int ) );
415 if ( ret != NULL )
416 *( int * )ret = atoi( value );
417 break;
418 }
419 }
420
421 return ret;
422 }
423
424 /** Get the error code - note that we simply the success return.
425 */
426
427 response_codes melted_command_get_error( command_argument cmd )
428 {
429 response_codes ret = mvcp_response_get_error_code( cmd->response );
430 if ( ret == RESPONSE_SUCCESS_N || ret == RESPONSE_SUCCESS_1 )
431 ret = RESPONSE_SUCCESS;
432 return ret;
433 }
434
435 /** Execute the command.
436 */
437
438 static mvcp_response melted_local_execute( melted_local local, char *command )
439 {
440 command_argument_t cmd;
441 cmd.parser = local->parser;
442 cmd.response = mvcp_response_init( );
443 cmd.tokeniser = mvcp_tokeniser_init( );
444 cmd.command = command;
445 cmd.unit = -1;
446 cmd.argument = NULL;
447 cmd.root_dir = local->root_dir;
448
449 /* Set the default error */
450 melted_command_set_error( &cmd, RESPONSE_UNKNOWN_COMMAND );
451
452 /* Parse the command */
453 if ( mvcp_tokeniser_parse_new( cmd.tokeniser, command, " " ) > 0 )
454 {
455 int index = 0;
456 char *value = mvcp_tokeniser_get_string( cmd.tokeniser, 0 );
457 int found = 0;
458
459 /* Strip quotes from all tokens */
460 for ( index = 0; index < mvcp_tokeniser_count( cmd.tokeniser ); index ++ )
461 mvcp_util_strip( mvcp_tokeniser_get_string( cmd.tokeniser, index ), '\"' );
462
463 /* Search the vocabulary array for value */
464 for ( index = 1; !found && vocabulary[ index ].command != NULL; index ++ )
465 if ( ( found = !strcasecmp( vocabulary[ index ].command, value ) ) )
466 break;
467
468 /* If we found something, the handle the args and call the handler. */
469 if ( found )
470 {
471 int position = 1;
472
473 melted_command_set_error( &cmd, RESPONSE_SUCCESS );
474
475 if ( vocabulary[ index ].is_unit )
476 {
477 cmd.unit = melted_command_parse_unit( &cmd, position );
478 if ( cmd.unit == -1 )
479 melted_command_set_error( &cmd, RESPONSE_MISSING_ARG );
480 position ++;
481 }
482
483 if ( melted_command_get_error( &cmd ) == RESPONSE_SUCCESS )
484 {
485 cmd.argument = melted_command_parse_argument( &cmd, position, vocabulary[ index ].type, command );
486 if ( cmd.argument == NULL && vocabulary[ index ].type != ATYPE_NONE )
487 melted_command_set_error( &cmd, RESPONSE_MISSING_ARG );
488 position ++;
489 }
490
491 if ( melted_command_get_error( &cmd ) == RESPONSE_SUCCESS )
492 {
493 response_codes error = vocabulary[ index ].operation( &cmd );
494 melted_command_set_error( &cmd, error );
495 }
496
497 free( cmd.argument );
498 }
499 }
500
501 mvcp_tokeniser_close( cmd.tokeniser );
502
503 return cmd.response;
504 }
505
506 static mvcp_response melted_local_receive( melted_local local, char *command, char *doc )
507 {
508 command_argument_t cmd;
509 cmd.parser = local->parser;
510 cmd.response = mvcp_response_init( );
511 cmd.tokeniser = mvcp_tokeniser_init( );
512 cmd.command = command;
513 cmd.unit = -1;
514 cmd.argument = NULL;
515 cmd.root_dir = local->root_dir;
516
517 /* Set the default error */
518 melted_command_set_error( &cmd, RESPONSE_SUCCESS );
519
520 /* Parse the command */
521 if ( mvcp_tokeniser_parse_new( cmd.tokeniser, command, " " ) > 0 )
522 {
523 int index = 0;
524 int position = 1;
525
526 /* Strip quotes from all tokens */
527 for ( index = 0; index < mvcp_tokeniser_count( cmd.tokeniser ); index ++ )
528 mvcp_util_strip( mvcp_tokeniser_get_string( cmd.tokeniser, index ), '\"' );
529
530 cmd.unit = melted_command_parse_unit( &cmd, position );
531 if ( cmd.unit == -1 )
532 melted_command_set_error( &cmd, RESPONSE_MISSING_ARG );
533 position ++;
534
535 melted_receive( &cmd, doc );
536 melted_command_set_error( &cmd, RESPONSE_SUCCESS );
537
538 free( cmd.argument );
539 }
540
541 mvcp_tokeniser_close( cmd.tokeniser );
542
543 return cmd.response;
544 }
545
546 static mvcp_response melted_local_push( melted_local local, char *command, mlt_service service )
547 {
548 command_argument_t cmd;
549 cmd.parser = local->parser;
550 cmd.response = mvcp_response_init( );
551 cmd.tokeniser = mvcp_tokeniser_init( );
552 cmd.command = command;
553 cmd.unit = -1;
554 cmd.argument = NULL;
555 cmd.root_dir = local->root_dir;
556
557 /* Set the default error */
558 melted_command_set_error( &cmd, RESPONSE_SUCCESS );
559
560 /* Parse the command */
561 if ( mvcp_tokeniser_parse_new( cmd.tokeniser, command, " " ) > 0 )
562 {
563 int index = 0;
564 int position = 1;
565
566 /* Strip quotes from all tokens */
567 for ( index = 0; index < mvcp_tokeniser_count( cmd.tokeniser ); index ++ )
568 mvcp_util_strip( mvcp_tokeniser_get_string( cmd.tokeniser, index ), '\"' );
569
570 cmd.unit = melted_command_parse_unit( &cmd, position );
571 if ( cmd.unit == -1 )
572 melted_command_set_error( &cmd, RESPONSE_MISSING_ARG );
573 position ++;
574
575 melted_push( &cmd, service );
576 melted_command_set_error( &cmd, RESPONSE_SUCCESS );
577
578 free( cmd.argument );
579 }
580
581 mvcp_tokeniser_close( cmd.tokeniser );
582
583 return cmd.response;
584 }
585
586 /** Close the parser.
587 */
588
589 static void melted_local_close( melted_local local )
590 {
591 melted_delete_all_units();
592 #ifdef linux
593 //pthread_kill_other_threads_np();
594 melted_log( LOG_DEBUG, "Clean shutdown." );
595 //free( local );
596 //mlt_factory_close( );
597 #endif
598 }