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