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) { |
59 | if ( ( $event->IN_CREATE && $event->IN_ISDIR ) ) { |
7f564068 |
60 | $self->_add_directory( $event->fullname ); |
61 | push @interesting, $event; |
62 | } |
4b947012 |
63 | elsif ( $event->IN_DELETE_SELF ) { |
7f564068 |
64 | $event->w->cancel; |
65 | push @interesting, $event; |
66 | } |
4b947012 |
67 | elsif ( $event->fullname =~ /$regex/ ) { |
7f564068 |
68 | push @interesting, $event; |
69 | } |
70 | } |
71 | |
72 | return @interesting if @interesting; |
73 | } |
74 | } |
75 | |
7f564068 |
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 | }, |
4b947012 |
100 | $dir |
7f564068 |
101 | ); |
102 | } |
103 | |
104 | sub _event_to_change { |
105 | my $self = shift; |
106 | my $event = shift; |
107 | |
4b947012 |
108 | my %change = ( file => $event->fullname ); |
109 | if ( $event->IN_CREATE ) { |
7f564068 |
110 | $change{status} = 'added'; |
111 | } |
4b947012 |
112 | elsif ( $event->IN_MODIFY ) { |
7f564068 |
113 | $change{status} = 'modified'; |
114 | } |
4b947012 |
115 | elsif ( $event->IN_DELETE ) { |
7f564068 |
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 |