File Coverage

blib/lib/IO/Tail.pm
Criterion Covered Total %
statement 164 187 87.7
branch 41 70 58.5
condition 13 17 76.4
subroutine 21 22 95.4
pod 13 13 100.0
total 252 309 81.5


line stmt bran cond sub pod time code
1             ## ----------------------------------------------------------------------------
2             # IO::Tail
3             # -----------------------------------------------------------------------------
4             # Mastering programmed by YAMASHINA Hio
5             #
6             # Copyright 2007 YAMASHINA Hio
7             # -----------------------------------------------------------------------------
8             # $Id$
9             # -----------------------------------------------------------------------------
10             package IO::Tail;
11 8     8   273121 use strict;
  8         17  
  8         270  
12 8     8   47 use warnings;
  8         16  
  8         503  
13              
14 8     8   7002 use IO::Poll qw(POLLIN POLLERR POLLHUP POLLNVAL);
  8         69261  
  8         9469  
15             our $SEEK_SET = 0;
16             our $SEEK_END = 2;
17             our $POLL_FLAGS = POLLIN | POLLERR | POLLHUP | POLLNVAL;
18              
19             our $VERSION = '0.01';
20              
21             1;
22              
23              
24             =begin COMMENT
25              
26             format for an item of IO::Tail.
27              
28             $type is one of "handle", "file", "timeout", "interval".
29              
30             common case:
31              
32             my $item = {
33             type => $type,
34             name => $name, # e.g. "$type:$obj".
35             callback => $callback,
36             buffer => '',
37             _read => \&_read_handle,
38             };
39              
40             when type is "handle":
41              
42             my $item = {
43             type => 'handle',
44             name => "handle:$handle",
45             handle => $handle,
46             callback => $callback,
47             buffer => '',
48             _read => \&_read_handle,
49             };
50              
51             when type is "file":
52              
53             my $item = {
54             type => 'file',
55             name => $file,
56             handle => $fh,
57             pos => $pos,
58             callback => $callback,
59             buffer => '',
60             _read => \&_read_file,
61             };
62              
63             when type is "timeout":
64              
65             my $item = {
66             type => 'timeout',
67             name => "timeout:$callback",
68             interval => $timeout_secs,
69             timeout => $next_timeout,
70             callback => $callback,
71             };
72              
73             when type is "interval":
74              
75             my $item = {
76             type => 'interval',
77             name => "interval:$callback",
78             interval => $interval_secs,
79             timeout => $next_timeout,
80             callback => $callback,
81             };
82              
83             =end COMMENT
84              
85             =cut
86              
87             # -----------------------------------------------------------------------------
88             # $pkg->new();
89             #
90             sub new
91             {
92 10     10 1 7852 my $pkg = shift;
93 10         39 my $this = bless {}, $pkg;
94 10         66 $this->{poll} = undef;
95 10         30 $this->{handles} = {};
96 10         29 $this->{files} = [];
97 10         28 $this->{timeout} = {};
98            
99 10         31 $this;
100             }
101              
102             # -----------------------------------------------------------------------------
103             # $tail->add($obj, $callback, $opts);
104             #
105             sub add
106             {
107 5     5 1 3718 my $this = shift;
108 5         28 $this->_do('add', @_);
109             }
110              
111             # -----------------------------------------------------------------------------
112             # $tail->remove($obj);
113             #
114             sub remove
115             {
116 2     2 1 4347 my $this = shift;
117 2         112 $this->_do('remove', @_);
118             }
119              
120             # -----------------------------------------------------------------------------
121             # $tail->_do($cmd, $obj, $callback, $opts);
122             #
123             sub _do
124             {
125 7     7   29 my $this = shift;
126 7         14 my $cmd = shift;
127 7         16 my $obj = shift;
128            
129 7 50       73 $cmd =~ /^(?:add|remove)\z/ or die "_do: $cmd";
130 7         13 my $type;
131 7 100       68 if( UNIVERSAL::isa($obj, 'GLOB') )
    100          
132             {
133 2         5 $type = 'handle';
134             }elsif( UNIVERSAL::isa($obj, 'HASH') )
135             {
136 1         3 $type = $obj->{type};
137 1         4 unshift @_, $obj;
138 1 50       8 $obj = $type =~ /^(file|handle)$/ ? $obj->{$type} : $obj->{callback};
139             }else
140             {
141 4         12 $type = 'file';
142             }
143 7         69 my $subname = "${cmd}_${type}";
144 7 50       87 my $sub = $this->can($subname) or die "_do: $subname";
145 7         32 $this->$sub($obj, @_);
146             }
147              
148             # -----------------------------------------------------------------------------
149             # $tail->add_handle($handle, $callback, $opts);
150             #
151             sub add_handle
152             {
153 3     3 1 6 my $this = shift;
154 3         6 my $handle = shift;
155 3         6 my $callback = shift;
156 3         7 my $opts = shift;
157            
158 3   33     7383 my $poll = $this->{poll} ||= IO::Poll->new();
159 3         122 $handle->blocking(0);
160 3         23 $poll->mask($handle, POLLIN);
161            
162 3         112 my $item = {
163             type => 'handle',
164             name => "handle:$handle",
165             handle => $handle,
166             callback => $callback,
167             buffer => '',
168             _read => \&_read_handle,
169             };
170 3         11 $this->{handles}{$handle} = $item;
171            
172 3         15 $this;
173             }
174              
175             # -----------------------------------------------------------------------------
176             # $tail->remove_handle($handle);
177             #
178             sub remove_handle
179             {
180 0     0 1 0 my $this = shift;
181 0         0 my $handle = shift;
182 0 0       0 if( my $item = delete $this->{handles}{$handle} )
183             {
184 0         0 my $poll = $this->{poll};
185 0         0 $poll->remove($handle);
186 0         0 delete $this->{handles}{$handle};
187            
188 0 0       0 if( keys %{$this->{handles}}==0 )
  0         0  
189             {
190 0         0 $this->{poll} = undef;
191             }
192             }
193 0         0 $this;
194             }
195              
196             # -----------------------------------------------------------------------------
197             # $tail->_read_handle($item);
198             #
199             sub _read_handle
200             {
201 2     2   5 my $this = shift;
202 2         4 my $item = shift;
203 2         4 my $handle = $item->{handle};
204 2         25 READ:{
205 2         3 my $len = sysread($handle, $item->{buffer}, 1024, length $item->{buffer});
206 2 50       26 if( $len )
207             {
208 0         0 my $ret = $this->_callback_read($item);
209 0 0       0 $ret or return; # quit.
210 0         0 redo READ;
211             }
212 2 50       9 if( defined($len) )
213             {
214             # eof.
215 2         6 return;
216             }
217 6 0   6   5111 $!{EAGAIN} and last READ;
  6         8626  
  6         9981  
  0         0  
218 0         0 die "sysread: $item->{name}: $!";
219             }
220 0         0 1;
221             }
222              
223             # -----------------------------------------------------------------------------
224             # $tail->add_file($file, $callback, $opts);
225             #
226             sub add_file
227             {
228 3     3 1 6 my $this = shift;
229 3         5 my $file = shift;
230 3         6 my $callback = shift;
231 3         5 my $opts = shift;
232            
233 3 100       22 if( $file eq '-' )
234             {
235 1         8 return $this->add_handle(\*STDIN, $callback, @_);
236             }
237            
238 2 50       140 open(my $fh, '<', $file) or die "$file: $!";
239 2   50     18 my $pos = sysseek($fh, 0, $SEEK_END) || 0;
240 2         20 my $item = {
241             type => 'file',
242             name => $file,
243             handle => $fh,
244             pos => $pos,
245             callback => $callback,
246             buffer => '',
247             _read => \&_read_file,
248             };
249 2         8 push(@{$this->{files}}, $item);
  2         6  
250            
251 2         12 $this;
252             }
253              
254             # -----------------------------------------------------------------------------
255             # $tail->remove_file($file);
256             #
257             sub remove_file
258             {
259 1     1 1 4 my $this = shift;
260 1         2 my $file = shift;
261            
262 1 50       15 if( $file eq '-' )
263             {
264 0         0 return $this->remove_handle(\*STDIN, @_);
265             }
266            
267 1         3 my $files = $this->{files};
268 1         3 foreach my $item (@$files)
269             {
270 1 50       4 $item->{name} eq $file or next;
271 1         3 $item = undef;
272             }
273 1         3 @$files = grep {$_} @$files;
  1         4  
274 1         4 $this;
275             }
276              
277             # -----------------------------------------------------------------------------
278             # $tail->_read_file($item);
279             #
280             sub _read_file
281             {
282 12     12   33 my $this = shift;
283 12         22 my $item = shift;
284            
285 12         237 my $pos = sysseek($item->{handle}, 0, $SEEK_END);
286 12 50       49 defined($pos) or die "sysseek: $item->{name}: $!";
287 12 100       59 if( $pos==$item->{pos} )
288             {
289 8         32 return 1;
290             }
291 4         22 sysseek($item->{handle}, $item->{pos}, $SEEK_SET);
292            
293 4         92 my $len = sysread($item->{handle}, $item->{buffer}, $pos-$item->{pos}, length $item->{buffer});
294 4         17 $item->{pos} = $pos;
295 4 50       16 if( $len )
    0          
296             {
297 4         19 my $ret = $this->_callback_read($item);
298 4 100       13110 $ret or return; # quit.
299             }elsif( defined($len) )
300             {
301             # eof.
302 0         0 return;
303             }else
304             {
305 0 0       0 $!{EAGAIN} and last READ;
306 0         0 die "sysread: $item->{name}: $!";
307             }
308 3         10 1;
309             }
310              
311             # -----------------------------------------------------------------------------
312             # $tail->add_timeout($callback, $timeout_secs, $opts);
313             #
314             sub add_timeout
315             {
316 2     2 1 736 my $this = shift;
317 2         3 my $callback = shift;
318 2         4 my $timeout_secs = shift;
319 2         3 my $opts = shift;
320            
321 2         6 my $next_timeout = time + $timeout_secs;
322 2         15 my $item = {
323             type => 'timeout',
324             name => "timeout:$callback",
325             interval => $timeout_secs,
326             timeout => $next_timeout,
327             callback => $callback,
328             };
329 2         6 $this->{timeout}{$item} = $item;
330 2         6 $this;
331             }
332              
333             # -----------------------------------------------------------------------------
334             # $tail->remove_timeout($callback);
335             #
336             sub remove_timeout
337             {
338 1     1 1 3 my $this = shift;
339 1         2 my $callback = shift;
340 1         12 my $timeout = $this->{timeout};
341 1         6 foreach my $item (values %$timeout)
342             {
343 1 50       8 $item->{callback} eq $callback or next;
344 1         4 delete $timeout->{$item};
345             }
346 1         5 $this;
347             }
348              
349             # -----------------------------------------------------------------------------
350             # $tail->add_interval($callback, $interval_secs, $opts);
351             #
352             sub add_interval
353             {
354 2     2 1 1257 my $this = shift;
355 2         3 my $callback = shift;
356 2         3 my $interval_secs = shift;
357 2         3 my $opts = shift;
358            
359 2         9 my $next_timeout = time + $interval_secs;
360 2         13 my $item = {
361             type => 'interval',
362             name => "interval:$callback",
363             interval => $interval_secs,
364             timeout => $next_timeout,
365             callback => $callback,
366             };
367 2         7 $this->{timeout}{$item} = $item;
368 2         7 $this;
369             }
370              
371             # -----------------------------------------------------------------------------
372             # $tail->remove_interval($callback);
373             #
374             sub remove_interval
375             {
376 2     2 1 5 my $this = shift;
377 2         5 my $callback = shift;
378 2         12 my $timeout = $this->{timeout};
379 2         6 foreach my $item (values %$timeout)
380             {
381 2 50       10 $item->{callback} eq $callback or next;
382 2         10 delete $timeout->{$item};
383             }
384 2         51 $this;
385             }
386              
387             # -----------------------------------------------------------------------------
388             # $tail->_callback_read($item) @ private.
389             #
390             sub _callback_read
391             {
392 4     4   11 my $this = shift;
393 4         9 my $item = shift;
394 4         55 scalar $item->{callback}->(\$item->{buffer}, undef, $item->{args}, $item);
395             }
396              
397             # -----------------------------------------------------------------------------
398             # $tail->_callback_eof($item) @ private.
399             #
400             sub _callback_eof
401             {
402 3     3   6 my $this = shift;
403 3         6 my $item = shift;
404 3 100 66     24 $item && $item->{callback} or return;
405 2         14 scalar $item->{callback}->(\$item->{buffer}, 'eof', $item->{args}, $item);
406             }
407              
408             # -----------------------------------------------------------------------------
409             # $tail->check();
410             #
411             sub check
412             {
413 120     120 1 5002 my $this = shift;
414            
415             # check_handles.
416 120 100       885 if( my $poll = $this->{poll} )
417             {
418 33         295 my $ev = $poll->poll(0);
419 33 50       2015 $ev==-1 and die "poll: $!";
420 33         171 foreach my $handle ($poll->handles($POLL_FLAGS))
421             {
422 2         48 my $item = $this->{handles}{$handle};
423 2         13 my $ret = $item->{_read}->($this, $item);
424 2 50       9 if( !$ret )
425             {
426 2         7 $this->_callback_eof($item);
427 0         0 $poll->remove($handle);
428 0         0 delete $this->{handles}{$handle};
429             }
430             }
431 31 50       773 if( keys %{$this->{handles}}==0 )
  31         264  
432             {
433 0         0 $this->{poll} = undef;
434             }
435             }
436            
437             # check files.
438 118 50       828 if( my $files = $this->{files} )
439             {
440 118         592 foreach my $item (@$files)
441             {
442 12         954 my $ret = $item->{_read}->($this, $item);
443 12 100       85 if( !$ret )
444             {
445 1         31 $this->_callback_eof($item);
446 1         3 $item = undef;
447             }
448             }
449 118         449 @$files = grep {$_} @$files;
  11         65  
450             }
451            
452             # check timeouts and intervals.
453 118 50       864 if( my $timeout = $this->{timeout} )
454             {
455 118         327 my $now = time;
456 118         527 foreach my $item (values %$timeout)
457             {
458 69 100       567 if( $now > $item->{timeout} )
459             {
460 4         77 $item->{callback}->(undef, undef, $item->{args}, $item);
461 4 100       1636 if( $item->{type} eq 'interval' )
462             {
463 3         13 $item->{timeout} = $now + $item->{interval};
464             }else
465             {
466 1         26 delete $timeout->{$item};
467             }
468             }
469             }
470             }
471            
472 118 100 100     1001 ($this->{poll} || @{$this->{files}} || keys %{$this->{timeout}}) && 1;
  87   100     674  
  76         738  
473             }
474              
475             # -----------------------------------------------------------------------------
476             # $tail->loop();
477             #
478             sub loop
479             {
480 7     7 1 19 my $this = shift;
481 7         15 my $timeout_secs = shift;
482 7         10 my $enter_at = time;
483            
484 7         57 while( $this->check() )
485             {
486 102 100 100     656 if( defined($timeout_secs) && time - $enter_at > $timeout_secs )
487             {
488 1         8 last;
489             }
490 101         10142349 select(undef,undef,undef,0.1);
491             }
492             }
493              
494              
495             # -----------------------------------------------------------------------------
496             # End of Module.
497             # -----------------------------------------------------------------------------
498             # -----------------------------------------------------------------------------
499             # End of File.
500             # -----------------------------------------------------------------------------
501             __END__