File Coverage

blib/lib/File/Maintenance.pm
Criterion Covered Total %
statement 97 104 93.2
branch 17 30 56.6
condition 8 24 33.3
subroutine 21 21 100.0
pod 6 6 100.0
total 149 185 80.5


line stmt bran cond sub pod time code
1             package File::Maintenance;
2 6     6   1099328 use warnings;
  6         15  
  6         256  
3 6     6   328 use strict;
  6         13  
  6         231  
4 6     6   35 use base qw(Class::Accessor);
  6         15  
  6         12200  
5 6     6   21006 use File::Find::Rule;
  6         70743  
  6         66  
6 6     6   6687 use File::Stat::OO;
  6         454664  
  6         60  
7 6     6   6725 use File::Copy;
  6         29102  
  6         501  
8 6     6   50 use File::Path;
  6         13  
  6         353  
9 6     6   39 use File::Basename;
  6         12  
  6         390  
10 6     6   37 use DateTime;
  6         14  
  6         127  
11 6     6   722 use Carp;
  6         10  
  6         490  
12 6     6   10346 use IO::Compress::Gzip qw($GzipError);
  6         407647  
  6         819  
13 6     6   7442 use IO::Compress::Zip qw($ZipError);
  6         113836  
  6         781  
14 6     6   92 use IO::Compress::Bzip2 qw($Bzip2Error);
  6         13  
  6         630  
15            
16             File::Maintenance->mk_accessors(
17             qw(age test recurse directory pattern
18             archive_directory)
19             );
20            
21 6         6487 use constant UNIT_MAP => {
22             s => 'seconds',
23             m => 'minutes',
24             h => 'hours',
25             d => 'days'
26 6     6   44 };
  6         13  
