File Coverage

blib/lib/App/bk.pm
Criterion Covered Total %
statement 91 110 82.7
branch 31 48 64.5
condition 7 17 41.1
subroutine 15 17 88.2
pod 7 7 100.0
total 151 199 75.8


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