File Coverage

blib/lib/App/BackupPlan.pm
Criterion Covered Total %
statement 25 94 26.6
branch 0 38 0.0
condition 0 3 0.0
subroutine 9 26 34.6
pod n/a
total 34 161 21.1


line stmt bran cond sub pod time code
1             package App::BackupPlan::Policy;
2              
3 1     1   33418 use Archive::Tar;
  1         326374  
  1         152  
4 1     1   19 use File::Find;
  1         5  
  1         1840  
5              
6             sub new {
7 0     0     my $class = shift;
8 0           my $self = {
9             maxFiles => shift,
10             prefix => shift,
11             frequency => shift,
12             targetDir => shift,
13             sourceDir => shift};
14              
15 0           bless $self,$class;
16 0           return $self;
17             }
18              
19             sub setMaxFiles {
20 0     0     my ( $self, $maxFiles ) = @_;
21 0 0         $self->{maxFiles} = $maxFiles if defined($maxFiles);
22 0           return $self->{maxFiles};
23             }
24              
25             sub getMaxFiles {
26 0     0     my( $self ) = @_;
27 0           return $self->{maxFiles};
28             }
29              
30             sub setPrefix {
31 0     0     my ( $self, $prefix ) = @_;
32 0 0         $self->{prefix} = $prefix if defined($prefix);
33 0           return $self->{prefix};
34             }
35              
36             sub getPrefix {
37 0     0     my( $self ) = @_;
38 0           return $self->{prefix};
39             }
40              
41             sub setFrequency {
42 0     0     my ( $self, $frequency ) = @_;
43 0 0         $self->{frequency} = $frequency if defined($frequency);
44 0           return $self->{frequency};
45             }
46              
47             sub getFrequency {
48 0     0     my( $self ) = @_;
49 0           return $self->{frequency};
50             }
51              
52             sub setTargetDir {
53 0     0     my ( $self, $targetDir ) = @_;
54 0 0         $self->{targetDir} = $targetDir if defined($targetDir);
55 0           return $self->{targetDir};
56             }
57              
58             sub getTargetDir {
59 0     0     my( $self ) = @_;
60 0           return $self->{targetDir};
61             }
62              
63             sub setSourceDir {
64 0     0     my ( $self, $sourceDir ) = @_;
65 0 0         $self->{sourceDir} = $sourceDir if defined($sourceDir);
66 0           return $self->{sourceDir};
67             }
68              
69             sub getSourceDir {
70 0     0     my( $self ) = @_;
71 0           return $self->{sourceDir};
72             }
73              
74             sub set {
75 0     0     my ($self, $name, $value) = @_;
76 0 0 0       $self->{$name} = $value if defined($value) && defined($name);
77             }
78              
79             sub print {
80 0     0     my( $self ) = @_;
81 0 0         $self->{maxFiles} = "n/a" unless defined($self->{maxFiles});
82 0 0         $self->{prefix} = "n/a" unless defined($self->{prefix});
83 0 0         $self->{frequency} = "n/a" unless defined($self->{frequency});
84 0 0         $self->{targetDir} = "n/a" unless defined($self->{targetDir});
85 0 0         $self->{sourceDir} = "n/a" unless defined($self->{sourceDir});
86 0           print "Policy: maxFiles=$self->{maxFiles},
87             prefix=$self->{prefix},
88             frequency=$self->{frequency},
89             targetDir=$self->{targetDir},
90             sourceDir=$self->{sourceDir}\n";
91             }
92              
93             sub info {
94 0     0     my( $self ) = @_;
95 0 0         $self->{maxFiles} = "n/a" unless defined($self->{maxFiles});
96 0 0         $self->{prefix} = "n/a" unless defined($self->{prefix});
97 0 0         $self->{frequency} = "n/a" unless defined($self->{frequency});
98 0 0         $self->{targetDir} = "n/a" unless defined($self->{targetDir});
99 0 0         $self->{sourceDir} = "n/a" unless defined($self->{sourceDir});
100 0           return "Policy: maxFiles=$self->{maxFiles},
101             prefix=$self->{prefix},
102             frequency=$self->{frequency},
103             targetDir=$self->{targetDir},
104             sourceDir=$self->{sourceDir}";
105             }
106              
107             sub tar {
108 0     0     my( $self, $ts, $hasExcludeTag ) = @_;
109 0           my $filename = sprintf("%s/%s_%s.tar.gz",$self->{targetDir},$self->{prefix},$ts);
110 0           my $option = '';
111 0 0         $option = '--exclude-tag-all=NOTAR' if $hasExcludeTag;
112 0           my $output = `tar cvzf $filename $option $self->{sourceDir} 2>&1 1>/dev/null`;
113 0 0         if (-e $filename) {
114 0           my $stat = `ls -lh $filename`;
115 0           return "system tar: $stat";
116             }
117 0           return "Error: tar failed to produce $filename\n$output\n";
118             }
119              
120             sub perlTar {
121 0     0     my( $self, $ts ) = @_;
122 0           my $filename = sprintf("%s/%s_%s.tar.gz",$self->{targetDir},$self->{prefix},$ts);
123 0           my $tar = new Archive::Tar;
124 0           our @files=();
125 0     0     find(sub {push(@files,$File::Find::name);},$self->{sourceDir});
  0            
126 0           $tar->add_files(@files);
127 0           $tar->write($filename,COMPRESS_GZIP);
128 0 0         if (-e $filename) {
129 0           my $stat = `ls -lh $filename`;
130 0           return "perl tar: $stat";
131             }
132 0           my $err = $tar->error();
133 0           return "Error: tar failed to produce $filename\n$err\n";
134             }
135              
136              
137             package App::BackupPlan;
138              
139 1     1   28 use 5.012003;
  1         12  
  1         312  
