]> git.sesse.net Git - mlt/blob - src/miracle/miracle_local.c
c1029a19d828530f712ed605e998370692f58dcc
[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 valerie_response miracle_local_receive( miracle_local, char *, char * );
65 static void miracle_local_close( miracle_local );
66 response_codes miracle_help( command_argument arg );
67 response_codes miracle_run( command_argument arg );
68 response_codes miracle_shutdown( command_argument arg );
69
70 /** DV Parser constructor.
71 */
72
73 valerie_parser miracle_parser_init_local( )
74 {
75         valerie_parser parser = malloc( sizeof( valerie_parser_t ) );
76         miracle_local local = malloc( sizeof( miracle_local_t ) );
77
78         if ( parser != NULL )
79         {
80                 memset( parser, 0, sizeof( valerie_parser_t ) );
81
82                 parser->connect = (parser_connect)miracle_local_connect;
83                 parser->execute = (parser_execute)miracle_local_execute;
84                 parser->push = (parser_push)miracle_local_push;
85                 parser->received = (parser_received)miracle_local_receive;
86                 parser->close = (parser_close)miracle_local_close;
87                 parser->real = local;
88
89                 if ( local != NULL )
90                 {
91                         memset( local, 0, sizeof( miracle_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", miracle_help, 0, ATYPE_NONE, "Display this information!"},
173         {"NLS", miracle_list_nodes, 0, ATYPE_NONE, "List the AV/C nodes on the 1394 bus."},
174         {"UADD", miracle_add_unit, 0, ATYPE_STRING, "Create a new DV unit (virtual VTR) to transmit to receiver specified in GUID argument."},
175         {"ULS", miracle_list_units, 0, ATYPE_NONE, "Lists the units that have already been added to the server."},
176         {"CLS", miracle_list_clips, 0, ATYPE_STRING, "Lists the clips at directory name argument."},
177         {"SET", miracle_set_global_property, 0, ATYPE_PAIR, "Set a server configuration property."},
178         {"GET", miracle_get_global_property, 0, ATYPE_STRING, "Get a server configuration property."},
179         {"RUN", miracle_run, 0, ATYPE_STRING, "Run a batch file." },
180         {"LIST", miracle_list, 1, ATYPE_NONE, "List the playlist associated to a unit."},
181         {"LOAD", miracle_load, 1, ATYPE_STRING, "Load clip specified in absolute filename argument."},
182         {"INSERT", miracle_insert, 1, ATYPE_STRING, "Insert a clip at the given clip index."},
183         {"REMOVE", miracle_remove, 1, ATYPE_NONE, "Remove a clip at the given clip index."},
184         {"CLEAN", miracle_clean, 1, ATYPE_NONE, "Clean a unit by removing all but the currently playing clip."},
185         {"WIPE", miracle_wipe, 1, ATYPE_NONE, "Clean a unit by removing everything before the currently playing clip."},
186         {"CLEAR", miracle_clear, 1, ATYPE_NONE, "Clear a unit by removing all clips."},
187         {"MOVE", miracle_move, 1, ATYPE_INT, "Move a clip to another clip index."},
188         {"APND", miracle_append, 1, ATYPE_STRING, "Append a clip specified in absolute filename argument."},
189         {"PLAY", miracle_play, 1, ATYPE_NONE, "Play a loaded clip at speed -2000 to 2000 where 1000 = normal forward speed."},
190         {"STOP", miracle_stop, 1, ATYPE_NONE, "Stop a loaded and playing clip."},
191         {"PAUSE", miracle_pause, 1, ATYPE_NONE, "Pause a playing clip."},
192         {"REW", miracle_rewind, 1, ATYPE_NONE, "Rewind a unit. If stopped, seek to beginning of clip. If playing, play fast backwards."},
193         {"FF", miracle_ff, 1, ATYPE_NONE, "Fast forward a unit. If stopped, seek to beginning of clip. If playing, play fast forwards."},
194         {"STEP", miracle_step, 1, ATYPE_INT, "Step argument number of frames forward or backward."},
195         {"GOTO", miracle_goto, 1, ATYPE_INT, "Jump to frame number supplied as argument."},
196         {"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"},
197         {"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."},
198         {"USTA", miracle_get_unit_status, 1, ATYPE_NONE, "Report information about the unit."},
199         {"USET", miracle_set_unit_property, 1, ATYPE_PAIR, "Set a unit configuration property."},
200         {"UGET", miracle_get_unit_property, 1, ATYPE_STRING, "Get a unit configuration property."},
201         {"XFER", miracle_transfer, 1, ATYPE_STRING, "Transfer the unit's clip to another unit specified as argument."},
202         {"SHUTDOWN", miracle_shutdown, 0, ATYPE_NONE, "Shutdown the server."},
203         {NULL, NULL, 0, ATYPE_NONE, NULL}
204 };
205
206 /** Usage message 
207 */
208
209 static char helpstr [] = 
210         "Miracle -- A Multimedia Playout Server\n" 
211         "       Copyright (C) 2002-2003 Ushodaya Enterprises Limited\n"
212         "       Authors:\n"
213         "               Dan Dennedy <dan@dennedy.org>\n"
214         "               Charles Yates <charles.yates@pandora.be>\n"
215         "Available commands:\n";
216
217 /** Lookup the response message for a status code.
218 */
219
220 inline const char *get_response_msg( int code )
221 {
222         int i = 0;
223         for ( i = 0; responses[ i ].message != NULL && code != responses[ i ].code; i ++ ) ;
224         return responses[ i ].message;
225 }
226
227 /** Tell the user the miracle command set
228 */
229
230 response_codes miracle_help( command_argument cmd_arg )
231 {
232         int i = 0;
233         
234         valerie_response_printf( cmd_arg->response, 10240, "%s", helpstr );
235         
236         for ( i = 0; vocabulary[ i ].command != NULL; i ++ )
237                 valerie_response_printf( cmd_arg->response, 1024,
238                                                         "%-10.10s%s\n", 
239                                                         vocabulary[ i ].command, 
240                                                         vocabulary[ i ].help );
241
242         valerie_response_printf( cmd_arg->response, 2, "\n" );
243
244         return RESPONSE_SUCCESS_N;
245 }
246
247 /** Execute a batch file.
248 */
249
250 response_codes miracle_run( command_argument cmd_arg )
251 {
252         valerie_response temp = valerie_parser_run( cmd_arg->parser, (char *)cmd_arg->argument );
253
254         if ( temp != NULL )
255         {
256                 int index = 0;
257
258                 valerie_response_set_error( cmd_arg->response, 
259                                                            valerie_response_get_error_code( temp ),
260                                                            valerie_response_get_error_string( temp ) );
261
262                 for ( index = 1; index < valerie_response_count( temp ); index ++ )
263                         valerie_response_printf( cmd_arg->response, 10240, "%s\n", valerie_response_get_line( temp, index ) );
264
265                 valerie_response_close( temp );
266         }
267
268         return valerie_response_get_error_code( cmd_arg->response );
269 }
270
271 response_codes miracle_shutdown( command_argument cmd_arg )
272 {
273         exit( 0 );
274         return RESPONSE_SUCCESS;
275 }
276
277 /** Processes 'thread' id
278 */
279
280 static pthread_t self;
281
282 /* Signal handler to deal with various shutdown signals. Basically this
283    should clean up and power down the motor. Note that the death of any
284    child thread will kill all thrads. */
285
286 void signal_handler( int sig )
287 {
288         if ( pthread_equal( self, pthread_self( ) ) )
289         {
290
291 #ifdef _GNU_SOURCE
292                 miracle_log( LOG_DEBUG, "Received %s - shutting down.", strsignal(sig) );
293 #else
294                 miracle_log( LOG_DEBUG, "Received signal %i - shutting down.", sig );
295 #endif
296
297                 exit(EXIT_SUCCESS);
298         }
299 }
300
301 static void sigsegv_handler()
302 {
303 #ifdef linux
304         void *array[ 10 ];
305         size_t size;
306         char **strings;
307         size_t i;
308
309         miracle_log( LOG_CRIT, "\a\nMiracle experienced a segmentation fault.\n"
310                 "Dumping stack from the offending thread\n\n" );
311         size = backtrace( array, 10 );
312         strings = backtrace_symbols( array, size );
313
314         miracle_log( LOG_CRIT, "Obtained %zd stack frames.\n", size );
315
316         for ( i = 0; i < size; i++ )
317                  miracle_log( LOG_CRIT, "%s", strings[ i ] );
318
319         free( strings );
320
321         miracle_log( LOG_CRIT, "\nDone dumping - exiting.\n" );
322 #else
323         miracle_log( LOG_CRIT, "\a\nMiracle experienced a segmentation fault.\n" );
324 #endif
325         exit( EXIT_FAILURE );
326 }
327
328
329
330 /** Local 'connect' function.
331 */
332
333 static valerie_response miracle_local_connect( miracle_local local )
334 {
335         valerie_response response = valerie_response_init( );
336
337         self = pthread_self( );
338
339         valerie_response_set_error( response, 100, "VTR Ready" );
340
341         signal( SIGHUP, signal_handler );
342         signal( SIGINT, signal_handler );
343         signal( SIGTERM, SIG_DFL );
344         signal( SIGSTOP, signal_handler );
345         signal( SIGPIPE, signal_handler );
346         signal( SIGALRM, signal_handler );
347         signal( SIGCHLD, SIG_IGN );
348         if ( getenv( "MLT_SIGSEGV" ) )
349                 signal( SIGSEGV, sigsegv_handler );
350
351         return response;
352 }
353
354 /** Set the error and determine the message associated to this command.
355 */
356
357 void miracle_command_set_error( command_argument cmd, response_codes code )
358 {
359         valerie_response_set_error( cmd->response, code, get_response_msg( code ) );
360 }
361
362 /** Parse the unit argument.
363 */
364
365 int miracle_command_parse_unit( command_argument cmd, int argument )
366 {
367         int unit = -1;
368         char *string = valerie_tokeniser_get_string( cmd->tokeniser, argument );
369         if ( string != NULL && ( string[ 0 ] == 'U' || string[ 0 ] == 'u' ) && strlen( string ) > 1 )
370                 unit = atoi( string + 1 );
371         return unit;
372 }
373
374 /** Parse a normal argument.
375 */
376
377 void *miracle_command_parse_argument( command_argument cmd, int argument, arguments_types type, char *command )
378 {
379         void *ret = NULL;
380         char *value = valerie_tokeniser_get_string( cmd->tokeniser, argument );
381
382         if ( value != NULL )
383         {
384                 switch( type )
385                 {
386                         case ATYPE_NONE:
387                                 break;
388
389                         case ATYPE_FLOAT:
390                                 ret = malloc( sizeof( float ) );
391                                 if ( ret != NULL )
392                                         *( float * )ret = atof( value );
393                                 break;
394
395                         case ATYPE_STRING:
396                                 ret = strdup( value );
397                                 break;
398                                         
399                         case ATYPE_PAIR:
400                                 if ( strchr( command, '=' ) )
401                                 {
402                                         char *ptr = strchr( command, '=' );
403                                         while ( *( ptr - 1 ) != ' ' ) 
404                                                 ptr --;
405                                         ret = strdup( ptr );
406                                         ptr = ret;
407                                         while( ptr[ strlen( ptr ) - 1 ] == ' ' )
408                                                 ptr[ strlen( ptr ) - 1 ] = '\0';
409                                 }
410                                 break;
411
412                         case ATYPE_INT:
413                                 ret = malloc( sizeof( int ) );
414                                 if ( ret != NULL )
415                                         *( int * )ret = atoi( value );
416                                 break;
417                 }
418         }
419
420         return ret;
421 }
422
423 /** Get the error code - note that we simply the success return.
424 */
425
426 response_codes miracle_command_get_error( command_argument cmd )
427 {
428         response_codes ret = valerie_response_get_error_code( cmd->response );
429         if ( ret == RESPONSE_SUCCESS_N || ret == RESPONSE_SUCCESS_1 )
430                 ret = RESPONSE_SUCCESS;
431         return ret;
432 }
433
434 /** Execute the command.
435 */
436
437 static valerie_response miracle_local_execute( miracle_local local, char *command )
438 {
439         command_argument_t cmd;
440         cmd.parser = local->parser;
441         cmd.response = valerie_response_init( );
442         cmd.tokeniser = valerie_tokeniser_init( );
443         cmd.command = command;
444         cmd.unit = -1;
445         cmd.argument = NULL;
446         cmd.root_dir = local->root_dir;
447
448         /* Set the default error */
449         miracle_command_set_error( &cmd, RESPONSE_UNKNOWN_COMMAND );
450
451         /* Parse the command */
452         if ( valerie_tokeniser_parse_new( cmd.tokeniser, command, " " ) > 0 )
453         {
454                 int index = 0;
455                 char *value = valerie_tokeniser_get_string( cmd.tokeniser, 0 );
456                 int found = 0;
457
458                 /* Strip quotes from all tokens */
459                 for ( index = 0; index < valerie_tokeniser_count( cmd.tokeniser ); index ++ )
460                         valerie_util_strip( valerie_tokeniser_get_string( cmd.tokeniser, index ), '\"' );
461
462                 /* Search the vocabulary array for value */
463                 for ( index = 1; !found && vocabulary[ index ].command != NULL; index ++ )
464                         if ( ( found = !strcasecmp( vocabulary[ index ].command, value ) ) )
465                                 break;
466
467                 /* If we found something, the handle the args and call the handler. */
468                 if ( found )
469                 {
470                         int position = 1;
471
472                         miracle_command_set_error( &cmd, RESPONSE_SUCCESS );
473
474                         if ( vocabulary[ index ].is_unit )
475                         {
476                                 cmd.unit = miracle_command_parse_unit( &cmd, position );
477                                 if ( cmd.unit == -1 )
478                                         miracle_command_set_error( &cmd, RESPONSE_MISSING_ARG );
479                                 position ++;
480                         }
481
482                         if ( miracle_command_get_error( &cmd ) == RESPONSE_SUCCESS )
483                         {
484                                 cmd.argument = miracle_command_parse_argument( &cmd, position, vocabulary[ index ].type, command );
485                                 if ( cmd.argument == NULL && vocabulary[ index ].type != ATYPE_NONE )
486                                         miracle_command_set_error( &cmd, RESPONSE_MISSING_ARG );
487                                 position ++;
488                         }
489
490                         if ( miracle_command_get_error( &cmd ) == RESPONSE_SUCCESS )
491                         {
492                                 response_codes error = vocabulary[ index ].operation( &cmd );
493                                 miracle_command_set_error( &cmd, error );
494                         }
495
496                         free( cmd.argument );
497                 }
498         }
499
500         valerie_tokeniser_close( cmd.tokeniser );
501
502         return cmd.response;
503 }
504
505 static valerie_response miracle_local_receive( miracle_local local, char *command, char *doc )
506 {
507         command_argument_t cmd;
508         cmd.parser = local->parser;
509         cmd.response = valerie_response_init( );
510         cmd.tokeniser = valerie_tokeniser_init( );
511         cmd.command = command;
512         cmd.unit = -1;
513         cmd.argument = NULL;
514         cmd.root_dir = local->root_dir;
515
516         /* Set the default error */
517         miracle_command_set_error( &cmd, RESPONSE_SUCCESS );
518
519         /* Parse the command */
520         if ( valerie_tokeniser_parse_new( cmd.tokeniser, command, " " ) > 0 )
521         {
522                 int index = 0;
523                 int position = 1;
524
525                 /* Strip quotes from all tokens */
526                 for ( index = 0; index < valerie_tokeniser_count( cmd.tokeniser ); index ++ )
527                         valerie_util_strip( valerie_tokeniser_get_string( cmd.tokeniser, index ), '\"' );
528
529                 cmd.unit = miracle_command_parse_unit( &cmd, position );
530                 if ( cmd.unit == -1 )
531                         miracle_command_set_error( &cmd, RESPONSE_MISSING_ARG );
532                 position ++;
533
534                 miracle_receive( &cmd, doc );
535                 miracle_command_set_error( &cmd, RESPONSE_SUCCESS );
536
537                 free( cmd.argument );
538         }
539
540         valerie_tokeniser_close( cmd.tokeniser );
541
542         return cmd.response;
543 }
544
545 static valerie_response miracle_local_push( miracle_local local, char *command, mlt_service service )
546 {
547         command_argument_t cmd;
548         cmd.parser = local->parser;
549         cmd.response = valerie_response_init( );
550         cmd.tokeniser = valerie_tokeniser_init( );
551         cmd.command = command;
552         cmd.unit = -1;
553         cmd.argument = NULL;
554         cmd.root_dir = local->root_dir;
555
556         /* Set the default error */
557         miracle_command_set_error( &cmd, RESPONSE_SUCCESS );
558
559         /* Parse the command */
560         if ( valerie_tokeniser_parse_new( cmd.tokeniser, command, " " ) > 0 )
561         {
562                 int index = 0;
563                 int position = 1;
564
565                 /* Strip quotes from all tokens */
566                 for ( index = 0; index < valerie_tokeniser_count( cmd.tokeniser ); index ++ )
567                         valerie_util_strip( valerie_tokeniser_get_string( cmd.tokeniser, index ), '\"' );
568
569                 cmd.unit = miracle_command_parse_unit( &cmd, position );
570                 if ( cmd.unit == -1 )
571                         miracle_command_set_error( &cmd, RESPONSE_MISSING_ARG );
572                 position ++;
573
574                 miracle_push( &cmd, service );
575                 miracle_command_set_error( &cmd, RESPONSE_SUCCESS );
576
577                 free( cmd.argument );
578         }
579
580         valerie_tokeniser_close( cmd.tokeniser );
581
582         return cmd.response;
583 }
584
585 /** Close the parser.
586 */
587
588 static void miracle_local_close( miracle_local local )
589 {
590         miracle_delete_all_units();
591 #ifdef linux
592         //pthread_kill_other_threads_np();
593         miracle_log( LOG_DEBUG, "Clean shutdown." );
594         //free( local );
595         //mlt_factory_close( );
596 #endif
597 }