File Coverage

blib/lib/App/PerlWatcher/Watcher/FileTail.pm
Criterion Covered Total %
statement 31 33 93.9
branch n/a
condition n/a
subroutine 11 11 100.0
pod n/a
total 42 44 95.4


line stmt bran cond sub pod time code
1             package App::PerlWatcher::Watcher::FileTail;
2             {
3             $App::PerlWatcher::Watcher::FileTail::VERSION = '0.18';
4             }
5             # ABSTRACT: Watches for changes file and outputs new added lines (a-la 'tail -f')
6              
7 2     2   286116 use 5.12.0;
  2         7  
  2         86  
8 2     2   11 use strict;
  2         4  
  2         51  
9 2     2   10 use warnings;
  2         8  
  2         61  
10              
11 2     2   10 use Carp;
  2         8  
  2         134  
12 2     2   9 use Devel::Comments;
  2         9  
  2         19  
13 2     2   8387 use File::ReadBackwards;
  2         4016  
  2         81  
14 2     2   4090 use Linux::Inotify2;
  2         8817  
  2         400  
15 2     2   2788 use Moo;
  2         46595  
  2         13  
16 2     2   7269 use aliased 'Path::Class::File';
  2         1975  
  2         12  
17              
18 2     2   77636 use AnyEvent::Handle;
  2         54791  
  2         102  
19 2     2   4757 use App::PerlWatcher::EventItem;
  0            
  0            
20             use App::PerlWatcher::Levels;
21             use aliased 'App::PerlWatcher::Status';
22             use App::PerlWatcher::Watcher;
23              
24              
25              
26              
27             has 'file' => ( is => 'ro', required => 1);
28              
29              
30             has 'lines_number' => ( is => 'ro', required => 1);
31              
32              
33             has 'filter' => ( is => 'ro', default => sub { return sub {1; } } );
34              
35              
36             has 'inotify' => ( is => 'lazy' );
37              
38              
39             has 'events' => ( is => 'lazy', default => sub { [] } );
40              
41              
42             has 'reverse' => ( is => 'lazy', default => sub{ 0 });
43              
44             with qw/App::PerlWatcher::Watcher/;
45              
46             sub _build_inotify {
47             my $inotify = Linux::Inotify2->new
48             or croak("unable to create new inotify object: $!");
49             return $inotify;
50             }
51              
52             sub build_watcher_guard {
53             my $self = shift;
54             return AnyEvent->io(
55             fh => $self->inotify->fileno,
56             poll => 'r',
57             cb => sub {
58             $self->inotify->poll
59             if $self->active;
60             },
61             );
62             }
63              
64             sub start {
65             my ($self, $callback) = @_;
66              
67             return unless($self->active);
68              
69             my $fail_start = sub {
70             my $msg = shift;
71             $self->poll_callback->($self);
72             $self->callback->(
73             Status->new(
74             watcher => $self,
75             level => LEVEL_ANY,
76             description => sub { $self->description . " : $msg " },
77             )
78             );
79             };
80             my $path = File->new($self->file);
81             return $fail_start->($!) unless $path->open('r');
82              
83             eval {
84             $self->_try_start;
85             $self->watcher_guard( $self->build_watcher_guard );
86             };
87             $fail_start->($@) if($@);
88             }
89              
90             sub _try_start {
91             my $self = shift;
92              
93             my $file_handle = $self->_initial_read;
94              
95             $self->inotify->watch(
96             $self->file,
97             IN_MODIFY,
98             sub {
99             my $e = shift;
100             my $name = $e->fullname;
101             # cancel this watcher: remove no further events
102             #$e->w->cancel;
103             my $ae_handle;
104             $ae_handle = AnyEvent::Handle->new(
105             fh => $file_handle,
106             on_read => sub {
107             my ($ea_handle) = @_;
108             $ea_handle->push_read(
109             line => sub {
110             my ( $ea_handle, $line, $eof ) = @_;
111             #print $line, $eof;
112             $self->_add_line($line);
113             }
114             );
115             },
116             on_eof => sub {
117             # eof
118             undef $ae_handle;
119             },
120             );
121             }
122             );
123             }
124              
125             sub description {
126             my $self = shift;
127             return "FileWatcher [" . $self->file . "]";
128             }
129              
130             sub _add_item {
131             my ($self, $item) = @_;
132             my $events = $self->events;
133             if (! $self->reverse) {
134             push @$events, $item;
135             shift @$events if @$events > $self->lines_number;
136             }
137             else {
138             unshift @$events, $item;
139             pop @$events if @$events > $self->lines_number;
140             }
141             }
142              
143             sub _add_line {
144             my ( $self, $line ) = @_;
145             if ( defined $line ) {
146             chomp $line;
147             if ( $self->filter->(local $_ = $line) ) {
148             my $event_item = App::PerlWatcher::EventItem->new(
149             content => $line,
150             timestamp => 0,
151             );
152             $self->_add_item($event_item);
153             $self->_trigger_callback;
154             }
155             }
156             }
157              
158             sub _trigger_callback {
159             my ($self) = @_;
160             my @events = @{ $self->events };
161             my $status = Status->new(
162             watcher => $self,
163             level => LEVEL_NOTICE,
164             description => sub { $self->description },
165             items => sub { \@events },
166             );
167             $self->poll_callback->($self);
168             $self->callback->($status);
169             }
170              
171             sub _initial_read {
172             my ($self) = @_;
173             my $frb = File::ReadBackwards->new( $self->file );
174             my $end_position = $frb->tell;
175             my @last_lines;
176             my $line;
177             do {
178             $line = $frb->readline;
179             unshift @last_lines, $line
180             if ( $line && $self->filter->(local $_ = $line) );
181             } while (defined($line) && @last_lines < $self->lines_number );
182              
183             $self->_add_line($_) for (@last_lines);
184              
185             my $file_handle = $frb->get_handle;
186              
187             # move file pointer to the end
188             seek $file_handle, 0, 2;
189             return $file_handle;
190             }
191              
192             1;
193              
194             __END__