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   272424 use strict;
  3         8  
  3         129  
3 3     3   15 use warnings;
  3         6  
  3         147  
4             our $VERSION = '0.09';
5              
6 3     3   34 use File::Find ();
  3         11  
  3         69  
7 3     3   4901 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($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             until (@$events) {
103             $events = $self->get_events;
104             }
105              
106             $cb->(@$events);
107             }
108              
109             sub get_events {
110             my $self = shift;
111              
112             my @kevents = $self->kqueue->kevent($self->timeout);
113              
114             my @events;
115             foreach my $kevent (@kevents) {
116             my $path = $kevent->[KQ_UDATA];
117             my $flags = $kevent->[KQ_FFLAGS];
118              
119             if(($flags & NOTE_DELETE) or ($flags & NOTE_RENAME)) {
120             my $event = ($flags & NOTE_DELETE) ? 'delete' : 'rename';
121              
122             if (-d $path) {
123             my @stored_paths = grep { m{^${path}/} } $self->files;
124             $self->unwatch(@stored_paths);
125             push @events => map {
126             +{
127             event => $event,
128             path => $_,
129             }
130             } @stored_paths;
131             }
132              
133             $self->unwatch($path);
134             push @events => +{
135             event => $event,
136             path => $path,
137             };
138             }
139             elsif ($flags & NOTE_WRITE) {
140             if (-f $path) {
141             push @events => +{
142             event => 'modify',
143             path => $path,
144             };
145             }
146             elsif (-d $path) {
147             File::Find::finddepth +{
148             wanted => sub {
149             return if exists($self->{_files}{$File::Find::name});
150             push @events => +{
151             event => 'create',
152             path => $File::Find::name,
153             };
154             $self->add($File::Find::name);
155             },
156             no_chdir => 1,
157             } => $path;
158             }
159             }
160             }
161              
162             return \@events;
163             }
164              
165             1;
166             __END__