]> git.sesse.net Git - mlt/blob - src/miracle/miracle_local.c
9f3e706e09d45aa37d28b9da012a4364428bbf2d
[mlt] / src / miracle / miracle_local.c
1 /*
2  * miracle_local.c -- Local Miracle Parser
3  * Copyright (C) 2002-2003 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 /* Valerie header files */
36 #include <valerie/valerie_util.h>
37
38 /* MLT header files. */
39 #include <framework/mlt_factory.h>
40
41 /* Application header files */
42 #include "miracle_local.h"
43 #include "miracle_connection.h"
44 #include "miracle_commands.h"
45 #include "miracle_unit_commands.h"
46 #include "miracle_log.h"
47
48 /** Private miracle_local structure.
49 */
50
51 typedef struct
52 {
53         valerie_parser parser;
54         char root_dir[1024];
55 }
56 *miracle_local, miracle_local_t;
57
58 /** Forward declarations.
59 */
60
61 static valerie_response miracle_local_connect( miracle_local );
62 static valerie_response miracle_local_execute( miracle_local, char * );
63 static valerie_response miracle_local_push( miracle_local, char *, mlt_service );
64 static void miracle_local_close( miracle_local );
65 response_codes miracle_help( command_argument arg );
66 response_codes miracle_run( command_argument arg );
67 response_codes miracle_shutdown( command_argument arg );
68
69 /** DV Parser constructor.
70 */
71
72 valerie_parser miracle_parser_init_local( )
73 {
74         valerie_parser parser = malloc( sizeof( valerie_parser_t ) );
75         miracle_local local = malloc( sizeof( miracle_local_t ) );
76
77         if ( parser != NULL )
78         {
79                 memset( parser, 0, sizeof( valerie_parser_t ) );
80
81                 parser->connect = (parser_connect)miracle_local_connect;
82                 parser->execute = (parser_execute)miracle_local_execute;
83                 parser->push = (parser_push)miracle_local_push;
84                 parser->close = (parser_close)miracle_local_close;
85                 parser->real = local;
86
87                 if ( local != NULL )
88                 {
89                         memset( local, 0, sizeof( miracle_local_t ) );
90                         local->parser = parser;
91                         local->root_dir[0] = '/';
92                 }
93
94                 // Construct the factory
95                 mlt_factory_init( getenv( "MLT_REPOSITORY" ) );
96         }
97         return parser;
98 }
99
100 /** response status code/message pair 
101 */
102
103 typedef struct 
104 {
105         int code;
106         char *message;
107
108 responses_t;
109
110 /** response messages 
111 */
112
113 static responses_t responses [] = 
114 {
115         {RESPONSE_SUCCESS, "OK"},
116         {RESPONSE_SUCCESS_N, "OK"},
117         {RESPONSE_SUCCESS_1, "OK"},
118         {RESPONSE_UNKNOWN_COMMAND, "Unknown command"},
119         {RESPONSE_TIMEOUT, "Operation timed out"},
120         {RESPONSE_MISSING_ARG, "Argument missing"},
121         {RESPONSE_INVALID_UNIT, "Unit not found"},
122         {RESPONSE_BAD_FILE, "Failed to locate or open clip"},
123         {RESPONSE_OUT_OF_RANGE, "Argument value out of range"},
124         {RESPONSE_TOO_MANY_FILES, "Too many files open"},
125         {RESPONSE_ERROR, "Server Error"}
126 };
127
128 /** Argument types.
129 */
130
131 typedef enum 
132 {
133         ATYPE_NONE,
134         ATYPE_FLOAT,
135         ATYPE_STRING,
136         ATYPE_INT,
137         ATYPE_PAIR
138
139 arguments_types;
140
141 /** A command definition.
142 */
143
144 typedef struct 
145 {
146 /* The command string corresponding to this operation (e.g. "play") */
147         char *command;
148 /* The function associated with it */
149         response_codes (*operation) ( command_argument );
150 /* a boolean to indicate if this is a unit or global command
151    unit commands require a unit identifier as first argument */
152         int is_unit;
153 /* What type is the argument (RTTI :-) ATYPE_whatever */
154         int type;
155 /* online help information */
156         char *help;
157
158 command_t;
159
160 /* The following define the queue of commands available to the user. The
161    first entry is the name of the command (the string which must be typed),
162    the second command is the function associated with it, the third argument
163    is for the type of the argument, and the last argument specifies whether
164    this is something which should be handled immediately or whether it
165    should be queued (only robot motion commands need to be queued). */
166
167 static command_t vocabulary[] = 
168 {
169         {"BYE", NULL, 0, ATYPE_NONE, "Terminates the session. Units are not removed and task queue is not flushed."},
170         {"HELP", miracle_help, 0, ATYPE_NONE, "Display this information!"},
171         {"NLS", miracle_list_nodes, 0, ATYPE_NONE, "List the AV/C nodes on the 1394 bus."},
172         {"UADD", miracle_add_unit, 0, ATYPE_STRING, "Create a new DV unit (virtual VTR) to transmit to receiver specified in GUID argument."},
173         {"ULS", miracle_list_units, 0, ATYPE_NONE, "Lists the units that have already been added to the server."},
174         {"CLS", miracle_list_clips, 0, ATYPE_STRING, "Lists the clips at directory name argument."},
175         {"SET", miracle_set_global_property, 0, ATYPE_PAIR, "Set a server configuration property."},
176         {"GET", miracle_get_global_property, 0, ATYPE_STRING, "Get a server configuration property."},
177         {"RUN", miracle_run, 0, ATYPE_STRING, "Run a batch file." },
178         {"LIST", miracle_list, 1, ATYPE_NONE, "List the playlist associated to a unit."},
179         {"LOAD", miracle_load, 1, ATYPE_STRING, "Load clip specified in absolute filename argument."},
180         {"INSERT", miracle_insert, 1, ATYPE_STRING, "Insert a clip at the given clip index."},
181         {"REMOVE", miracle_remove, 1, ATYPE_NONE, "Remove a clip at the given clip index."},
182         {"CLEAN", miracle_clean, 1, ATYPE_NONE, "Clean a unit by removing all but the currently playing clip."},
183         {"CLEAR", miracle_clear, 1, ATYPE_NONE, "Clear a unit by removing all clips."},
184         {"MOVE", miracle_move, 1, ATYPE_INT, "Move a clip to another clip index."},
185         {"APND", miracle_append, 1, ATYPE_STRING, "Append a clip specified in absolute filename argument."},
186         {"PLAY", miracle_play, 1, ATYPE_NONE, "Play a loaded clip at speed -2000 to 2000 where 1000 = normal forward speed."},
187         {"STOP", miracle_stop, 1, ATYPE_NONE, "Stop a loaded and playing clip."},
188         {"PAUSE", miracle_pause, 1, ATYPE_NONE, "Pause a playing clip."},
189         {"REW", miracle_rewind, 1, ATYPE_NONE, "Rewind a unit. If stopped, seek to beginning of clip. If playing, play fast backwards."},
190         {"FF", miracle_ff, 1, ATYPE_NONE, "Fast forward a unit. If stopped, seek to beginning of clip. If playing, play fast forwards."},
191         {"STEP", miracle_step, 1, ATYPE_INT, "Step argument number of frames forward or backward."},
192         {"GOTO", miracle_goto, 1, ATYPE_INT, "Jump to frame number supplied as argument."},
193         {"SIN", miracle_set_in_point, 1, ATYPE_INT, "Set the IN point of the loaded clip to frame number argument. -1 = reset in point to 0"},
194         {"SOUT", miracle_set_out_point, 1, ATYPE_INT, "Set the OUT point of the loaded clip to frame number argument. -1 = reset out point to maximum."},
195         {"USTA", miracle_get_unit_status, 1, ATYPE_NONE, "Report information about the unit."},
196         {"USET", miracle_set_unit_property, 1, ATYPE_PAIR, "Set a unit configuration property."},
197         {"UGET", miracle_get_unit_property, 1, ATYPE_STRING, "Get a unit configuration property."},
198         {"XFER", miracle_transfer, 1, ATYPE_STRING, "Transfer the unit's clip to another unit specified as argument."},
199         {"SHUTDOWN", miracle_shutdown, 0, ATYPE_NONE, "Shutdown the server."},
200         {NULL, NULL, 0, ATYPE_NONE, NULL}
201 };
202
203 /** Usage message 
204 */
205
206 static char helpstr [] = 
207         "Miracle -- A Multimedia Playout Server\n" 
208         "       Copyright (C) 2002-2003 Ushodaya Enterprises Limited\n"
209         "       Authors:\n"
210         "               Dan Dennedy <dan@dennedy.org>\n"
211         "               Charles Yates <charles.yates@pandora.be>\n"
212         "Available commands:\n";
213
214 /** Lookup the response message for a status code.
215 */
216
217 inline char *get_response_msg( int code )
218 {
219         int i = 0;
220         for ( i = 0; responses[ i ].message != NULL && code != responses[ i ].code; i ++ ) ;
221         return responses[ i ].message;
222 }
223
224 /** Tell the user the miracle command set
225 */
226
227 response_codes miracle_help( command_argument cmd_arg )
228 {
229         int i = 0;
230         
231         valerie_response_printf( cmd_arg->response, 10240, "%s", helpstr );
232         
233         for ( i = 0; vocabulary[ i ].command != NULL; i ++ )
234                 valerie_response_printf( cmd_arg->response, 1024,
235                                                         "%-10.10s%s\n", 
236                                                         vocabulary[ i ].command, 
237                                                         vocabulary[ i ].help );
238
239         valerie_response_printf( cmd_arg->response, 2, "\n" );
240
241         return RESPONSE_SUCCESS_N;
242 }
243
244 /** Execute a batch file.
245 */
246
247 response_codes miracle_run( command_argument cmd_arg )
248 {
249         valerie_response temp = valerie_parser_run( cmd_arg->parser, (char *)cmd_arg->argument );
250
251         if ( temp != NULL )
252         {
253                 int index = 0;
254
255                 valerie_response_set_error( cmd_arg->response, 
256                                                            valerie_response_get_error_code( temp ),
257                                                            valerie_response_get_error_string( temp ) );
258
259                 for ( index = 1; index < valerie_response_count( temp ); index ++ )
260                         valerie_response_printf( cmd_arg->response, 10240, "%s\n", valerie_response_get_line( temp, index ) );
261
262                 valerie_response_close( temp );
263         }
264
265         return valerie_response_get_error_code( cmd_arg->response );
266 }
267
268 response_codes miracle_shutdown( command_argument cmd_arg )
269 {
270         exit( 0 );
271         return RESPONSE_SUCCESS;
272 }
273
274 /** Processes 'thread' id
275 */
276
277 static pthread_t self;
278
279 /* Signal handler to deal with various shutdown signals. Basically this
280    should clean up and power down the motor. Note that the death of any
281    child thread will kill all thrads. */
282
283 void signal_handler( int sig )
284 {
285         if ( pthread_equal( self, pthread_self( ) ) )
286         {
287
288 #ifdef _GNU_SOURCE
289                 miracle_log( LOG_DEBUG, "Received %s - shutting down.", strsignal(sig) );
290 #else
291                 miracle_log( LOG_DEBUG, "Received signal %i - shutting down.", sig );
292 #endif
293
294                 exit(EXIT_SUCCESS);
295         }
296 }
297
298 static void sigsegv_handler()
299 {
300 #ifdef linux
301         void *array[ 10 ];
302         size_t size;
303         char **strings;
304         size_t i;
305
306         miracle_log( LOG_CRIT, "\a\nMiracle experienced a segmentation fault.\n"
307                 "Dumping stack from the offending thread\n\n" );
308         size = backtrace( array, 10 );
309         strings = backtrace_symbols( array, size );
310
311         miracle_log( LOG_CRIT, "Obtained %zd stack frames.\n", size );
312
313         for ( i = 0; i < size; i++ )
314                  miracle_log( LOG_CRIT, "%s", strings[ i ] );
315
316         free( strings );
317
318         miracle_log( LOG_CRIT, "\nDone dumping - exiting.\n" );
319 #else
320         miracle_log( LOG_CRIT, "\a\nMiracle experienced a segmentation fault.\n" );
321 #endif
322         exit( EXIT_FAILURE );
323 }
324
325
326
327 /** Local 'connect' function.
328 */
329
330 static valerie_response miracle_local_connect( miracle_local local )
331 {
332         valerie_response response = valerie_response_init( );
333
334         self = pthread_self( );
335
336         valerie_response_set_error( response, 100, "VTR Ready" );
337
338         signal( SIGHUP, signal_handler );
339         signal( SIGINT, signal_handler );
340         signal( SIGTERM, SIG_DFL );
341         signal( SIGSTOP, signal_handler );
342         signal( SIGPIPE, signal_handler );
343         signal( SIGALRM, signal_handler );
344         signal( SIGCHLD, SIG_IGN );
345         if ( getenv( "MLT_SIGSEGV" ) )
346                 signal( SIGSEGV, sigsegv_handler );
347
348         return response;
349 }
350
351 /** Set the error and determine the message associated to this command.
352 */
353
354 void miracle_command_set_error( command_argument cmd, response_codes code )
355 {
356         valerie_response_set_error( cmd->response, code, get_response_msg( code ) );
357 }
358
359 /** Parse the unit argument.
360 */
361
362 int miracle_command_parse_unit( command_argument cmd, int argument )
363 {
364         int unit = -1;
365         char *string = valerie_tokeniser_get_string( cmd->tokeniser, argument );
366         if ( string != NULL && ( string[ 0 ] == 'U' || string[ 0 ] == 'u' ) && strlen( string ) > 1 )
367                 unit = atoi( string + 1 );
368         return unit;
369 }
370
371 /** Parse a normal argument.
372 */
373
374 void *miracle_command_parse_argument( command_argument cmd, int argument, arguments_types type, char *command )
375 {
376         void *ret = NULL;
377         char *value = valerie_tokeniser_get_string( cmd->tokeniser, argument );
378
379         if ( value != NULL )
380         {
381                 switch( type )
382                 {
383                         case ATYPE_NONE:
384                                 break;
385
386                         case ATYPE_FLOAT:
387                                 ret = malloc( sizeof( float ) );
388                                 if ( ret != NULL )
389                                         *( float * )ret = atof( value );
390                                 break;
391
392                         case ATYPE_STRING:
393                                 ret = strdup( value );
394                                 break;
395                                         
396                         case ATYPE_PAIR:
397                                 if ( strchr( command, '=' ) )
398                                 {
399                                         char *ptr = strchr( command, '=' );
400                                         while ( *( ptr - 1 ) != ' ' ) 
401                                                 ptr --;
402                                         ret = strdup( ptr );
403                                         ptr = ret;
404                                         while( ptr[ strlen( ptr ) - 1 ] == ' ' )
405                                                 ptr[ strlen( ptr ) - 1 ] = '\0';
406                                 }
407                                 break;
408
409                         case ATYPE_INT:
410                                 ret = malloc( sizeof( int ) );
411                                 if ( ret != NULL )
412                                         *( int * )ret = atoi( value );
413                                 break;
414                 }
415         }
416
417         return ret;
418 }
419
420 /** Get the error code - note that we simply the success return.
421 */
422
423 response_codes miracle_command_get_error( command_argument cmd )
424 {
425         response_codes ret = valerie_response_get_error_code( cmd->response );
426         if ( ret == RESPONSE_SUCCESS_N || ret == RESPONSE_SUCCESS_1 )
427                 ret = RESPONSE_SUCCESS;
428         return ret;
429 }
430
431 /** Execute the command.
432 */
433
434 static valerie_response miracle_local_execute( miracle_local local, char *command )
435 {
436         command_argument_t cmd;
437         cmd.parser = local->parser;
438         cmd.response = valerie_response_init( );
439         cmd.tokeniser = valerie_tokeniser_init( );
440         cmd.command = command;
441         cmd.unit = -1;
442         cmd.argument = NULL;
443         cmd.root_dir = local->root_dir;
444
445         /* Set the default error */
446         miracle_command_set_error( &cmd, RESPONSE_UNKNOWN_COMMAND );
447
448         /* Parse the command */
449         if ( valerie_tokeniser_parse_new( cmd.tokeniser, command, " " ) > 0 )
450         {
451                 int index = 0;
452                 char *value = valerie_tokeniser_get_string( cmd.tokeniser, 0 );
453                 int found = 0;
454
455                 /* Strip quotes from all tokens */
456                 for ( index = 0; index < valerie_tokeniser_count( cmd.tokeniser ); index ++ )
457                         valerie_util_strip( valerie_tokeniser_get_string( cmd.tokeniser, index ), '\"' );
458
459                 /* Search the vocabulary array for value */
460                 for ( index = 1; !found && vocabulary[ index ].command != NULL; index ++ )
461                         if ( ( found = !strcasecmp( vocabulary[ index ].command, value ) ) )
462                                 break;
463
464                 /* If we found something, the handle the args and call the handler. */
465                 if ( found )
466                 {
467                         int position = 1;
468
469                         miracle_command_set_error( &cmd, RESPONSE_SUCCESS );
470
471                         if ( vocabulary[ index ].is_unit )
472                         {
473                                 cmd.unit = miracle_command_parse_unit( &cmd, position );
474                                 if ( cmd.unit == -1 )
475                                         miracle_command_set_error( &cmd, RESPONSE_MISSING_ARG );
476                                 position ++;
477                         }
478
479                         if ( miracle_command_get_error( &cmd ) == RESPONSE_SUCCESS )
480                         {
481                                 cmd.argument = miracle_command_parse_argument( &cmd, position, vocabulary[ index ].type, command );
482                                 if ( cmd.argument == NULL && vocabulary[ index ].type != ATYPE_NONE )
483                                         miracle_command_set_error( &cmd, RESPONSE_MISSING_ARG );
484                                 position ++;
485                         }
486
487                         if ( miracle_command_get_error( &cmd ) == RESPONSE_SUCCESS )
488                         {
489                                 response_codes error = vocabulary[ index ].operation( &cmd );
490                                 miracle_command_set_error( &cmd, error );
491                         }
492
493                         free( cmd.argument );
494                 }
495         }
496
497         valerie_tokeniser_close( cmd.tokeniser );
498
499         return cmd.response;
500 }
501
502 static valerie_response miracle_local_push( miracle_local local, char *command, mlt_service service )
503 {
504         command_argument_t cmd;
505         cmd.parser = local->parser;
506         cmd.response = valerie_response_init( );
507         cmd.tokeniser = valerie_tokeniser_init( );
508         cmd.command = command;
509         cmd.unit = -1;
510         cmd.argument = NULL;
511         cmd.root_dir = local->root_dir;
512
513         /* Set the default error */
514         miracle_command_set_error( &cmd, RESPONSE_SUCCESS );
515
516         /* Parse the command */
517         if ( valerie_tokeniser_parse_new( cmd.tokeniser, command, " " ) > 0 )
518         {
519                 int index = 0;
520                 int position = 1;
521
522                 /* Strip quotes from all tokens */
523                 for ( index = 0; index < valerie_tokeniser_count( cmd.tokeniser ); index ++ )
524                         valerie_util_strip( valerie_tokeniser_get_string( cmd.tokeniser, index ), '\"' );
525
526                 cmd.unit = miracle_command_parse_unit( &cmd, position );
527                 if ( cmd.unit == -1 )
528                         miracle_command_set_error( &cmd, RESPONSE_MISSING_ARG );
529                 position ++;
530
531                 miracle_push( &cmd, service );
532                 miracle_command_set_error( &cmd, RESPONSE_SUCCESS );
533
534                 free( cmd.argument );
535         }
536
537         valerie_tokeniser_close( cmd.tokeniser );
538
539         return cmd.response;
540 }
541
542 /** Close the parser.
543 */
544
545 static void miracle_local_close( miracle_local local )
546 {
547         miracle_delete_all_units();
548 #ifdef linux
549         //pthread_kill_other_threads_np();
550         miracle_log( LOG_DEBUG, "Clean shutdown." );
551         free( local );
552         mlt_factory_close( );
553 #endif
554 }