[Catalyst-commits] r9986 - in Catalyst-Devel/1.00/branches/improved-restarter/lib/Catalyst: . Watcher

autarch at dev.catalyst.perl.org autarch at dev.catalyst.perl.org
Sat May 2 01:55:01 GMT 2009


Author: autarch
Date: 2009-05-02 01:55:00 +0000 (Sat, 02 May 2009)
New Revision: 9986

Added:
   Catalyst-Devel/1.00/branches/improved-restarter/lib/Catalyst/Watcher/
   Catalyst-Devel/1.00/branches/improved-restarter/lib/Catalyst/Watcher/FileModified.pm
   Catalyst-Devel/1.00/branches/improved-restarter/lib/Catalyst/Watcher/Inotify.pm
Removed:
   Catalyst-Devel/1.00/branches/improved-restarter/lib/Catalyst/Watcher.pm
Modified:
   Catalyst-Devel/1.00/branches/improved-restarter/lib/Catalyst/Helper.pm
   Catalyst-Devel/1.00/branches/improved-restarter/lib/Catalyst/Restarter.pm
Log:
Lots of changes to how the restarter & watcher work.

First, the "wait on events" loop is no longer in the restarter. The
restarter lets the watcher handle this. When an event happens, the
watcher calls

   $restarter->handle_changes(@events);

This made it _much_ easier to implement the Inotify-based watcher I've
added. Catalyst::Watcher is now a parent & factory. The old watcher
code has been split between ::Watcher and ::Watcher::FileModified.

The FileModified version has been fixed to handle file deletions a
little better, but it's still kind of broken.

The Inotify version handles everything correctly, though sometimes
it's reports of what changed can be off (but it restarts as needed,
and adds new directories to the watch list properly).

Finally, I fixed some bugs in the Helper module where the generated
script did not use the right names for arguments passed when creating
the restarter.

Modified: Catalyst-Devel/1.00/branches/improved-restarter/lib/Catalyst/Helper.pm
===================================================================
--- Catalyst-Devel/1.00/branches/improved-restarter/lib/Catalyst/Helper.pm	2009-05-02 01:12:46 UTC (rev 9985)
+++ Catalyst-Devel/1.00/branches/improved-restarter/lib/Catalyst/Helper.pm	2009-05-02 01:55:00 UTC (rev 9986)
@@ -1030,11 +1030,13 @@
         if $background;
 
     my %args;
-    $args{watch_directory} = $watch_directory
+    $args{follow_symlinks} = 1
+        if $follow_symlinks;
+    $args{directories} = $watch_directory
         if defined $watch_directory;
-    $args{check_interval} = $check_interval
+    $args{interval} = $check_interval
         if defined $check_interval;
