Add built local::lib
[catagits/Gitalist.git] / local-lib5 / lib / perl5 / File / ChangeNotify / Watcher / KQueue.pm
CommitLineData
3fea05b9 1package File::ChangeNotify::Watcher::KQueue;
2
3use strict;
4use warnings;
5
6use Moose;
7
8our $VERSION = '0.11';
9
10use File::Find ();
11use IO::KQueue;
12
13extends 'File::ChangeNotify::Watcher';
14
15has 'absorb_delay' => (
16 is => 'ro',
17 isa => 'Int',
18 default => 100,
19);
20
21has '_kqueue' => (
22 is => 'ro',
23 isa => 'IO::KQueue',
24 default => sub { IO::KQueue->new },
25 init_arg => undef,
26);
27
28# We need to keep hold of filehandles for all the directories *and* files in the
29# tree. KQueue events will be automatically deleted when the filehandles go out
30# of scope.
31has '_files' => (
32 is => 'ro',
33 isa => 'HashRef',
34 default => sub { {} },
35 init_arg => undef,
36);
37
38sub sees_all_events {0}
39
40sub BUILD {
41 my ($self) = @_;
42 $self->_watch_dir($_) for @{ $self->directories };
43}
44
45sub wait_for_events {
46 my ($self) = @_;
47
48 while (1) {
49 my @events = $self->_get_events;
50 return @events if @events;
51 }
52}
53
54sub new_events {
55 my ($self) = @_;
56 my @events = $self->_get_events(0);
57}
58
59sub _get_events {
60 my ( $self, $timeout ) = @_;
61
62 my @kevents = $self->_kqueue->kevent( $timeout || () );
63
64 # Events come in groups, wait for a short period to absorb any extra ones
65 # that might happen immediately after the ones we've detected.
66 push @kevents, $self->_kqueue->kevent( $self->absorb_delay )
67 if $self->absorb_delay;
68
69 my @events;
70 foreach my $kevent (@kevents) {
71
72 my $path = $kevent->[KQ_UDATA];
73 my $flags = $kevent->[KQ_FFLAGS];
74
75 # Delete - this works reasonably well with KQueue
76 if ( $flags & NOTE_DELETE ) {
77 delete $self->_files->{$path};
78 push @events, $self->_event( $path, 'delete' );
79 }
80
81 # Rename - represented as deletes and creates
82 elsif ( $flags & NOTE_RENAME ) {
83
84 # Renamed dirs
85 # Use the stored filehandle (it survives renaming) to identify a dir
86 # and remove any filehandles we're storing to its contents
87 my $fh = $self->_files->{$path};
88 if ( -d $fh ) {
89 foreach my $stored_path ( keys %{ $self->_files } ) {
90 next unless index( $stored_path, $path ) == 0;
91 delete $self->_files->{$stored_path};
92 push @events, $self->_event( $stored_path, 'delete' );
93 }
94 }
95
96 # Renamed files
97 else {
98 delete $self->_files->{$path};
99 push @events, $self->_event( $path, 'delete' );
100 }
101 }
102
103 # Modify/Create - writes to files indicate modification, but we get
104 # writes to dirs too, which indicates a file (or dir) was created or
105 # removed from the dir. Deletes are picked up by delete events, but to
106 # find created files we have to scan the dir again.
107 elsif ( $flags & NOTE_WRITE ) {
108
109 if ( -f $path ) {
110 push @events, $self->_event( $path, 'modify' );
111 }
112 elsif ( -d $path ) {
113 push @events,
114 map { $self->_event( $_, 'create' ) }
115 $self->_watch_dir($path);
116 }
117 }
118 }
119
120 return @events;
121}
122
123sub _event {
124 my ( $self, $path, $type ) = @_;
125 return $self->event_class->new( path => $path, type => $type );
126}
127
128sub _watch_dir {
129 my ( $self, $dir ) = @_;
130
131 my @new_files;
132
133 # use find(), finddepth() doesn't support pruning
134 $self->_find(
135 $dir,
136 sub {
137 my $path = $File::Find::name;
138
139 # Don't monitor anything below excluded dirs
140 return $File::Find::prune = 1
141 if $self->_path_is_excluded($path);
142
143 # Skip file names that don't match the filter
144 return unless $self->_is_included_file($path);
145
146 # Skip if we're watching it already
147 return if $self->_files->{$path};
148
149 $self->_watch_file($path);
150 push @new_files, $path;
151 }
152 );
153
154 return @new_files;
155}
156
157sub _is_included_file {
158 my ( $self, $path ) = @_;
159
160 return 1 if -d $path;
161
162 my $filter = $self->filter;
163 my $filename = ( File::Spec->splitpath($path) )[2];
164 return 1 if $filename =~ m{$filter};
165}
166
167sub _find {
168 my ( $self, $dir, $wanted ) = @_;
169 File::Find::find(
170 {
171 wanted => $wanted,
172 no_chdir => 1,
173 follow_fast => ( $self->follow_symlinks ? 1 : 0 ),
174 },
175 $dir,
176 );
177}
178
179sub _watch_file {
180 my ( $self, $file ) = @_;
181
182 # Don't panic if we can't open a file
183 open my $fh, '<', $file or warn "Can't open '$file': $!";
184 return unless $fh;
185
186 # Store this filehandle (this will automatically nuke any existing events
187 # assigned to the file)
188 $self->_files->{$file} = $fh;
189
190 # Watch it for changes
191 $self->_kqueue->EV_SET(
192 fileno($fh),
193 EVFILT_VNODE,
194 EV_ADD | EV_CLEAR,
195 NOTE_DELETE | NOTE_WRITE | NOTE_RENAME | NOTE_REVOKE,
196 0,
197 $file,
198 );
199}
200
201no Moose;
202__PACKAGE__->meta->make_immutable;
203
2041;
205
206=head1 NAME
207
208File::ChangeNotify::Watcher::KQueue - KQueue-based watcher for BSD systems.
209
210=head1 DESCRIPTION
211
212This class implements watching using KQueue. This is a BSD alternative to
213Linux's Inotify and similar event-based systems.
214
215=head1 CAVEATS
216
217Although this watcher is more efficient and accurate than the
218C<File::ChangeNotify::Watcher::Default> class, in order to monitor files and
219directories, it must open filehandles to each of them. Because many BSD
220systems have relatively low defaults for the maximum number of files each
221process can open, you may find you run out of file descriptors.
222
223On FreeBSD, you can check (and alter) your system's settings with C<sysctl> if
224necessary. The important keys are: C<kern.maxfiles> and
225C<kern.maxfilesperproc>. You can see how many files your system current has
226open with C<kern.openfiles>.
227
228=head1 AUTHOR
229
230Dan Thomas, E<lt>dan@cpan.orgE<gt>
231
232=head1 SUPPORT
233
234I (Dave Rolsky) cannot test this class, as I have no BSD systems. Reasonable
235patches will be applied as-is, and when possible I will consult with Dan
236Thomas or other BSD users before releasing.
237
238=head1 LICENSE
239
240Copyright 2009 Dan Thomas, All Rights Reserved.
241
242This program is free software; you can redistribute it and/or modify it under
243the same terms as Perl itself.
244
245=cut