File Coverage

blib/lib/App/bk.pm
Criterion Covered Total %
statement 89 96 92.7
branch 29 40 72.5
condition 7 13 53.8
subroutine 15 15 100.0
pod 5 5 100.0
total 145 169 85.8


line stmt bran cond sub pod time code
1             package App::bk;
2              
3 7     7   1835439 use warnings;
  7         16  
  7         275  
4 7     7   40 use strict;
  7         13  
  7         299  
5              
6 7     7   38140 use Getopt::Long qw(:config no_ignore_case bundling no_auto_abbrev);
  7         115161  
  7         52  
7 7     7   16509 use Pod::Usage;
  7         590443  
  7         1277  
8 7     7   8094 use English 'no-match-vars';
  7         26997  
  7         52  
9 7     7   12357 use POSIX qw(strftime);
  7         62727  
  7         55  
10 7     7   9570 use File::Basename;
  7         21  
  7         568  
11 7     7   21034 use File::Copy;
  7         20995  
  7         555  
12 7     7   7372 use File::Which qw(which);
  7         4381  
  7         424  
13 7     7   112 use Carp;
  7         13  
  7         9356  
14              
15             =head1 NAME
16              
17             App::bk - A module for functions used by the F program.
18              
19             =head1 VERSION
20              
21             Version 0.05
22              
23             =cut
24              
25             our $VERSION = '0.05';
26              
27             my %opts = (
28             'help|h|?' => 0,
29             'man' => 0,
30             'version|V' => 0,
31             'debug:+' => 0,
32             'directory|d' => 0,
33             );
34             my %options;
35              
36             # 'tidier' way to store global variables
37             # probably shouldnt do it like this - will rework later
38             $options{debug} ||= 0;
39             $options{username} = getpwuid($EUID);
40              
41             if ( $options{username} eq 'root' ) {
42             logmsg( 2, 'Running as root so dropping username from file backups' );
43             $options{username} = '';
44             }
45              
46             =head1 SYNOPSIS
47              
48             Please see the file F for more information about the F program.
49              
50             =head1 SUBROUTINES/METHODS
51              
52             =head2 backup_files
53              
54             Main function to process ARGV and backup files as necessary
55              
56             =cut
57              
58             sub backup_files {
59              
60             # make sure we don't clobber any callers variables
61              
62 9     9 1 64307 local @ARGV = @ARGV;
63 9 50       136 GetOptions( \%options, keys(%opts) ) || pod2usage( -verbose => 1 );
64              
65 9 50       11466 die("Version: $VERSION\n") if ( $options{version} );
66 9 50 33     94 pod2usage( -verbose => 1 ) if ( $options{'?'} || $options{help} );
67 9 50 33     76 pod2usage( -verbose => 2 ) if ( $options{HELP} || $options{man} );
68              
69 9   50     52 $options{debug} ||= 0;
70 9 50       45 $options{debug} = 8 if ( $options{debug} > 8 );
71              
72 9 100       85 if ( !@ARGV ) {
73 1         9 pod2usage(
74             -message => 'No filenames provided.',
75             -verbose => 0,
76             );
77             }
78              
79 8         904 my $date = strftime( '%Y%m%d', localtime() );
80 8         383 my $time = strftime( '%H%M%S', localtime() );
81              
82 8         30 foreach my $filename (@ARGV) {
83 11         962 my ( $basename, $dirname ) = fileparse($filename);
84              
85             # do this via savedir as we might move this somewhere else dir in future
86 11         27 my $savedir = $dirname;
87              
88 11         54 logmsg( 2, "dirname=$dirname" );
89 11         48 logmsg( 2, "basename=$basename" );
90              
91 11 100       278 if ( !-f $filename ) {
92 2         26 warn "WARNING: File $filename not found", $/;
93 2         127 next;
94             }
95              
96 9 50       32 if ( !$savedir ) {
97 0         0 warn "WARNING: $savedir does not exist", $/;
98 0         0 next;
99             }
100              
101             # compare the last file found with the current file
102 9         128 my $last_backup = get_last_backup( $savedir, $basename );
103 9 100       26 if ($last_backup) {
104 6         41 logmsg( 1, "Found last backup as: $last_backup" );
105              
106 6         25 my $last_backup_sum = get_chksum($last_backup);
107 6         58 my $current_sum = get_chksum($filename);
108              
109 6         197 logmsg( 2, "Last backup file $options{sum}: $last_backup_sum" );
110 6         230 logmsg( 2, "Current file $options{sum}: $current_sum" );
111              
112 6 100       45 if ( $last_backup_sum eq $current_sum ) {
113 3         30 logmsg( 0, "No change since last backup of $filename" );
114 3         43 next;
115             }
116             }
117              
118 6         30 my $savefilename = "$savedir$basename";
119 6 50       24 $savefilename .= ".$options{username}" if ( $options{username} );
120 6         27 $savefilename .= ".$date";
121 6 100       159 if ( -f $savefilename ) {
122 3         31 $savefilename .= ".$time";
123             }
124              
125 6         39 logmsg( 1, "Backing up to $savefilename" );
126              
127             # use OS cp to preserve ownership/permissions/etc
128 6 50       85438 if ( system("cp $filename $savefilename") != 0 ) {
129 0         0 warn "Failed to back up $filename", $/;
130 0         0 next;
131             }
132              
133 6         298 logmsg( 0, "Backed up $filename to $savefilename" );
134             }
135              
136 8         221 return 1;
137             }
138              
139             =head2 logmsg($level, @message);
140              
141             Output @message if $level is equal or less than $options{debug}
142              
143             =cut
144              
145             sub logmsg {
146 65     65 1 284 my ( $level, @text ) = @_;
147 65 100       1372 print @text, $/ if ( $level <= $options{debug} );
148             }
149              
150             =head2 $binary = find_sum_binary();
151              
152             Locate a binary to use to calculate a file checksum. Looks first for md5sum, then sum. Dies on failure to find either.
153              
154             =cut
155              
156             sub find_sum_binary {
157             return
158 5   100 5 1 17035 which('md5sum')
159             || which('sum')
160             || die 'Unable to locate "md5sum" or "sum"', $/;
161             }
162              
163             =head2 $sum = get_chksum($file);
164              
165             Get the chksum of a file
166              
167             =cut
168              
169             sub get_chksum {
170 14     14 1 8230 my ($filename) = @_;
171              
172 14 100       405 croak 'No filename provided' if ( !$filename );
173              
174 13 100       67 if ( !$options{sum} ) {
175 3         20 $options{sum} = find_sum_binary();
176 3         738 logmsg( 2, "Using $options{sum}" );
177             }
178              
179 13         151925 my $chksum = qx/$options{sum} $filename/;
180 13         272 chomp($chksum);
181              
182 13         601 ($chksum) = $chksum =~ m/^(\w+)\s/;
183 13         278 return $chksum;
184             }
185              
186             =head2 $filename = get_last_backup($file);
187              
188             Get the last backup filename for given file
189              
190             =cut
191              
192             sub get_last_backup {
193 16     16 1 33729 my ( $savedir, $filename ) = @_;
194              
195 16 100 66     379 if ( !$savedir || !-d $savedir ) {
196 1         199 croak 'Invalid save directory provided';
197             }
198              
199             # get last backup and compare to current file to prevent
200             # unnecessary backups being created
201 15 50       831 opendir( my $savedir_fh, $savedir )
202             || die( "Unable to read $savedir: $!", $/ );
203 15         1342 my @save_files = sort
204             grep( /$filename\.(?:$options{username}\.)?\d{8}/,
205             readdir($savedir_fh) );
206 15 50       290 closedir($savedir_fh) || die( "Unable to close $savedir: $!", $/ );
207              
208 15 50       57 if ( $options{debug} > 2 ) {
209 0         0 logmsg( 3, "Previous backups found:" );
210 0         0 foreach my $bk (@save_files) {
211 0         0 logmsg( 3, "\t$bk" );
212             }
213             }
214              
215 15         200 return $save_files[-1];
216             }
217              
218             =head1 AUTHOR
219              
220             Duncan Ferguson, C<< >>
221              
222             =head1 BUGS
223              
224             Please report any bugs or feature requests via the web interface at
225             L/
226             I will be notified, and then you'll automatically be notified of
227             progress on your bug as I make changes.
228              
229             =head1 SUPPORT
230              
231             You can find documentation for this module with the perldoc command.
232              
233             perldoc App::bk
234              
235              
236             You can also look for information at:
237              
238             =over 4
239              
240             =item * HitHUB: request tracker
241              
242             L
243              
244             =item * AnnoCPAN: Annotated CPAN documentation
245              
246             L
247              
248             =item * CPAN Ratings
249              
250             L
251              
252             =item * Search CPAN
253              
254             L
255              
256             =back
257              
258              
259             =head1 ACKNOWLEDGEMENTS
260              
261              
262             =head1 LICENSE AND COPYRIGHT
263              
264             Copyright 2011 Duncan Ferguson.
265              
266             This program is free software; you can redistribute it and/or modify it
267             under the terms of either: the GNU General Public License as published
268             by the Free Software Foundation; or the Artistic License.
269              
270             See http://dev.perl.org/licenses/ for more information.
271              
272              
273             =cut
274              
275             1; # End of App::bk