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