Add docs, and don't bother to make components mutable if you can detect packages...
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Engine / HTTP / Restarter / Watcher.pm
CommitLineData
65586a18 1package Catalyst::Engine::HTTP::Restarter::Watcher;
2
7fa2c9c1 3use Moose;
531f1ab6 4with 'MooseX::Emulate::Class::Accessor::Fast';
5
65586a18 6use File::Find;
7use File::Modified;
8use File::Spec;
9use Time::HiRes qw/sleep/;
65b708a6 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
7fa2c9c1 23has delay => (is => 'rw');
24has regex => (is => 'rw');
25has modified => (is => 'rw');
26has directory => (is => 'rw');
27has watch_list => (is => 'rw');
f7174b10 28has follow_symlinks => (is => 'rw');
65586a18 29
0fc2d522 30sub BUILD {
65b708a6 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;
ac5c933b 53
951572c0 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
65b708a6 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) {
145 $meta->make_mutable if $meta->is_immutable;
146 }
147 });
148 }
149
150 delete $INC{$file}; # Remove from %INC so it will reload
65586a18 151 local $SIG{__WARN__} = sub { };
b5ecfcf0 152
65586a18 153 open my $olderr, '>&STDERR';
154 open STDERR, '>', File::Spec->devnull;
155 eval "require '$file'";
156 open STDERR, '>&', $olderr;
b5ecfcf0 157
65b708a6 158 B::Hooks::OP::Check::StashChange::unregister($id) if $id;
159
65586a18 160 return ($@) ? $@ : 0;
b5ecfcf0 161}
65586a18 162
1631;
164__END__
165
166=head1 NAME
167
168Catalyst::Engine::HTTP::Restarter::Watcher - Watch for changed application
169files
170
171=head1 SYNOPSIS
172
173 my $watcher = Catalyst::Engine::HTTP::Restarter::Watcher->new(
174 directory => '/path/to/MyApp',
2f381252 175 regex => '\.yml$|\.yaml$|\.conf|\.pm$',
65586a18 176 delay => 1,
177 );
ac5c933b 178
65586a18 179 while (1) {
180 my @changed_files = $watcher->watch();
181 }
182
183=head1 DESCRIPTION
184
185This class monitors a directory of files for changes made to any file
186matching a regular expression. It correctly handles new files added to the
187application as well as files that are deleted.
188
189=head1 METHODS
190
191=head2 new ( directory => $path [, regex => $regex, delay => $delay ] )
192
193Creates a new Watcher object.
194
195=head2 watch
196
197Returns a list of files that have been added, deleted, or changed since the
198last time watch was called.
199
c03163b8 200=head2 DETECT_PACKAGE_COMPILATION
201
202Returns true if L<B::Hooks::OP::Check::StashChange> is installed and
203can be used to detect when files are compiled. This is used internally
204to make the L<Moose> metaclass of any class being reloaded immutable.
205
206If L<B::Hooks::OP::Check::StashChange> is not installed, then the
207restarter makes all application components immutable. This covers the
208simple case, but is less useful if you're using Moose in components
209outside Catalyst's namespaces, but inside your application directory.
210
65586a18 211=head1 SEE ALSO
212
213L<Catalyst>, L<Catalyst::Engine::HTTP::Restarter>, L<File::Modified>
214
215=head1 AUTHORS
216
2f381252 217Catalyst Contributors, see Catalyst.pm
65586a18 218
219=head1 THANKS
220
221Many parts are ripped out of C<HTTP::Server::Simple> by Jesse Vincent.
222
223=head1 COPYRIGHT
224
225This program is free software, you can redistribute it and/or modify it under
226the same terms as Perl itself.
227
228=cut