File Coverage

blib/lib/Filesys/Notify/KQueue.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package Filesys::Notify::KQueue;
2 3     3   123319 use strict;
  3         8  
  3         119  
3 3     3   17 use warnings;
  3         4  
  3         137  
4             our $VERSION = '0.11';
5              
6 3     3   14 use File::Find ();
  3         9  
  3         63  
7 3     3   2581 use IO::KQueue;
  0            
  0            
8              
9             sub default_timeout { 1000 }
10              
11             sub new {
12             my $class = shift;
13             my $args = (@_ == 1) ? $_[0] : +{ @_ };
14             my $self = bless(+{} => $class);
15              
16             $self->timeout(exists $args->{timeout} ? $args->{timeout} : $class->default_timeout);
17             $self->{_kqueue} = $args->{kqueue} if exists($args->{kqueue});
18             $self->add(@{$args->{path}}) if exists($args->{path});
19              
20             return $self;
21             }
22              
23             sub kqueue {
24             my $self = shift;
25             $self->{_kqueue} ||= IO::KQueue->new;
26             }
27              
28             sub timeout {
29             my $self = shift;
30             (@_ == 1) ? ($self->{_timeout} = shift) : $self->{_timeout};
31             }
32              
33             sub add {
34             my $self = shift;
35              
36             foreach my $path (@_) {
37             next if exists($self->{_files}{$path});
38             if (-f $path) {
39             $self->add_file($path);
40             }
41             elsif (-d $path) {
42             $self->add_dir($path);
43             }
44             else {
45             die "Unknown file '$path'";
46             }
47             }
48             }
49              
50             sub add_file {
51             my($self, $file) = @_;
52              
53             $self->{_files}{$file} = do {
54             open(my $fh, '<', $file) or die("Can't open '$file': $!");
55             die "Can't get fileno '$file'" unless defined fileno($fh);
56              
57             # add to watch
58             $self->kqueue->EV_SET(
59             fileno($fh),
60             EVFILT_VNODE,
61             EV_ADD | EV_CLEAR,
62             NOTE_DELETE | NOTE_WRITE | NOTE_RENAME | NOTE_REVOKE,
63             0,
64             $file,
65             );
66              
67             $fh;
68             };
69             }
70              
71             sub add_dir {
72             my($self, $dir) = @_;
73              
74             $self->add_file($dir);
75             File::Find::find +{
76             wanted => sub { $self->add($File::Find::name) },
77             no_chdir => 1,
78             } => $dir;
79             }
80              
81             sub files { keys %{shift->{_files}} }
82             sub file_handles { values %{shift->{_files}} }
83             sub get_fh {
84             my %files = %{shift->{_files}};
85             @files{@_};
86             }
87              
88             sub unwatch {
89             my $self = shift;
90             my @path = @_;
91              
92             foreach my $path (@_) {
93             close($self->{_files}{$path});
94             delete($self->{_files}{$path});
95             }
96             }
97              
98             sub wait {
99             my ($self, $cb) = @_;
100              
101             my $events = $self->get_events;
102             if ($self->timeout) {
103             until (@$events) {
104             $events = $self->get_events;
105             }
106             }
107              
108             $cb->(@$events);
109             }
110              
111             sub get_events {
112             my $self = shift;
113              
114             my @kevents = $self->kqueue->kevent($self->timeout);
115              
116             my @events;
117             foreach my $kevent (@kevents) {
118             my $path = $kevent->[KQ_UDATA];
119             my $flags = $kevent->[KQ_FFLAGS];
120              
121             if(($flags & NOTE_DELETE) or ($flags & NOTE_RENAME)) {
122             my $event = ($flags & NOTE_DELETE) ? 'delete' : 'rename';
123              
124             if (-d $path) {
125             my @stored_paths = grep { m{^${path}/} } $self->files;
126             $self->unwatch(@stored_paths);
127             push @events => map {
128             +{
129             event => $event,
130             path => $_,
131             }
132             } @stored_paths;
133             }
134              
135             $self->unwatch($path);
136             push @events => +{
137             event => $event,
138             path => $path,
139             };
140             }
141             elsif ($flags & NOTE_WRITE) {
142             if (-f $path) {
143             push @events => +{
144             event => 'modify',
145             path => $path,
146             };
147             }
148             elsif (-d $path) {
149             File::Find::finddepth +{
150             wanted => sub {
151             return if exists($self->{_files}{$File::Find::name});
152             push @events => +{
153             event => 'create',
154             path => $File::Find::name,
155             };
156             $self->add($File::Find::name);
157             },
158             no_chdir => 1,
159             } => $path;
160             }
161             }
162             }
163              
164             return \@events;
165             }
166              
167             1;
168             __END__