Merge branch 'review-1' of git://github.com/rayl/mlt
[melted] / src / miracle / miracle_connection.c
1 /*
2 * miracle_connection.c -- DV Connection Handler
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 <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <signal.h>
30 #include <sys/types.h>
31 #include <unistd.h>
32 #include <time.h>
33 #include <netdb.h>
34 #include <sys/socket.h>
35 #include <arpa/inet.h>
36
37 #include <valerie/valerie_socket.h>
38
39 /* Application header files */
40 #include "miracle_commands.h"
41 #include "miracle_connection.h"
42 #include "miracle_server.h"
43 #include "miracle_log.h"
44
45 /** This is a generic replacement for fgets which operates on a file
46 descriptor. Unlike fgets, we can also specify a line terminator. Maximum
47 of (max - 1) chars can be read into buf from fd. If we reach the
48 end-of-file, *eof_chk is set to 1.
49 */
50
51 int fdgetline( int fd, char *buf, int max, char line_terminator, int *eof_chk )
52 {
53 int count = 0;
54 char tmp [1];
55 *eof_chk = 0;
56
57 if (fd)
58 while (count < max - 1) {
59 if (read (fd, tmp, 1) > 0) {
60 if (tmp [0] != line_terminator)
61 buf [count++] = tmp [0];
62 else
63 break;
64
65 /* Is it an EOF character (ctrl-D, i.e. ascii 4)? If so we definitely want
66 to break. */
67
68 if (tmp [0] == 4) {
69 *eof_chk = 1;
70 break;
71 }
72 } else {
73 *eof_chk = 1;
74 break;
75 }
76 }
77
78 buf [count] = '\0';
79
80 return count;
81 }
82
83 static int connection_initiate( int );
84 static int connection_send( int, valerie_response );
85 static int connection_read( int, char *, int );
86 static void connection_close( int );
87
88 static int connection_initiate( int fd )
89 {
90 int error = 0;
91 valerie_response response = valerie_response_init( );
92 valerie_response_set_error( response, 100, "VTR Ready" );
93 error = connection_send( fd, response );
94 valerie_response_close( response );
95 return error;
96 }
97
98 static int connection_send( int fd, valerie_response response )
99 {
100 int error = 0;
101 int index = 0;
102 int code = valerie_response_get_error_code( response );
103
104 if ( code != -1 )
105 {
106 int items = valerie_response_count( response );
107
108 if ( items == 0 )
109 valerie_response_set_error( response, 500, "Unknown error" );
110
111 if ( code == 200 && items > 2 )
112 valerie_response_set_error( response, 201, "OK" );
113 else if ( code == 200 && items > 1 )
114 valerie_response_set_error( response, 202, "OK" );
115
116 code = valerie_response_get_error_code( response );
117 items = valerie_response_count( response );
118
119 for ( index = 0; !error && index < items; index ++ )
120 {
121 char *line = valerie_response_get_line( response, index );
122 int length = strlen( line );
123 if ( length == 0 && index != valerie_response_count( response ) - 1 && write( fd, " ", 1 ) != 1 )
124 error = -1;
125 else if ( length > 0 && write( fd, line, length ) != length )
126 error = -1;
127 if ( write( fd, "\r\n", 2 ) != 2 )
128 error = -1;
129 }
130
131 if ( ( code == 201 || code == 500 ) && strcmp( valerie_response_get_line( response, items - 1 ), "" ) )
132 write( fd, "\r\n", 2 );
133 }
134 else
135 {
136 const char *message = "500 Empty Response\r\n\r\n";
137 write( fd, message, strlen( message ) );
138 }
139
140 return error;
141 }
142
143 static int connection_read( int fd, char *command, int length )
144 {
145 int eof_chk;
146 int nchars = fdgetline( fd, command, length, '\n', &eof_chk );
147 char *cr = strchr( command, '\r');
148 if ( cr != NULL )
149 cr[0] = '\0';
150 if ( eof_chk || strncasecmp( command, "BYE", 3 ) == 0 )
151 nchars = 0;
152 return nchars;
153 }
154
155 int connection_status( int fd, valerie_notifier notifier )
156 {
157 int error = 0;
158 int index = 0;
159 valerie_status_t status;
160 char text[ 10240 ];
161 valerie_socket socket = valerie_socket_init_fd( fd );
162
163 for ( index = 0; !error && index < MAX_UNITS; index ++ )
164 {
165 valerie_notifier_get( notifier, &status, index );
166 valerie_status_serialise( &status, text, sizeof( text ) );
167 error = valerie_socket_write_data( socket, text, strlen( text ) ) != strlen( text );
168 }
169
170 while ( !error )
171 {
172 if ( valerie_notifier_wait( notifier, &status ) == 0 )
173 {
174 valerie_status_serialise( &status, text, sizeof( text ) );
175 error = valerie_socket_write_data( socket, text, strlen( text ) ) != strlen( text );
176 }
177 else
178 {
179 struct timeval tv = { 0, 0 };
180 fd_set rfds;
181
182 FD_ZERO( &rfds );
183 FD_SET( fd, &rfds );
184
185 if ( select( socket->fd + 1, &rfds, NULL, NULL, &tv ) )
186 error = 1;
187 }
188 }
189
190 valerie_socket_close( socket );
191
192 return error;
193 }
194
195 static void connection_close( int fd )
196 {
197 close( fd );
198 }
199
200 void *parser_thread( void *arg )
201 {
202 struct hostent *he;
203 connection_t *connection = arg;
204 mlt_properties owner = connection->owner;
205 char address[ 512 ];
206 char command[ 1024 ];
207 int fd = connection->fd;
208 valerie_parser parser = connection->parser;
209 valerie_response response = NULL;
210
211 /* Get the connecting clients ip information */
212 he = gethostbyaddr( (char *) &( connection->sin.sin_addr.s_addr ), sizeof(u_int32_t), AF_INET);
213 if ( he != NULL )
214 strcpy( address, he->h_name );
215 else
216 inet_ntop( AF_INET, &( connection->sin.sin_addr.s_addr), address, 32 );
217
218 miracle_log( LOG_NOTICE, "Connection established with %s (%d)", address, fd );
219
220 /* Execute the commands received. */
221 if ( connection_initiate( fd ) == 0 )
222 {
223 int error = 0;
224
225 while( !error && connection_read( fd, command, 1024 ) )
226 {
227 response = NULL;
228
229 if ( !strncmp( command, "PUSH ", 5 ) )
230 {
231 char temp[ 20 ];
232 int bytes;
233 char *buffer = NULL;
234 int total = 0;
235 mlt_service service = NULL;
236
237 connection_read( fd, temp, 20 );
238 bytes = atoi( temp );
239 buffer = malloc( bytes + 1 );
240 while ( total < bytes )
241 {
242 int count = read( fd, buffer + total, bytes - total );
243 if ( count >= 0 )
244 total += count;
245 else
246 break;
247 }
248 buffer[ bytes ] = '\0';
249 if ( bytes > 0 && total == bytes )
250 {
251 if ( mlt_properties_get( owner, "push-parser-off" ) == 0 )
252 {
253 service = ( mlt_service )mlt_factory_producer( NULL, "westley-xml", buffer );
254 mlt_events_fire( owner, "push-received", &response, command, service, NULL );
255 if ( response == NULL )
256 response = valerie_parser_push( parser, command, service );
257 }
258 else
259 {
260 response = valerie_parser_received( parser, command, buffer );
261 }
262 }
263 error = connection_send( fd, response );
264 valerie_response_close( response );
265 mlt_service_close( service );
266 free( buffer );
267 }
268 else if ( strncmp( command, "STATUS", 6 ) )
269 {
270 mlt_events_fire( owner, "command-received", &response, command, NULL );
271 if ( response == NULL )
272 response = valerie_parser_execute( parser, command );
273 miracle_log( LOG_INFO, "%s \"%s\" %d", address, command, valerie_response_get_error_code( response ) );
274 error = connection_send( fd, response );
275 valerie_response_close( response );
276 }
277 else
278 {
279 error = connection_status( fd, valerie_parser_get_notifier( parser ) );
280 }
281 }
282 }
283
284 /* Free the resources associated with this connection. */
285 connection_close( fd );
286
287 miracle_log( LOG_NOTICE, "Connection with %s (%d) closed", address, fd );
288
289 free( connection );
290
291 return NULL;
292 }