1 package Catalyst::Engine::HTTP::Restarter::Watcher;
4 with 'MooseX::Emulate::Class::Accessor::Fast' => { excludes => [ 'BUILD' ] };
9 use Time::HiRes qw/sleep/;
10 use Moose::Util qw/find_meta/;
11 use namespace::clean -except => 'meta';
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 = $@
23 has delay => (is => 'rw');
24 has regex => (is => 'rw');
25 has modified => (is => 'rw');
26 has directory => (is => 'rw');
27 has watch_list => (is => 'rw');
28 has follow_symlinks => (is => 'rw');
37 my $watch_list = $self->_index_directory;
38 $self->watch_list($watch_list);
43 files => [ keys %{$watch_list} ],
54 my $delay = ( defined $self->delay ) ? $self->delay : 1;
56 sleep $delay if $delay > 0;
58 eval { @changes = $self->modified->changed };
61 # File::Modified will die if a file is deleted.
62 my ($deleted_file) = $@ =~ /stat '(.+)'/;
63 push @changed_files, $deleted_file || 'unknown file';
68 # update all mtime information
69 $self->modified->update;
71 # check if any files were changed
72 @changed_files = grep { -f $_ } @changes;
74 # Check if only directories were changed. This means
75 # a new file was created.
76 unless (@changed_files) {
78 # re-index to find new files
79 my $new_watch = $self->_index_directory;
81 # look through the new list for new files
82 my $old_watch = $self->watch_list;
83 @changed_files = grep { !defined $old_watch->{$_} }
86 return unless @changed_files;
90 for my $file (@changed_files) {
91 next unless $file =~ /\.pm$/;
92 if ( my $error = $self->_test($file) ) {
93 print STDERR qq/File "$file" modified, not restarting\n\n/;
94 print STDERR '*' x 80, "\n";
96 print STDERR '*' x 80, "\n";
102 return @changed_files;
105 sub _index_directory {
108 my $dir = $self->directory;
109 die "No directory specified" if !$dir or ref($dir) && !@{$dir};
111 my $regex = $self->regex || '\.pm$';
117 my $file = File::Spec->rel2abs($File::Find::name);
118 return unless $file =~ /$regex/;
119 return unless -f $file;
120 $file =~ s{/script/..}{};
123 # also watch the directory for changes
124 my $cur_dir = File::Spec->rel2abs($File::Find::dir);
125 $cur_dir =~ s{/script/..}{};
128 follow_fast => $self->follow_symlinks ? 1 : 0,
131 ref $dir eq 'ARRAY' ? @{$dir} : $dir
137 my ( $self, $file ) = @_;
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 eval { # Do not explode the watcher process if this fails.
149 my $superclasses = $meta->can('superclasses');
150 $meta->$superclasses('Moose::Object') if $superclasses;
156 local $Catalyst::__AM_RESTARTING = 1; # Hack to avoid C3 fail
157 delete $INC{$file}; # Remove from %INC so it will reload
158 local $SIG{__WARN__} = sub { };
160 open my $olderr, '>&STDERR';
161 open STDERR, '>', File::Spec->devnull;
162 eval "require '$file'";
163 open STDERR, '>&', $olderr;
165 B::Hooks::OP::Check::StashChange::unregister($id) if $id;
167 return ($@) ? $@ : 0;
175 Catalyst::Engine::HTTP::Restarter::Watcher - Watch for changed application
180 my $watcher = Catalyst::Engine::HTTP::Restarter::Watcher->new(
181 directory => '/path/to/MyApp',
182 regex => '\.yml$|\.yaml$|\.conf|\.pm$',
187 my @changed_files = $watcher->watch();
192 This class monitors a directory of files for changes made to any file
193 matching a regular expression. It correctly handles new files added to the
194 application as well as files that are deleted.
198 =head2 new ( directory => $path [, regex => $regex, delay => $delay ] )
200 Creates a new Watcher object.
204 Returns a list of files that have been added, deleted, or changed since the
205 last time watch was called.
207 =head2 DETECT_PACKAGE_COMPILATION
209 Returns true if L<B::Hooks::OP::Check::StashChange> is installed and
210 can be used to detect when files are compiled. This is used internally
211 to make the L<Moose> metaclass of any class being reloaded immutable.
213 If L<B::Hooks::OP::Check::StashChange> is not installed, then the
214 restarter makes all application components immutable. This covers the
215 simple case, but is less useful if you're using Moose in components
216 outside Catalyst's namespaces, but inside your application directory.
220 L<Catalyst>, L<Catalyst::Engine::HTTP::Restarter>, L<File::Modified>
224 Catalyst Contributors, see Catalyst.pm
228 Many parts are ripped out of C<HTTP::Server::Simple> by Jesse Vincent.
232 This program is free software, you can redistribute it and/or modify it under
233 the same terms as Perl itself.