#!/usr/bin/perl # Created on: 2014-12-16 14:07:12 # Create by: Ivan Wills # $Id$ # $Revision$, $HeadURL$, $Date$ # $Revision$, $Source$, $Date$ use strict; use warnings; use Getopt::Long; use Pod::Usage; use Data::Dumper qw/Dumper/; use English qw/ -no_match_vars /; use FindBin qw/$Bin/; use Path::Tiny; use AnyEvent; use AnyEvent::Loop; use Linux::Inotify2; use File::TypeCategories; our $VERSION = 0.005; my ($name) = $PROGRAM_NAME =~ m{^.*/(.*?)$}mxs; my $types = [qw/ IN_ACCESS IN_MODIFY IN_ATTRIB IN_CLOSE_WRITE IN_CLOSE_NOWRITE IN_OPEN IN_ALL_EVENTS IN_MOVED_FROM IN_MOVED_TO IN_CREATE IN_DELETE IN_DELETE_SELF IN_MOVE_SELF /]; my %option = ( dir => 0, verbose => 0, man => 0, help => 0, VERSION => 0, ); my %watch; my $inotify; main(); exit 0; sub main { Getopt::Long::Configure('bundling'); GetOptions( \%option, 'dir|d!', 'type|t=s@', 'report|r', 'command|cmd|c=s', 'verbose|v+', 'man', 'help', 'version', ) or pod2usage(2); if ( $option{'version'} ) { print "$name Version = $VERSION\n"; exit 1; } elsif ( $option{'man'} ) { pod2usage( -verbose => 2 ); } elsif ( $option{'help'} ) { pod2usage( -verbose => 1 ); } # do stuff here my @files = map { path($_) } @ARGV ? @ARGV : ('.'); my $tc = File::TypeCategories->new(); $inotify = Linux::Inotify2->new; while ( my $file = shift @files ) { next if !$tc->file_ok($file); if ( -d $file ) { push @files, $file->children; next if !$option{dir}; } warn "$file\n" if $option{verbose}; watch($file); } if ( $option{type} && @{ $option{type} } == 1 && $option{type}[0] eq 'ALL' ) { $option{type} = $types; } else { $option{type} ||= ['IN_MODIFY', 'IN_MOVE_SELF']; } if ( $option{report} ) { $SIG{INT} = \&report; } my $refresh = AE::timer(1, 1, sub { report('changes'); }); AnyEvent::Loop::run; return; } sub watch { my ($file) = @_; warn "The file '$file' no longer exists!\n" if !-e $file; $watch{$file} = { notify => scalar $inotify->watch( $file, Linux::Inotify2::IN_ALL_EVENTS(), \&changed ), ae => scalar AE::io $inotify->fileno, 0, sub { $inotify->poll }, }; return; } my @refresh; my %changed; my %changes; sub changed { my ($evt) = @_; my $file = $evt->{w}{name}; $changed{$file}{time} = time; $changed{$file}{types} = { %{ $changed{$file}{types} || {} }, map { $_ => 1 } grep { $evt->$_() } @$types }; $changes{$file}{time} = time; $changes{$file}{types} = { %{ $changes{$file}{types} || {} }, map { $_ => 1 } grep { $evt->$_() } @{ $option{type} } }; push @refresh, AE::timer(1, 0, sub { watch($file); }); return; } sub report { my $changes = shift; my %change = $changes eq 'changes' ? %changes : %changed; my $files_changed = keys %change; if ( $option{verbose} ) { for my $file ( sort keys %change ) { print "$file\t", ( join ', ', sort keys %{ $change{$file}{types} } ), "\n"; } } if ($changes eq 'changes') { if ($option{command} && $files_changed) { local $ENV{CHANGED} = join ':', sort keys %change; system $option{command}; } %changes = (); return; } exit; } __DATA__ =head1 NAME file-watch - Watch files and directories for changes =head1 VERSION This documentation refers to file-watch version 0.005 =head1 SYNOPSIS file-watch [option] [file(s)] OPTIONS: -d --dir Check directories --no-dir Don't check directories (Default) -t --type[=]str Specify the type of file events to listen for, more than one event can be specified. - IN_ACCESS - IN_MODIFY - IN_ATTRIB - IN_CLOSE_WRITE - IN_CLOSE_NOWRITE - IN_OPEN - IN_ALL_EVENTS - IN_MOVED_FROM - IN_MOVED_TO - IN_CREATE - IN_DELETE - IN_DELETE_SELF - IN_MOVE_SELF - ALL - special pseudo event setting all events on so you can see which ones are actually fired -r --report When killed, a report will be generated of all files modified -c --command[=]'command to run' When a file is detected to have changed this command can be run -v --verbose Show more detailed option --version Prints the version information --help Prints this help information --man Prints the full documentation for file-watch =head1 DESCRIPTION C uses iNotify to track when files change. The types of changes can be found in L. =head2 Running Commands When you use the C<--command> option changes will be chunked and displayed every second. The program you run can find the files that triggered it by inspecting the environment variable C<$CHANGED> which is a colon (:) separated list of files. =head1 SUBROUTINES/METHODS =head1 DIAGNOSTICS =head1 DEPENDENCIES =head1 INCOMPATIBILITIES =head1 BUGS AND LIMITATIONS There are no known bugs in this module. Please report problems to Ivan Wills (ivan.wills@gmail.com). Patches are welcome. =head1 AUTHOR Ivan Wills - (ivan.wills@gmail.com) =head1 LICENSE AND COPYRIGHT Copyright (c) 2014-2015 Ivan Wills (14 Mullion Close, Hornsby Heights, NSW Australia 2077). All rights reserved. This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See L. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. =cut