File Coverage

blib/lib/Festival/Client/Async.pm
Criterion Covered Total %
statement 27 180 15.0
branch 2 58 3.4
condition 3 22 13.6
subroutine 9 27 33.3
pod 2 19 10.5
total 43 306 14.0


line stmt bran cond sub pod time code
1             # Festival::Client::Async: Non-blocking interface to a Festival server
2             #
3             # Copyright (c) 2000 Cepstral LLC. All rights Reserved.
4             #
5             # This module is free software; you can redistribute it and/or modify
6             # it under the same terms as Perl itself.
7             #
8             # Written by David Huggins-Daines
9              
10             package Festival::Client::Async;
11 1     1   985 use strict;
  1         2  
  1         40  
12 1     1   1107 use IO::Socket;
  1         36438  
  1         5  
13 1     1   862 use Fcntl;
  1         7  
  1         397  
14              
15             BEGIN {
16 1 50   1   6 unless (defined &DEBUG) {
17             *DEBUG = sub () { 0 }
18 1         32 }
19             };
20              
21 1     1   7 use vars qw($VERSION @ISA @EXPORT_OK);
  1         2  
  1         857  
22             $VERSION = 0.03_03;
23             @ISA = qw(Exporter);
24             @EXPORT_OK = qw(parse_lisp);
25              
26             sub parse_lisp {
27 0     0 0 0 my $lisp = shift;
28              
29 0         0 my (@stack, $top);
30 0         0 $top = [];
31 0         0 @stack = ($top);
32 0         0 while ($lisp =~ m{(
33             [()]
34             |
35             "(?:[^"\\]+|\\.)*"
36             |
37             \#<[^>]+>
38             |
39             [^()\s]+
40             )}xg) {
41 0         0 my $tok = $1;
42 0 0       0 if ($tok eq '(') {
    0          
43 0         0 my $newtop = [];
44 0         0 push @$top, $newtop;
45 0         0 push @stack, ($top = $newtop);
46             } elsif ($tok eq ')') {
47 0         0 pop @stack;
48 0         0 $top = $stack[-1];
49 0 0       0 die "stack underflow" unless defined $top;
50             } else {
51 0         0 push @$top, $tok;
52             }
53             }
54              
55 0         0 return $top->[0];
56             }
57              
58             sub new {
59 1     1 1 312 my $this = shift;
60 1   33     8 my $class = ref $this || $this;
61              
62 1         3 my ($host, $port) = @_;
63              
64 1 50 50     30 my $s = IO::Socket::INET->new(Proto => 'tcp',
      50        
65             PeerAddr => $host || 'localhost',
66             PeerPort => $port || 1314)
67             or return undef;
68 0           binmode $s;
69              
70 0           my $self = bless {
71             blocked => 0,
72             sock => $s,
73             outbuf => "",
74             outq => {
75             LP => [],
76             },
77             intag => "",
78             inbuf => "",
79             inq => {
80             LP => [],
81             WV => [],
82             OK => [],
83             ER => [],
84             },
85             }, $class;
86 0           $self->unblock;
87 0           return $self;
88             }
89              
90             sub fh {
91 0     0 1   my $self = shift;
92 0           return $self->{sock};
93             }
94              
95             sub block {
96 0     0 0   my $self = shift;
97 0           my $flags = 0;
98 0 0         fcntl $self->{sock}, F_GETFL, $flags
99             or die "fcntl(F_GETFL) failed: $!";
100 0 0         fcntl $self->{sock}, F_SETFL, $flags & ~O_NONBLOCK
101             or die "fcntl(F_SETFL) failed: $!";
102 0           $self->{blocked} = 1;
103             }
104              
105             sub unblock {
106 0     0 0   my $self = shift;
107 0           my $flags = 0;
108 0 0         fcntl $self->{sock}, F_GETFL, $flags
109             or die "fcntl(F_GETFL) failed: $!";
110 0 0         fcntl $self->{sock}, F_SETFL, $flags | O_NONBLOCK
111             or die "fcntl(F_SETFL) failed: $!";
112 0           $self->{blocked} = 0;
113             }
114              
115             # Protocol encoding
116 1     1   8 use constant KEY => "ft_StUfF_key";
  1         2  
  1         225  
