Fix regression for "sub foo : Path {}" in the root controller which was introduced...
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Engine / HTTP / Restarter / Watcher.pm
CommitLineData
65586a18 1package Catalyst::Engine::HTTP::Restarter::Watcher;
2
3use strict;
4use warnings;
5use base 'Class::Accessor::Fast';
6use File::Find;
7use File::Modified;
8use File::Spec;
9use Time::HiRes qw/sleep/;
10
b5ecfcf0 11__PACKAGE__->mk_accessors(
12 qw/delay
13 directory
14 modified
15 regex
9c71d51d 16 follow_symlinks
b5ecfcf0 17 watch_list/
18);
65586a18 19
20sub new {
21 my ( $class, %args ) = @_;
b5ecfcf0 22
23 my $self = {%args};
24
65586a18 25 bless $self, $class;
b5ecfcf0 26
65586a18 27 $self->_init;
b5ecfcf0 28
65586a18 29 return $self;
30}
31
32sub _init {
33 my $self = shift;
b5ecfcf0 34
65586a18 35 my $watch_list = $self->_index_directory;
b5ecfcf0 36 $self->watch_list($watch_list);
37
65586a18 38 $self->modified(
39 File::Modified->new(
40 method => 'mtime',
41 files => [ keys %{$watch_list} ],
42 )
43 );
44}
45
46sub watch {
47 my $self = shift;
b5ecfcf0 48
65586a18 49 my @changes;
50 my @changed_files;
951572c0 51
52 my $delay = ( defined $self->delay ) ? $self->delay : 1;
b5ecfcf0 53
951572c0 54 sleep $delay if $delay > 0;
b5ecfcf0 55
65586a18 56 eval { @changes = $self->modified->changed };
b5ecfcf0 57 if ($@) {
58
65586a18 59 # File::Modified will die if a file is deleted.
60 my ($deleted_file) = $@ =~ /stat '(.+)'/;
61 push @changed_files, $deleted_file || 'unknown file';
62 }
b5ecfcf0 63
64 if (@changes) {
65
65586a18 66 # update all mtime information
67 $self->modified->update;
b5ecfcf0 68
65586a18 69 # check if any files were changed
70 @changed_files = grep { -f $_ } @changes;
b5ecfcf0 71
65586a18 72 # Check if only directories were changed. This means
73 # a new file was created.
b5ecfcf0 74 unless (@changed_files) {
75
65586a18 76 # re-index to find new files
77 my $new_watch = $self->_index_directory;
b5ecfcf0 78
65586a18 79 # look through the new list for new files
80 my $old_watch = $self->watch_list;
b5ecfcf0 81 @changed_files = grep { !defined $old_watch->{$_} }
82 keys %{$new_watch};
83
65586a18 84 return unless @changed_files;
85 }
86
87 # Test modified pm's
b5ecfcf0 88 for my $file (@changed_files) {
65586a18 89 next unless $file =~ /\.pm$/;
90 if ( my $error = $self->_test($file) ) {
b5ecfcf0 91 print STDERR qq/File "$file" modified, not restarting\n\n/;
65586a18 92 print STDERR '*' x 80, "\n";
93 print STDERR $error;
94 print STDERR '*' x 80, "\n";
95 return;
96 }
97 }
98 }
b5ecfcf0 99
65586a18 100 return @changed_files;
101}
102
103sub _index_directory {
104 my $self = shift;
b5ecfcf0 105
9c71d51d 106 my $dir = $self->directory;
107 die "No directory specified" if !$dir or ref($dir) && !@{$dir};
108
b5ecfcf0 109 my $regex = $self->regex || '\.pm$';
65586a18 110 my %list;
b5ecfcf0 111
65586a18 112 finddepth(
113 {
114 wanted => sub {
115 my $file = File::Spec->rel2abs($File::Find::name);
116 return unless $file =~ /$regex/;
117 return unless -f $file;
118 $file =~ s{/script/..}{};
119 $list{$file} = 1;
b5ecfcf0 120
65586a18 121 # also watch the directory for changes
122 my $cur_dir = File::Spec->rel2abs($File::Find::dir);
b5ecfcf0 123 $cur_dir =~ s{/script/..}{};
65586a18 124 $list{$cur_dir} = 1;
125 },
9c71d51d 126 follow_fast => $self->follow_symlinks ? 1 : 0,
65586a18 127 no_chdir => 1
128 },
9c71d51d 129 ref $dir eq 'ARRAY' ? @{$dir} : $dir
65586a18 130 );
131 return \%list;
132}
133
134sub _test {
135 my ( $self, $file ) = @_;
b5ecfcf0 136
65586a18 137 delete $INC{$file};
138 local $SIG{__WARN__} = sub { };
b5ecfcf0 139
65586a18 140 open my $olderr, '>&STDERR';
141 open STDERR, '>', File::Spec->devnull;
142 eval "require '$file'";
143 open STDERR, '>&', $olderr;
b5ecfcf0 144
65586a18 145 return ($@) ? $@ : 0;
b5ecfcf0 146}
65586a18 147
1481;
149__END__
150
151=head1 NAME
152
153Catalyst::Engine::HTTP::Restarter::Watcher - Watch for changed application
154files
155
156=head1 SYNOPSIS
157
158 my $watcher = Catalyst::Engine::HTTP::Restarter::Watcher->new(
159 directory => '/path/to/MyApp',
160 regex => '\.yml$|\.yaml$|\.pm$',
161 delay => 1,
162 );
163
164 while (1) {
165 my @changed_files = $watcher->watch();
166 }
167
168=head1 DESCRIPTION
169
170This class monitors a directory of files for changes made to any file
171matching a regular expression. It correctly handles new files added to the
172application as well as files that are deleted.
173
174=head1 METHODS
175
176=head2 new ( directory => $path [, regex => $regex, delay => $delay ] )
177
178Creates a new Watcher object.
179
180=head2 watch
181
182Returns a list of files that have been added, deleted, or changed since the
183last time watch was called.
184
185=head1 SEE ALSO
186
187L<Catalyst>, L<Catalyst::Engine::HTTP::Restarter>, L<File::Modified>
188
189=head1 AUTHORS
190
191Sebastian Riedel, <sri@cpan.org>
192
193Andy Grundman, <andy@hybridized.org>
194
195=head1 THANKS
196
197Many parts are ripped out of C<HTTP::Server::Simple> by Jesse Vincent.
198
199=head1 COPYRIGHT
200
201This program is free software, you can redistribute it and/or modify it under
202the same terms as Perl itself.
203
204=cut