File Coverage

blib/lib/Mojo/File/ChangeNotify.pm
Criterion Covered Total %
statement 69 100 69.0
branch 9 22 40.9
condition 3 15 20.0
subroutine 14 17 82.3
pod 0 1 0.0
total 95 155 61.2


line stmt bran cond sub pod time code
1             package Mojo::File::ChangeNotify 0.03;
2 8     8   892545 use 5.020;
  8         36  
3 8     8   2843 use lib '../Filesys-Notify-Win32-ReadDirectoryChanges/lib';
  8         4026  
  8         57  
4 8     8   5659 use Mojo::Base 'Mojo::EventEmitter', -signatures;
  8         105598  
  8         44  
5 8     8   29216 use Mojo::File::ChangeNotify::WatcherProcess 'watch';
  8         37  
  8         739  
6 8     8   3965 use Mojo::IOLoop::Subprocess;
  8         3310353  
  8         83  
7 8     8   441 use Scalar::Util 'weaken';
  8         27  
  8         441  
8 8     8   4227 use PerlX::Maybe;
  8         27943  
  8         32  
9              
10             our $is_win32 = $^O =~ /mswin32/i;
11              
12             =head1 NAME
13              
14             Mojo::File::ChangeNotify - turn file changes into Mojo events
15              
16             =head1 SYNOPSIS
17              
18             my $watcher =
19             Mojo::File::ChangeNotify->instantiate_watcher
20             ( directories => [ '/my/path', '/my/other' ],
21             filter => qr/\.(?:pm|conf|yml)$/,
22             on_change => sub( $watcher, @event_lists ) {
23             ...
24             },
25             );
26              
27             # alternatively
28             $watcher->on( 'change' => sub( $watcher, @event_lists ) {
29             for my $l (@event_lists) {
30             for my $e ($l->@*) {
31             print "[$e->{type}] $e->{path}\n";
32             }
33             }
34             });
35             # note that the watcher might need about 1s to start up
36              
37             =head1 IMPLEMENTATION
38              
39             L only supports blocking waits or polling as an
40             interface. This module creates a subprocess that blocks and communicates
41             the changes to the main process.
42              
43             =head1 SEE ALSO
44              
45             L - the file watching implementation
46              
47             =cut
48              
49             has 'watcher';
50             has 'watcher_pid'; # Store the PID so we can access it in DESTROY
51              
52             our %PIDs;
53              
54 42     42   213 sub _spawn_watcher_subprocess( $self, $args ) {
  42         95  
  42         80  
  42         91  
55 42         1012 my $subprocess = Mojo::IOLoop::Subprocess->new();
56              
57             {
58 42         1922 weaken( my $weak_self = $self );
  42         251  
59 42     42   339329 $subprocess->on('spawn' => sub( $w ) {
  42         223  
  42         180  
60 42         1780 my $pid = $w->pid;
61 42         2129 $PIDs{ $pid } = 1;
62 42 50       2121 $weak_self->watcher_pid($pid) if $weak_self; # Store PID for DESTROY access
63 42         3402 });
64              
65 21     21   20224358 $subprocess->on('progress' => sub( $w, $events ) {
  21         63  
  21         49  
  21         144  
66 21 50       342 $weak_self->emit('change' => $events ) if $weak_self;
67 42         1025 });
68             }
69             #use Data::Dumper; warn Dumper( File::ChangeNotify->usable_classes );
70 0     0   0 $subprocess->run( sub( $subprocess ) {
  0         0  
  0         0  
71 0         0 watch( $subprocess, $args );
72             return () # return an empty list here to not return anything after the process ends
73 0     31   0 }, sub ($subprocess, $err, @results ) {
  31         4803275  
  31         71  
  31         179  
  31         89  
  31         55  
74 31 50 33     511 if( $err
      33        
75             and $err !~ /malformed JSON string/ # caused by us terminating the child
76             and $err !~ /Missing or empty input/ # caused by us terminating the child
77             ) {
78 0 0       0 warn "Subprocess error: $err" and return;
79             }
80 31 50       156 say "Surprising results: @results"
81             if @results;
82 42         1453 });
83             }
84              
85             our %action_map = (
86             added => 'create',
87             removed => 'delete',
88             modified => 'modify',
89             );
90              
91 0     0   0 sub _spawn_watcher_win32( $self, $args) {
  0         0  
  0         0  
  0         0  
92 0         0 require Filesys::Notify::Win32::ReadDirectoryChanges;
93 0         0 weaken $self;
94              
95             # This spawns a thread
96             my $watcher = Filesys::Notify::Win32::ReadDirectoryChanges->new(
97             maybe directories => $args->{directories},
98 0         0 subtree => 1,
99             );
100              
101             # ... and we poll its queue frequently
102 0         0 my $poll;
103 0     0   0 $poll = Mojo::IOLoop->recurring( 0.1, sub($loop) {
  0         0  
  0         0  
104 0 0 0     0 if( ! $self
105             || !defined $watcher->queue->pending ) {
106 0         0 $watcher->stop;
107 0         0 $loop->remove( $poll );
108 0         0 return;
109             };
110             my @events = map {
111 0   0     0 my $action = $action_map{ $_->{action} } // $_->{action};
112             +{ type => $action, path => $_->{path}}
113 0         0 } grep {
114 0         0 defined $_
  0         0  
115             } $self->watcher->queue->dequeue_nb(32);
116 0 0       0 if( @events ) {
117 0         0 $self->emit(change => \@events);
118             }
119 0         0 });
120              
121 0         0 return $watcher
122             }
123              
124 42     42 0 13078406 sub instantiate_watcher( $class, %args ) {
  42         169  
  42         375  
  42         93  
125 42         124 my $handler = delete $args{ on_change };
126 42         524 my $self = $class->new();
127 42 100       601 if( $handler ) {
128 7         66 $self->on( 'change' => $handler );
129             }
130              
131 42 50       275 if( $is_win32 ) {
132 0         0 $self->watcher( $self->_spawn_watcher_win32( \%args ));
133              
134             } else {
135 42         354 $self->watcher( $self->_spawn_watcher_subprocess( \%args ));
136             }
137              
138 42         8542 return $self;
139             }
140              
141             # Cleanup watchers when we are removed
142 35     35   92625349 sub DESTROY( $self ) {
  35         138  
  35         113  
143 35         436 my $pid = $self->watcher_pid;
144 35 50       574 if( $pid ) {
145 35         308 delete $PIDs{ $pid };
146 35         9018 kill KILL => $pid;
147 35         235848 waitpid($pid,0); # gobble up the child exit code
148             }
149 35 50 33     1912 if( $is_win32 and my $w = $self->watcher ) {
150 0         0 $w->stop;
151             }
152             }
153              
154             # Clean up watchers if we die for other reasons
155             END {
156 7     7   9054 kill KILL => keys %PIDs
157             }
158              
159             1;