117 1     1   7 use constant KEYLEN => length KEY;
  1         2  
  1         402  
118              
119             sub write_more {
120 0     0 0   my $self = shift;
121              
122 0           while (defined(my $expr = shift @{$self->{outq}{LP}})) {
  0            
123 0           $self->{outbuf} .= $expr;
124             }
125              
126 0           my $count;
127 0           while (defined(my $b = syswrite($self->{sock}, $self->{outbuf}, 4096))) {
128 0           print "wrote $b bytes\n" if DEBUG;
129 0 0         last if $b == 0;
130              
131 0           $count += $b;
132 0           substr($self->{outbuf}, 0, $b) = "";
133 0 0 0       last if $self->{blocked} and $b < 4096;
134             }
135              
136 0           return $count;
137             }
138              
139             sub read_more {
140 0     0 0   my $self = shift;
141 0           my $fh = $self->{sock};
142              
143 0           my $count = 0;
144 0           my $burf = sysread $fh, my($rbuf), 4096;
145 0           print "read $burf bytes\n" if DEBUG;
146 0           $self->{inbuf} .= $rbuf;
147              
148             CHUNK:
149 0           while (length($self->{inbuf}) > 0) {
150             # In the middle of a tag?
151 0 0         if ($self->{intag}) {
152             # Look for the stuff key
153 1 0   1   1108 if ((my $i = index($self->{inbuf}, KEY)) != $[-1) {
  1         481  
  1         1500  
  0            
154 0 0         if (substr($self->{inbuf}, $i+KEYLEN, 1) eq 'X') {
155             # If there's an X at the end, it's literal
156 0           substr($self->{inbuf}, $i+KEYLEN, 1) = "";
157             } else {
158             # Otherwise, we've got a complete waveform/expr/whatever
159 0           push @{$self->{inq}{$self->{intag}}},
  0            
160             substr($self->{inbuf}, 0, $i);
161 0           print "queued $i bytes of $self->{intag}\n" if DEBUG;
162 0           substr($self->{inbuf}, 0, $i+KEYLEN) = "";
163 0           $self->{intag} = "";
164 0           $count += $i;
165             }
166             } else {
167             # Maybe we got *part* of the stuff key at the end of
168             # this block. Stranger things have happened.
169 0           my $leftover = "";
170             PARTIAL:
171 0           for my $sub (1..KEYLEN-1) {
172 0           my $foo = \substr($self->{inbuf}, -$sub);
173 0           my $bar = substr(KEY, 0, $sub);
174 0 0         if ($$foo eq $bar) {
175 0           $$foo = "";
176 0           $leftover = $bar;
177 0           last PARTIAL;
178             }
179             }
180              
181             # In any case we don't have any more data
182 0           push @{$self->{inq}{$self->{intag}}}, $self->{inbuf};
  0            
183 0           print "queued ", length($self->{inbuf}), " bytes of $self->{intag}\n"
184             if DEBUG;
185 0           $count += length($self->{inbuf});
186 0           $self->{inbuf} = $leftover;
187              
188             # But don't keep looping if we left some stuff in there!
189 0 0         last CHUNK if $leftover;
190             }
191             } else {
192 0 0         if ($self->{inbuf} =~ s/^(WV|LP|ER|OK)\n//) {
193 0           print "got tag $1\n" if DEBUG;
194 0           $count += length($1);
195             # We got a tag, so a new type of data is coming
196 0 0         if ($1 eq 'OK') {
    0          
197 0           push @{$self->{inq}{OK}}, time;
  0            
198             } elsif ($1 eq 'ER') {
199 0           push @{$self->{inq}{ER}}, time;
  0            
200             } else {
201 0           $self->{intag} = $1;
202             }
203             } else {
204             # Should not actually be fatal, it's always possible
205             # we just got the middle of a tag.
206 0           last CHUNK;
207             }
208             }
209             }
210              
211 0           return $count;
212             }
213              
214             sub server_eval_sync {
215 0     0 0   my ($self, $lisp, $actions) = @_;
216 0           $self->block;
217 0           $self->server_eval($lisp);
218              
219 0 0         unless ($self->write_more) {
220 0           $self->unblock;
221 0           return undef;
222             }
223 0           while ($self->read_more) {
224 0           while (defined(my $wav = $self->dequeue_wave)) {
225 0 0         $actions->{WV}->($wav) if exists $actions->{WV};
226             }
227 0           while (defined(my $lisp = $self->dequeue_lisp)) {
228 0 0         $actions->{LP}->($lisp) if exists $actions->{LP};
229             }
230 0 0         if (defined($self->dequeue_error)) {
231 0           $self->unblock;
232 0           return undef;
233             }
234 0 0         if (defined($self->dequeue_ok)) {
235 0           last;
236             }
237             }
238 0           $self->unblock;
239 0           return 1;
240             }
241              
242             # Don't mix this with async operations :(
243             sub server_eval_sync_old {
244 0     0 0   my ($self, $lisp, $actions) = @_;
245 0           my $fh = $self->{sock};
246 0           $self->block;
247              
248 0           local $|=1;
249              
250 0           my ($rbuf, $rest, $tag);
251 0           print $fh $lisp;
252 0   0       while (defined($rbuf = $rest) or defined($rbuf = <$fh>)) {
253 0           undef $rest;
254 0 0         if ($rbuf =~ s/^(WV|LP|ER|OK)\n$//s) {
255 0           $tag = $1;
256 0 0 0       last if $tag eq 'OK' or $tag eq 'ER';
257             }
258              
259 0 0         if ((my $i = index($rbuf, KEY)) != $[-1) {
260 0 0         if (substr($rbuf, $i+KEYLEN, 1) eq 'X') {
261 0           substr($rbuf, $i+KEYLEN, 1) = "";
262             } else {
263 0           $rest = substr($rbuf, $i+KEYLEN);
264 0           substr($rbuf, $i) = "";
265             }
266             }
267              
268 0 0 0       if (defined $tag and exists $actions->{$tag}) {
269 0           $actions->{$tag}->($rbuf);
270             }
271             }
272              
273 0           $self->unblock;
274 0   0       return defined($tag) && ($tag eq 'OK');
275             }
276              
277             sub server_eval {
278 0     0 0   my $self = shift;
279 0           push @{$self->{outq}{LP}}, @_;
  0            
280             }
281              
282             sub write_pending {
283 0     0 0   my $self = shift;
284 0           return @{$self->{outq}{LP}};
  0            
285             }
286              
287             sub wave_pending {
288 0     0 0   my $self = shift;
289 0           return @{$self->{inq}{WV}};
  0            
290             }
291              
292             sub lisp_pending {
293 0     0 0   my $self = shift;
294 0           return @{$self->{inq}{LP}};
  0            
295             }
296              
297             sub ok_pending {
298 0     0 0   my $self = shift;
299 0           return @{$self->{inq}{OK}};
  0            
300             }
301              
302             sub error_pending {
303 0     0 0   my $self = shift;
304 0           return @{$self->{inq}{ER}};
  0            
305             }
306              
307             sub dequeue_wave {
308 0     0 0   my $self = shift;
309 0           shift @{$self->{inq}{WV}};
  0            
310             }
311              
312             sub dequeue_lisp {
313 0     0 0   my $self = shift;
314 0           shift @{$self->{inq}{LP}};
  0            
315             }
316              
317             sub dequeue_ok {
318 0     0 0   my $self = shift;
319 0           shift @{$self->{inq}{OK}};
  0            
320             }
321              
322             sub dequeue_error {
323 0     0 0   my $self = shift;
324 0           shift @{$self->{inq}{ER}};
  0            
325             }
326              
327             1;
328             __END__