File Coverage

blib/lib/Mojo/File/ChangeNotify.pm
Criterion Covered Total %
statement 60 66 90.9
branch 7 14 50.0
condition 2 6 33.3
subroutine 12 13 92.3
pod 0 1 0.0
total 81 100 81.0


line stmt bran cond sub pod time code
1             package Mojo::File::ChangeNotify 0.02;
2 8     8   900964 use 5.020;
  8         48  
3 8     8   4289 use Mojo::Base 'Mojo::EventEmitter', -signatures;
  8         100786  
  8         52  
4 8     8   30333 use Mojo::File::ChangeNotify::WatcherProcess 'watch';
  8         31  
  8         774  
5 8     8   5098 use Mojo::IOLoop::Subprocess;
  8         3372659  
  8         54  
6 8     8   462 use Scalar::Util 'weaken';
  8         14  
  8         6375  
7              
8             =head1 NAME
9              
10             Mojo::File::ChangeNotify - turn file changes into Mojo events
11              
12             =head1 SYNOPSIS
13              
14             my $watcher =
15             Mojo::File::ChangeNotify->instantiate_watcher
16             ( directories => [ '/my/path', '/my/other' ],
17             filter => qr/\.(?:pm|conf|yml)$/,
18             on_change => sub( $watcher, @event_lists ) {
19             ...
20             },
21             );
22              
23             # alternatively
24             $watcher->on( 'change' => sub( $watcher, @event_lists ) {
25             for my $l (@event_lists) {
26             for my $e ($l->@*) {
27             print "[$e->{type}] $e->{path}\n";
28             }
29             }
30             });
31             # note that the watcher might need about 1s to start up
32              
33             =head1 IMPLEMENTATION
34              
35             L only supports blocking waits or polling as an
36             interface. This module creates a subprocess that blocks and communicates
37             the changes to the main process.
38              
39             =head1 SEE ALSO
40              
41             L - the file watching implementation
42              
43             =cut
44              
45             has 'watcher';
46             has 'watcher_pid'; # Store the PID so we can access it in DESTROY
47              
48             our %PIDs;
49              
50 42     42   106 sub _spawn_watcher( $self, $args ) {
  42         127  
  42         102  
  42         99  
51 42         888 my $subprocess = Mojo::IOLoop::Subprocess->new();
52              
53             {
54 42         1955 weaken( my $weak_self = $self );
  42         128  
55 42     42   277393 $subprocess->on('spawn' => sub( $w ) {
  42         281  
  42         147  
56 42         2030 my $pid = $w->pid;
57 42         1981 $PIDs{ $pid } = 1;
58 42 50       1740 $weak_self->watcher_pid($pid) if $weak_self; # Store PID for DESTROY access
59 42         940 });
60              
61 21     21   20250111 $subprocess->on('progress' => sub( $w, $events ) {
  21         52  
  21         44  
  21         40  
62 21 50       353 $weak_self->emit('change' => $events ) if $weak_self;
63 42         928 });
64             }
65             #use Data::Dumper; warn Dumper( File::ChangeNotify->usable_classes );
66 0     0   0 $subprocess->run( sub( $subprocess ) {
  0         0  
  0         0  
67 0         0 watch( $subprocess, $args );
68             return () # return an empty list here to not return anything after the process ends
69 0     31   0 }, sub ($subprocess, $err, @results ) {
  31         6327649  
  31         102  
  31         123  
  31         49  
  31         51  
70 31 50 33     954 if( $err
      33        
71             and $err !~ /malformed JSON string/ # caused by us terminating the child
72             and $err !~ /Missing or empty input/ # caused by us terminating the child
73             ) {
74 0 0       0 warn "Subprocess error: $err" and return;
75             }
76 31 50       243 say "Surprising results: @results"
77             if @results;
78 42         1884 });
79             }
80              
81 42     42 0 14156087 sub instantiate_watcher( $class, %args ) {
  42         277  
  42         313  
  42         93  
82 42         176 my $handler = delete $args{ on_change };
83 42         364 my $self = $class->new();
84 42 100       2233 if( $handler ) {
85 7         80 $self->on( 'change' => $handler );
86             }
87              
88 42         378 $self->watcher( $self->_spawn_watcher( \%args ));
89              
90             # Use weaken to avoid circular reference that prevents DESTROY
91              
92              
93 42         8652 return $self;
94             }
95              
96             # Cleanup watchers when we are removed
97 35     35   96500713 sub DESTROY( $self ) {
  35         127  
  35         142  
98 35         384 my $pid = $self->watcher_pid;
99 35 50       519 if( $pid ) {
100 35         352 delete $PIDs{ $pid };
101 35         2717 kill KILL => $pid;
102             }
103             }
104              
105             # Clean up watchers if we die for other reasons
106             END {
107 7     7   15676 kill KILL => keys %PIDs
108             }
109              
110             1;