Bump required Module::Install version in everything. janus++
[catagits/Catalyst-Devel.git] / lib / Catalyst / Watcher / FileModified.pm
1 package Catalyst::Watcher::FileModified;
2
3 use Moose;
4
5 use File::Find;
6 use File::Modified;
7 use File::Spec;
8 use Time::HiRes qw/sleep/;
9 use namespace::clean -except => 'meta';
10
11 extends 'Catalyst::Watcher';
12
13 has interval => (
14     is      => 'ro',
15     isa     => 'Int',
16     default => 1,
17 );
18
19 has _watched_files => (
20     is         => 'ro',
21     isa        => 'HashRef[Str]',
22     lazy_build => 1,
23     clearer    => '_clear_watched_files',
24 );
25
26 has _modified => (
27     is         => 'rw',
28     isa        => 'File::Modified',
29     lazy_build => 1,
30     clearer    => '_clear_modified',
31 );
32
33
34 sub _build__watched_files {
35     my $self = shift;
36
37     my $regex = $self->regex;
38
39     my %list;
40     finddepth(
41         {
42             wanted => sub {
43                 my $path = File::Spec->rel2abs($File::Find::name);
44                 return unless $path =~ /$regex/;
45                 return unless -f $path;
46
47                 $list{$path} = 1;
48
49                 # also watch the directory for changes
50                 my $cur_dir = File::Spec->rel2abs($File::Find::dir);
51                 $cur_dir =~ s{/script/..}{};
52                 $list{$cur_dir} = 1;
53             },
54             follow_fast => $self->follow_symlinks ? 1 : 0,
55             no_chdir    => 1
56         },
57         @{ $self->directories }
58     );
59
60     return \%list;
61 }
62
63 sub _build__modified {
64     my $self = shift;
65
66     return File::Modified->new(
67         method => 'mtime',
68         files  => [ keys %{ $self->_watched_files } ],
69     );
70 }
71
72 sub watch {
73     my $self      = shift;
74     my $restarter = shift;
75
76     while (1) {
77         sleep $self->interval if $self->interval > 0;
78
79         my @changes = $self->_changed_files;
80
81         next unless @changes;
82
83         $restarter->handle_changes(@changes);
84
85         last;
86     }
87 }
88
89 sub _changed_files {
90     my $self = shift;
91
92     my @changes;
93
94     eval {
95         @changes = map { { file => $_, status => 'modified' } }
96             grep { -f $_ } $self->_modified->changed;
97     };
98
99     if ($@) {
100         # File::Modified will die if a file is deleted.
101         die unless $@ =~ /stat '(.+)'/;
102
103         push @changes, {
104             file   => $1 || 'unknown file',
105             status => 'deleted',
106         };
107
108         $self->_clear_watched_files;
109         $self->_clear_modified;
110     }
111     else {
112         $self->_modified->update;
113
114         my $old_watch = $self->_watched_files;
115
116         $self->_clear_watched_files;
117
118         my $new_watch = $self->_watched_files;
119
120         my @new_files = grep { !defined $old_watch->{$_} }
121             grep {-f}
122             keys %{$new_watch};
123
124         if (@new_files) {
125             $self->_clear_modified;
126             push @changes, map { { file => $_, status => 'added' } } @new_files;
127         }
128     }
129
130     return @changes;
131 }
132
133 __PACKAGE__->meta->make_immutable;
134
135 1;
136
137 __END__
138
139 =head1 NAME
140
141 Catalyst::Watcher::FileModified - Watch for changed application files using File::Modified
142
143 =head1 SYNOPSIS
144
145     my $watcher = Catalyst::Watcher::FileModified->new(
146         directories => '/path/to/MyApp',
147         regex       => '\.yml$|\.yaml$|\.conf|\.pm$',
148     );
149
150     while (1) {
151         my @changed_files = $watcher->watch();
152         ...
153     }
154
155 =head1 DESCRIPTION
156
157 This class monitors a directory of files for changes made to any file
158 matching a regular expression. It correctly handles new files added to the
159 application as well as files that are deleted.
160
161 =head1 METHODS
162
163 =head2 new ( directory => $path [, regex => $regex, delay => $delay ] )
164
165 Creates a new Watcher object.
166
167 =head2 find_changed_files
168
169 Returns a list of files that have been added, deleted, or changed
170 since the last time watch was called. Each element returned is a hash
171 reference with two keys. The C<file> key contains the filename, and
172 the C<status> key contains one of "modified", "added", or "deleted".
173
174 =head1 SEE ALSO
175
176 L<Catalyst>, L<Catalyst::Watcher>, L<Catalyst::Restarter>,
177 <File::Modified>
178
179 =head1 AUTHORS
180
181 Catalyst Contributors, see Catalyst.pm
182
183 =head1 COPYRIGHT
184
185 This program is free software, you can redistribute it and/or modify
186 it under the same terms as Perl itself.
187
188 =cut