File Coverage

blib/lib/File/Tail/App.pm
Criterion Covered Total %
statement 18 100 18.0
branch 0 70 0.0
condition 0 25 0.0
subroutine 6 13 46.1
pod 1 3 33.3
total 25 211 11.8


line stmt bran cond sub pod time code
1             package File::Tail::App;
2              
3 1     1   24645 use strict;
  1         2  
  1         41  
4 1     1   6 use warnings;
  1         2  
  1         30  
5              
6 1     1   1718 use File::Tail;
  1         43914  
  1         61  
7 1     1   10 use Carp ();
  1         1  
  1         34  
8              
9             sub import {
10 1     1   11 my $caller = caller();
11 1     1   5 no strict 'refs';
  1         1  
  1         1349  
12 1         2 *{ $caller . '::tail_app' } = \&tail_app;
  1         51  
13             }
14              
15             $File::Tail::App::VERSION = '0.4';
16              
17             sub File::Tail::seek_to {
18 0     0 0   my($tail, $seek_to) = @_;
19 0 0         Carp::croak 'argument to seek_to() must be all digits' if $seek_to !~ m/^\d+$/;
20 0           $tail->{'curpos'} = sysseek $tail->{'handle'}, $seek_to, 0;
21             }
22              
23             sub File::Tail::app {
24 0     0 0   my($tail, $args_ref) = @_;
25              
26 0     0     $args_ref->{'line_handler'} = sub { print shift; }
27 0 0         if !$args_ref->{'line_handler'};
28 0 0         Carp::croak '"line_handler" must be an code ref'
29             if ref $args_ref->{'line_handler'} ne 'CODE';
30              
31 0 0         $args_ref->{'verbose'} = 0 if !defined $args_ref->{'verbose'};
32              
33 0           my $lastrun_file = $args_ref->{'lastrun_file'};
34 0   0       my $do_md5_check = $args_ref->{'do_md5_check'} || 0;
35              
36 0 0         my($previous_position, $file_ident, $md5_chk, $md5_len)
37             = defined $lastrun_file ? _get_lastrun_data($tail->{'input'},
38             $lastrun_file,
39             $do_md5_check,
40             $tail)
41             : ('','','','');
42              
43 0 0         $args_ref->{'seek_on_zero'} = 1 if !exists $args_ref->{'seek_on_zero'};
44 0 0 0       if(exists $args_ref->{'seek_to'} && defined $args_ref->{'seek_to'}) {
45 0 0 0       if($args_ref->{'seek_to'} eq '0' && $args_ref->{'seek_on_zero'}) {
    0          
46 0           $tail->seek_to($args_ref->{'seek_to'});
47             }
48             elsif($args_ref->{'seek_to'} =~ m/^\d+$/) {
49 0           $tail->seek_to($args_ref->{'seek_to'});
50             }
51             }
52              
53 0           my $start_size = -s $tail->{'input'};
54 0           my $start_handle = $tail->{'handle'};
55              
56 0 0 0       $tail->seek_to($previous_position) if $previous_position ne $start_size
57             && $previous_position ne '';
58              
59 0           while( defined( my $line = $tail->read() ) ) {
60 0           my @stat = stat $tail->{'input'};
61 0           my $replaced = 0;
62 0 0 0       if(-s $tail->{'input'} < $start_size) {
    0          
63 0 0         Carp::carp "$tail->{'input'} was truncated: " . sysseek($tail->{'handle'},0,1)
64             if $args_ref->{'verbose'};
65 0           $tail->seek_to(length $line);
66 0           $replaced++;
67             }
68             elsif($do_md5_check && $md5_chk ne _get_md5_info($tail,
69             $md5_len,
70             $do_md5_check)) {
71 0 0         Carp::carp "MD5 Check changed: " . sysseek($tail->{'handle'},0,1)
72             if $args_ref->{'verbose'};
73 0           $replaced++;
74             }
75              
76 0 0         if($replaced) {
77 0           $tail->seek_to(length $line);
78 0           $start_size = $stat[7];
79 0 0         $md5_len = $stat[7] < 42 ? $stat[7] : 42;
80 0           $md5_chk = _get_md5_info($tail, $md5_len, $do_md5_check);
81             }
82              
83             # do simple checks then tell them about it & reset some vars if needed
84 0 0 0       if($stat[1] ne $file_ident && $file_ident) {
85 0 0         Carp::carp "$tail->{'input'} was replaced: " . sysseek($tail->{'handle'},0,1)
86             if $args_ref->{'verbose'};
87 0           $file_ident = $stat[1];
88             }
89            
90 0 0         if($start_handle ne $tail->{'handle'}) {
91             # checking descriptor via fileno() is same check but numerically
92 0 0         Carp::carp "descriptor/handle changed: " . sysseek($tail->{'handle'},0,1)
93             if $args_ref->{'verbose'};
94 0           $start_handle = $tail->{'handle'};
95             }
96              
97 0           $args_ref->{'line_handler'}->($line);
98 0 0         _set_lastrun_data(
99             sysseek($tail->{'handle'},0,1),
100             $file_ident,
101             $md5_chk,
102             $md5_len,
103             $lastrun_file
104             ) if defined $lastrun_file;
105 0 0         Carp::carp "$tail->{'input'} is at : " . sysseek($tail->{'handle'},0,1)
106             if $args_ref->{'verbose'} > 1;
107             }
108             }
109              
110             sub tail_app {
111 0     0 1   my ($args_ref) = @_;
112              
113 0 0         Carp::croak 'tail_app() requires a hashref as its first argument'
114             if ref $args_ref ne 'HASH';
115              
116 0 0         Carp::croak 'missing "new" key from tail_app arg' if !exists $args_ref->{'new'};
117 0 0         Carp::croak '"new" must be an array ref' if ref $args_ref->{'new'} ne 'ARRAY';
118              
119 0 0         my $tail = File::Tail->new(@{ $args_ref->{'new'} })
  0            
120             or Carp::croak "Could not create File::Tail object: $!";
121              
122 0           $tail->app({
123             'line_handler' => $args_ref->{'line_handler'},
124             'verbose' => $args_ref->{'verbose'},
125             'seek_to' => $args_ref->{'seek_to'},
126             'seek_on_zero' => $args_ref->{'seek_on_zero'},
127             'lastrun_file' => $args_ref->{'lastrun_file'},
128             'do_md5_check' => $args_ref->{'do_md5_check'},
129             });
130             }
131              
132             sub _get_lastrun_data {
133 0     0     my($tail_file, $cur_file, $do_md5_check, $tail) = @_;
134              
135 0           my @stat = stat $tail_file;
136 0           my $previous_position = 0; # start at zero if 1st time ...
137 0           my $cur_tail_ident = $stat[1]; #:$stat[10]# or if file's changed
138 0 0         my $_md5_len = $stat[7] < 42 ? $stat[7] : 42;
139 0           my $_md5_chk = _get_md5_info($tail, $_md5_len, $do_md5_check);
140              
141 0           my($curpos, $logged_ident, $md5_chk, $md5_len) = ('','',$_md5_chk,$_md5_len);
142 0 0         if(-e $cur_file) {
143 0 0         open my $curat_fh, '<', $cur_file
144             or Carp::croak "Could not read $cur_file: $!";
145 0           chomp(my $first_line = <$curat_fh>);
146 0           close $curat_fh;
147              
148 0           ($curpos, $logged_ident, $md5_chk, $md5_len) = split /-/, $first_line;
149 0 0         $curpos = 0 if $logged_ident ne $cur_tail_ident;
150 0           $previous_position = int($curpos);
151            
152 0 0         if($do_md5_check) {
153 0 0 0       $md5_len = $_md5_len if !defined $md5_len || !$md5_len;
154 0 0 0       $md5_chk = $_md5_chk if !defined $md5_chk || !$md5_chk;
155             }
156             }
157              
158 0           return ($previous_position, $cur_tail_ident, $md5_chk, $md5_len);
159             }
160              
161             sub _set_lastrun_data {
162 0     0     my($new_posi, $file_ident, $md5_chk, $md5_len, $cur_file) = @_;
163 0   0       $md5_chk ||= 0;
164              
165 0 0         open my $curpos_fh, '>', $cur_file
166             or Carp::croak "Could not write $cur_file: $!";
167 0           print {$curpos_fh} qq($new_posi-$file_ident-$md5_chk-$md5_len);
  0            
168 0           close $curpos_fh;
169             }
170              
171             sub _get_md5_info {
172 0     0     my($tail, $md5_len, $do_md5_check) = @_;
173              
174 0 0         return if !$do_md5_check;
175 0           require Digest::MD5; # only do the module if needed
176              
177 0           my $data_to_md5 = ''; # to avoid uninitialized value warnings
178 0           my $origpos = sysseek($tail->{'handle'},0,1);
179              
180 0           $tail->seek_to(0);
181 0           sysread $tail->{'handle'}, $data_to_md5, $md5_len;
182 0           $tail->seek_to($origpos);
183              
184 0           return Digest::MD5::md5_hex($data_to_md5);
185             }
186              
187             1;
188              
189             __END__