From: Dave Rolsky Date: Thu, 7 May 2009 23:06:08 +0000 (+0000) Subject: Rewrote restarter to use the soon-to-be-on-CPAN File::ChangeNotify X-Git-Tag: 1.14_01~8 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=a13b99da9aeb2204721a7708dfdf5c01d1ae802b;p=catagits%2FCatalyst-Devel.git Rewrote restarter to use the soon-to-be-on-CPAN File::ChangeNotify --- diff --git a/Makefile.PL b/Makefile.PL index 5e8acfd..067bc20 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -9,6 +9,7 @@ requires 'Catalyst::Plugin::Static::Simple' => '0.16'; requires 'Catalyst::Plugin::ConfigLoader'; requires 'Class::Accessor::Fast'; requires 'Config::General'; # as of 1.07, we use .conf and not .yaml +requires 'File::ChangeNotify'; requires 'File::Copy::Recursive'; requires 'Module::Install' => '0.64'; requires 'parent'; # as of 1.04 diff --git a/lib/Catalyst/Helper.pm b/lib/Catalyst/Helper.pm index 27accf4..34b2b72 100644 --- a/lib/Catalyst/Helper.pm +++ b/lib/Catalyst/Helper.pm @@ -1034,9 +1034,9 @@ if ( $restart ) { if $follow_symlinks; $args{directories} = $watch_directory if defined $watch_directory; - $args{interval} = $check_interval + $args{sleep_interval} = $check_interval if defined $check_interval; - $args{regex} = qr/$file_regex/ + $args{filter} = qr/$file_regex/ if defined $file_regex; my $restarter = Catalyst::Restarter->new( diff --git a/lib/Catalyst/Restarter.pm b/lib/Catalyst/Restarter.pm index e35731b..93dbfa7 100644 --- a/lib/Catalyst/Restarter.pm +++ b/lib/Catalyst/Restarter.pm @@ -2,7 +2,7 @@ package Catalyst::Restarter; use Moose; -use Catalyst::Watcher; +use File::ChangeNotify; use namespace::clean -except => 'meta'; has restart_sub => ( @@ -13,7 +13,7 @@ has restart_sub => ( has _watcher => ( is => 'rw', - isa => 'Catalyst::Watcher', + isa => 'File::ChangeNotify::Watcher', ); has _child => ( @@ -27,9 +27,11 @@ sub BUILD { delete $p->{restart_sub}; + $p->{filter} ||= qr/(?:\/|^)(?!\.\#).+(?:\.yml$|\.yaml$|\.conf|\.pm)$/; + # We could make this lazily, but this lets us check that we # received valid arguments for the watcher up front. - $self->_watcher( Catalyst::Watcher->instantiate_subclass( %{$p} ) ); + $self->_watcher( File::ChangeNotify->instantiate_watcher( %{$p} ) ); } sub run_and_watch { @@ -56,16 +58,24 @@ sub _fork_and_start { sub _restart_on_changes { my $self = shift; - $self->_watcher->watch($self); + my @events = $self->_watcher->wait_for_events(); + $self->_handle_events(@events); } -sub handle_changes { - my $self = shift; - my @files = @_; +sub _handle_events { + my $self = shift; + my @events = @_; print STDERR "\n"; print STDERR "Saw changes to the following files:\n"; - print STDERR " - $_->{file} ($_->{status})\n" for @files; + + for my $event (@events) { + my $path = $event->path(); + my $type = $event->type(); + + print STDERR " - $path ($type)\n"; + } + print STDERR "\n"; print STDERR "Attempting to restart the server\n\n"; diff --git a/lib/Catalyst/Watcher.pm b/lib/Catalyst/Watcher.pm deleted file mode 100644 index f267866..0000000 --- a/lib/Catalyst/Watcher.pm +++ /dev/null @@ -1,59 +0,0 @@ -package Catalyst::Watcher; - -use Moose; -use Moose::Util::TypeConstraints; - -use Cwd qw( abs_path ); -use File::Spec; -use FindBin; -use namespace::clean -except => 'meta'; - -has regex => ( - is => 'ro', - isa => 'RegexpRef', - default => sub { qr/(?:\/|^)(?!\.\#).+(?:\.yml|\.yaml|\.conf|\.pm)$/ }, -); - -my $dir = subtype - as 'Str' - => where { -d $_ } - => message { "$_ is not a valid directory" }; - -my $array_of_dirs = subtype - as 'ArrayRef[Str]', - => where { map { -d } @{$_} } - => message { "@{$_} is not a list of valid directories" }; - -coerce $array_of_dirs - => from $dir - => via { [ $_ ] }; - -has directories => ( - is => 'ro', - isa => $array_of_dirs, - default => sub { [ abs_path( File::Spec->catdir( $FindBin::Bin, '..' ) ) ] }, - coerce => 1, -); - -has follow_symlinks => ( - is => 'ro', - isa => 'Bool', - default => 0, -); - -sub instantiate_subclass { - my $class = shift; - - if ( eval { require Catalyst::Watcher::Inotify; 1; } ) { - return Catalyst::Watcher::Inotify->new(@_); - } - else { - die $@ if $@ && $@ !~ /Can't locate/; - require Catalyst::Watcher::FileModified; - return Catalyst::Watcher::FileModified->new(@_); - } -} - -__PACKAGE__->meta->make_immutable; - -1; diff --git a/lib/Catalyst/Watcher/FileModified.pm b/lib/Catalyst/Watcher/FileModified.pm deleted file mode 100644 index 4a25874..0000000 --- a/lib/Catalyst/Watcher/FileModified.pm +++ /dev/null @@ -1,188 +0,0 @@ -package Catalyst::Watcher::FileModified; - -use Moose; - -use File::Find; -use File::Modified; -use File::Spec; -use Time::HiRes qw/sleep/; -use namespace::clean -except => 'meta'; - -extends 'Catalyst::Watcher'; - -has interval => ( - is => 'ro', - isa => 'Int', - default => 1, -); - -has _watched_files => ( - is => 'ro', - isa => 'HashRef[Str]', - lazy_build => 1, - clearer => '_clear_watched_files', -); - -has _modified => ( - is => 'rw', - isa => 'File::Modified', - lazy_build => 1, - clearer => '_clear_modified', -); - - -sub _build__watched_files { - my $self = shift; - - my $regex = $self->regex; - - my %list; - finddepth( - { - wanted => sub { - my $path = File::Spec->rel2abs($File::Find::name); - return unless $path =~ /$regex/; - return unless -f $path; - - $list{$path} = 1; - - # also watch the directory for changes - my $cur_dir = File::Spec->rel2abs($File::Find::dir); - $cur_dir =~ s{/script/..}{}; - $list{$cur_dir} = 1; - }, - follow_fast => $self->follow_symlinks ? 1 : 0, - no_chdir => 1 - }, - @{ $self->directories } - ); - - return \%list; -} - -sub _build__modified { - my $self = shift; - - return File::Modified->new( - method => 'mtime', - files => [ keys %{ $self->_watched_files } ], - ); -} - -sub watch { - my $self = shift; - my $restarter = shift; - - while (1) { - sleep $self->interval if $self->interval > 0; - - my @changes = $self->_changed_files; - - next unless @changes; - - $restarter->handle_changes(@changes); - - last; - } -} - -sub _changed_files { - my $self = shift; - - my @changes; - - eval { - @changes = map { { file => $_, status => 'modified' } } - grep { -f $_ } $self->_modified->changed; - }; - - if ($@) { - # File::Modified will die if a file is deleted. - die unless $@ =~ /stat '(.+)'/; - - push @changes, { - file => $1 || 'unknown file', - status => 'deleted', - }; - - $self->_clear_watched_files; - $self->_clear_modified; - } - else { - $self->_modified->update; - - my $old_watch = $self->_watched_files; - - $self->_clear_watched_files; - - my $new_watch = $self->_watched_files; - - my @new_files = grep { !defined $old_watch->{$_} } - grep {-f} - keys %{$new_watch}; - - if (@new_files) { - $self->_clear_modified; - push @changes, map { { file => $_, status => 'added' } } @new_files; - } - } - - return @changes; -} - -__PACKAGE__->meta->make_immutable; - -1; - -__END__ - -=head1 NAME - -Catalyst::Watcher::FileModified - Watch for changed application files using File::Modified - -=head1 SYNOPSIS - - my $watcher = Catalyst::Watcher::FileModified->new( - directories => '/path/to/MyApp', - regex => '\.yml$|\.yaml$|\.conf|\.pm$', - ); - - while (1) { - my @changed_files = $watcher->watch(); - ... - } - -=head1 DESCRIPTION - -This class monitors a directory of files for changes made to any file -matching a regular expression. It correctly handles new files added to the -application as well as files that are deleted. - -=head1 METHODS - -=head2 new ( directory => $path [, regex => $regex, delay => $delay ] ) - -Creates a new Watcher object. - -=head2 find_changed_files - -Returns a list of files that have been added, deleted, or changed -since the last time watch was called. Each element returned is a hash -reference with two keys. The C key contains the filename, and -the C key contains one of "modified", "added", or "deleted". - -=head1 SEE ALSO - -L, L, L, - - -=head1 AUTHORS - -Catalyst Contributors, see Catalyst.pm - -=head1 COPYRIGHT - -This program is free software, you can redistribute it and/or modify -it under the same terms as Perl itself. - -=cut diff --git a/lib/Catalyst/Watcher/Inotify.pm b/lib/Catalyst/Watcher/Inotify.pm deleted file mode 100644 index 3e4e61a..0000000 --- a/lib/Catalyst/Watcher/Inotify.pm +++ /dev/null @@ -1,176 +0,0 @@ -package Catalyst::Watcher::Inotify; - -use Moose; - -use File::Find; -use Linux::Inotify2; -use namespace::clean -except => 'meta'; - -extends 'Catalyst::Watcher'; - -has _inotify => ( - is => 'rw', - isa => 'Linux::Inotify2', - default => sub { Linux::Inotify2->new }, - init_arg => undef, -); - -has _mask => ( - is => 'rw', - isa => 'Int', - lazy_build => 1, -); - -sub BUILD { - my $self = shift; - - # If this is done via a lazy_build then the call to - # ->_add_directory ends up causing endless recursion when it calls - # ->_inotify itself. - $self->_add_directory($_) for @{ $self->directories }; - - return $self; -} - -sub watch { - my $self = shift; - my $restarter = shift; - - my @events = $self->_wait_for_events; - - $restarter->handle_changes( map { $self->_event_to_change($_) } @events ); - - return; -} - -sub _wait_for_events { - my $self = shift; - - my $regex = $self->regex; - - while (1) { - # This is a blocking read, so it will not return until - # something happens. The restarter will end up calling ->watch - # again after handling the changes. - my @events = $self->_inotify->read; - - my @interesting; - for my $event (@events) { - if ( $event->IN_CREATE && $event->IN_ISDIR ) { - $self->_add_directory( $event->fullname ); - push @interesting, $event; - } - elsif ( $event->IN_DELETE_SELF - || $event->fullname =~ /$regex/ ) { - push @interesting, $event; - } - } - - return @interesting if @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 _add_directory { - my $self = shift; - my $dir = shift; - - finddepth( - { - wanted => sub { - my $path = File::Spec->rel2abs($File::Find::name); - return unless -d $path; - - $self->_inotify->watch( $path, $self->_mask ); - }, - follow_fast => $self->follow_symlinks ? 1 : 0, - no_chdir => 1 - }, - $dir - ); -} - -sub _event_to_change { - my $self = shift; - my $event = shift; - - my %change = ( file => $event->fullname ); - if ( $event->IN_CREATE ) { - $change{status} = 'added'; - } - elsif ( $event->IN_MODIFY ) { - $change{status} = 'modified'; - } - elsif ( $event->IN_DELETE ) { - $change{status} = 'deleted'; - } - else { - $change{status} = 'containing directory modified'; - } - - return \%change; -} - -__PACKAGE__->meta->make_immutable; - -1; - -__END__ - -=head1 NAME - -Catalyst::Watcher - Watch for changed application files - -=head1 SYNOPSIS - - my $watcher = Catalyst::Watcher->new( - directory => '/path/to/MyApp', - regex => '\.yml$|\.yaml$|\.conf|\.pm$', - interval => 3, - ); - - while (1) { - my @changed_files = $watcher->watch(); - } - -=head1 DESCRIPTION - -This class monitors a directory of files for changes made to any file -matching a regular expression. It correctly handles new files added to the -application as well as files that are deleted. - -=head1 METHODS - -=head2 new ( directory => $path [, regex => $regex, delay => $delay ] ) - -Creates a new Watcher object. - -=head2 find_changed_files - -Returns a list of files that have been added, deleted, or changed -since the last time watch was called. Each element returned is a hash -reference with two keys. The C key contains the filename, and -the C key contains one of "modified", "added", or "deleted". - -=head1 SEE ALSO - -L, L, - -=head1 AUTHORS - -Catalyst Contributors, see Catalyst.pm - -=head1 COPYRIGHT - -This program is free software, you can redistribute it and/or modify -it under the same terms as Perl itself. - -=cut