The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package CTK::Arc; # $Id: Arc.pm 193 2017-04-29 07:30:55Z minus $
use Moose::Role; # use Data::Dumper; $Data::Dumper::Deparse = 1;

=head1 NAME

CTK::Arc - Archives working

=head1 VERSION

Version 1.71

=head1 SYNOPSIS

    # External extracting
    $c->fextract(
            -in     => CTK::catfile($CTK::DATADIR,'in'),  # Source directory (archives)
            -out    => CTK::catfile($CTK::DATADIR,'out'), # Destination directory (files)
            -method => 'ext',
            -list   => qr/rar/, # Source mask (regular expression, filename or ArrayRef of files)
            -arcdef => $config->{arc}, # Archive attributes (Hashref)
        );

    # Internal extracting
    $c->fextract(
            -in     => CTK::catfile($CTK::DATADIR,'in'),  # Source directory (archives)
            -out    => CTK::catfile($CTK::DATADIR,'out'), # Destination directory (files)
            -method => 'zip', # Zip archive
            -list   => qr/zip/, # Source mask (regular expression, filename or ArrayRef of files)
            -arcdef => $config->{arc}, # Archive attributes (Hashref)
        );

    # External files compressing
    $c->fcompress(
            -in     => CTK::catfile($CTK::DATADIR,'in'), # Source directory (files)
            -out    => CTK::catfile($CTK::DATADIR,'out','ttt.rar'), # Archive name (filename)
            -list   => qr//, # Source mask (regular expression, filename or ArrayRef of files)
            -arcdef => $config->{arc}, # Archive attributes (Hashref)
        );

=head1 DESCRIPTION

Sample of $config->{arc} records:

    ARC => {
        tgz   =>  {
            "type"       => "tar", # name
            "ext"        => "tgz", # extension
            "create"     => "tar -zcpf [FILE] [LIST]", # create command
            "extract"    => "tar -zxpf [FILE] [DIRDST]", # extract command
            "exclude"    => "--exclude-from ",
            "list"       => "tar -ztf [FILE]",
            "nocompress" => "tar -cpf [FILE]"
        },
        ...
    }

=head2 KEYS

=over 8

=item B<FILE>

Path and filename

=item B<FILENAME>

Filename only

=item B<DIRSRC>

Source directory. Path only

=item B<DIRIN>

See DIRSRC

=item B<DIRDST>

Destination directory. Path only

=item B<DIROUT>

See DIRDST

=item B<EXC>

Reserved

=item B<LIST>

Reserved

=back

=head2 METHODS

=head3 fextract

    # External extracting
    $c->fextract(
            -in     => CTK::catfile($CTK::DATADIR,'in'),  # Source directory (archives)
            -out    => CTK::catfile($CTK::DATADIR,'out'), # Destination directory (files)
            -method => 'ext',
            -list   => qr/rar/, # Source mask (regular expression, filename or ArrayRef of files)
            -arcdef => $config->{arc}, # Archive attributes (Hashref)
        );

    # Internal extracting
    $c->fextract(
            -in     => CTK::catfile($CTK::DATADIR,'in'),  # Source directory (archives)
            -out    => CTK::catfile($CTK::DATADIR,'out'), # Destination directory (files)
            -method => 'zip', # Zip archive
            -list   => qr/zip/, # Source mask (regular expression, filename or ArrayRef of files)
            -arcdef => $config->{arc}, # Archive attributes (Hashref)
        );

Extracting the files group

=head3 fcompress

    # External files compressing
    $c->fcompress(
            -in     => CTK::catfile($CTK::DATADIR,'in'), # Source directory (files)
            -out    => CTK::catfile($CTK::DATADIR,'out','ttt.rar'), # Archive name (filename)
            -list   => qr//, # Source mask (regular expression, filename or ArrayRef of files)
            -arcdef => $config->{arc}, # Archive attributes (Hashref)
        );

Compressing the files group

=head1 AUTHOR

Sergey Lepenkov (Serz Minus) L<http://www.serzik.com> E<lt>minus@mail333.comE<gt>

=head1 COPYRIGHT

Copyright (C) 1998-2017 D&D Corporation. All Rights Reserved

=head1 LICENSE

This program is free software; you can redistribute it and/or modify it under the same terms and conditions as Perl itself.

This program is distributed under the GNU LGPL v3 (GNU Lesser General Public License version 3).

See C<LICENSE> file