140 1     1   7 use strict;
  1         1  
  1         39  
141 1     1   13 use warnings;
  1         1  
  1         113  
142 1     1   6 use Config;
  1         3  
  1         71  
143 1     1   2603 use DateTime;
  1         427186  
  1         36  
144 1     1   11 use Time::Local;
  1         2  
  1         63  
145 1     1   498 use XML::DOM;
  0            
  0            
146             use Log::Log4perl qw(:easy);
147              
148             require XML::DOM;
149             require Log::Log4perl;
150              
151             require Exporter;
152             use AutoLoader qw(AUTOLOAD);
153              
154             our @ISA = qw(Exporter);
155              
156             # Items to export into callers namespace by default. Note: do not export
157             # names by default without a very good reason. Use EXPORT_OK instead.
158             # Do not simply export all your public functions/methods/constants.
159              
160             # This allows declaration use App::BackupPlan ':all';
161             # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
162             # will save memory.
163             our %EXPORT_TAGS = ( 'all' => [ qw(
164            
165             ) ] );
166              
167             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
168              
169             our @EXPORT = qw();
170              
171              
172             BEGIN {
173             our $VERSION = '0.0.7';
174             print "App::BackupPlan by Gualtiero Chiaia, version $VERSION\n";
175             }
176              
177             # Preloaded methods go here.
178              
179             our $TAR = 'system'; #use system tar
180             our $HAS_EXCLUDE_TAG = 0; #has tar option --exclude-tag
181              
182             sub new {
183             my $class = shift;
184             my $self = {
185             config => shift,
186             log => shift
187             };
188            
189             bless $self,$class;
190             return $self;
191             }
192              
193             sub run {
194             my ($self) = @_;
195            
196             #validate the config file
197             die "App::BackupPlan configuration file is required, but was not given!" unless defined $self->{config};
198              
199             #logging config
200             if (defined $self->{log}) {
201             Log::Log4perl::init($self->{log});
202             } else {
203             Log::Log4perl->easy_init( { level => $INFO,
204             file => ">>easy.log" } );
205             }
206            
207             my $logger = Log::Log4perl::get_logger();
208            
209             #get the environment
210             &getEnvironment;
211              
212             #--now read config file
213             my $parser = new XML::DOM::Parser;
214             my $doc = $parser->parsefile ($self->{config}) or die "Could not parse $self->{config}";
215              
216             #get policies
217             my ($obj,%policies) = &getPolicies($doc);
218             foreach my $k (keys %policies) {
219             #policy info
220             print "**$k policy**\n";
221             $logger->info("**$k policy**");
222            
223             my $policy = $policies{$k};
224             $policy->print;
225             $logger->debug($policy->info);
226             my $now = time;
227             my $ts = &formatTimeSpan(time);
228             my %files = &getFiles($policy->getTargetDir,$policy->getPrefix);
229             #get last
230             my $lastts = &getLastTs(keys %files);
231             my $threshold = &subTimeSpan($now,$policy->getFrequency);
232             if (!defined($lastts) || $lastts < $threshold ) { #needs a new tar file
233             my $lastTS = '';
234             $lastTS = &formatTimeSpan($lastts) if defined $lastts;
235             $logger->info("Need a new tar file, last tar was on $lastTS");
236             my $tarout;
237             if (lc $TAR eq 'perl') {$tarout= $policy->perlTar($ts);}
238             else {$tarout = $policy->tar($ts,$HAS_EXCLUDE_TAG);}
239             if ($tarout =~ /Error/i) {
240             $logger->error($tarout);
241             } else {
242             $logger->debug($tarout);
243             }
244            
245             #now delete old
246             %files = &getFiles($policy->getTargetDir,$policy->getPrefix);
247             my $maxFiles = $policy->getMaxFiles;
248             my $cnt = scalar(keys %files);
249             while ($cnt > $maxFiles && $cnt >0) {
250             my $oldts = &getFirstTs(keys %files);
251             my $oldTS = '';
252             $oldTS = &formatTimeSpan($oldts) if defined $oldts;
253             unlink $files{$oldts};
254             $logger->info("Deleted old tar file, with time stamp $oldTS");
255             %files = &getFiles($policy->getTargetDir,$policy->getPrefix);
256             $cnt--;
257             } #end while
258             } #end if
259             } #end foreach
260             } #end sub
261              
262             sub getEnvironment {
263             my $env = $Config{osname};
264             if ($Config{osname} =~ /linux/i) {
265             my $output = `man tar | grep /\-\-exclude\-tag/ | wc -l`;
266             $HAS_EXCLUDE_TAG = 1 unless ($output eq '0');
267             } else {$TAR = 'perl';}
268            
269             }
270              
271              
272             sub getPolicies {
273             my $xml = $_[0];
274             my $defaultPolicy = new App::BackupPlan::Policy;
275             #get default policy first
276             #first default policy
277             my $nodes = $xml->getElementsByTagName("default");
278             if ($nodes->getLength > 0) {
279             my $node = $nodes->item(0);
280             foreach my $child ($node->getChildNodes) {
281             if ($child->getNodeType == ELEMENT_NODE){
282             my $name = $child->getNodeName;
283             my $value = $child->getFirstChild->getNodeValue;
284             $defaultPolicy->set($name,$value);
285             }
286             }
287             }
288             #then all policies
289             my %raw_policies;
290             $nodes = $xml->getElementsByTagName("task");
291             for (my $i=0;$i<$nodes->getLength; $i++) {
292             my $task = $nodes->item($i);
293             my $taskName = $task->getAttributes->getNamedItem('name')->getNodeValue;
294             my $p = new App::BackupPlan::Policy;
295             foreach my $child ($task->getChildNodes) {
296             if ($child->getNodeType == ELEMENT_NODE){
297             my $name = $child->getNodeName;
298             my $value = $child->getFirstChild->getNodeValue;
299             $p->set($name,$value);
300             }
301             }
302             $raw_policies{$taskName} = $p;
303             }
304             %raw_policies = injectDefaultPolicy($defaultPolicy,%raw_policies);
305             return ($defaultPolicy,%raw_policies);
306             }
307              
308             sub injectDefaultPolicy {
309             my ($defPolicy,%raw_pcs) = @_;
310             foreach my $k (keys %raw_pcs) {
311             $raw_pcs{$k}->setMaxFiles($defPolicy->getMaxFiles) unless defined($raw_pcs{$k}->getMaxFiles);
312             $raw_pcs{$k}->setPrefix($defPolicy->getPrefix) unless defined($raw_pcs{$k}->getPrefix);
313             $raw_pcs{$k}->setFrequency($defPolicy->getFrequency) unless defined($raw_pcs{$k}->getFrequency);
314             $raw_pcs{$k}->setSourceDir($defPolicy->getSourceDir) unless defined($raw_pcs{$k}->getSourceDir);
315             $raw_pcs{$k}->setTargetDir($defPolicy->getTargetDir) unless defined($raw_pcs{$k}->getTargetDir);
316             }
317             return %raw_pcs;
318             }
319              
320             sub addTimeSpan{
321             my ($timestamp,$span) = @_;
322             my @ts = localtime $timestamp;
323             my $year = $ts[5]+1900;
324             my $month = $ts[4]+1;
325             my $day = $ts[3];
326             my $dt = DateTime->new(year => $year, month => $month, day => $day);
327             if ($span=~/(\d+)d/) {
328             $dt->add_duration(DateTime::Duration->new(days => $1));
329             return timelocal(0,0,0,$dt->day(),$dt->month()-1,$dt->year());
330             }
331             if ($span=~/(\d+)m/) {
332             $dt->add_duration(DateTime::Duration->new(months => $1));
333             return timelocal(0,0,0,$dt->day(),$dt->month()-1,$dt->year());
334             }
335             if ($span=~/(\d+)y/) {
336             $dt->add_duration(DateTime::Duration->new(years => $1));
337             return timelocal(0,0,0,$dt->day(),$dt->month()-1,$dt->year());
338             }
339             }
340              
341             sub subTimeSpan{
342             my ($timestamp,$span) = @_;
343             my @ts = localtime $timestamp;
344             my $year = $ts[5]+1900;
345             my $month = $ts[4]+1;
346             my $day = $ts[3];
347             my $dt = DateTime->new(year => $year, month => $month, day => $day,);
348             if ($span=~/(\d+)d/) {
349             $dt->subtract_duration(DateTime::Duration->new(days => $1));
350             return timelocal(0,0,0,$dt->day(),$dt->month()-1,$dt->year());
351             }
352             if ($span=~/(\d+)m/) {
353             $dt->subtract_duration(DateTime::Duration->new(months => $1));
354             return timelocal(0,0,0,$dt->day(),$dt->month()-1,$dt->year());
355             }
356             if ($span=~/(\d+)y/) {
357             $dt->subtract_duration(DateTime::Duration->new(years => $1));
358             return timelocal(0,0,0,$dt->day(),$dt->month()-1,$dt->year());
359             }
360             return $timestamp;
361             }
362              
363             sub formatTimeSpan {
364             my $ts = $_[0];
365             my @ts = localtime $ts;
366             my $year = $ts[5]+1900;
367             my $month = $ts[4]+1;
368             my $day = $ts[3];
369             return sprintf("%4d%02d%02d",$year,$month,$day);
370             }
371              
372             sub getFiles {
373             my %fileMap;
374             my ($sourceDir, $pattern) = @_;
375             opendir DH, $sourceDir or die "Cannot open directory $sourceDir: $!\n";
376             foreach my $f (readdir DH) {
377             if ($f=~/$pattern.*/) {
378             my $fname = $sourceDir."/".$f;
379             #print "$fname\n";
380             my @s = stat $fname;
381             my $mtime = $s[9];
382             $fileMap{$mtime}= $fname;
383             }
384             }
385             closedir DH;
386             return %fileMap;
387             }
388              
389             sub getLastTs {
390             my (@ts) = sort @_;
391             my $nts = scalar @ts;
392             return $ts[$nts-1];
393             }
394              
395             sub getFirstTs {
396             my (@ts) = sort @_;
397             return $ts[0];
398             }
399              
400              
401              
402             # Autoload methods go after =cut, and are processed by the autosplit program.
403              
404             1;
405             __END__