package File::ChangeNotify::Watcher::Inotify;
use strict;
use warnings;
use namespace::autoclean;
our $VERSION = '0.25';
use File::Find ();
use Linux::Inotify2 1.2;
use Types::Standard qw( Bool Int );
use Type::Utils qw( class_type );
use Moo;
has is_blocking => (
is => 'ro',
isa => Bool,
default => 1,
);
has _inotify => (
is => 'ro',
isa => class_type('Linux::Inotify2'),
default => sub {
Linux::Inotify2->new()
or die "Cannot construct a Linux::Inotify2 object: $!";
},
init_arg => undef,
);
has _mask => (
is => 'ro',
isa => Int,
lazy => 1,
builder => '_build_mask',
);
with 'File::ChangeNotify::Watcher';
sub sees_all_events {1}
sub BUILD {
my $self = shift;
$self->_inotify()->blocking( $self->is_blocking() );
# If this is done via a lazy_build then the call to
# ->_watch_directory ends up causing endless recursion when it
# calls ->_inotify itself.
$self->_watch_directory($_) for @{ $self->directories() };
return $self;
}
sub wait_for_events {
my $self = shift;
$self->_inotify()->blocking(1);
while (1) {
my @events = $self->_interesting_events();
return @events if @events;
}
}
around new_events => sub {
my $orig = shift;
my $self = shift;
$self->_inotify()->blocking(0);
return $self->$orig(@_);
};
sub _interesting_events {
my $self = shift;
my $filter = $self->filter();
my @interesting;
# This may be a blocking read, in which case it will not return until
# something happens. For Catalyst, the restarter will end up calling
# ->watch again after handling the changes.
for my $event ( $self->_inotify()->read() ) {
# An excluded path will show up here if ...
#
# Something created a new directory and that directory needs to be
# excluded or when the exclusion excludes a file, not a dir.
next if $self->_path_is_excluded( $event->fullname() );
if ( $event->IN_CREATE() && $event->IN_ISDIR() ) {
$self->_watch_directory( $event->fullname() );
push @interesting, $event;
push @interesting,
$self->_fake_events_for_new_dir( $event->fullname() );
}
elsif ( $event->IN_DELETE_SELF() ) {
$self->_remove_directory( $event->fullname() );
}
# We just want to check the _file_ name
elsif ( $event->name() =~ /$filter/ ) {
push @interesting, $event;
}
}
return
map { $_->can('path') ? $_ : $self->_convert_event($_) } @interesting;
}
sub _build_mask {
my $self = shift;
my $mask
= IN_MODIFY | IN_CREATE | IN_DELETE | IN_DELETE_SELF | IN_MOVE_SELF;
$mask |= IN_DONT_FOLLOW unless $self->follow_symlinks();
return $mask;
}
sub _watch_directory {
my $self = shift;
my $dir = shift;
# A directory could be created & then deleted before we get a
# chance to act on it.
return unless -d $dir;
File::Find::find(
{
wanted => sub {
my $path = $File::Find::name;
if ( $self->_path_is_excluded($path) ) {
$File::Find::prune = 1;
return;
}
$self->_add_watch_if_dir($path);
},
follow_fast => ( $self->follow_symlinks() ? 1 : 0 ),
no_chdir => 1,
follow_skip => 2,
},
$dir
);
}
sub _add_watch_if_dir {
my $self = shift;
my $path = shift;
return if -l $path && !$self->follow_symlinks();
return unless -d $path;
return if $self->_path_is_excluded($path);
$self->_inotify()->watch( $path, $self->_mask() );
}
sub _fake_events_for_new_dir {
my $self = shift;
my $dir = shift;
return unless -d $dir;
my @events;
File::Find::find(
{
wanted => sub {
my $path = $File::Find::name;
return if $path eq $dir;
if ( $self->_path_is_excluded($path) ) {
$File::Find::prune = 1;
return;
}
push @events,
$self->event_class()->new(
path => $path,
type => 'create',
);
},
follow_fast => ( $self->follow_symlinks() ? 1 : 0 ),
no_chdir => 1
},
$dir
);
return @events;
}
sub _convert_event {
my $self = shift;
my $event = shift;
return $self->event_class()->new(
path => $event->fullname(),
type => (
$event->IN_CREATE() ? 'create'
: $event->IN_MODIFY() ? 'modify'
: $event->IN_DELETE() ? 'delete'
: 'unknown'
),
);
}
__PACKAGE__->meta()->make_immutable();
1;
# ABSTRACT: Inotify-based watcher subclass
__END__
=pod
=encoding UTF-8
=head1 NAME
File::ChangeNotify::Watcher::Inotify - Inotify-based watcher subclass
=head1 VERSION
version 0.25
=head1 DESCRIPTION
This class implements watching by using the L<Linux::Inotify2>
module. This only works on Linux 2.6.13 or newer.
This watcher is much more efficient and accurate than the
C<File::ChangeNotify::Watcher::Default> class.
=head1 SUPPORT
Bugs may be submitted through L<the RT bug tracker|http://rt.cpan.org/Public/Dist/Display.html?Name=File-ChangeNotify>
(or L<bug-file-changenotify@rt.cpan.org|mailto:bug-file-changenotify@rt.cpan.org>).
I am also usually active on IRC as 'drolsky' on C<irc://irc.perl.org>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENCE
This software is Copyright (c) 2016 by Dave Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
=cut