1package Brackup::CompositeChunk; 2 3use strict; 4use warnings; 5use Carp qw(croak); 6use Fcntl qw(SEEK_SET); 7use Brackup::Util qw(tempfile_obj io_sha1 io_print_to_fh); 8 9use fields ( 10 'used_up', 11 'max_size', 12 'target', 13 'digest', # memoized 14 'finalized', # if we've written ourselves to the target yet 15 'subchunks', # the chunks this composite chunk is made of 16 'sha1', # Digest::SHA1 object 17 '_chunk_fh', # tempfile file containing the whole composite chunk 18 ); 19 20sub new { 21 my ($class, $root, $target) = @_; 22 my $self = ref $class ? $class : fields::new($class); 23 $self->{used_up} = 0; # bytes 24 $self->{finalized} = 0; # false 25 $self->{max_size} = $root->max_composite_size; 26 $self->{target} = $target; 27 $self->{subchunks} = []; 28 $self->{sha1} = Digest::SHA1->new; 29 $self->{_chunk_fh} = tempfile_obj(); 30 return $self; 31} 32 33sub append_little_chunk { 34 my ($self, $schunk) = @_; 35 die "ASSERT" if $self->{digest}; # its digest was already requested? 36 37 my $from = $self->{used_up}; 38 $self->{used_up} += $schunk->backup_length; 39 io_print_to_fh($schunk->chunkref, $self->{_chunk_fh}, $self->{sha1}); 40 my $to = $self->{used_up}; 41 42 $schunk->set_composite_chunk($self, $from, $to); 43 push @{$self->{subchunks}}, $schunk; 44} 45 46sub digest { 47 my $self = shift; 48 return $self->{digest} ||= "sha1:" . $self->{sha1}->hexdigest; 49} 50 51sub can_fit { 52 my ($self, $len) = @_; 53 return $len <= ($self->{max_size} - $self->{used_up}); 54} 55 56# return on success; die on any failure 57sub finalize { 58 my $self = shift; 59 die "ASSERT" if $self->{finalized}++; 60 61 $self->{target}->store_chunk($self) 62 or die "chunk storage of composite chunk failed.\n"; 63 64 foreach my $schunk (@{$self->{subchunks}}) { 65 $self->{target}->add_to_inventory($schunk->pchunk => $schunk); 66 } 67 68 $self->forget_chunkref; 69 70 return 1; 71} 72 73sub stored_chunk_from_dup_internal_raw { 74 my ($self, $pchunk) = @_; 75 my $ikey = $pchunk->inventory_key; 76 foreach my $schunk (@{$self->{subchunks}}) { 77 next unless $schunk->pchunk->inventory_key eq $ikey; 78 # match! found a duplicate within ourselves 79 return $schunk->clone_but_for_pchunk($pchunk); 80 } 81 return undef; 82} 83 84# <duck-typing> 85# make this duck-typed like a StoredChunk, so targets can store it 86*backup_digest = \&digest; 87sub backup_length { 88 my $self = shift; 89 return $self->{used_up}; 90} 91# return handle to data 92sub chunkref { 93 my $self = shift; 94 croak "ASSERT: _chunk_fh not opened" unless $self->{_chunk_fh}->opened; 95 seek($self->{_chunk_fh}, 0, SEEK_SET); 96 return $self->{_chunk_fh}; 97} 98sub inventory_value { 99 die "ASSERT: don't expect this to be called"; 100} 101# called when chunk data not needed anymore 102sub forget_chunkref { 103 my $self = shift; 104 if ($self->{_chunk_fh}) { 105 die "ASSERT: used_up: $self->{used_up}, size: " . -s $self->{_chunk_fh}->filename 106 unless -s $self->{_chunk_fh}->filename == $self->{used_up}; 107 close $self->{_chunk_fh}; 108 delete $self->{_chunk_fh}; # also deletes the temp file 109 } 110} 111# </duck-typing> 112 1131; 114