Commit | Line | Data |
7f564068 |
1 | package Catalyst::Watcher::Inotify; |
2 | |
3 | use Moose; |
4 | |
4b947012 |
5 | use File::Find; |
7f564068 |
6 | use Linux::Inotify2; |
7 | use namespace::clean -except => 'meta'; |
8 | |
9 | extends 'Catalyst::Watcher'; |
10 | |
11 | has _inotify => ( |
4b947012 |
12 | is => 'rw', |
13 | isa => 'Linux::Inotify2', |
14 | default => sub { Linux::Inotify2->new }, |
15 | init_arg => undef, |
7f564068 |
16 | ); |
17 | |
18 | has _mask => ( |
19 | is => 'rw', |
20 | isa => 'Int', |
21 | lazy_build => 1, |
22 | ); |
23 | |
4b947012 |
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 | |
7f564068 |
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 | |
4b947012 |
49 | my $regex = $self->regex; |
50 | |
7f564068 |
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; |
4b947012 |
58 | for my $event (@events) { |
0d15a4b0 |
59 | if ( $event->IN_CREATE && $event->IN_ISDIR ) { |
7f564068 |
60 | $self->_add_directory( $event->fullname ); |
61 | push @interesting, $event; |
62 | } |
0e86962f |
63 | elsif ( $event->IN_DELETE_SELF |
64 | || $event->fullname =~ /$regex/ ) { |
7f564068 |
65 | push @interesting, $event; |
66 | } |
67 | } |
68 | |
69 | return @interesting if @interesting; |
70 | } |
71 | } |
72 | |
7f564068 |
73 | sub _build__mask { |
74 | my $self = shift; |
75 | |
76 | my $mask = IN_MODIFY | IN_CREATE | IN_DELETE | IN_DELETE_SELF | IN_MOVE_SELF; |
77 | $mask |= IN_DONT_FOLLOW unless $self->follow_symlinks; |
78 | |
79 | return $mask; |
80 | } |
81 | |
82 | sub _add_directory { |
83 | my $self = shift; |
84 | my $dir = shift; |
85 | |
86 | finddepth( |
87 | { |
88 | wanted => sub { |
89 | my $path = File::Spec->rel2abs($File::Find::name); |
90 | return unless -d $path; |
91 | |
92 | $self->_inotify->watch( $path, $self->_mask ); |
93 | }, |
94 | follow_fast => $self->follow_symlinks ? 1 : 0, |
95 | no_chdir => 1 |
96 | }, |
4b947012 |
97 | $dir |
7f564068 |
98 | ); |
99 | } |
100 | |
101 | sub _event_to_change { |
102 | my $self = shift; |
103 | my $event = shift; |
104 | |
4b947012 |
105 | my %change = ( file => $event->fullname ); |
106 | if ( $event->IN_CREATE ) { |
7f564068 |
107 | $change{status} = 'added'; |
108 | } |
4b947012 |
109 | elsif ( $event->IN_MODIFY ) { |
7f564068 |
110 | $change{status} = 'modified'; |
111 | } |
4b947012 |
112 | elsif ( $event->IN_DELETE ) { |
7f564068 |
113 | $change{status} = 'deleted'; |
114 | } |
115 | else { |
116 | $change{status} = 'containing directory modified'; |
117 | } |
118 | |
119 | return \%change; |
120 | } |
121 | |
122 | __PACKAGE__->meta->make_immutable; |
123 | |
124 | 1; |
125 | |
126 | __END__ |
127 | |
128 | =head1 NAME |
129 | |
130 | Catalyst::Watcher - Watch for changed application files |
131 | |
132 | =head1 SYNOPSIS |
133 | |
134 | my $watcher = Catalyst::Watcher->new( |
135 | directory => '/path/to/MyApp', |
136 | regex => '\.yml$|\.yaml$|\.conf|\.pm$', |
137 | interval => 3, |
138 | ); |
139 | |
140 | while (1) { |
141 | my @changed_files = $watcher->watch(); |
142 | } |
143 | |
144 | =head1 DESCRIPTION |
145 | |
146 | This class monitors a directory of files for changes made to any file |
147 | matching a regular expression. It correctly handles new files added to the |
148 | application as well as files that are deleted. |
149 | |
150 | =head1 METHODS |
151 | |
152 | =head2 new ( directory => $path [, regex => $regex, delay => $delay ] ) |
153 | |
154 | Creates a new Watcher object. |
155 | |
156 | =head2 find_changed_files |
157 | |
158 | Returns a list of files that have been added, deleted, or changed |
159 | since the last time watch was called. Each element returned is a hash |
160 | reference with two keys. The C<file> key contains the filename, and |
161 | the C<status> key contains one of "modified", "added", or "deleted". |
162 | |
163 | =head1 SEE ALSO |
164 | |
165 | L<Catalyst>, L<Catalyst::Restarter>, <File::Modified> |
166 | |
167 | =head1 AUTHORS |
168 | |
169 | Catalyst Contributors, see Catalyst.pm |
170 | |
171 | =head1 COPYRIGHT |
172 | |
173 | This program is free software, you can redistribute it and/or modify |
174 | it under the same terms as Perl itself. |
175 | |
176 | =cut |