package Brackup::PositionedChunk; use strict; use warnings; use Carp qw(croak); use Digest::SHA1 qw(sha1_hex); 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; } my $rchunk = $self->raw_chunkref; $dig = "sha1:" . sha1_hex($$rchunk); $cache->set($key => $dig); return $self->{_raw_digest} = $dig; } sub raw_chunkref { my $self = shift; return $self->{_raw_chunkref} if $self->{_raw_chunkref}; my $data; my $fullpath = $self->{file}->fullpath; open(my $fh, $fullpath) or die "Failed to open $fullpath: $!\n"; binmode($fh); seek($fh, $self->{offset}, 0) or die "Couldn't seek: $!\n"; my $rv = read($fh, $data, $self->{length}) or die "Failed to read: $!\n"; unless ($rv == $self->{length}) { Carp::confess("Read $rv bytes, not $self->{length}"); } return $self->{_raw_chunkref} = \$data; } # useful string for targets to key on. of one of the forms: # ";to=" # ";raw" # ";gz" (future) sub inventory_key { my $self = shift; my $key = $self->raw_digest; if (my $rcpt = $self->root->gpg_rcpt) { $key .= ";to=$rcpt"; } 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;