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