Remove a useless newline
[catagits/Catalyst-Devel.git] / lib / Catalyst / Watcher / Inotify.pm
1 package Catalyst::Watcher::Inotify;
2
3 use Moose;
4
5 use File::Find;
6 use Linux::Inotify2;
7 use namespace::clean -except => 'meta';
8
9 extends 'Catalyst::Watcher';
10
11 has _inotify => (
12     is       => 'rw',
13     isa      => 'Linux::Inotify2',
14     default  => sub { Linux::Inotify2->new },
15     init_arg => undef,
16 );
17
18 has _mask => (
19     is         => 'rw',
20     isa        => 'Int',
21     lazy_build => 1,
22 );
23
24 sub BUILD {
25     my $self = shift;
26
27     # If this is done via a lazy_build then the call to
28     # ->_add_directory ends up causing endless recursion when it calls
29     # ->_inotify itself.
30     $self->_add_directory($_) for @{ $self->directories };
31
32     return $self;
33 }
34
35 sub watch {
36     my $self      = shift;
37     my $restarter = shift;
38
39     my @events = $self->_wait_for_events;
40
41     $restarter->handle_changes( map { $self->_event_to_change($_) } @events );
42
43     return;
44 }
45
46 sub _wait_for_events {
47     my $self = shift;
48
49     my $regex = $self->regex;
50
51     while (1) {
52         # This is a blocking read, so it will not return until
53         # something happens. The restarter will end up calling ->watch
54         # again after handling the changes.
55         my @events = $self->_inotify->read;
56
57         my @interesting;
58         for my $event (@events) {
59             if ( ( $event->IN_CREATE && $event->IN_ISDIR ) ) {
60                 $self->_add_directory( $event->fullname );
61                 push @interesting, $event;
62             }
63             elsif ( $event->IN_DELETE_SELF ) {
64                 $event->w->cancel;
65                 push @interesting, $event;
66             }
67             elsif ( $event->fullname =~ /$regex/ ) {
68                 push @interesting, $event;
69             }
70         }
71
72         return @interesting if @interesting;
73     }
74 }
75
76 sub _build__mask {
77     my $self = shift;
78
79     my $mask = IN_MODIFY | IN_CREATE | IN_DELETE | IN_DELETE_SELF | IN_MOVE_SELF;
80     $mask |= IN_DONT_FOLLOW unless $self->follow_symlinks;
81
82     return $mask;
83 }
84
85 sub _add_directory {
86     my $self = shift;
87     my $dir  = shift;
88
89     finddepth(
90         {
91             wanted => sub {
92                 my $path = File::Spec->rel2abs($File::Find::name);
93                 return unless -d $path;
94
95                 $self->_inotify->watch( $path, $self->_mask );
96             },
97             follow_fast => $self->follow_symlinks ? 1 : 0,
98             no_chdir    => 1
99         },
100         $dir
101     );
102 }
103
104 sub _event_to_change {
105     my $self  = shift;
106     my $event = shift;
107
108     my %change = ( file => $event->fullname );
109     if ( $event->IN_CREATE ) {
110         $change{status} = 'added';
111     }
112     elsif ( $event->IN_MODIFY ) {
113         $change{status} = 'modified';
114     }
115     elsif ( $event->IN_DELETE ) {
116         $change{status} = 'deleted';
117     }
118     else {
119         $change{status} = 'containing directory modified';
120     }
121
122     return \%change;
123 }
124
125 __PACKAGE__->meta->make_immutable;
126
127 1;
128
129 __END__
130
131 =head1 NAME
132
133 Catalyst::Watcher - Watch for changed application files
134
135 =head1 SYNOPSIS
136
137     my $watcher = Catalyst::Watcher->new(
138         directory => '/path/to/MyApp',
139         regex     => '\.yml$|\.yaml$|\.conf|\.pm$',
140         interval  => 3,
141     );
142
143     while (1) {
144         my @changed_files = $watcher->watch();
145     }
146
147 =head1 DESCRIPTION
148
149 This class monitors a directory of files for changes made to any file
150 matching a regular expression. It correctly handles new files added to the
151 application as well as files that are deleted.
152
153 =head1 METHODS
154
155 =head2 new ( directory => $path [, regex => $regex, delay => $delay ] )
156
157 Creates a new Watcher object.
158
159 =head2 find_changed_files
160
161 Returns a list of files that have been added, deleted, or changed
162 since the last time watch was called. Each element returned is a hash
163 reference with two keys. The C<file> key contains the filename, and
164 the C<status> key contains one of "modified", "added", or "deleted".
165
166 =head1 SEE ALSO
167
168 L<Catalyst>, L<Catalyst::Restarter>, <File::Modified>
169
170 =head1 AUTHORS
171
172 Catalyst Contributors, see Catalyst.pm
173
174 =head1 COPYRIGHT
175
176 This program is free software, you can redistribute it and/or modify
177 it under the same terms as Perl itself.
178
179 =cut