/usr/local/CPAN/Brackup/Brackup/PositionedChunk.pm
package Brackup::PositionedChunk;
use strict;
use warnings;
use Carp qw(croak);
use Brackup::Util qw(io_sha1);
use IO::File;
use IO::InnerFile;
use Fcntl qw(SEEK_SET);
use fields (
'file', # the Brackup::File object
'offset', # offset within said file
'length', # length of data
'_raw_digest',
'_raw_chunkref',
);
sub new {
my ($class, %opts) = @_;
my $self = ref $class ? $class : fields::new($class);
$self->{file} = delete $opts{'file'}; # Brackup::File object
$self->{offset} = delete $opts{'offset'};
$self->{length} = delete $opts{'length'};
croak("Unknown options: " . join(', ', keys %opts)) if %opts;
croak("offset not numeric") unless $self->{offset} =~ /^\d+$/;
croak("length not numeric") unless $self->{length} =~ /^\d+$/;
return $self;
}
sub as_string {
my $self = shift;
return $self->{file}->as_string . "{off=$self->{offset},len=$self->{length}}";
}
# the original length, pre-encryption
sub length {
my $self = shift;
return $self->{length};
}
sub offset {
my $self = shift;
return $self->{offset};
}
sub file {
my $self = shift;
return $self->{file};
}
sub root {
my $self = shift;
return $self->file->root;
}
sub raw_digest {
my $self = shift;
return $self->{_raw_digest} ||= $self->_calc_raw_digest;
}
sub _calc_raw_digest {
my $self = shift;
my $n_chunks = $self->{file}->chunks
or die "zero chunks?";
if ($n_chunks == 1) {
# don't calculate this chunk's digest.. it's the same as our
# file's digest, since this chunk spans the entire file.
die "ASSERT" unless $self->length == $self->{file}->size;
return $self->{file}->full_digest;
}
my $cache = $self->root->digest_cache;
my $key = $self->cachekey;
my $dig;
if ($dig = $cache->get($key)) {
return $self->{_raw_digest} = $dig;
}
$dig = "sha1:" . io_sha1($self->raw_chunkref);
$cache->set($key => $dig);
return $self->{_raw_digest} = $dig;
}
sub raw_chunkref {
my $self = shift;
if ($self->{_raw_chunkref}) {
$self->{_raw_chunkref}->seek(0, SEEK_SET);
return $self->{_raw_chunkref};
}
my $fullpath = $self->{file}->fullpath;
my $fh = IO::File->new($fullpath, 'r') or die "Failed to open $fullpath: $!";
binmode($fh);
my $ifh = IO::InnerFile->new($fh, $self->{offset}, $self->{length})
or die "Failed to create inner file handle for $fullpath: $!\n";
return $self->{_raw_chunkref} = $ifh;
}
# useful string for targets to key on. of one of the forms:
# "<digest>;to=<enc_to>"
# "<digest>;raw"
# "<digest>;gz" (future)
sub inventory_key {
my $self = shift;
my $key = $self->raw_digest;
if (my @rcpts = $self->root->gpg_rcpts) {
$key .= ";to=@rcpts";
} else {
$key .= ";raw";
}
return $key;
}
sub forget_chunkref {
my $self = shift;
delete $self->{_raw_chunkref};
}
sub cachekey {
my $self = shift;
return $self->{file}->cachekey . ";o=$self->{offset};l=$self->{length}";
}
sub is_entire_file {
my $self = shift;
return $self->{file}->chunks == 1;
}
1;