1 package Catalyst::Engine::HTTP::Restarter::Watcher;
4 with 'MooseX::Emulate::Class::Accessor::Fast';
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', builder => '_build_modified', lazy => 1);
26 has directory => (is => 'rw');
27 has watch_list => (is => 'rw', builder => '_build_watch_list', lazy => 1);
28 has follow_symlinks => (is => 'rw');
30 sub _build_watch_list {
32 return $self->_index_directory;
37 return File::Modified->new(
39 files => [ keys %{ $self->watch_list } ],
49 my $delay = ( defined $self->delay ) ? $self->delay : 1;
51 sleep $delay if $delay > 0;
53 eval { @changes = $self->modified->changed };
56 # File::Modified will die if a file is deleted.
57 my ($deleted_file) = $@ =~ /stat '(.+)'/;
58 push @changed_files, $deleted_file || 'unknown file';
63 # update all mtime information
64 $self->modified->update;
66 # check if any files were changed
67 @changed_files = grep { -f $_ } @changes;
69 # Check if only directories were changed. This means
70 # a new file was created.
71 unless (@changed_files) {
73 # re-index to find new files
74 my $new_watch = $self->_index_directory;
76 # look through the new list for new files
77 my $old_watch = $self->watch_list;
78 @changed_files = grep { !defined $old_watch->{$_} }
81 return unless @changed_files;
85 for my $file (@changed_files) {
86 next unless $file =~ /\.pm$/;
87 if ( my $error = $self->_test($file) ) {
88 print STDERR qq/File "$file" modified, not restarting\n\n/;
89 print STDERR '*' x 80, "\n";
91 print STDERR '*' x 80, "\n";
97 return @changed_files;
100 sub _index_directory {
103 my $dir = $self->directory;
104 die "No directory specified" if !$dir or ref($dir) && !@{$dir};
106 my $regex = $self->regex || '\.pm$';
112 my $file = File::Spec->rel2abs($File::Find::name);
113 return unless $file =~ /$regex/;
114 return unless -f $file;
115 $file =~ s{/script/..}{};
118 # also watch the directory for changes
119 my $cur_dir = File::Spec->rel2abs($File::Find::dir);
120 $cur_dir =~ s{/script/..}{};
123 follow_fast => $self->follow_symlinks ? 1 : 0,
126 ref $dir eq 'ARRAY' ? @{$dir} : $dir
132 my ( $self, $file ) = @_;
135 if (DETECT_PACKAGE_COMPILATION) {
136 $id = B::Hooks::OP::Check::StashChange::register(sub {
137 my ($new, $old) = @_;
138 my $meta = find_meta($new);
139 if ($meta) { # A little paranoia here - Moose::Meta::Role has neither of these methods.
140 my $is_immutable = $meta->can('is_immutable');
141 my $make_mutable = $meta->can('make_mutable');
142 $meta->$make_mutable() if $is_immutable && $make_mutable && $meta->$is_immutable();
143 eval { # Do not explode the watcher process if this fails.
144 my $superclasses = $meta->can('superclasses');
145 $meta->$superclasses('Moose::Object') if $superclasses;
151 local $Catalyst::__AM_RESTARTING = 1; # Hack to avoid C3 fail
152 delete $INC{$file}; # Remove from %INC so it will reload
153 local $SIG{__WARN__} = sub { };
155 open my $olderr, '>&STDERR';
156 open STDERR, '>', File::Spec->devnull;
157 eval "require '$file'";
158 open STDERR, '>&', $olderr;
160 B::Hooks::OP::Check::StashChange::unregister($id) if $id;
162 return ($@) ? $@ : 0;
170 Catalyst::Engine::HTTP::Restarter::Watcher - Watch for changed application
175 my $watcher = Catalyst::Engine::HTTP::Restarter::Watcher->new(
176 directory => '/path/to/MyApp',
177 regex => '\.yml$|\.yaml$|\.conf|\.pm$',
182 my @changed_files = $watcher->watch();
187 This class monitors a directory of files for changes made to any file
188 matching a regular expression. It correctly handles new files added to the
189 application as well as files that are deleted.
193 =head2 new ( directory => $path [, regex => $regex, delay => $delay ] )
195 Creates a new Watcher object.
199 Returns a list of files that have been added, deleted, or changed since the
200 last time watch was called.
202 =head2 DETECT_PACKAGE_COMPILATION
204 Returns true if L<B::Hooks::OP::Check::StashChange> is installed and
205 can be used to detect when files are compiled. This is used internally
206 to make the L<Moose> metaclass of any class being reloaded immutable.
208 If L<B::Hooks::OP::Check::StashChange> is not installed, then the
209 restarter makes all application components immutable. This covers the
210 simple case, but is less useful if you're using Moose in components
211 outside Catalyst's namespaces, but inside your application directory.
215 L<Catalyst>, L<Catalyst::Engine::HTTP::Restarter>, L<File::Modified>
219 Catalyst Contributors, see Catalyst.pm
223 Many parts are ripped out of C<HTTP::Server::Simple> by Jesse Vincent.
227 This program is free software, you can redistribute it and/or modify it under
228 the same terms as Perl itself.