=cut

use constant {
    ARC       => {
        tgz   =>  {
            "type"       => "tar",
            "ext"        => "tgz",
            "create"     => "tar -zcpf [FILE] [LIST]",
            "extract"    => "tar -zxpf [FILE] [DIRDST]",
            "exclude"    => "--exclude-from ",
            "list"       => "tar -ztf [FILE]",
            "nocompress" => "tar -cpf [FILE]"
        },
        gz   =>  {
            "type"       => "tar",
            "ext"        => "gz",
            "create"     => "tar -zcpf [FILE] [LIST]",
            "extract"    => "tar -zxpf [FILE] -C [DIRDST]",
            "exclude"    => "--exclude-from ",
            "list"       => "tar -ztf [FILE]",
            "nocompress" => "tar -cpf [FILE]"
        },
        zip   => {
            "type"       => "zip",
            "ext"        => "zip",
            "create"     => $^O =~ /mswin/i ? "zip -rqq [FILE] [LIST]" : "zip -rqqy [FILE] [LIST]",
            "extract"    => $^O =~ /mswin/i ? "unzip -uqqoX [FILE] -d [DIRDST]" : "unzip -uqqoX [FILE] [DIRDST]",
            "exclude"    => "-x\@",
            "list"       => "unzip -lqq",
            "nocompress" => "zip -qq0"
        },
        rar   => {
            "type"       => "rar",
            "ext"        => "rar",
            "create"     => $^O =~ /mswin/i ? "rar a -r -y [FILE] [LIST]" : "rar a -r -ol -y [FILE] [LIST]",
            "extract"    => "rar x -y [FILE] [DIRDST]",
            "exclude"    => "-x\@",
            "list"       => "rar vb",
            "nocompress" => "rar a -m0"
        }
    },
};

use vars qw/$VERSION/;
$VERSION = '1.71';

use CTK::Util qw(:API :FORMAT :ATOM);
use Archive::Tar;
use Archive::Zip qw(:ERROR_CODES :CONSTANTS);
use Archive::Extract;

