File Coverage

blib/lib/Brackup/Target.pm
Criterion Covered Total %
statement 94 113 83.1
branch 22 40 55.0
condition 9 22 40.9
subroutine 14 22 63.6
pod 0 15 0.0
total 139 212 65.5


line stmt bran cond sub pod time code
1             package Brackup::Target;
2              
3 13     13   102 use strict;
  13         30  
  13         520  
4 13     13   78 use warnings;
  13         29  
  13         591  
5 13     13   7699 use Brackup::InventoryDatabase;
  13         44  
  13         538  
6 13     13   11227 use Brackup::TargetBackupStatInfo;
  13         42  
  13         401  
7 13     13   91 use Brackup::Util 'tempfile';
  13         30  
  13         724  
8 13     13   79 use Brackup::DecryptedFile;
  13         31  
  13         340  
9 13     13   68 use Carp qw(croak);
  13         25  
  13         28082  
10              
11             sub new {
12 7     7 0 20 my ($class, $confsec) = @_;
13 7         27 my $self = bless {}, $class;
14 7         51 $self->{name} = $confsec->name;
15 7 50       358 $self->{name} =~ s/^TARGET://
16             or die "No target found matching " . $confsec->name;
17 7 50       126 die "Target name must be only a-z, A-Z, 0-9, and _."
18             unless $self->{name} =~ /^\w+/;
19              
20 7         42 $self->{keep_backups} = $confsec->value("keep_backups");
21 7   33     36 $self->{inv_db} =
22             Brackup::InventoryDatabase->new($confsec->value("inventorydb_file") ||
23             $confsec->value("inventory_db") ||
24             "$ENV{HOME}/.brackup-target-$self->{name}.invdb",
25             $confsec);
26              
27 7         55 return $self;
28             }
29              
30             sub name {
31 8     8 0 29 my $self = shift;
32 8         57 return $self->{name};
33             }
34              
35             # return hashref of key/value pairs you want returned to you during a restore
36             # you should include anything you need to restore.
37             # keys must match /^\w+$/
38             sub backup_header {
39             return {}
40 0     0 0 0 }
41              
42             # returns bool
43             sub has_chunk {
44 0     0 0 0 my ($self, $chunk) = @_;
45 0         0 die "ERROR: has_chunk not implemented in sub-class $self";
46             }
47              
48             # returns true on success, or returns false or dies otherwise.
49             sub store_chunk {
50 0     0 0 0 my ($self, $chunk) = @_;
51 0         0 die "ERROR: store_chunk not implemented in sub-class $self";
52             }
53              
54             # returns true on success, or returns false or dies otherwise.
55             sub delete_chunk {
56 0     0 0 0 my ($self, $chunk) = @_;
57 0         0 die "ERROR: delete_chunk not implemented in sub-class $self";
58             }
59              
60             # returns a list of names of all chunks
61             sub chunks {
62 0     0 0 0 my ($self) = @_;
63 0         0 die "ERROR: chunks not implemented in sub-class $self";
64             }
65              
66             sub inventory_db {
67 236     236 0 1288 my $self = shift;
68 236         1439 return $self->{inv_db};
69             }
70              
71             sub add_to_inventory {
72 81     81 0 606 my ($self, $pchunk, $schunk) = @_;
73 81         1519 my $key = $pchunk->inventory_key;
74 81         331 my $db = $self->inventory_db;
75 81         1206 $db->set($key => $schunk->inventory_value);
76             }
77              
78             # return stored chunk, given positioned chunk, or undef. no
79             # need to override this, unless you have a good reason.
80             sub stored_chunk_from_inventory {
81 144     144 0 1262 my ($self, $pchunk) = @_;
82 144         1589 my $key = $pchunk->inventory_key;
83 144         8822 my $db = $self->inventory_db;
84 144 100       3085 my $invval = $db->get($key)
85             or return undef;
86 23         515 return Brackup::StoredChunk->new_from_inventory_value($pchunk, $invval);
87             }
88              
89             # return a list of TargetBackupStatInfo objects representing the
90             # stored backup metafiles on this target.
91             sub backups {
92 0     0 0 0 my ($self) = @_;
93 0         0 die "ERROR: backups method not implemented in sub-class $self";
94             }
95              
96             # downloads the given backup name to the current directory (with
97             # *.brackup extension)
98             sub get_backup {
99 0     0 0 0 my ($self, $name) = @_;
100 0         0 die "ERROR: get_backup method not implemented in sub-class $self";
101             }
102              
103             # deletes the given backup from this target
104             sub delete_backup {
105 0     0 0 0 my ($self, $name) = @_;
106 0         0 die "ERROR: delete_backup method not implemented in sub-class $self";
107             }
108              
109             # removes old metafiles from this target
110             sub prune {
111 1     1 0 6 my ($self, %opt) = @_;
112              
113 1 50 33     8 my $keep_backups = $opt{keep_backups} || $self->{keep_backups}
114             or die "ERROR: keep_backups option not set\n";
115 1 50       6 die "ERROR: keep_backups option must be at least 1\n"
116             unless $keep_backups > 0;
117              
118             # select backups to delete
119 1         3 my (%backups, @backups_to_delete) = ();
120 1         6 foreach my $backup_name (map {$_->filename} $self->backups) {
  2         8  
121 2         9 $backup_name =~ /^(.+)-\d+$/;
122 2   100     19 $backups{$1} ||= [];
123 2         4 push @{ $backups{$1} }, $backup_name;
  2         8  
124             }
125 1         7 foreach my $source (keys %backups) {
126 1 50 33     6 next if $opt{source} && $source ne $opt{source};
127 1         3 my @b = reverse sort @{ $backups{$source} };
  1         9  
128 1 50       9 push @backups_to_delete, splice(@b, ($keep_backups > $#b+1) ? $#b+1 : $keep_backups);
129             }
130              
131 1 0       5 warn ($opt{dryrun} ? "Pruning:\n" : "Pruned:\n") if $opt{verbose};
    50          
132 1         3 foreach my $backup_name (@backups_to_delete) {
133 1 50       6 warn " $backup_name\n" if $opt{verbose};
134 1 50       10 $self->delete_backup($backup_name) unless $opt{dryrun};
135             }
136 1         7 return scalar @backups_to_delete;
137             }
138              
139             # removes orphaned chunks in the target
140             sub gc {
141 2     2 0 633 my ($self, %opt) = @_;
142              
143             # get all chunks and then loop through metafiles to detect
144             # referenced ones
145 2         13 my %chunks = map {$_ => 1} $self->chunks;
  34         167  
146 2         13 my $total_chunks = scalar keys %chunks;
147 2         16 my $tempfile = +(tempfile())[1];
148 2         37 my @backups = $self->backups;
149 2         10 BACKUP: foreach my $i (0 .. $#backups) {
150 2         4 my $backup = $backups[$i];
151 2 50       9 warn sprintf "Collating chunks from backup %s [%d/%d]\n",
152             $backup->filename, $i+1, scalar(@backups)
153             if $opt{verbose};
154 2         13 $self->get_backup($backup->filename, $tempfile);
155 2         24 my $decrypted_backup = new Brackup::DecryptedFile(filename => $tempfile);
156 2         13 my $parser = Brackup::Metafile->open($decrypted_backup->name);
157 2         9 $parser->readline; # skip header
158 2         7 ITEM: while (my $it = $parser->readline) {
159 30 100       83 next ITEM unless $it->{Chunks};
160 24   50     81 my @item_chunks = map { (split /;/)[3] } grep { $_ } split(/\s+/, $it->{Chunks} || "");
  28         113  
  28         53  
161 24         196 delete $chunks{$_} for (@item_chunks);
162             }
163             }
164 2         12 my @orphaned_chunks = keys %chunks;
165              
166             # report orphaned chunks
167 2 50 33     20 if (@orphaned_chunks && $opt{verbose} && $opt{verbose} >= 2) {
      33        
168 0         0 warn "Orphaned chunks:\n";
169 0         0 warn " $_\n" for (@orphaned_chunks);
170             }
171              
172             # remove orphaned chunks
173 2 50 33     16 if (@orphaned_chunks && ! $opt{dryrun}) {
174 2         4 my $confirm = 'y';
175 2 50       6 if ($opt{interactive}) {
176 0         0 printf "Run gc, removing %d/%d orphaned chunks? [y/N] ",
177             scalar @orphaned_chunks, $total_chunks;
178 0         0 $confirm = <>;
179             }
180              
181 2 50       12 if (lc substr($confirm,0,1) eq 'y') {
182 2 50       8 warn "Removing orphaned chunks\n" if $opt{verbose};
183 2         18 $self->delete_chunk($_) for (@orphaned_chunks);
184              
185             # delete orphaned chunks from inventory
186 2         11 my $inventory_db = $self->inventory_db;
187 2         14 while (my ($k, $v) = $inventory_db->each) {
188 27         117 $v =~ s/ .*$//; # strip value back to hash
189 27 100       128 $inventory_db->delete($k) if exists $chunks{$v};
190             }
191             }
192             }
193              
194 2 50       44 return wantarray ? ( scalar @orphaned_chunks, $total_chunks ) : scalar @orphaned_chunks;
195             }
196              
197              
198              
199             1;
200              
201             __END__