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