File Coverage

blib/lib/File/ChangeNotify/Watcher/Default.pm
Criterion Covered Total %
statement 73 74 98.6
branch 24 26 92.3
condition 8 9 88.8
subroutine 17 17 100.0
pod 0 3 0.0
total 122 129 94.5


line stmt bran cond sub pod time code
1             package File::ChangeNotify::Watcher::Default;
2              
3 3     3   2577 use strict;
  3         12  
  3         82  
4 3     3   19 use warnings;
  3         5  
  3         70  
5 3     3   520 use namespace::autoclean;
  3         17927  
  3         15  
6              
7             our $VERSION = '0.29';
8              
9 3     3   273 use File::Find qw( finddepth );
  3         7  
  3         223  
10 3     3   21 use File::Spec;
  3         5  
  3         90  
11 3     3   1082 use Time::HiRes qw( sleep );
  3         6086  
  3         12  
12 3     3   3218 use Types::Standard qw( HashRef );
  3         223597  
  3         29  
13              
14             # Trying to import this just blows up on Win32, and checking
15             # Time::HiRes::d_hires_stat() _also_ blows up on Win32.
16             BEGIN {
17             ## no critic (ErrorHandling::RequireCheckingReturnValueOfEval)
18 3     3   2421 eval { Time::HiRes->import('stat') };
  3         22  
19             }
20              
21 3     3   1388 use Moo;
  3         23385  
  3         14  
22              
23             with 'File::ChangeNotify::Watcher';
24              
25             has _map => (
26             is => 'ro',
27             writer => '_set_map',
28             isa => HashRef,
29             default => sub { {} },
30             );
31              
32 4     4 0 13 sub sees_all_events {0}
33              
34             sub BUILD {
35 17     17 0 1079 my $self = shift;
36              
37 17         41 $self->_set_map( $self->_build_map() );
38             }
39              
40             sub _build_map {
41 38     38   66 my $self = shift;
42              
43 38         62 my %map;
44              
45             File::Find::find(
46             {
47             wanted => sub {
48 87     87   271 my $path = $File::Find::name;
49              
50 87 100       328 if ( $self->_path_is_excluded($path) ) {
51 4         9 $File::Find::prune = 1;
52 4         42 return;
53             }
54              
55 83 100       176 my $entry = $self->_entry_for_map($path) or return;
56 79         3687 $map{$path} = $entry;
57             },
58             follow_fast => ( $self->follow_symlinks() ? 1 : 0 ),
59             no_chdir => 1,
60             follow_skip => 2,
61             },
62 38 100       334 @{ $self->directories() },
  38         2598  
63             );
64              
65 38         669 return \%map;
66             }
67              
68             sub _entry_for_map {
69 83     83   130 my $self = shift;
70 83         124 my $path = shift;
71              
72 83 100       1223 my $is_dir = -d $path ? 1 : 0;
73              
74 83 100 100     1142 return if -l $path && !$is_dir;
75              
76 79 100       223 unless ($is_dir) {
77 28         78 my $filter = $self->filter();
78 28 50       511 return unless ( File::Spec->splitpath($path) )[2] =~ /$filter/;
79             }
80              
81             return {
82 79 100       224 is_dir => $is_dir,
83             mtime => _mtime(*_),
84             size => ( $is_dir ? 0 : -s _ ),
85             };
86             }
87              
88             # It seems that Time::HiRes's stat does not act exactly like the
89             # built-in, so if I do ( stat _ )[9] it will not work (grr).
90             sub _mtime {
91 79     79   1112 my @stat = stat;
92              
93 79         652 return $stat[9];
94             }
95              
96             sub wait_for_events {
97 8     8 0 2497 my $self = shift;
98              
99 8         16 while (1) {
100 8         17 my @events = $self->_interesting_events();
101 8 50       81 return @events if @events;
102              
103 0         0 sleep $self->sleep_interval();
104             }
105             }
106              
107             sub _interesting_events {
108 21     21   37 my $self = shift;
109              
110 21         37 my @interesting;
111              
112 21         53 my $old_map = $self->_map();
113 21         46 my $new_map = $self->_build_map();
114              
115 21         39 for my $path ( sort keys %{$old_map} ) {
  21         93  
116 33 100 100     268 if ( !exists $new_map->{$path} ) {
    100 66        
117 6 100       16 if ( $old_map->{$path}{is_dir} ) {
118 2         12 $self->_remove_directory($path);
119             }
120              
121 6         205 push @interesting, $self->event_class()->new(
122             path => $path,
123             type => 'delete',
124             );
125             }
126             elsif (
127             !$old_map->{$path}{is_dir}
128             && ( $old_map->{$path}{mtime} != $new_map->{$path}{mtime}
129             || $old_map->{$path}{size} != $new_map->{$path}{size} )
130             ) {
131 2         107 push @interesting, $self->event_class()->new(
132             path => $path,
133             type => 'modify',
134             );
135             }
136             }
137              
138 21         392 for my $path ( sort grep { !exists $old_map->{$_} } keys %{$new_map} ) {
  41         110  
  21         63  
139 14 100       315 if ( -d $path ) {
140 2         75 push @interesting, $self->event_class()->new(
141             path => $path,
142             type => 'create',
143             ),
144             ;
145             }
146             else {
147 12         346 push @interesting, $self->event_class()->new(
148             path => $path,
149             type => 'create',
150             );
151             }
152             }
153              
154 21         3522 $self->_set_map($new_map);
155              
156 21         612 return @interesting;
157             }
158              
159             __PACKAGE__->meta()->make_immutable();
160              
161             1;
162              
163             # ABSTRACT: Fallback default watcher subclass
164              
165             __END__