Archive-Libarchive

 view release on metacpan or  search on metacpan

lib/Archive/Libarchive/ArchiveRead.pm  view on Meta::CPAN

package Archive::Libarchive::ArchiveRead;

use strict;
use warnings;
use 5.020;
use Archive::Libarchive::Lib;
use FFI::Platypus::Buffer qw( scalar_to_buffer scalar_to_pointer grow set_used_length window );
use FFI::Platypus::Memory qw( strdup free );
use Ref::Util qw( is_plain_scalarref is_plain_coderef is_blessed_ref is_plain_arrayref );
use Carp ();
use Scalar::Util qw( refaddr );
use experimental qw( signatures );
use parent qw( Archive::Libarchive::Archive );
use constant;

# ABSTRACT: Libarchive read archive class
our $VERSION = '0.09'; # VERSION

my $ffi = Archive::Libarchive::Lib->ffi;
constant->import(_opaque_size => $ffi->sizeof('opaque'));
my %keep;


$ffi->mangler(sub ($name) { "archive_read_$name"  });

$ffi->attach( new => [] => 'opaque' => sub {
  my($xsub, $class) = @_;
  my $ptr = $xsub->();
  bless { ptr => $ptr }, $class;
});

$ffi->attach( [ free => 'DESTROY' ] => ['archive_read'] => 'int' => sub {
  my($xsub, $self) = @_;
  free delete $self->{passphrase} if defined $self->{passphrase};
  return if $self->{cb}                 # inside a callback, we don't own the archive pointer
    || ${^GLOBAL_PHASE} eq 'DESTRUCT';  # during global shutdown the xsub might go away
  my $ret = $xsub->($self);
  delete $keep{refaddr $self};
  warn "destroying archive pointer did not return ARCHIVE_OK" unless $ret == 0;
});


my %set_callback = map {
  ($_ => $ffi->function( "set_${_}_callback" => ['archive_read',"archive_${_}_callback"] => 'int' )->sub_ref)
} qw( open close read seek skip );

$ffi->attach( [ set_callback_data => '_set_callback_data' ] => ['archive_read','opaque'] => 'int' );

$ffi->attach( [ open1 => 'open' ] => [ 'archive_read'] => 'int' => sub {
  my($xsub, $self, %callbacks) = @_;

  Carp::croak("The read callback is required") unless $callbacks{read};

  foreach my $name (keys %set_callback)
  {
    my $set = $set_callback{$name};
    my $sub = delete $callbacks{$name};

    unless(defined $sub)
    {
      $set->($self, undef);
      next;
    }

    Carp::croak("Callback for $name is not a code reference") unless is_plain_coderef $sub;

    my $closure;

    if($name eq 'read')
    {
      $closure = FFI::Platypus->closure(sub ($r, $, $ptrptr) {
        $r = bless { ptr => $r, cb => 1 }, __PACKAGE__;
        $self->{read_buffer} = undef;
        my $size = $sub->($r, \$self->{read_buffer});
        my $ptr = defined $self->{read_buffer} ? scalar_to_pointer($self->{read_buffer}) : undef;
        _memcpy($ptrptr, [$ptr], _opaque_size());
        return $size;
      });
    }
    else
    {
      $closure = FFI::Platypus->closure(sub ($r, $, @therest) {
        $r = bless { ptr => $r, cb => 1 }, __PACKAGE__;
        $sub->($r, @therest);
      });
    }

    push @{ $keep{refaddr $self} }, $closure;

    $set->($self, $closure);
  }

  Carp::croak("No such read callbacks: @{[ sort keys %callbacks ]}") if %callbacks;

  _set_callback_data($self, undef);

  $xsub->($self);
});


$ffi->attach( open_memory => ['archive_read','opaque','size_t'] => 'int' => sub {
  my($xsub, $self, $ref) = @_;
  Carp::croak("buffer must be a scalar reference")
    unless defined $ref && is_plain_scalarref $ref;
  push @{ $keep{refaddr $self} }, \($$ref);
  my($ptr, $size) = scalar_to_buffer $$ref;
  $xsub->($self, $ptr, $size);
});


$ffi->attach( open_FILE => ['archive_read', 'opaque'] => 'int' => sub {
  my($xsub, $self, $fp) = @_;
  $fp = $$fp if is_blessed_ref $fp && $fp->isa('FFI::C::File');
  $xsub->($self, $fp);
});


