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