File Coverage

blib/lib/Tail/Tool/File.pm
Criterion Covered Total %
statement 45 107 42.0
branch 10 56 17.8
condition 5 31 16.1
subroutine 9 16 56.2
pod 3 3 100.0
total 72 213 33.8


line stmt bran cond sub pod time code
1             package Tail::Tool::File;
2              
3             # Created on: 2010-10-25 11:11:38
4             # Create by: Ivan Wills
5             # $Id$
6             # $Revision$, $HeadURL$, $Date$
7             # $Revision$, $Source$, $Date$
8              
9 3     3   903 use Moose;
  3         294075  
  3         18  
10 3     3   14038 use warnings;
  3         4  
  3         81  
11 3     3   873 use version;
  3         1512  
  3         18  
12 3     3   161 use Carp;
  3         3  
  3         169  
13 3     3   9 use Scalar::Util qw/openhandle/;
  3         3  
  3         122  
14 3     3   392 use English qw/ -no_match_vars /;
  3         1284  
  3         14  
15 3     3   3499 use AnyEvent;
  3         11854  
  3         3422  
16              
17             our $VERSION = version->new('0.4.7');
18              
19             has name => (
20             is => 'rw',
21             isa => 'Str',
22             documentation => 'The name of a file to be watched',
23             );
24             has remote => (
25             is => 'rw',
26             isa => 'Bool',
27             default => 0,
28             init_arg => undef,
29             documentation => 'Flags that the file is located on a remote server',
30             );
31             has cmd => (
32             is => 'rw',
33             isa => 'Str',
34             documentation => '',
35             );
36             has pid => (
37             is => 'rw',
38             isa => 'Str',
39             documentation => '',
40             );
41             has handle => (
42             is => 'rw',
43             isa => 'FileHandle',
44             clearer => 'clear_handle',
45             documentation => 'The opened filehandle of name',
46             );
47             has size => (
48             is => 'rw',
49             isa => 'Int',
50             init_arg => undef,
51             default => 0,
52             documentation => 'The size of file when last read',
53             );
54             has pause => (
55             is => 'rw',
56             isa => 'Bool',
57             documentation => 'Flags not to display any lines from the file',
58             );
59             has auto_unpause => (
60             is => 'rw',
61             isa => 'Bool',
62             default => 0,
63             init_arg => undef,
64             documentation => 'If a file was missing moved or deleted this flags that tailing should be restarted when the file reappears',
65             );
66             has no_inotify => (
67             is => 'ro',
68             isa => 'Bool',
69             documentation => 'Flags not to use the INotify the file (can be useful when a file is on a network file system like sshfs)',
70             );
71             has watcher => (
72             is => 'rw',
73             init_arg => undef,
74             clearer => 'clear_watcher',
75             documentation => 'This is the event watcher ojbect handle',
76             );
77             has runner => (
78             is => 'rw',
79             isa => 'CodeRef',
80             documentation => 'This is the subroutine reference that should be run with each file change',
81             );
82             has started => (
83             is => 'rw',
84             isa => 'Bool',
85             default => 0,
86             init_arg => undef,
87             documentation => 'Flags that tailing has started and not to limit the number of lines any more',
88             );
89             has stat_time => (
90             is => 'rw',
91             isa => 'Int',
92             default => time,
93             init_arg => undef,
94             documentation => 'The last time a file was stat()ed',
95             );
96             has stat_period => (
97             is => 'rw',
98             isa => 'Int',
99             default => 1,
100             documentation => 'The time period between checks if a file has been moved or deleted',
101             );
102             has tailer => (
103             is => 'rw',
104             isa => 'Tail::Tool',
105             documentation => 'The object that this file belongs to',
106             );
107             has restart => (
108             is => 'ro',
109             default => 0,
110             );
111              
112             my $inotify;
113             my $watcher;
114             sub watch {
115 1     1 1 3612 my ($self, $lines) = @_;
116              
117 1 50       30 return 0 if $self->pause;
118 1 50       23 return $self->watcher if $self->watcher;
119              
120 1         4 $self->_get_file_handle();
121              
122 1 50       4 if ( !defined $inotify ) {
123 1         3 eval { require Linux::Inotify2 };
  1         565  
124 1 50       1519 if ($EVAL_ERROR) {
125 0         0 $inotify = 0;
126             }
127             else {
128 1         4 $inotify = Linux::Inotify2->new;
129             }
130             }
131              
132 1         12 my $w;
133 1 50 33     30 if ( $self->name ne '-' && !$self->remote && $inotify && !$self->no_inotify ) {
    0 33        
      33        
134             # IN_MODIFY | IN_ALL_EVENTS & ~IN_ACCESS
135 1     0   20 $w = $inotify->watch( $self->name, Linux::Inotify2::IN_ALL_EVENTS(), sub { $self->run } );
  0         0  
136 1 50       27 if ( !$watcher ) {
137 1     0   3 $watcher = AE::io $inotify->fileno, 0, sub { $inotify->poll };
  0         0  
138             }
139             }
140             elsif ( $self->name eq '-' ) {
141 0         0 $self->started(1);
142             $w = AE::io \*STDIN, 0, sub {
143 0 0   0   0 if ( !defined fileno \*STDIN ) {
144 0         0 close STDIN;
145             }
146             $self->run
147 0         0 };
  0         0  
148             # TODO work out how to end if STDIN closed
149             }
150             else {
151 0     0   0 $w = AE::timer 0, 1, sub { $self->run };
  0         0  
152             }
153              
154 1         4019 $self->watcher($w);
155              
156 1         20 return $self->watcher;
157             }
158              
159             sub run {
160 0     0 1 0 my ($self, $first) = @_;
161 0         0 $self->runner->($self, $first);
162             }
163              
164             sub get_line {
165 0     0 1 0 my ($self) = @_;
166 0         0 my $fh = $self->_get_file_handle;
167              
168 0 0       0 return if $self->pause;
169              
170 0 0       0 if ( !$self->remote ) {
171 0   0     0 my $size = -s $self->name || 0;
172 0 0       0 if ( $size < $self->size ) {
    0          
173 0         0 warn $self->name . " was truncated!\n";
174 0         0 close $fh;
175 0         0 $self->clear_handle;
176 0         0 $fh = $self->_get_file_handle;
177             }
178             elsif ($self->restart) {
179             # reset file handle
180 0         0 seek $fh, 0, 1;
181             }
182             else {
183 0         0 $self->clear_watcher;
184             }
185 0   0     0 $self->size($size || 0);
186             }
187              
188 0         0 my @lines = <$fh>;
189              
190             # re-check the stat time of the file to make sure that the file has not been rotated
191 0 0 0     0 if ( !$self->remote && !@lines && time > $self->stat_time + $self->stat_period * 60 ) {
      0        
192 0         0 $self->stat_time(time);
193             # TODO why is this being run if the file has finished? Should not be run for STDIN reading
194 0         0 my @stat_file = stat $self->name;
195 0         0 my @stat_handle = stat $fh;
196             # check if the file handle's modified time is not the same as files'
197 0 0 0     0 if ( !defined $stat_handle[1] || !defined $stat_file[1] || $stat_handle[1] != $stat_file[1] ) {
      0        
198             # close and reopen file incase the file has been rotated
199 0         0 close $fh;
200 0         0 $self->_get_file_handle();
201             }
202             }
203 0         0 return @lines;
204             }
205              
206             sub _get_file_handle {
207 1     1   2 my ($self) = @_;
208              
209 1         22 my $fh = $self->handle;
210 1 50       21 if ( $self->name eq '-' ) {
211 0 0       0 if ( !$fh ) {
    0          
212 0         0 $self->handle(\*STDIN);
213             }
214             elsif ( !openhandle($fh) ) {
215 0         0 $self->clear_watcher;
216             }
217 0         0 return $self->handle;
218             }
219              
220 1 50 33     21 if ( $self->remote || $self->name =~ m{^s(sh|cp)://}xms ) {
    50 33        
221 0         0 $self->remote(1);
222 0 0       0 return if $self->pause;
223              
224 0         0 my $host_re = qr/( [\w.-]+ )/xms;
225 0         0 my $user_re = qr/([^@]+) [@]/xms;
226 0         0 my $port_re = qr/[:] (\d*)/xms;
227 0         0 my $file_re = qr/(.*)/xms;
228 0         0 my $ssh_re = qr{^ssh://(?: $user_re )? $host_re (?: $port_re )? / $file_re$}xms;
229 0         0 my $scp_re = qr{^scp://(?: $user_re )? $host_re $port_re $file_re$}xms;
230 0 0       0 my $re = $self->name =~ /^ssh/ ? $ssh_re : $scp_re;
231              
232 0         0 my ($user, $host, $port, $file) = $self->name =~ m{$re}xms;
233              
234 0 0       0 if ( !$fh ) {
235 0 0       0 my $cmd = sprintf "ssh %s$host %s 'tail -f -n %d %s'",
    0          
    0          
236             ( $user ? "$user\@" : '' ),
237             ( $port ? "-P $port" : '' ),
238             ( $self->tailer ? $self->tailer->lines : 10 ),
239             _shell_quote($file);
240              
241 0 0       0 if ( my $pid = open $fh, '-|', $cmd ) {
242 0         0 $fh->blocking(0);
243 0         0 $self->pid($pid);
244 0         0 $self->handle($fh);
245             }
246             else {
247 0         0 $self->pause(1);
248 0         0 warn "Could not tail remote file (" . $self->name . "): $!";
249             }
250             }
251             }
252             elsif ( !$fh || tell $fh == -1 ) {
253 1 50       24 if ( open $fh, '<', $self->name ) {
254 1         25 $self->handle($fh);
255 1         20 $self->size(-s $self->name);
256             }
257             else {
258 0 0       0 warn "Could not open '".$self->name."': $!\n" if !$self->auto_unpause;
259 0         0 $self->pause(1);
260 0         0 $self->auto_unpause(1);
261             }
262             }
263              
264 1         2 return $fh;
265             }
266              
267             sub _shell_quote {
268 0     0     my ($file) = @_;
269 0           $file =~ s{ ( [^\w\-./?*] ) }{\\$1}gxms;
270              
271 0           return $file;
272             }
273              
274             1;
275              
276             __END__
277              
278             =head1 NAME
279              
280             Tail::Tool::File - Looks after individual files
281              
282             =head1 VERSION
283              
284             This documentation refers to Tail::Tool::File version 0.4.7.
285              
286             =head1 SYNOPSIS
287              
288             use Tail::Tool::File;
289              
290             # Create a new Tail::Tool::File object
291             my $file = Tail::Tool::File->new( '/my/file' );
292              
293             =head1 DESCRIPTION
294              
295             =head1 SUBROUTINES/METHODS
296              
297             =head2 C<watch ()>
298              
299             Return: AnyEvent watcher or Linux::Inotify2 watcher
300              
301             Description: Creates the watcher for the file if the file exists and is not
302             paused.
303              
304             =head2 C<run ($first)>
305              
306             Param: C<$first> - bool - Specifies that this is the first time run has been
307             called.
308              
309             Description: Runs the the file event.
310              
311             =head2 C<get_line ()>
312              
313             Description: Gets any unread lines from the file.
314              
315             =head1 DIAGNOSTICS
316              
317             =head1 DEPENDENCIES
318              
319             =head1 INCOMPATIBILITIES
320              
321             =head1 BUGS AND LIMITATIONS
322              
323             There are no known bugs in this module.
324              
325             Please report problems to Ivan Wills (ivan.wills@gamil.com).
326              
327             Patches are welcome.
328              
329             =head1 AUTHOR
330              
331             Ivan Wills - (ivan.wills@gamil.com)
332             <Author name(s)> (<contact address>)
333              
334             =head1 LICENSE AND COPYRIGHT
335              
336             Copyright (c) 2010 Ivan Wills (14 Mullion Close, Hornsby Heights, NSW, Australia, 2077).
337             All rights reserved.
338              
339             This module is free software; you can redistribute it and/or modify it under
340             the same terms as Perl itself. See L<perlartistic>. This program is
341             distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
342             without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
343             PARTICULAR PURPOSE.
344              
345             =cut