sub fextract {
    # Ðàçàðõèâèðîâàíèå ñîäåðæèìîãî óêàçàííîãî ñïèñêà àðõèâà
    my $self; $self = shift if (@_ && $_[0] && ref($_[0]) eq 'CTK');

    my @args = @_;
    my ($method, $dirin, $dirout, $listmsk, $arcdef);
       ($method, $dirin, $dirout, $listmsk, $arcdef) =
            read_attributes([
                ['METHOD','METH','TYPE'],
                ['DIRIN','IN','INPUT','DIRSRC','SRC'],
                ['DIROUT','OUT','OUTPUT','DIRDST','DST'],
                ['LISTMSK','LIST','MASK','LST','MSK','FILE','FILES'],
                ['ARC','ARCDEF','ARCSET','SET','DEF'],

            ],@args) if defined $args[0];

    $method   ||= 'ext'; # Ìåòîä èçâëå÷åíèå ôàéëîâ zip / tar / ext
    $dirin    ||= ''; # Âõîäíàÿ äèðåêòîðèÿ
    $dirout   ||= ''; # Äèðåêòîðèÿ îáðàáîòêè
    $listmsk  ||= ''; # Ñïèñîê èìåí ôàéëîâ äëÿ ïðîöåññà èëè ìàñêà
    $arcdef   ||= ''; # Ñåêöèÿ (ññûëêà íà õýø) äëÿ ðó÷íîãî îïðåäëåíèÿ ïàðàìåòðà arc (ïîèñê ïî ðàñøèðåíèþ)
    my $list;

    if (ref($listmsk) eq 'ARRAY') {
        # Ñïèñîê
        $list = $listmsk;
    } elsif (ref($listmsk) eq 'Regexp') { # Regexp
        # Âñå ôàéëû ïî åãî Ìàñêå
        $list = getlist($dirin,$listmsk);
    } else {
        # Êîíêðåòíûé ôàéë íî âñå ðàâíî êàê ìàñêà èëè æå âñå ôàéëû
        $list = getlist($dirin,qr/$listmsk/);
    }

    # Íà ýòîì ýòàïå èìååì ëèíåéíûé ñïèñîê ôàëîâ
    if ($method eq 'tar') {
        #CTK::debug("Èçâëå÷åíèå TAR-àðõèâîâ êàòàëîãà \"$dirin\" â êàòàëîã \"$dirout\"...");
        my $tar = Archive::Tar->new;
        my $i = 0;
        my $c = scalar(@$list) || 0;
        foreach my $fn (@$list) {$i++;
            my $fin = $dirin ? catfile($dirin,$fn) : $fn;
            my $fs   = -e $fin ? (-s $fin) : 0; # Ðàçìåð ôàéëà àðõèâà
            #CTK::debug("   Ðàçàðõèâèðóåòñÿ ôàéë $i/$c $fn [".correct_number($fs)." b]...");
            $tar->read($fin);
            foreach my $fan ( $tar->list_files() ) {
                #CTK::debug("   --- Extracting \"$fan\"...");
                $tar->extract_file( $fan, $dirout ? catfile($dirout,$fan) : $fan );
            }

        }
    } elsif ($method eq 'zip') {
        #CTK::debug("Èçâëå÷åíèå ZIP-àðõèâîâ êàòàëîãà \"$dirin\" â êàòàëîã \"$dirout\"...");
        my $i = 0;
        my $c = scalar(@$list) || 0;
        foreach my $fn (@$list) {$i++;
            my $fin = $dirin ? catfile($dirin,$fn) : $fn;
            my $fs   = -e $fin ? (-s $fin) : 0; # Ðàçìåð ôàéëà àðõèâà
            #CTK::debug("   Ðàçàðõèâèðóåòñÿ ôàéë $i/$c $fn [".correct_number($fs)." b]...");
            my $ae = Archive::Extract->new( archive => $fin );
            my $ok = $ae->extract( to => $dirout );
            if ( $ok ) {
                #my $filesok = $ae->files;
                #foreach (@$filesok) {CTK::debug("   --- File \"$_\": OK")};
            } else {
                #CTK::debug("   --- ERROR: File extract FAILED: ".$ae->error);
                carp("ZIP EXTRACTION ERROR: File extract FAILED: ".$ae->error); # unless CTK::debugmode();
            }
        }
    } elsif ($method eq 'ext') {
        #CTK::debug("Âíåøíåå èçâëå÷åíèå \"$dirin\" â êàòàëîã \"$dirout\"...");
        my $i = 0;
        my $c = scalar(@$list) || 0;
        foreach my $fn (@$list) {$i++;
            my $fin = $dirin ? catfile($dirin,$fn) : $fn;
            my $fs   = -e $fin ? (-s $fin) : 0; # Ðàçìåð ôàéëà àðõèâà
            #CTK::debug("   Ðàçàðõèâèðóåòñÿ ôàéë $i/$c $fn [".correct_number($fs)." b]...");
            my $arc = _getarc(
                    FILE     => $fin,
                    FILENAME => $fn,
                    DIRSRC   => $dirin,
                    DIRIN    => $dirin,
                    DIRDST   => $dirout,
                    DIROUT   => $dirout,
                    # EXC    => 'exclude file', # Çàðåçåðâèðîâàíî!!!
                    LIST     => '',
                    ARCDEF   => $arcdef,
                );
            if ($arc) {
                my @C;
                push @C, $arc->{extract};
                #push @C, $fin;
                #push @C, ::WIN &&  $arc->{type} eq 'zip' ? "-d $dirproc" : $dirproc;
                procexec(\@C);
            } else {
                #CTK::debug("   --- ERROR: Íåóäàëîñü îïðåäåëèòü ôîðìàò àðõèâà ïî ðàñøèðåíèþ: $fn");
                carp("EXTERNAL EXTRACTION ERROR: Unknow format $fn"); #unless CTK::debugmode();
            }
        }

    } else {
        #CTK::debug("ERROR: Íåèçâåñòíûé ôîðìàò (ìåòîä) àðõèâà: $method");
        carp("ERROR: Unknow archive's format or method: $method"); #unless CTK::debugmode();
    }
}
sub fcompress {
    # Àðõèâèðîâàíèå ñîäåðæèìîãî óêàçàííîãî ñïèñêà äèðåêòîðèè
    my $self; $self = shift if (@_ && $_[0] && ref($_[0]) eq 'CTK');

    my @args = @_;
    my ($dirin, $fout, $listmsk, $arcdef);
       ($dirin, $fout, $listmsk, $arcdef) =
            read_attributes([
                ['DIRIN','IN','INPUT','DIRSRC','SRC'],
                ['FILEOUT','OUT','OUTPUT','FILEDST','DST','FOUT','NAME','ARCHIVE'],
                ['LISTMSK','LIST','MASK','LST','MSK','FILE','FILES'],
                ['ARC','ARCDEF','ARCSET','SET','DEF'],

            ],@args) if defined $args[0];


    $dirin    ||= ''; # Âõîäíàÿ äèðåêòîðèÿ (àðõèâèðóþòñÿ îáúåêòû âíóòðè!!! - ôàéëû è ïàïêè)
    $fout     ||= ''; # Ôàéë âûõîäíîãî àðõèâà (ïîëíûé ïóòü)
    $listmsk  ||= ''; # Ñïèñîê èìåí ôàéëîâ è êàòàëîãîâ äëÿ ïðîöåññà èëè ìàñêà ïîèñêà
    $arcdef   ||= ''; # Ñåêöèÿ (ññûëêà íà õýø) äëÿ ðó÷íîãî îïðåäëåíèÿ ïàðàìåòðà arc (ïîèñê ïî ðàñøèðåíèþ)
    my $list;
    my $dlist;

    if (ref($listmsk) eq 'ARRAY') {
        # Ñïèñîê
        $list = $listmsk;
    } elsif (ref($listmsk) eq 'Regexp') { # Regexp
        # Âñå ôàéëû ïî åãî Ìàñêå
        $list = getlist($dirin,$listmsk);
        $dlist = getdirlist($dirin,$listmsk);
    } else {
        # Êîíêðåòíûé ôàéë íî âñå ðàâíî êàê ìàñêà èëè æå âñå ôàéëû
        $list = getlist($dirin,qr/$listmsk/);
        $dlist = getdirlist($dirin,qr/$listmsk/);
    }
    $list   ||= [];
    $dlist  ||= [];


    # Íà ýòîì ýòàïå èìååì ëèíåéíûé ñïèñîê ôàëîâ  ::debug(join "; ", @$list);

    #
    # Ñæèìàåì:
    # $arc{create} $bfile ".join(" ",@dirlist)." $exclude ";
    #
    #CTK::debug("Âíåøíåå ñæàòèå \"$dirin\" â ôàéë \"$fout\"...");
    my @reallist;
    foreach (@$list,@$dlist) {
        push @reallist, $dirin ? catfile($dirin,$_) : $_;
    }

    my $arc = _getarc(
            FILE     => $fout,
            DIRSRC   => $dirin,
            DIRIN    => $dirin,
            # EXC    => 'exclude file', # Çàðåçåðâèðîâàíî!!!
            LIST     => join(" ",@reallist),
            ARCDEF   => $arcdef,
        );
    #use Data::Dumper; ::debug(Dumper($arc)); return 1;

    if ($arc) {
        my @C;
        push @C, $arc->{create};
        procexec(\@C);
    } else {
        #CTK::debug("   ERROR: Íåóäàëîñü îïðåäåëèòü ôîðìàò àðõèâà ïî ðàñøèðåíèþ: $fout");
        carp("EXTERNAL COMPRESSING ERROR: Format undefined by $fout"); # unless CTK::debugmode();
    }
}
sub _getarc {
    # Ïîëó÷åíèå ïðåäïîëàãàåìîãî òèïà àðõèâà ïî èìåíè ôàéëà èëè undef
    # Äàííûå ìàñêðèðóþòñÿ êàê â ñëó÷àå _splitformat
    my %dn = @_;
	my $file = $dn{FILE} || '';
    my $def  = $dn{ARCDEF} || '';

    my $ext = '';
	$ext = $1 if ($file && $file =~ /\.(\w+)$/);

    my $sec;
    if ($def && $def->{$ext}) {
        $sec = $def->{$ext};
    } else {
        $sec = ARC->{$ext} || undef;
    }

    unless( $sec ) {
        #CTK::debug( "Error: unknown archive format of file \"$file\": $ext" );
        carp("ERROR: Unknown archive format of file \"$file\": $ext"); # unless CTK::debugmode();
        return undef;
    }
    my %arc = %$sec;

    # Ïðîáåãàåìñÿ ïî âñåì êëþ÷àì íàéäííîãî òèïà
    foreach (values %arc) {
        $_ =~ s/\[(.+?)\]/($dn{uc($1)} || '')/eg;
    }

	return {%arc}; # ññûëêà íà àíîíèìíûé õýø
}

1;
__END__