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