sub open_perlfile ($self, $fh)
{
  $self->open(
    read => sub ($r, $ref) {
      return sysread $fh, $$ref, 512;
    },
    close => sub ($r) {
      close $fh;
    },
  );
}


$ffi->attach( open_filenames => ['archive_read', 'string[]', 'size_t'] => 'int' => sub {
  my($xsub, $self, $filenames, $size) = @_;
  Carp::croak("Filenames must be provided as an array reference")
    unless defined $filenames && is_plain_arrayref $filenames;
  $xsub->($self, defined $filenames->[-1] ? [@$filenames, undef] : $filenames, $size);
});


$ffi->attach( [ next_header2 => 'next_header' ] => ['archive_read','archive_entry'] => 'int' => sub {
  my($xsub, $self, $entry) = @_;
  $xsub->($self, $entry);
});


$ffi->attach( [data => 'read_data'] => ['archive_read', 'opaque', 'size_t'] => 'ssize_t' => sub {
  my($xsub, $self, $ref, $size) = @_;
  $size ||= 512;

  grow $$ref, $size, { clear => 0 };
  my $rsize = $xsub->($self, (scalar_to_buffer $$ref));
  set_used_length $$ref, $rsize;

  return $rsize;

lib/Archive/Libarchive/ArchiveRead.pm  view on Meta::CPAN


require Archive::Libarchive::Lib::ArchiveRead unless $Archive::Libarchive::no_gen;

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

Archive::Libarchive::ArchiveRead - Libarchive read archive class

=head1 VERSION

version 0.09

=head1 SYNOPSIS

 use 5.020;
 use Archive::Libarchive qw( :const );
 
 my $r = Archive::Libarchive::ArchiveRead->new;
 $r->support_filter_all;
 $r->support_format_all;
 $r->open_filename("archive.tar", 10240) == ARCHIVE_OK
   or die $r->error_string;
 
 my $e = Archive::Libarchive::Entry->new;
 say $e->pathname while $r->next_header($e) == ARCHIVE_OK;

=head1 DESCRIPTION

This class represents an archive instance for reading from archives.

=head1 CONSTRUCTOR

=head2 new

 # archive_read_new
 my $r = Archive::Libarchive::ArchiveRead->new;

Create a new archive read object.

=head1 METHODS

This is a subset of total list of methods available to all archive classes.
For the full list see L<Archive::Libarchive::API/Archive::Libarchive::ArchiveRead>.

=head2 open

 # archive_read_open1
 # archive_read_set_callback_data
 # archive_read_set_close_callback
 # archive_read_set_open_callback
 # archive_read_set_read_callback
 # archive_read_set_seek_callback
 # archive_read_set_skip_callback
 $r->open(%callbacks);

This is a basic open method, which relies on callbacks for its implementation.  The
only callback that is required is the C<read> callback.  The C<open> and C<close>
callbacks are made available mostly for the benefit of the caller.  The C<skip>
and C<seek> callbacks are used if available for some formats like C<zip> to improve
performance.  All callbacks should return a L<normal status code|Archive::Libarchive/CONSTANTS>,
which is C<ARCHIVE_OK> on success.

Unlike the C<libarchive> C-API, this interface doesn't provide a facility for
passing in "client" data.  In Perl this is implemented using a closure, which should
allow you to pass in arbitrary variables via proper scoping.

=over 4

=item open

 $r->open(open => sub ($r) {
   ...
 });

Called immediately when the archive is "opened";

=item read

 $r->open(read => sub ($r, $ref) {
   $$ref = ...;
   ...
   return $size.
 });

Called when new data is required.  What is passed in is a scalar reference.  You should
set this scalar to the next block of data.  On success you should return the size of
the data in bytes, and on failure return a L<normal status code|Archive::Libarchive/CONSTANTS>.

=item seek

 $r->open(seek => sub ($r, $offset, $whence) {
   ...
 });

Called to seek to the new location.  The C<$offset> and C<$whence> arguments work exactly
like the C<libc> C<fseek> function.

=item skip

 $r->open(skip => sub ($r, $request) {
   ...
 });

Called to skip the next C<$request> bytes.  Should return the actual number of bytes skipped
on success (which can be less than or equal to C<$request>), and on failure return a
L<normal status code|Archive::Libarchive/CONSTANTS>.

=item close

 $r->open(close => sub ($r) {
   ...
 });

This is called when the archive instance is closed.

=back

=head2 open_memory

 # archive_write_open_memory



( run in 0.769 second using v1.01-cache-2.11-cpan-39bf76dae61 )