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