File Coverage

blib/lib/AnyEvent/Filesys/Watcher/Inotify2.pm
Criterion Covered Total %
statement 77 77 100.0
branch 15 18 83.3
condition 3 3 100.0
subroutine 17 17 100.0
pod 1 1 100.0
total 113 116 97.4


line stmt bran cond sub pod time code
1             package AnyEvent::Filesys::Watcher::Inotify2;
2              
3 13     13   216581 use strict;
  13         59  
  13         916  
4              
5             our $VERSION = 'v0.1.1'; # VERSION
6              
7 13     13   116 use Locale::TextDomain ('AnyEvent-Filesys-Watcher');
  13         30  
  13         163  
8              
9 13     13   8841 use AnyEvent;
  13         29456  
  13         546  
10 13     13   5181 use Linux::Inotify2;
  12         27768  
  12         2112  
11 12     12   121 use Carp;
  12         28  
  12         829  
12 12     12   76 use Path::Iterator::Rule;
  12         42  
  12         434  
13 12     12   60 use Scalar::Util qw(weaken);
  12         31  
  12         703  
14              
15 12     12   71 use base qw(AnyEvent::Filesys::Watcher);
  12         63  
  12         13142  
16              
17             sub new {
18 18     18 1 128 my ($class, %args) = @_;
19              
20 18         162 my $self = $class->SUPER::_new(%args);
21              
22 18 50       451 my $inotify = Linux::Inotify2->new
23             or croak "Unable to create new Linux::INotify2 object: $!";
24              
25             # Need to add all the subdirs to the watch list, this will catch
26             # modifications to files too.
27 18         1774 my $old_fs = $self->_oldFilesystem;
28 18         120 my @dirs = grep { $old_fs->{$_}->{is_directory} } keys %$old_fs;
  167         452  
29              
30 18         73 my $alter_ego = $self;
31 18         90 for my $dir (@dirs) {
32             $inotify->watch(
33             $dir,
34             IN_MODIFY | IN_CREATE | IN_DELETE | IN_DELETE_SELF |
35             IN_MOVE | IN_MOVE_SELF | IN_ATTRIB,
36 34     34   1259 sub { my $e = shift; $alter_ego->_processEvents($e); }
  34         185  
37 28         1311 );
38             }
39 18         1367 weaken $alter_ego;
40              
41 18         288 $self->_filesystemMonitor($inotify);
42              
43             $self->_watcher([AnyEvent->io(
44             fh => $inotify->fileno,
45             poll => 'r',
46             cb => sub {
47 17     17   32448 $inotify->poll;
48             }
49 18         148 )]);
50              
51 18         442 bless $self, $class;
52             }
53              
54             # Parse the events returned by Inotify2 instead of rescanning the files.
55             # There are small changes in behaviour compared to the previous releases
56             # without parse_events:
57             #
58             # 1. `touch test` causes an additional "modified" event after the "created"
59             # 2. `mv test2 test` if test exists before, event for test would be "modified"
60             # in parent code, but is "created" here
61             #
62             # Because of these differences, we default to the original behavior unless the
63             # parse_events flag is true.
64             sub _parseEvents {
65 37     37   95 my ($self, $filter_cb, @raw_events) = @_;
66              
67             my @events =
68 37         157 map { $filter_cb->($_) }
69 37         113 grep { defined }
70 37         87 map { $self->__makeEvent($_) } @raw_events;
  37         116  
71              
72             # New directories are not automatically watched by inotify.
73 37         265 $self->__addEventsToWatch(@events);
74              
75             # Any entities that were created in new dirs (before the call to
76             # _add_events_to_watch) will have been missed. So we walk the filesystem
77             # now.
78             push @events,
79 5         23 map { $self->__addEntitiesInSubdir($filter_cb, $_) }
80 37 100       82 grep { $_->isDirectory && $_->isCreated }
  37         105  
81             @events;
82              
83 37         151 return @events;
84             }
85              
86             sub __addEntitiesInSubdir {
87 5     5   19 my ($self, $filter_cb, $e) = @_;
88 5         13 my @events;
89              
90 5         54 my $rule = Path::Iterator::Rule->new;
91 5         67 my $next = $rule->iter($e->path);
92 5         697 while (my $file = $next->()) {
93 10 100       1735 next if $file eq $e->path; # $e->path will have already been added
94              
95 5         132 my $new_event = AnyEvent::Filesys::Watcher::Event->new(
96             path => $file,
97             type => 'created',
98             isDirectory => -d $file,
99             );
100              
101 5 50       21 next unless $filter_cb->($new_event);
102 5         75 $self->__addEventsToWatch($new_event);
103 5         21 push @events, $new_event;
104             }
105              
106 5         164 return @events;
107             }
108              
109             sub __makeEvent {
110 37     37   97 my ($self, $e) = @_;
111              
112 37         71 my $type = undef;
113              
114 37 100       180 $type = 'modified' if ($e->mask & (IN_MODIFY | IN_ATTRIB));
115 37 100       301 $type = 'deleted'
116             if ($e->mask &
117             (IN_DELETE | IN_DELETE_SELF | IN_MOVED_FROM | IN_MOVE_SELF));
118 37 100       284 $type = 'created' if ($e->mask & (IN_CREATE | IN_MOVED_TO));
119              
120 37 50       235 return unless $type;
121 37         152 return AnyEvent::Filesys::Watcher::Event->new(
122             path => $e->fullname,
123             type => $type,
124             is_directory => !!$e->IN_ISDIR,
125             );
126             }
127              
128             # Needed if `parse_events => 0`
129             sub _postProcessEvents {
130 37     37   84 my ($self, @events) = @_;
131 37         88 return $self->__addEventsToWatch(@events);
132             }
133              
134             sub __addEventsToWatch {
135 79     79   158 my ($self, @events) = @_;
136              
137 79         173 for my $event (@events) {
138 84 100 100     454 next unless $event->isDirectory && $event->isCreated;
139              
140             $self->_filesystemMonitor->watch(
141             $event->path,
142             IN_MODIFY | IN_CREATE | IN_DELETE | IN_DELETE_SELF |
143             IN_MOVE | IN_MOVE_SELF | IN_ATTRIB,
144 10     3   67 sub { my $e = shift; $self->_processEvents($e); });
  3         100  
  3         20  
145             }
146              
147 79         468 return;
148             }
149              
150             1;