File Coverage

blib/lib/AnyEvent/Filesys/Notify/Role/Inotify2.pm
Criterion Covered Total %
statement 71 71 100.0
branch 16 18 88.8
condition 3 3 100.0
subroutine 16 16 100.0
pod n/a
total 106 108 98.1


line stmt bran cond sub pod time code
1             package AnyEvent::Filesys::Notify::Role::Inotify2;
2              
3             # ABSTRACT: Use Linux::Inotify2 to watch for changed files
4              
5 8     8   5449 use Moo::Role;
  8         16  
  8         50  
6 8     8   2770 use MooX::late;
  8         25  
  8         52  
7 8     8   1094 use namespace::autoclean;
  8         16  
  8         70  
8 8     8   577 use AnyEvent;
  8         21  
  8         184  
9 8     8   2373 use Linux::Inotify2;
  7         12135  
  7         719  
10 7     7   44 use Carp;
  7         12  
  7         321  
11 7     7   41 use Path::Iterator::Rule;
  7         11  
  7         4838  
12              
13             our $VERSION = '1.23';
14              
15             # use Scalar::Util qw(weaken); # Attempt to address RT#57104, but alas...
16              
17             sub _init {
18 6     6   13 my $self = shift;
19              
20 6 50       20 my $inotify = Linux::Inotify2->new()
21             or croak "Unable to create new Linux::Inotify2 object: $!";
22              
23             # Need to add all the subdirs to the watch list, this will catch
24             # modifications to files too.
25 6         298 my $old_fs = $self->_old_fs;
26 6         82 my @dirs = grep { $old_fs->{$_}->{is_dir} } keys %$old_fs;
  38         76  
27              
28             # weaken $self; # Attempt to address RT#57104, but alas...
29              
30 6         15 for my $dir (@dirs) {
31             $inotify->watch(
32             $dir,
33             IN_MODIFY | IN_CREATE | IN_DELETE | IN_DELETE_SELF |
34             IN_MOVE | IN_MOVE_SELF | IN_ATTRIB,
35 13     32   452 sub { my $e = shift; $self->_process_events($e); } );
  32         832  
  32         152  
36             }
37              
38 6         193 $self->_fs_monitor($inotify);
39              
40             $self->_watcher(
41             AnyEvent->io(
42             fh => $inotify->fileno,
43             poll => 'r',
44             cb => sub {
45 25     25   12036100 $inotify->poll;
46 6         16 } ) );
47              
48 6         8010 return 1;
49             }
50              
51             # Parse the events returned by Inotify2 instead of rescanning the files.
52             # There are small changes in behavior compared to the previous releases
53             # without parse_events:
54             #
55             # 1. `touch test` causes an additional "modified" event after the "created"
56             # 2. `mv test2 test` if test exists before, event for test would be "modified"
57             # in parent code, but is "created" here
58             #
59             # Because of these differences, we default to the original behavior unless the
60             # parse_events flag is true.
61             sub _parse_events {
62 18     18   49 my ( $self, $filter_cb, @raw_events ) = @_;
63              
64             my @events =
65 18         60 map { $filter_cb->($_) } # filter new event
66 18         6314 grep { defined } # filter undef events
67 18         38 map { $self->_mk_event($_) } @raw_events;
  18         49  
68              
69             # New directories are not automatically watched by inotify.
70 18         68 $self->_add_events_to_watch(@events);
71              
72             # Any entities that were created in new dirs (before the call to
73             # _add_events_to_watch) will have been missed. So we walk the filesystem
74             # now.
75             push @events, # add to @events
76 3         17 map { $self->_add_entities_in_subdir( $filter_cb, $_ ) } # ret new events
77 18 100       36 grep { $_->is_dir and $_->is_created } # only new directories
  15         76  
78             @events;
79              
80 18         109 return @events;
81             }
82              
83             sub _add_entities_in_subdir {
84 3     3   6 my ( $self, $filter_cb, $e ) = @_;
85 3         5 my @events;
86              
87 3         16 my $rule = Path::Iterator::Rule->new;
88 3         25 my $next = $rule->iter( $e->path );
89 3         238 while ( my $file = $next->() ) {
90 8 100       844 next if $file eq $e->path; # $e->path will have already been added
91              
92 5         156 my $new_event = AnyEvent::Filesys::Notify::Event->new(
93             path => $file,
94             type => 'created',
95             is_dir => -d $file,
96             );
97              
98 5 100       311 next unless $filter_cb->($new_event);
99 2         29 $self->_add_events_to_watch( $new_event );
100 2         6 push @events, $new_event;
101             }
102              
103 3         71 return @events;
104             }
105              
106             sub _mk_event {
107 18     18   40 my ( $self, $e ) = @_;
108              
109 18         31 my $type = undef;
110              
111 18 100       53 $type = 'modified' if ( $e->mask & ( IN_MODIFY | IN_ATTRIB ) );
112 18 100       123 $type = 'deleted'
113             if ( $e->mask &
114             ( IN_DELETE | IN_DELETE_SELF | IN_MOVED_FROM | IN_MOVE_SELF ) );
115 18 100       86 $type = 'created' if ( $e->mask & ( IN_CREATE | IN_MOVED_TO ) );
116              
117 18 50       92 return unless $type;
118 18         47 return AnyEvent::Filesys::Notify::Event->new(
119             path => $e->fullname,
120             type => $type,
121             is_dir => !!$e->IN_ISDIR,
122             );
123             }
124              
125             # Needed if `parse_events => 0`
126             sub _post_process_events {
127 20     20   41 my ( $self, @events ) = @_;
128 20         44 return $self->_add_events_to_watch(@events);
129             }
130              
131             sub _add_events_to_watch {
132 40     40   75 my ( $self, @events ) = @_;
133              
134 40         81 for my $event (@events) {
135 35 100 100     141 next unless $event->is_dir && $event->is_created;
136              
137             $self->_fs_monitor->watch(
138             $event->path,
139             IN_MODIFY | IN_CREATE | IN_DELETE | IN_DELETE_SELF |
140             IN_MOVE | IN_MOVE_SELF | IN_ATTRIB,
141 4     6   26 sub { my $e = shift; $self->_process_events($e); } );
  6         207  
  6         28  
142             }
143              
144 40         206 return;
145             }
146              
147             1;
148              
149             __END__