-    $args{file_regex} = qr/$file_regex/
+    $args{regex} = qr/$file_regex/
         if defined $file_regex;
 
     my $restarter = Catalyst::Restarter->new(
@@ -1069,6 +1071,7 @@
    -r -restart        restart when files get modified
                       (defaults to false)
    -rd -restartdelay  delay between file checks
+                      (ignored if you have Linux::Inotify2 installed)
    -rr -restartregex  regex match files that trigger
                       a restart when modified
                       (defaults to '\.yml$|\.yaml$|\.conf|\.pm$')

Modified: Catalyst-Devel/1.00/branches/improved-restarter/lib/Catalyst/Restarter.pm
===================================================================
--- Catalyst-Devel/1.00/branches/improved-restarter/lib/Catalyst/Restarter.pm	2009-05-02 01:12:46 UTC (rev 9985)
+++ Catalyst-Devel/1.00/branches/improved-restarter/lib/Catalyst/Restarter.pm	2009-05-02 01:55:00 UTC (rev 9986)
@@ -3,8 +3,6 @@
 use Moose;
 
 use Catalyst::Watcher;
-use File::Spec;
-use FindBin;
 use namespace::clean -except => 'meta';
 
 has restart_sub => (
@@ -31,7 +29,7 @@
 
     # We could make this lazily, but this lets us check that we
     # received valid arguments for the watcher up front.
-    $self->_watcher( Catalyst::Watcher->new( %{$p} ) );
+    $self->_watcher( Catalyst::Watcher->instantiate_subclass( %{$p} ) );
 }
 
 sub run_and_watch {
@@ -58,24 +56,24 @@
 sub _restart_on_changes {
     my $self = shift;
 
-    my $watcher = $self->_watcher;
+    $self->_watcher->watch($self);
+}
 
-    while (1) {
-        my @files = $watcher->find_changed_files
-            or next;
+sub handle_changes {
+    my $self  = shift;
+    my @files = @_;
 
-        print STDERR "\n";
-        print STDERR "Saw changes to the following files:\n";
-        print STDERR " - $_->{file} ($_->{status})\n" for @files;
-        print STDERR "\n";
-        print STDERR "Attempting to restart the server\n\n";
+    print STDERR "\n";
+    print STDERR "Saw changes to the following files:\n";
+    print STDERR " - $_->{file} ($_->{status})\n" for @files;
+    print STDERR "\n";
+    print STDERR "Attempting to restart the server\n\n";
 
-        $self->_kill_child;
+    $self->_kill_child;
 
-        $self->_fork_and_start;
+    $self->_fork_and_start;
 
-        return unless $self->_child;
-    }
+    $self->_restart_on_changes;
 }
 
 sub _kill_child {

Copied: Catalyst-Devel/1.00/branches/improved-restarter/lib/Catalyst/Watcher/FileModified.pm (from rev 9812, Catalyst-Devel/1.00/branches/improved-restarter/lib/Catalyst/Watcher.pm)
===================================================================
--- Catalyst-Devel/1.00/branches/improved-restarter/lib/Catalyst/Watcher/FileModified.pm	                        (rev 0)
+++ Catalyst-Devel/1.00/branches/improved-restarter/lib/Catalyst/Watcher/FileModified.pm	2009-05-02 01:55:00 UTC (rev 9986)
@@ -0,0 +1,188 @@
+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<file> key contains the filename, and
+the C<status> key contains one of "modified", "added", or "deleted".
+
+=head1 SEE ALSO
+
+L<Catalyst>, L<Catalyst::Watcher>, L<Catalyst::Restarter>,
+<File::Modified>
+
+=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

Added: Catalyst-Devel/1.00/branches/improved-restarter/lib/Catalyst/Watcher/Inotify.pm
===================================================================
--- Catalyst-Devel/1.00/branches/improved-restarter/lib/Catalyst/Watcher/Inotify.pm	                        (rev 0)
+++ Catalyst-Devel/1.00/branches/improved-restarter/lib/Catalyst/Watcher/Inotify.pm	2009-05-02 01:55:00 UTC (rev 9986)
@@ -0,0 +1,174 @@
+package Catalyst::Watcher::Inotify;
+
+use Moose;
+
+use Linux::Inotify2;
+use namespace::clean -except => 'meta';
+
+extends 'Catalyst::Watcher';
+
+has _inotify => (
+    is         => 'rw',
+    isa        => 'Linux::Inotify2',
+    lazy_build => 1,
+);
+
+has _mask => (
+    is         => 'rw',
+    isa        => 'Int',
+    lazy_build => 1,
+);
+
+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;
+
+    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 ( grep { $_->mask | IN_ISDIR } @events ) {
+            if ( $event->mask | IM_CREATE ) {
+                $self->_add_directory( $event->fullname );
+                push @interesting, $event;
+            }
+            elsif ( $event->mask | IM_DELETE_SELF ) {
+                $event->w->cancel;
+                push @interesting, $event;
+            }
+            elsif ( $event->name =~ /$regex/ ) {
+                push @interesting, $event;
+            }
+        }
+
+        return @interesting if @interesting;
+    }
+}
+
+sub _build__inotify {
+    my $self = shift;
+
+    my $inotify = Linux::Inotify2->new();
+
+    $self->_add_directory($_) for @{ $self->directories };
+
+    return $inotify;
+}
+
+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->mask() | IN_CREATE || $event->mask() ) {
+        $change{status} = 'added';
+    }
+    elsif ( $event->mask() | IN_MODIFY ) {
+        $change{status} = 'modified';
+    }
+    elsif ( $event->mask() | IN_DELETE || $event->mask() ) {
+        $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<file> key contains the filename, and
+the C<status> key contains one of "modified", "added", or "deleted".
+
+=head1 SEE ALSO
+
+L<Catalyst>, L<Catalyst::Restarter>, <File::Modified>
+
+=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


Property changes on: Catalyst-Devel/1.00/branches/improved-restarter/lib/Catalyst/Watcher/Inotify.pm
___________________________________________________________________
Name: svn:keywords
   + Author Date Id Rev
Name: svn:eol-style
   + native

Deleted: Catalyst-Devel/1.00/branches/improved-restarter/lib/Catalyst/Watcher.pm
===================================================================
--- Catalyst-Devel/1.00/branches/improved-restarter/lib/Catalyst/Watcher.pm	2009-05-02 01:12:46 UTC (rev 9985)
+++ Catalyst-Devel/1.00/branches/improved-restarter/lib/Catalyst/Watcher.pm	2009-05-02 01:55:00 UTC (rev 9986)
@@ -1,201 +0,0 @@
-package Catalyst::Watcher;
-
-use Moose;
-use Moose::Util::TypeConstraints;
-
-use File::Find;
-use File::Modified;
-use File::Spec;
-use Time::HiRes qw/sleep/;
-use namespace::clean -except => 'meta';
-
-has interval => (
-    is      => 'ro',
-    isa     => 'Int',
-    default => 1,
-);
-
-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 directory => (
-    is      => 'ro',
-    isa     => $array_of_dirs,
-    default => sub { [ File::Spec->rel2abs( File::Spec->catdir( $FindBin::Bin, '..' ) ) ] },
-    coerce  => 1,
-);
-
-has follow_symlinks => (
-    is      => 'ro',
-    isa     => 'Bool',
-    default => 0,
-);
-
-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,
-);
-
-sub _build__watched_files {
-    my $self = shift;
-
-    my $regex = $self->regex;
-
-    my %list;
-    finddepth(
-        {
-            wanted => sub {
-                my $file = File::Spec->rel2abs($File::Find::name);
-                return unless $file =~ /$regex/;
-                return unless -f $file;
-
-                $list{$file} = 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->directory }
-    );
-
-    return \%list;
-}
-
-sub _build__modified {
-    my $self = shift;
-
-    return File::Modified->new(
-        method => 'mtime',
-        files  => [ keys %{ $self->_watched_files } ],
-    );
-}
-
-sub find_changed_files {
-    my $self = shift;
-
-    my @changes;
-    my @changed_files;
-
-    sleep $self->interval if $self->interval > 0;
-
-    eval { @changes = $self->_modified->changed };
-    if ($@) {
-        # File::Modified will die if a file is deleted.
-        my ($deleted_file) = $@ =~ /stat '(.+)'/;
-        push @changed_files,
-            {
-            file => $deleted_file || 'unknown file',
-            status => 'deleted',
-            };
-    }
-
-    if (@changes) {
-        $self->_modified->update;
-
-        @changed_files = map { { file => $_, status => 'modified' } }
-            grep { -f $_ } @changes;
-
-        # We also need to check to see if a new directory was created
-        unless (@changed_files) {
-            my $old_watch = $self->_watched_files;
-
-            $self->_clear_watched_files;
-
-            my $new_watch = $self->_watched_files;
-
-            @changed_files
-                = map { { file => $_, status => 'added' } }
-                grep { !defined $old_watch->{$_} }
-                keys %{$new_watch};
-
-            return unless @changed_files;
-        }
-    }
-
-    return @changed_files;
-}
-
-__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<file> key contains the filename, and
-the C<status> key contains one of "modified", "added", or "deleted".
-
-=head1 SEE ALSO
-
-L<Catalyst>, L<Catalyst::Restarter>, <File::Modified>
-
-=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




More information about the Catalyst-commits mailing list