27            
28             =head1 NAME
29            
30             File::Maintenance - Maintain files based on their age.
31            
32             =head1 VERSION
33            
34             Version 0.02
35            
36             =cut
37            
38             our $VERSION = '0.03';
39            
40             =head1 SYNOPSIS
41            
42             This module allows you to purge files from a directory based on age
43            
44             use File::Maintenance;
45            
46             my $fm = File::Maintenance->new({
47             directory => '/tmp',
48             pattern => '*.sess',
49             age => '5d', #older than five days
50             });
51            
52             $fm->test(1); # don't execute the purge
53             $fm->purge; # prints the action to STDOUT but doesn't purge files
54            
55             $fm->test(0); # It's all for real
56             $fm->purge; # Will delete old *.sess files from /tmp
57             $fm->recurse(1);
58             $fm->purge; # Will delete old *.sess files from /tmp and sub-directories
59            
60             You can also archive files (move to another directory) based on age as well
61            
62             use File::Maintenance;
63            
64             my $fm = File::Maintenance->new({
65             directory => '/my/data/files',
66             archive_directory => '/my/archive/files'
67             pattern => '*',
68             recurse => 1, # subdirectories too
69             age => '30m' # older than 30 minutes
70             });
71            
72             $fm->archive;
73            
74             Each value passed to the constructor has a corresponding method for
75             setting the value, so the archive above could have been written as:
76            
77             use File::Maintenance;
78            
79             my $fm = File::Maintenance->new();
80             $fm->directory('/my/data/files');
81             $fm->archive_directory('/my/archive/files);
82             $fm->pattern('*');
83             $fm->recurse(1);
84             $fm->age('30m);
85             $fm->archive;
86            
87             Instead of purging, files can be compressed with either zip, gzip or bzip2 formats:
88            
89             $fm->zip;
90            
91             or
92            
93             $fm->gzip;
94            
95             or
96            
97             $fm->bzip2;
98            
99             =head1 METHODS
100            
101             =head2 directory
102            
103             The root directory for purging
104            
105             $fm->directory('/tmp');
106            
107             =head2 pattern
108            
109             The pattern mask for files to process
110            
111             $fm->pattern('backup*.tar.gz');
112            
113             By default, the pattern is a glob. To use a regular expression, it must be
114             quoted with the qr operator:
115            
116             $fm->pattern(qr/^(foo|bar)\d\d\.jpg$/);
117            
118             =head2 archive_directory
119            
120             The directory that files will be archived to. If the recurse attribute
121             is set, the archive directory hierarchy will match the source directory
122             hierarchy
123            
124             =head2 age
125            
126             Files older than the age will either be archived or purged, depending on
127             the requested action. The age can be specified by s, m, h or d -
128             (seconds, minutes, hours or days)
129            
130             $fm->age('1d'); # Files older than 1 day
131             $fm->age('4h'); # Files older than 4 hours
132            
133             =head2 recurse
134            
135             Whether to traverse subdirectories
136            
137             =head2 purge
138            
139             Delete files older than age
140            
141             =cut
142            
143             sub purge {
144 3     3 1 41227 my $self = shift;
145            
146 3         14 foreach my $file ($self->get_files) {
147 7 50       33 if ($self->test) {
148 0         0 print "TEST: Purging $file\n";
149             } else {
150 7   33     766 unlink $file || croak("Unable to purge $file: $!");
151             }
152             }
153             }
154            
155             =head2 gzip
156            
157             Compresses files older than age using the gzip format
158            
159             =cut
160            
161             sub gzip {
162 1     1 1 8782 my $self = shift;
163            
164 1         6 foreach my $file ($self->get_files) {
165 4 50       23 if ($self->test) {
166 0         0 print "TEST: gzipping $file\n";
167             } else {
168 4 50       61 IO::Compress::Gzip::gzip $file => $file . '.gz'
169             or croak ("Unable to gzip $file: $GzipError");
170 4   33     9195 unlink $file || croak("Unable to purge $file: $!");
171             }
172             }
173             }
174            
175             =head2 zip
176            
177             Compresses files older than age using the zip format
178            
179             =cut
180            
181             sub zip {
182 1     1 1 23784 my $self = shift;
183            
184 1         8 foreach my $file ($self->get_files) {
185 4 50       27 if ($self->test) {
186 0         0 print "TEST: zipping $file\n";
187             } else {
188 4 50       111 IO::Compress::Zip::zip $file => $file . '.zip'
189             or croak ("Unable to zip $file: $ZipError");
190 4   33     16178 unlink $file || croak("Unable to purge $file: $!");
191             }
192             }
193             }
194            
195             =head2 bzip2
196            
197             Compresses files older than age using the bzip2 format
198            
199             =cut
200            
201             sub bzip2 {
202 1     1 1 8232 my $self = shift;
203            
204 1         5 foreach my $file ($self->get_files) {
205 4 50       19 if ($self->test) {
206 0         0 print "TEST: bzipping $file\n";
207             } else {
208 4 50       58 IO::Compress::Bzip2::bzip2 $file => $file . '.bz2'
209             or croak ("Unable to bzip2 $file: $Bzip2Error");
210 4   33     5183 unlink $file || croak("Unable to purge $file: $!");
211             }
212             }
213             }
214            
215             =head2 archive
216            
217             Archive files older than age
218            
219             =cut
220            
221             sub archive {
222 3     3 1 48161 my $self = shift;
223 3   33     15 my $archive_dir = $self->archive_directory
224             || croak("Archive directory not specified");
225 3         70 my $directory = $self->directory;
226 3         27 my %dir_map;
227            
228 3 50       14 croak("You cannot archive to the source directory")
229             if ($directory eq $archive_dir);
230            
231 3         13 foreach my $file ($self->get_files) {
232            
233 7         1097 my $path;
234            
235 7 50       28 if ($self->recurse) {
236 7         356 $path = dirname($file);
237 7         59 $path =~ s/^$directory//g;
238 7         30 $path =~ s/\/(.*)$/$1/g;
239 7         18 $path = $archive_dir . '/' . $path;
240             } else {
241 0         0 $path = $archive_dir;
242             }
243            
244 7 50       25 if ($self->test) {
245 0         0 print "TEST: move $file to $path\n";
246             } else {
247 7 100       253 unless (-d $path) {
248 4   33     1004 mkpath $path || croak("Cannot make directory $path: $!");
249             }
250 7 50       29 move($file, $path) || croak("Cannot move $file to $path: $!");
251             }
252             }
253             }
254            
255             =head2 get_files
256            
257             Return an array of files that match the filter criteria. This method is used
258             internally, but is useful enough to be offered externally
259            
260             =cut
261            
262             sub get_files {
263 15     15 1 23659 my $self = shift;
264 15   33     71 my $directory = $self->directory || croak("Directory not specified");
265 15   33     274 my $pattern = $self->pattern || croak("Pattern not specified");
266 15         184 my $epoch = $self->_get_threshold_date();
267 15         200 my @files;
268            
269 15         189 my $rule = File::Find::Rule->new;
270 15         792 $rule->file;
271 15         652 $rule->name($pattern);
272 15         1719 $rule->mtime("<$epoch");
273 15 100       829 $rule->maxdepth(1) unless $self->recurse;
274 15         237 @files = $rule->in($directory);
275            
276 15         24502 return @files;
277             }
278            
279             sub _get_threshold_date {
280 15     15   30 my $self = shift;
281 15 50       59 croak("Age parameter not specified") unless $self->age;
282 15         228 my $date = DateTime->now(time_zone => 'local');
283 15 50       92620 if ($self->age =~ /^(\d+)(s|m|h|d)$/) {
284 15         320 my $measure = $1;
285 15         34 my $unit = $2;
286            
287 15         129 $date->add(UNIT_MAP->{$unit} => -$measure);
288            
289 15         7496 return $date->epoch;
290             } else {
291 0           croak("Invalid age");
292             }
293             }
294            
295             =head1 AUTHOR
296            
297             Dan Horne, C<< >>
298            
299             =head1 BUGS
300            
301             Please report any bugs or feature requests to
302             C, or through the web interface at
303             L.
304             I will be notified, and then you'll automatically be notified of progress on
305             your bug as I make changes.
306            
307             =head1 SUPPORT
308            
309             You can find documentation for this module with the perldoc command.
310            
311             perldoc File::Maintenance
312            
313             You can also look for information at:
314            
315             =over 4
316            
317             =item * AnnoCPAN: Annotated CPAN documentation
318            
319             L
320            
321             =item * CPAN Ratings
322            
323             L
324            
325             =item * RT: CPAN's request tracker
326            
327             L
328            
329             =item * Search CPAN
330            
331             L
332            
333             =back
334            
335             =head1 ACKNOWLEDGEMENTS
336            
337             =head1 COPYRIGHT & LICENSE
338            
339             Copyright 2008 Dan Horne, all rights reserved.
340            
341             This program is free software; you can redistribute it and/or modify it
342             under the same terms as Perl itself.
343            
344             =cut
345            
346             1; # End of File::Maintenance