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