Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add options to constructor; pass object to wait_* handlers #3

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 128 additions & 14 deletions lib/Filesys/Notify/Simple.pm
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,29 @@ use Cwd;
use constant NO_OPT => $ENV{PERL_FNS_NO_OPT};

sub new {
my($class, $path) = @_;
my($class, $path, %opt) = @_;

unless (ref $path eq 'ARRAY') {
Carp::croak('Usage: Filesys::Notify::Simple->new([ $path1, $path2 ])');
}

my $self = bless { paths => $path }, $class;
if (defined($opt{Detail})) {
$self->{detail} = $opt{Detail};
} else {
$self->{detail} = 1;
}

if (defined($opt{Latency})) {
$self->{latency} = $opt{Latency};
} else {
$self->{latency} = 2;
}

if (defined($opt{Method})) {
$self->{method} = $opt{Method};
}

$self->init;

$self;
Expand All @@ -24,24 +40,39 @@ sub new {
sub wait {
my($self, $cb) = @_;

$self->{watcher} ||= $self->{watcher_cb}->(@{$self->{paths}});
$self->{watcher} ||= $self->{watcher_cb}->($self, @{$self->{paths}});
$self->{watcher}->($cb);
}

sub init {
my $self = shift;

local $@;
if ($^O eq 'linux' && !NO_OPT && eval { require Linux::Inotify2; 1 }) {
if (!$self->{method}) {
if ($^O eq 'linux' && !NO_OPT && eval { require Linux::Inotify2; 1 }) {
$self->{method} = 'inotify2';
} elsif ($^O eq 'darwin' && !NO_OPT && eval { require Mac::FSEvents; 1 }) {
$self->{method} = 'fsevents';
} else {
$self->{method} = 'timer';
}
}

if ($self->{method} eq 'inotify2') {
require Linux::Inotify2;
$self->{watcher_cb} = \&wait_inotify2;
} elsif ($^O eq 'darwin' && !NO_OPT && eval { require Mac::FSEvents; 1 }) {
$self->{watcher_cb} = \&wait_fsevents;
} else {
} elsif ($self->{method} eq 'fsevents') {
require Mac::FSEvents;
$self->{watcher_cb} = \&wait_fsevents;
} elsif ($self->{method} eq 'timer') {
$self->{watcher_cb} = \&wait_timer;
} else {
die "Invalid method requested\n";
}
}

sub wait_inotify2 {
my $obj = shift;
my @path = @_;

Linux::Inotify2->import;
Expand All @@ -62,14 +93,18 @@ sub wait_inotify2 {

sub wait_fsevents {
require IO::Select;
my $obj = shift;
my @path = @_;

my $fs = _full_scan(@path);
my $fs;
if ($obj->{detail} > 0) {
$fs = _full_scan(@path);
}
my $sel = IO::Select->new;

my %events;
for my $path (@path) {
my $fsevents = Mac::FSEvents->new({ path => $path, latency => 1 });
my $fsevents = Mac::FSEvents->new({ path => $path, latency => $obj->{latency} });
my $fh = $fsevents->watch;
$sel->add($fh);
$events{fileno $fh} = $fsevents;
Expand All @@ -84,11 +119,14 @@ sub wait_fsevents {
my $fsevents = $events{fileno $fh};
my %uniq;
my @path = grep !$uniq{$_}++, map { $_->path } $fsevents->read_events;

my $new_fs = _full_scan(@path);
my $old_fs = +{ map { ($_ => $fs->{$_}) } keys %$new_fs };
_compare_fs($old_fs, $new_fs, sub { push @events, { path => $_[0] } });
$fs->{$_} = $new_fs->{$_} for keys %$new_fs;
if ($obj->{details} > 0) {
my $new_fs = _full_scan(@path);
my $old_fs = +{ map { ($_ => $fs->{$_}) } keys %$new_fs };
_compare_fs($old_fs, $new_fs, sub { push @events, { path => $_[0] } });
$fs->{$_} = $new_fs->{$_} for keys %$new_fs;
} else {
push(@events,map { { path => $_ } } @path);
}
last if @events;
}

Expand All @@ -97,6 +135,7 @@ sub wait_fsevents {
}

sub wait_timer {
my $obj = shift;
my @path = @_;

my $fs = _full_scan(@path);
Expand All @@ -105,7 +144,7 @@ sub wait_timer {
my $cb = shift;
my @events;
while (1) {
sleep 2;
sleep $obj->{latency};
my $new_fs = _full_scan(@path);
_compare_fs($fs, $new_fs, sub { push @events, { path => $_[0] } });
$fs = $new_fs;
Expand Down Expand Up @@ -220,6 +259,81 @@ In return, this module doesn't depend on any non-core
modules. Platform specific optimizations with L<Linux::Inotify2> and
L<Mac::FSEvents> are truely optional.

=head1 CONSTRUCTOR

=over 4

=item new ( [ @dirs ], opt1 => val1, ... )

Creates a new filesystem monitor. C<@dirs> is a list of directories
to monitor, given as an array reference. Additional named parameters
can be passed after that, including:

=over 4

=item Detail

Set to 1 (the default) or higher to get a notification for each change
to the filesystem with the full path to each changed file. Set to 0
to get just a notification of some change to the monitored
directories, without information about individual changes. Note that
if the individual changes have been detected already, they may be sent.

=item Latency

Requested time to wait between notifications, in seconds. The default
is 2 seconds. This is advisory, and you may get notifications more or
less frequently than this.

=item Method

Method to use to monitor for changes. The default is to auto-detect
the most efficient method based on your OS and installed modules.
Values are:

=over 4

=item timer

Periodically scan the entire filesystem for changes. See the
C<Latency> option to control how often files are checked.

=item inotify2

Use Linux inotify, version 2. Requires L<Linux::Inotify2>.

=item fsevent

Use MacOS X FS Events. Requires L<Mac::FSEvents>.

=back

=back

=back

=head1 METHODS

=over 4

=item wait ( &notify_sub )

Callback sub which should be called whenever a modified file is
changed. It will be passed a single parameter containing an array
reference; each item in the array is a hash reference with the
following values:

=over 4

=item path

The path of the file that changed, or the monitored directory if
C<Detail> was set to 0 in the constructor.

=back

=back

=head1 AUTHOR

Tatsuhiko Miyagawa E<lt>[email protected]<gt>
Expand Down