Create branch register_actions.
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Engine / HTTP / Restarter / Watcher.pm
CommitLineData
65586a18 1package Catalyst::Engine::HTTP::Restarter::Watcher;
2
ae29b412 3use Moose;
4with 'MooseX::Emulate::Class::Accessor::Fast';
5
65586a18 6use File::Find;
7use File::Modified;
8use File::Spec;
9use Time::HiRes qw/sleep/;
ae29b412 10use Moose::Util qw/find_meta/;
11use namespace::clean -except => 'meta';
12
13BEGIN {
14 # If we can detect stash changes, then we do magic
15 # to make their metaclass mutable (if they have one)
16 # so that restarting works as expected.
17 eval { require B::Hooks::OP::Check::StashChange; };
18 *DETECT_PACKAGE_COMPILATION = $@
19 ? sub () { 0 }
20 : sub () { 1 }
21}
65586a18 22
ae29b412 23has delay => (is => 'rw');
24has regex => (is => 'rw');
25has modified => (is => 'rw');
26has directory => (is => 'rw');
27has watch_list => (is => 'rw');
28has follow_symlinks => (is => 'rw');
b5ecfcf0 29
ae29b412 30sub BUILD {
31 shift->_init;
65586a18 32}
33
34sub _init {
35 my $self = shift;
b5ecfcf0 36
65586a18 37 my $watch_list = $self->_index_directory;
b5ecfcf0 38 $self->watch_list($watch_list);
39
65586a18 40 $self->modified(
41 File::Modified->new(
42 method => 'mtime',
43 files => [ keys %{$watch_list} ],
44 )
45 );
46}
47
48sub watch {
49 my $self = shift;
b5ecfcf0 50
65586a18 51 my @changes;
52 my @changed_files;
951572c0 53
54 my $delay = ( defined $self->delay ) ? $self->delay : 1;
b5ecfcf0 55
951572c0 56 sleep $delay if $delay > 0;
b5ecfcf0 57
65586a18 58 eval { @changes = $self->modified->changed };
b5ecfcf0 59 if ($@) {
60
65586a18 61 # File::Modified will die if a file is deleted.
62 my ($deleted_file) = $@ =~ /stat '(.+)'/;
63 push @changed_files, $deleted_file || 'unknown file';
64 }
b5ecfcf0 65
66 if (@changes) {
67
65586a18 68 # update all mtime information
69 $self->modified->update;
b5ecfcf0 70
65586a18 71 # check if any files were changed
72 @changed_files = grep { -f $_ } @changes;
b5ecfcf0 73
65586a18 74 # Check if only directories were changed. This means
75 # a new file was created.
b5ecfcf0 76 unless (@changed_files) {
77
65586a18 78 # re-index to find new files
79 my $new_watch = $self->_index_directory;
b5ecfcf0 80
65586a18 81 # look through the new list for new files
82 my $old_watch = $self->watch_list;
b5ecfcf0 83 @changed_files = grep { !defined $old_watch->{$_} }
84 keys %{$new_watch};
85
65586a18 86 return unless @changed_files;
87 }
88
89 # Test modified pm's
b5ecfcf0 90 for my $file (@changed_files) {
65586a18 91 next unless $file =~ /\.pm$/;
92 if ( my $error = $self->_test($file) ) {
b5ecfcf0 93 print STDERR qq/File "$file" modified, not restarting\n\n/;
65586a18 94 print STDERR '*' x 80, "\n";
95 print STDERR $error;
96 print STDERR '*' x 80, "\n";
97 return;
98 }
99 }
100 }
b5ecfcf0 101
65586a18 102 return @changed_files;
103}
104
105sub _index_directory {
106 my $self = shift;
b5ecfcf0 107
9c71d51d 108 my $dir = $self->directory;
109 die "No directory specified" if !$dir or ref($dir) && !@{$dir};
110
b5ecfcf0 111 my $regex = $self->regex || '\.pm$';
65586a18 112 my %list;
b5ecfcf0 113
65586a18 114 finddepth(
115 {
116 wanted => sub {
117 my $file = File::Spec->rel2abs($File::Find::name);
118 return unless $file =~ /$regex/;
119 return unless -f $file;
120 $file =~ s{/script/..}{};
121 $list{$file} = 1;
b5ecfcf0 122
65586a18 123 # also watch the directory for changes
124 my $cur_dir = File::Spec->rel2abs($File::Find::dir);
b5ecfcf0 125 $cur_dir =~ s{/script/..}{};
65586a18 126 $list{$cur_dir} = 1;
127 },
9c71d51d 128 follow_fast => $self->follow_symlinks ? 1 : 0,
65586a18 129 no_chdir => 1
130 },
9c71d51d 131 ref $dir eq 'ARRAY' ? @{$dir} : $dir
65586a18 132 );
133 return \%list;
134}
135
136sub _test {
137 my ( $self, $file ) = @_;
b5ecfcf0 138
ae29b412 139 my $id;
140 if (DETECT_PACKAGE_COMPILATION) {
141 $id = B::Hooks::OP::Check::StashChange::register(sub {
142 my ($new, $old) = @_;
143 my $meta = find_meta($new);
144 if ($meta) { # A little paranoia here - Moose::Meta::Role has neither of these methods.
145 my $is_immutable = $meta->can('is_immutable');
146 my $make_mutable = $meta->can('make_mutable');
147 $meta->$make_mutable() if $is_immutable && $make_mutable && $meta->$is_immutable();
148 }
149 });
150 }
151
152 delete $INC{$file}; # Remove from %INC so it will reload
65586a18 153 local $SIG{__WARN__} = sub { };
b5ecfcf0 154
65586a18 155 open my $olderr, '>&STDERR';
156 open STDERR, '>', File::Spec->devnull;
157 eval "require '$file'";
158 open STDERR, '>&', $olderr;
b5ecfcf0 159
ae29b412 160 B::Hooks::OP::Check::StashChange::unregister($id) if $id;
161
65586a18 162 return ($@) ? $@ : 0;
b5ecfcf0 163}
65586a18 164
1651;
166__END__
167
168=head1 NAME
169
170Catalyst::Engine::HTTP::Restarter::Watcher - Watch for changed application
171files
172
173=head1 SYNOPSIS
174
175 my $watcher = Catalyst::Engine::HTTP::Restarter::Watcher->new(
176 directory => '/path/to/MyApp',
d0b011ef 177 regex => '\.yml$|\.yaml$|\.conf|\.pm$',
65586a18 178 delay => 1,
179 );
180
181 while (1) {
182 my @changed_files = $watcher->watch();
183 }
184
185=head1 DESCRIPTION
186
187This class monitors a directory of files for changes made to any file
188matching a regular expression. It correctly handles new files added to the
189application as well as files that are deleted.
190
191=head1 METHODS
192
193=head2 new ( directory => $path [, regex => $regex, delay => $delay ] )
194
195Creates a new Watcher object.
196
197=head2 watch
198
199Returns a list of files that have been added, deleted, or changed since the
200last time watch was called.
201
ae29b412 202=head2 DETECT_PACKAGE_COMPILATION
203
204Returns true if L<B::Hooks::OP::Check::StashChange> is installed and
205can be used to detect when files are compiled. This is used internally
206to make the L<Moose> metaclass of any class being reloaded immutable.
207
208If L<B::Hooks::OP::Check::StashChange> is not installed, then the
209restarter makes all application components immutable. This covers the
210simple case, but is less useful if you're using Moose in components
211outside Catalyst's namespaces, but inside your application directory.
212
65586a18 213=head1 SEE ALSO
214
215L<Catalyst>, L<Catalyst::Engine::HTTP::Restarter>, L<File::Modified>
216
217=head1 AUTHORS
218
0bf7ab71 219Catalyst Contributors, see Catalyst.pm
65586a18 220
221=head1 THANKS
222
223Many parts are ripped out of C<HTTP::Server::Simple> by Jesse Vincent.
224
225=head1 COPYRIGHT
226
227This program is free software, you can redistribute it and/or modify it under
228the same terms as Perl itself.
229
230=cut