File Coverage

lib/File/Copy/Vigilant.pm
Criterion Covered Total %
statement 87 103 84.4
branch 52 70 74.2
condition 17 27 62.9
subroutine 10 10 100.0
pod 2 2 100.0
total 168 212 79.2


line stmt bran cond sub pod time code
1             package File::Copy::Vigilant;
2              
3 1     1   29735 use warnings;
  1         3  
  1         36  
4 1     1   5 use strict;
  1         2  
  1         31  
5              
6 1     1   4 use base qw(Exporter);
  1         13  
  1         147  
7             our @EXPORT = qw(copy_vigilant move_vigilant);
8             our @EXPORT_OK = qw(copy move cp mv);
9              
10 1     1   939 use File::Copy qw(); # Don't import copy / move into the local namespace
  1         6351  
  1         33  
11 1     1   997 use File::Compare;
  1         1154  
  1         79  
12 1     1   1000 use Digest::MD5::File qw(file_md5_hex);
  1         111609  
  1         10  
13              
14             =head1 NAME
15              
16             File::Copy::Vigilant - Copy and move files with verification and retries
17              
18             =cut
19              
20             our $VERSION = '1.2';
21              
22             =head1 SYNOPSIS
23              
24             use File::Copy::Vigilant;
25              
26             copy_vigilant( $source, $destination );
27             move_vigilant( $source, $destination );
28              
29             =head1 DESCRIPTION
30              
31             A module for when your files absolutely, positively have to get there.
32              
33             By default the copy and move functions will perform MD5 sums on the source
34             and destination file to ensure that the destination file is exactly the same
35             as the source file upon completion. If the copy or move fails for any reason
36             it will attempt 2 retries by default.
37              
38             =head1 FUNCTIONS
39              
40             =head2 copy_vigilant, copy, cp
41              
42             Copies a file, with post-copy verification and optional retries.
43              
44             copy_vigilant(
45             $source, $destination,
46             [ check => (checktype), ]
47             [ retires => (number of retries), ]
48             );
49              
50             "checktype" is one of the following:
51              
52             md5 - Get the MD5 sum of the source before copying, and compare it to
53             the MD5 of the destination after copying (default)
54             size - Get the size of the source before copying, and compare it to the
55             size of the destination after copying
56             compare - Perform a byte-for-byte comparison of the source and destination
57             after copying
58             none - Do not perform a check of the source to the destination
59              
60             "number of retries" is one of the following:
61              
62             0 - No retries are performed
63             integer - The number of retries to perform (if a 1 is passed, then the
64             copy will be attempted a maximum of 2 times). Must be a
65             positive, whole number.
66             'infinite' - The string 'infinite' (and all other strings for that matter)
67             will cause it to retry continuously until success
68              
69             The default is 2 retries (or three attempts total).
70              
71             If called in a scalar context, it returns 0 for failure or 1 for success. If
72             called in a list context, the return value is a 1 or 0, followed by a list of
73             error messages.
74              
75             The 'cp' and 'copy' named versions the function are not exported by default,
76             and you must specify them explicitly to use them:
77              
78             use File::Copy::Vigilant qw(cp mv);
79              
80             cp( $source, $destination );
81              
82             Or
83              
84             use File::Copy::Vigilant qw(copy move);
85              
86             copy( $source, $destination );
87              
88             =cut
89              
90             sub copy_vigilant
91             {
92              
93 21     21 1 9356026 my ( $source, $dest, %params ) = @_;
94              
95 21         56 my @errors = ();
96 21         49 my $success = eval {
97              
98 21 100       96 my $retries = defined( $params{'retries'} ) ? $params{'retries'} : 2;
99 21 50       214 if ( $retries !~ m/^\d+$/x )
100             {
101 0         0 $retries = 'infinite';
102             } # Blank = continuous
103              
104 21 100       81 my $check
105             = defined( $params{'check'} ) ? lc( $params{'check'} ) : 'md5';
106 21 50       113 if ( $check !~ m/^md5|size|compare|none$/x ) { $check = 'md5'; }
  0         0  
107              
108             # This hook allows us to do some whitebox testing by modifying the
109             # results of the copy. You probably don't want this unless you're
110             # testing this module.
111 21         49 my $postcopy = $params{'_postcopy'};
112 21 50 66     99 if ( defined($postcopy) && ref($postcopy) ne 'CODE' )
113             {
114 0         0 $postcopy = undef;
115             }
116              
117 21         61 my $check_error = _check_files( $source, $dest );
118 21 100       57 if ($check_error)
119             {
120 5         17 push @errors, "Pre-copy check failed: $check_error\n";
121 5         14 return 0;
122             }
123              
124 16         24 my $attempt = 0;
125 16   66     112 while ( ( $retries eq 'infinite' ) || ( $attempt++ <= $retries ) )
126             {
127              
128 19         60 my $copy_error = _try_copy( $source, $dest, $check, $postcopy );
129              
130 18 100       59 if ($copy_error)
131             {
132 7         99 push @errors, "Copy attempt $attempt failed: $copy_error\n";
133             }
134             else
135             {
136 11         56 return 1;
137             }
138              
139             }
140              
141             # If we got here, then we looped as many times as
142             # we were allowed without a success
143 4         15 return 0;
144              
145             };
146              
147 21 100       77 if ($@)
148             {
149 1         3 $success = 0;
150 1         4 push @errors, "Internal error in copy_vigilant: $@\n";
151             }
152              
153 21 50       169 return wantarray ? ( $success, @errors ) : $success;
154              
155             } ## end sub copy_vigilant
156              
157             # Syntax borrowed from core module File::Copy
158             sub cp;
159             *cp = \©_vigilant;
160              
161             sub copy;
162             *copy = \©_vigilant;
163              
164             sub _check_files
165             {
166              
167 21     21   43 my ( $source, $dest ) = @_;
168              
169 21 100       103 if ( ref $source )
    50          
170             {
171 2 50 66     11 if ( ref($source) eq 'GLOB' ||
      33        
172 1         16 eval { $source->isa('GLOB') } ||
173 0         0 eval { $source->isa('IO::Handle') } )
174             {
175 2         7 return "can't use filehandle for source";
176             }
177             }
178             elsif ( ref( \$source ) eq 'GLOB' )
179             {
180 0         0 return "can't use filehandle for source";
181             }
182              
183 19 100       83 if ( ref $dest )
    50          
184             {
185 2 50 66     11 if ( ref($dest) eq 'GLOB' ||
      33        
186 1         11 eval { $dest->isa('GLOB') } ||
187 0         0 eval { $dest->isa('IO::Handle') } )
188             {
189 2         5 return "Can't use filehandle for desination";
190             }
191             }
192             elsif ( ref( \$dest ) eq 'GLOB' )
193             {
194 0         0 return "can't use filehandle for destination";
195             }
196              
197 17 100       520 unless ( stat $source )
198             {
199 1         8 return "unable to stat source file $source";
200             }
201              
202 16 50       301 if ( -d $source )
203             {
204 0         0 return "unable to copy directory source $source";
205             }
206              
207 16 50 33     298 unless ( -f $source || -l $source )
208             {
209 0         0 return "unable to copy non-file source $source";
210             }
211              
212             # If we got this far then both the source and dest look OK
213 16         47 return '';
214             } ## end sub _check_files
215              
216             sub _try_copy
217             {
218              
219 19     19   43 my ( $source, $dest, $check, $postcopy ) = @_;
220              
221 19         33 my $source_md5 = undef;
222 19         30 my $source_size = undef;
223 19 100       51 if ( $check eq 'md5' )
224             {
225 14         78 $source_md5 = file_md5_hex($source);
226             }
227 19 100 100     180981 if ( ( $check eq 'md5' ) || ( $check eq 'size' ) )
228             {
229 16         436 $source_size = ( stat $source )[7];
230             }
231              
232 19 50       104 unless ( File::Copy::copy( $source, $dest ) )
233             {
234 0         0 return "copy failed: $!";
235             }
236              
237 19 100       683917 defined($postcopy) && $postcopy->(@_);
238              
239 18         18509876 my $dest_size = undef;
240 18 100 100     119 if ( $check eq 'md5' || $check eq 'size' )
241             {
242 15         543 $dest_size = ( stat $dest )[7];
243             }
244              
245 18 100       84 if ( $check eq 'md5' )
    100          
    100          
246             {
247 13 100       42 if ( $source_size != $dest_size )
248             {
249 5         46 return "pre-md5 size check failed";
250             }
251 8         54 my $dest_md5 = file_md5_hex($dest);
252 8 50       185884 if ( $source_md5 ne $dest_md5 )
253             {
254 0         0 return "md5 check failed";
255             }
256             }
257             elsif ( $check eq 'size' )
258             {
259 2 100       6 if ( $source_size != $dest_size )
260             {
261 1         4 return "size check failed";
262             }
263             }
264             elsif ( $check eq 'compare' )
265             {
266 2 100       9 if ( File::Compare::compare( $source, $dest ) )
267             {
268 1         123 return "file compare failed";
269             }
270             }
271              
272             # If we got this far then the copy was a success!
273 11         238 return '';
274             } ## end sub _try_copy
275              
276             =head2 move_vigilant, move, mv
277              
278             The syntax and behavior is exactly the same as copy_vigilant, except it
279             perfoms an unlink as the last step.
280              
281             This is terribly inefficient compared to File::Copy's move, which in most
282             cases is a simple filesystem rename.
283              
284             'move' and 'mv' are not imported by default, you'll have to add them in
285             the use syntax (see copy_vigilant for details).
286              
287             =cut
288              
289             sub move_vigilant
290             {
291              
292 3     3 1 9932774 my ( $source, $dest, %params ) = @_;
293              
294 3         22 my ( $copy_success, @copy_errors )
295             = copy_vigilant( $source, $dest, %params );
296              
297 3 50       45 unless ($copy_success)
298             {
299 0 0       0 return wantarray ? ( 0, @copy_errors ) : 0;
300             }
301              
302 3         8 my @errors = ();
303 3         5 my $success = eval {
304 3 50       6299 if ( unlink $source )
305             {
306 3         11 return 1;
307             }
308             else
309             {
310 0         0 push @errors, "Unable to remove source $source "
311             . "(destination file $dest has been left in place)\n";
312 0         0 return 0;
313             }
314             };
315              
316 3 50       11 if ($@)
317             {
318 0         0 $success = 0;
319 0         0 push @errors, "Internal error in move_vigilant: $@\n";
320             }
321              
322 3 50       32 return wantarray ? ( $success, @errors ) : $success;
323              
324             }
325              
326             # Syntax borrowed from core module File::Copy
327             sub mv;
328             *mv = \&move_vigilant;
329              
330             sub move;
331             *move = \&move_vigilant;
332              
333             =head1 AUTHOR
334              
335             Anthony Kilna, C<< >> - L
336              
337             =head1 BUGS
338              
339             Please report any bugs or feature requests to
340             C,
341             or through the web interface at
342             L.
343             I will be notified, and then you'll automatically be notified
344             of progress on your bug as I make changes.
345              
346              
347             =head1 SUPPORT
348              
349             You can find documentation for this module with the perldoc command.
350              
351             perldoc File::Copy::Vigilant
352              
353             You can also look for information at:
354              
355             =over 4
356              
357             =item * RT: CPAN's request tracker
358              
359             L
360              
361             =item * AnnoCPAN: Annotated CPAN documentation
362              
363             L
364              
365             =item * CPAN Ratings
366              
367             L
368              
369             =item * Search CPAN
370              
371             L
372              
373             =back
374              
375             =head1 SEE ALSO
376              
377             File::Copy - File::Copy::Reliable
378              
379             =head1 COPYRIGHT & LICENSE
380              
381             Copyright 2012 Kilna Companies.
382              
383             This program is free software; you can redistribute it and/or modify it
384             under the terms of either: the GNU General Public License as published
385             by the Free Software Foundation; or the Artistic License.
386              
387             See http://dev.perl.org/licenses/ for more information.
388              
389             =cut
390              
391             1; # End of File::Copy::Vigilant