File Coverage

blib/lib/File/Monitor/Delta.pm
Criterion Covered Total %
statement 123 124 99.1
branch 30 34 88.2
condition 16 16 100.0
subroutine 20 20 100.0
pod 3 3 100.0
total 192 197 97.4


line stmt bran cond sub pod time code
1             package File::Monitor::Delta;
2              
3 6     6   34 use strict;
  6         10  
  6         227  
4 6     6   32 use warnings;
  6         11  
  6         211  
5 6     6   29 use Carp;
  6         12  
  6         347  
6              
7 6     6   32 use base qw(File::Monitor::Base);
  6         12  
  6         3112  
8              
9             our $VERSION = '1.00';
10              
11             my %TAXONOMY;
12              
13             BEGIN {
14             my $created = sub {
15 90         145 my ( $this, $old, $new, $key ) = @_;
16 90   100     579 return ( !defined $old->{mode} && defined $new->{mode} ) || 0;
17 6     6   41 };
18              
19             my $deleted = sub {
20 45         95 my ( $this, $old, $new, $key ) = @_;
21 45         113 return $created->( $this, $new, $old, $key );
22 6         25 };
23              
24             my $num_diff = sub {
25 225         350 my ( $this, $old, $new, $key ) = @_;
26 225   100     1387 return ( $new->{$key} || 0 ) - ( $old->{$key} || 0 );
      100        
27 6         21 };
28              
29             my $bit_diff = sub { # XOR
30 45         77 my ( $this, $old, $new, $key ) = @_;
31 45   100     260 return ( $new->{$key} || 0 ) ^ ( $old->{$key} || 0 );
      100        
32 6         31 };
33              
34             my $nop = sub { # Just return value
35 90         140 my ( $this, $old, $new, $key ) = @_;
36 90         211 return $this->{delta}->{$key};
37 6         32 };
38              
39 6         93 %TAXONOMY = (
40             change => {
41             created => $created,
42             deleted => $deleted,
43             metadata => {
44             time => {
45             mtime => $num_diff,
46             ctime => $num_diff,
47             },
48             perms => {
49             uid => $num_diff,
50             gid => $num_diff,
51              
52             # Bit delta
53             mode => $bit_diff,
54             },
55              
56             # Value delta
57             size => $num_diff,
58             },
59             directory => {
60              
61             # List delta
62             files_created => $nop,
63             files_deleted => $nop
64             }
65             }
66             );
67              
68 6         39 my @OBJ_ATTR = qw(
69             dev inode mode num_links uid gid rdev size mtime ctime
70             blk_size blocks error files
71             );
72              
73 6         273 my $IS_ARRAY = qr/^files_/;
74              
75 6     6   38 no strict 'refs';
  6         16  
  6         3230  
76              
77             # Accessors for old/new attributes
78 6         17 for my $pfx ( qw(old new) ) {
79 12         34 for my $attr ( @OBJ_ATTR ) {
80 168         315 my $func_name = "${pfx}_${attr}";
81             *$func_name = sub {
82 168     168   70824 my $self = shift;
83 168 100       14348 croak "$func_name is read-only" if @_;
84 84         348 return $self->{ $pfx . '_info' }->{$attr};
85 168         1321 };
86             }
87             }
88              
89             # Accessors for deltas are named after the leaf keys in the taxonomy
90 6         17 my @work = \%TAXONOMY;
91 6         42 while ( my $obj = shift @work ) {
92 36         144 while ( my ( $n, $v ) = each %$obj ) {
93 90         169 my $is_name = "is_$n";
94             *$is_name = sub {
95 414     414   554 my $self = shift;
96 414         1028 return $self->is_event( $n );
97 90         791 };
98              
99 90 100       274 if ( ref $v eq 'CODE' ) {
    50          
100              
101             # Got a leaf item -> make an accessor
102 60         79 my $func_name = $n;
103 60 100       219 if ( $n =~ $IS_ARRAY ) {
104             *$func_name = sub {
105 82     82   46947 my $self = shift;
106 82 100       1351 croak "$func_name is read-only" if @_;
107 76 100       100 return @{ $self->{delta}->{$func_name} || [] };
  76         616  
108 12         126 };
109             }
110             else {
111             *$func_name = sub {
112 126     126   55961 my $self = shift;
113 126 100       4987 croak "$func_name is read-only" if @_;
114 102         473 return $self->{delta}->{$func_name};
115 48         3057 };
116             }
117             }
118             elsif ( ref $v eq 'HASH' ) {
119 30         147 push @work, $v;
120             }
121             else {
122 0         0 die "\%TAXONOMY contains a ", ref $v;
123             }
124             }
125             }
126             }
127              
128             sub _initialize {
129 333     333   426 my $self = shift;
130 333         420 my $args = shift;
131              
132 333         1056 $self->SUPER::_initialize( $args );
133              
134 333         704 for my $attr ( qw(object old_info new_info) ) {
135 999 50       2094 croak "You must supply a value for $attr"
136             unless exists $args->{$attr};
137 999         3047 $self->{$attr} = delete $args->{$attr};
138             }
139              
140 333         1084 $self->_report_extra( $args );
141              
142 333 100       1144 if ( !$self->_deep_compare( $self->{old_info}, $self->{new_info} ) ) {
143 45         4550 $self->_compute_delta;
144             }
145             }
146              
147             sub object {
148 167     167 1 248 my $self = shift;
149 167 50       406 croak "object is read-only" if @_;
150 167         882 return $self->{object};
151             }
152              
153             sub name {
154 122     122 1 71700 my $self = shift;
155 122         280 return $self->object->name( @_ );
156             }
157              
158             sub _deep_compare {
159 333     333   514 my ( $self, $this, $that ) = @_;
160 6     6   6957 use Storable qw/freeze/;
  6         37979  
  6         14031  
161 333         552 local $Storable::canonical = 1;
162 333         1049 return freeze( $this ) eq freeze( $that );
163             }
164              
165             sub _diff_list {
166 45     45   86 my ( $this, $that ) = @_;
167              
168 45         129 my %which = map { $_ => 1 } @$this;
  81         207  
169 45         258 $which{$_} |= 2 for @$that;
170              
171 45         128 my @diff = ( [], [] );
172 45         172 while ( my ( $v, $w ) = each %which ) {
173 157 100       394 push @{ $diff[ $w - 1 ] }, $v if $w < 3;
  95         452  
174             }
175              
176 45         235 return @diff;
177             }
178              
179             sub _walk_taxo {
180 270     270   337 my $self = shift;
181 270         413 my $taxo = shift;
182              
183 270         600 my $change_found = 0;
184              
185 270         1111 while ( my ( $n, $v ) = each %$taxo ) {
186 675 100       1268 if ( ref $v eq 'CODE' ) {
187 450         1121 my $diff
188             = $v->( $self, $self->{old_info}, $self->{new_info}, $n );
189 450 100       5866 if ( $diff ) {
190 173         352 $self->{delta}->{$n} = $diff;
191 173         335 $self->{"_is_event"}->{$n}++;
192 173         687 $change_found++;
193             }
194             }
195             else {
196 225 100       485 if ( $self->_walk_taxo( $v ) ) {
197 166         312 $self->{"_is_event"}->{$n}++;
198 166         581 $change_found++;
199             }
200             }
201             }
202              
203 270         843 return $change_found;
204             }
205              
206             sub _compute_delta {
207 45     45   76 my $self = shift;
208              
209             # Compute the file list deltas as a special case first
210 45   100     417 my @df = _diff_list(
      100        
211             $self->{old_info}->{files} || [],
212             $self->{new_info}->{files} || []
213             );
214              
215 45         147 my $monitor = $self->object->owner;
216 45         156 for my $attr ( qw(files_deleted files_created) ) {
217 90         183 my @ar = map { $monitor->_make_absolute( $_ ) } sort @{ shift @df };
  95         467  
  90         222  
218 90 100       661 $self->{delta}->{$attr} = \@ar if @ar;
219             }
220              
221 45         120 $self->{_is_event} = {};
222              
223             # Now do everything else
224 45         152 $self->_walk_taxo( \%TAXONOMY );
225             }
226              
227             sub is_event {
228 838     838 1 31747 my $self = shift;
229 838         1070 my $event = shift;
230              
231 838         13099 return $self->{_is_event}->{$event};
232             }
233              
234             sub _trigger_callbacks {
235 84     84   112 my $self = shift;
236 84   100     376 my $callbacks = shift || {};
237 84         256 my $name = $self->name;
238              
239 84 50       204 if ( $self->is_change ) {
240 84         491 while ( my ( $event, $cb ) = each %$callbacks ) {
241 240 100       1120 if ( $self->is_event( $event ) ) {
242 118         362 $cb->( $name, $event, $self );
243             }
244             }
245             }
246             }
247              
248             1;
249              
250             =head1 NAME
251              
252             File::Monitor::Delta - Encapsulate a change to a file or directory
253              
254             =head1 VERSION
255              
256             This document describes File::Monitor::Delta version 1.00
257              
258             =head1 SYNOPSIS
259              
260             use File::Monitor;
261              
262             my $monitor = File::Monitor->new();
263              
264             # Watch some files
265             for my $file (qw( myfile.txt yourfile.txt otherfile.txt some_directory )) {
266             $monitor->watch( $file );
267             }
268              
269             # First scan just finds out about the monitored files. No changes
270             # will be reported.
271             $object->scan;
272              
273             # After the first scan we get a list of File::Monitor::Delta objects
274             # that describe any changes
275             my @changes = $object->scan;
276              
277             for my $change (@changes) {
278             # Call methods on File::Monitor::Delta to discover what changed
279             if ($change->is_size) {
280             my $name = $change->name;
281             my $old_size = $change->old_size;
282             my $new_size = $change->new_size;
283             print "$name has changed size from $old_size to $new_size\n";
284             }
285             }
286              
287             =head1 DESCRIPTION
288              
289             When L or L detects a change to a
290             file or directory it packages the details of the change in a
291             C object.
292              
293             Methods exist to discover the nature of the change (C et al.),
294             retrieve the attributes of the file or directory before and after the
295             change (C, C, C, C etc),
296             retrieve details of the change in a convenient form (C,
297             C) and gain access to the L for
298             which the change was observed (C).
299              
300             Unless you are writing a subclass of C it
301             isn't normally necessary to instantiate C
302             objects directly.
303              
304             =head2 Changes Classified
305              
306             Various types of change are identified and classified into the following
307             hierarchy:
308              
309             change
310             created
311             deleted
312             metadata
313             time
314             mtime
315             ctime
316             perms
317             uid
318             gid
319             mode
320             size
321             directory
322             files_created
323             files_deleted
324              
325             The terminal nodes of that tree (C, C, C,
326             C, C, C, C, C, C and
327             C) represent actual change events. Non terminal nodes
328             represent broader classifications of events. For example if a file's
329             mtime changes the resulting C object will return
330             true for each of
331              
332             $delta->is_mtime; # The actual change
333             $delta->is_time; # One of the file times changed
334             $delta->is_metadata; # The file's metadata changed
335             $delta->is_change; # This is true for any change
336              
337             This event classification is used to target callbacks at specific events
338             or categories of events. See L and
339             L for more information about callbacks.
340              
341             =head2 Accessors
342              
343             Various accessors allow the state of the object before and after the
344             change and the details of the change to be queried.
345              
346             These accessors return information about the state of the file or
347             directory before the detected change:
348              
349             old_dev old_inode old_mode old_num_links old_uid old_gid
350             old_rdev old_size old_mtime old_ctime old_blk_size old_blocks
351             old_error old_files
352              
353             For example:
354              
355             my $mode_was = $delta->old_mode;
356              
357             These accessors return information about the state of the file or
358             directory after the detected change:
359              
360             new_dev new_inode new_mode new_num_links new_uid new_gid
361             new_rdev new_size new_mtime new_ctime new_blk_size new_blocks
362             new_error new_files
363              
364             For example:
365              
366             my $new_size = $delta->new_size;
367              
368             These accessors return a value that reflects the change in the
369             corresponding attribute:
370              
371             created deleted mtime ctime uid gid mode size
372              
373             With the exception of C, C and C they return
374             the difference between the old value and the new value. This is only
375             really useful in the case of C:
376              
377             my $grown_by = $delta->size;
378              
379             Is equivalent to
380              
381             my $grown_by = $delta->new_size - $delta->old_size;
382              
383             For the other values the subtraction is performed merely to ensure that
384             these values are non-zero.
385              
386             # Get the difference between the old and new UID. Unlikely to be
387             # interesting.
388             my $delta_uid = $delta->uid;
389              
390             As a special case the delta value for C is computed as old_mode ^
391             new_mode. The old mode is XORed with the new mode so that
392              
393             my $bits_changed = $delta->mode;
394              
395             gets a bitmask of the mode bits that have changed.
396              
397             If the detected change was the creation or deletion of a file C
398             or C respectively will be true.
399              
400             if ( $delta->created ) {
401             print "Yippee! We exist\n";
402             }
403              
404             if ( $delta->deleted ) {
405             print "Boo! We got deleted\n";
406             }
407              
408             For a directory which is being monitored with the C or C
409             options (see L for details) C and
410             C will contain respectively the list of new files below
411             this directory and the list of files that have been deleted.
412              
413             my @new_files = $delta->files_created;
414              
415             for my $file ( @new_files ) {
416             print "$file created\n";
417             }
418              
419             my @gone_away = $delta->files_deletedl
420              
421             for my $file ( @gone_away ) {
422             print "$file deleted\n";
423             }
424              
425             =head1 INTERFACE
426              
427             =over
428              
429             =item C<< new( $args ) >>
430              
431             Create a new C object. You don't normally need to
432             do this; deltas are created as necessary by L.
433              
434             The single argument is a reference to a hash that must contain the
435             following keys:
436              
437             =over
438              
439             =item object
440              
441             The L for which this change is being reported.
442              
443             =item old_info
444              
445             A hash describing the state of the file or directory before the change.
446              
447             =item new_info
448              
449             A hash describing the state of the file or directory after the change.
450              
451             =back
452              
453             =item C<< is_event( $event ) >>
454              
455             Returns true if this delta represents the specified event. For example,
456             if a file's size changes the following will all return true:
457              
458             $delta->is_event('size'); # The actual change
459             $delta->is_event('metadata'); # The file's metadata changed
460             $delta->is_event('change'); # This is true for any change
461              
462             Valid eventnames are
463              
464             change created deleted metadata time mtime ctime perms uid gid
465             mode size directory files_created files_deleted
466              
467             As an alternative interface you may call CI directly.
468             For example
469              
470             $delta->is_size;
471             $delta->is_metadata;
472             $delta->is_change;
473              
474             Unless the event you wish to test for is variable this is a cleaner,
475             less error prone interface.
476              
477             Normally your code won't see a C for which
478             C returns false. Any change causes C to be true
479             and the C methods of C and C
480             don't return deltas for unchanged files.
481              
482             =item C<< name >>
483              
484             The name of the file for which the change is being reported. Read only.
485              
486             =item C<< object >>
487              
488             The L for which this change is being reported.
489              
490             =back
491              
492             =head2 Other methods
493              
494             As mentioned above a large number of other accessors are provided to get
495             the state of the object before and after the change and query details of
496             the change:
497              
498             old_dev old_inode old_mode old_num_links old_uid old_gid old_rdev
499             old_size old_mtime old_ctime old_blk_size old_blocks old_error
500             old_files new_dev new_inode new_mode new_num_links new_uid new_gid
501             new_rdev new_size new_mtime new_ctime new_blk_size new_blocks
502             new_error new_files created deleted mtime ctime uid gid mode size
503             files_created files_deleted name
504              
505             See L for details of these.
506              
507             =head1 DIAGNOSTICS
508              
509             =over
510              
511             =item C<< %s is read-only >>
512              
513             C is an immutable description of a change in a
514             file's state. None of its accessors allow values to be changed.
515              
516             =item C<< You must supply a value for %s >>
517              
518             The three options that C (C, C and C)
519             are all mandatory.
520              
521             =back
522              
523             =head1 CONFIGURATION AND ENVIRONMENT
524              
525             File::Monitor::Delta requires no configuration files or environment variables.
526              
527             =head1 DEPENDENCIES
528              
529             None.
530              
531             =head1 INCOMPATIBILITIES
532              
533             None reported.
534              
535             =head1 BUGS AND LIMITATIONS
536              
537             No bugs have been reported.
538              
539             Please report any bugs or feature requests to
540             C, or through the web interface at
541             L.
542              
543             =head1 AUTHOR
544              
545             Andy Armstrong C<< >>
546              
547             Faycal Chraibi originally registered the File::Monitor namespace and
548             then kindly handed it to me.
549              
550             =head1 LICENCE AND COPYRIGHT
551              
552             Copyright (c) 2007, Andy Armstrong C<< >>. All rights reserved.
553              
554             This module is free software; you can redistribute it and/or
555             modify it under the same terms as Perl itself. See L.
556              
557             =head1 DISCLAIMER OF WARRANTY
558              
559             BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
560             FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
561             OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
562             PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
563             EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
564             WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
565             ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
566             YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
567             NECESSARY SERVICING, REPAIR, OR CORRECTION.
568              
569             IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
570             WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
571             REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE
572             LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL,
573             OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
574             THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
575             RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
576             FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
577             SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
578             SUCH DAMAGES.