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