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__ |