File Coverage

blib/lib/Archive/SevenZip.pm
Criterion Covered Total %
statement 104 286 36.3
branch 24 120 20.0
condition 5 29 17.2
subroutine 21 35 60.0
pod 11 20 55.0
total 165 490 33.6


line stmt bran cond sub pod time code
1             package Archive::SevenZip;
2 10     10   4183 use strict;
  10         61  
  10         313  
3 10     10   65 use Carp qw(croak);
  10         20  
  10         857  
4 10     10   5357 use Encode qw( decode encode );
  10         103687  
  10         958  
5 10     10   87 use File::Basename qw(dirname basename);
  10         26  
  10         1178  
6 10     10   4079 use Archive::SevenZip::Entry;
  10         43  
  10         395  
7 10     10   67 use File::Temp qw(tempfile tempdir);
  10         52  
  10         824  
8 10     10   74 use File::Copy;
  10         22  
  10         578  
9 10     10   5292 use IPC::Open3 'open3';
  10         29180  
  10         651  
10 10     10   77 use Path::Class;
  10         24  
  10         472  
11 10     10   63 use Exporter 'import'; # for the error codes, in Archive::Zip API compatibility
  10         23  
  10         438  
12              
13             =head1 NAME
14              
15             Archive::SevenZip - Read/write 7z , zip , ISO9960 and other archives
16              
17             =head1 SYNOPSIS
18              
19             my $ar = Archive::SevenZip->new(
20             find => 1,
21             archivename => $archivename,
22             verbose => $verbose,
23             );
24              
25             for my $entry ( $ar->list ) {
26             my $target = join "/", "$target_dir", $entry->basename;
27             $ar->extractMember( $entry->fileName, $target );
28             };
29              
30             =head1 METHODS
31              
32             =cut
33              
34             our $VERSION= '0.16';
35              
36             # Archive::Zip API
37             # Error codes
38 10     10   59 use constant AZ_OK => 0;
  10         20  
  10         558  
39              
40 10     10   60 use constant COMPRESSION_STORED => 'Store'; # file is stored (no compression)
  10         25  
  10         442  
41 10     10   59 use constant COMPRESSION_DEFLATED => 'Deflate'; # file is Deflated
  10         26  
  10         16442  
42              
43             our @EXPORT_OK = (qw(AZ_OK COMPRESSION_STORED COMPRESSION_DEFLATED));
44             our %EXPORT_TAGS = (
45             ERROR_CODES => [
46             qw(
47             AZ_OK
48             )
49             #AZ_STREAM_END
50             #AZ_ERROR
51             #AZ_FORMAT_ERROR
52             #AZ_IO_ERROR
53             ],
54             CONSTANTS => [
55             qw(COMPRESSION_STORED COMPRESSION_DEFLATED)
56             ],
57             );
58              
59             our %sevenzip_charsetname = (
60             'UTF-8' => 'UTF-8',
61             'Latin-1' => 'WIN',
62             'ISO-8859-1' => 'WIN',
63             '' => 'DOS', # dunno what the appropriate name would be
64             );
65              
66             our %sevenzip_stdin_support = (
67             #'7z' => 1,
68             'xz' => 1,
69             'lzma' => 1,
70             'tar' => 1,
71             'gzip' => 1,
72             'bzip2' => 1,
73             );
74              
75             if( $^O !~ /MSWin/i ) {
76             # Wipe all filesystem encodings because my Debian 7z 9.20 doesn't understand them
77             $sevenzip_charsetname{ $_ } = ''
78             for keys %sevenzip_charsetname;
79             };
80              
81             our %class_defaults = (
82             '7zip' => '7z',
83             fs_encoding => 'UTF-8',
84             default_options => [ "-y", "-bd" ],
85             type => 'zip',
86             system_needs_quotes => scalar ($^O =~ /MSWin/i),
87             );
88              
89             =head2 C<< Archive::SevenZip->find_7z_executable >>
90              
91             my $version = Archive::SevenZip->find_7z_executable()
92             or die "No 7z found.";
93             print "Found 7z version '$version'";
94              
95             Finds the 7z executable in the path or in C<< $ENV{ProgramFiles} >>
96             or C<< $ENV{ProgramFiles(x86)} >>. This is called
97             when a C<< Archive::SevenZip >> instance is created with the C
98             parameter set to 1.
99              
100             If C<< $ENV{PERL_ARCHIVE_SEVENZIP_BIN} >> is set, this value will be used as
101             the 7z executable and the path will not be searched.
102              
103             =cut
104              
105             sub find_7z_executable {
106 9     9 1 13017 my($class) = @_;
107 9         40 my $old_default = $class_defaults{ '7zip' };
108 9 50       73 my $envsep = $^O =~ /MSWin/ ? ';' : ':';
109 9         23 my $found;
110 9 100       38 if( $ENV{PERL_ARCHIVE_SEVENZIP_BIN}) {
111 1         4 $class_defaults{'7zip'} = $ENV{PERL_ARCHIVE_SEVENZIP_BIN};
112 1   33     5 $found = $class->version || "7zip not found via environment '($ENV{PERL_ARCHIVE_SEVENZIP_BIN})'";
113             } else {
114 8         18 my @search;
115 8         162 push @search, split /$envsep/, $ENV{PATH};
116 8 50       61 if( $^O =~ /MSWin/i ) {
117 0         0 push @search, map { "$_\\7-Zip" } grep {defined} ($ENV{'ProgramFiles'}, $ENV{'ProgramFiles(x86)'});
  0         0  
  0         0  
118             };
119 8         43 $found = $class->version;
120              
121 8   33     260 while( ! defined $found and @search) {
122 0         0 my $dir = shift @search;
123 0 0       0 if ($^O eq 'MSWin32') {
124 0 0       0 next unless -e file("$dir", "7z.exe" );
125             }
126 0         0 $class_defaults{'7zip'} = "" . file("$dir", "7z" );
127 0         0 $found = $class->version;
128             };
129             };
130              
131 9 100       105 if( ! $found) {
132 8         98 $class_defaults{ '7zip' } = $old_default;
133             };
134 9 50       175 return defined $found ? $found : ()
135             }
136              
137             =head2 C<< Archive::SevenZip->new >>
138              
139             my $ar = Archive::SevenZip->new( $archivename );
140              
141             my $ar = Archive::SevenZip->new(
142             archivename => $archivename,
143             find => 1,
144             );
145              
146             Creates a new class instance.
147              
148             C - will try to find the executable using C<< ->find_7z_executable >>
149              
150             =cut
151              
152             sub new {
153 13     13 1 1676 my( $class, %options);
154 13 100       54 if( @_ == 2 ) {
155 1         4 ($class, $options{ archivename }) = @_;
156             } else {
157 12         63 ($class, %options) = @_;
158             };
159              
160 13 50       54 if( $options{ find }) {
161 0         0 $class->find_7z_executable();
162             };
163              
164 13         59 for( keys %class_defaults ) {
165             $options{ $_ } = $class_defaults{ $_ }
166 65 100       162 unless defined $options{ $_ };
167             };
168              
169 13         70 bless \%options => $class
170             }
171              
172             sub version {
173 9     9 0 33 my( $self_or_class, %options) = @_;
174 9         63 for( keys %class_defaults ) {
175             $options{ $_ } = $class_defaults{ $_ }
176 45 50       146 unless defined $options{ $_ };
177             };
178 9 50       71 my $self = ref $self_or_class ? $self_or_class : $self_or_class->new( %options );
179              
180 9         70 my $cmd = $self->get_command(
181             command => '',
182             archivename => undef,
183             options => [], # on Debian, 7z doesn't like any options...
184             fs_encoding => undef, # on Debian, 7z doesn't like any options...
185             default_options => [], # on Debian, 7z doesn't like any options...
186             );
187 9         29 my $fh = eval { $self->run($cmd, binmode => ':raw') };
  9         37  
188              
189 9 50       125419 if( ! $@ ) {
190 0         0 local $/ = "\n";
191 0         0 my @output = <$fh>;
192 0 0       0 if( @output >= 3) {
193             # 7-Zip 19.00 (x64) : Copyright (c) 1999-2018 Igor Pavlov : 2019-02-21
194             # 7-Zip [64] 16.02 : Copyright (c) 1999-2016 Igor Pavlov : 2016-05-21
195             # 7-Zip [64] 9.20 Copyright (c) 1999-2010 Igor Pavlov 2010-11-18
196 0 0       0 $output[1] =~ /^7-Zip\s+.*?\b(\d+\.\d+)\s+(?:\(x64\))?(?:\s*:\s*)?Copyright/
197             or return undef;
198 0         0 return $1;
199             } else {
200             return undef
201 0         0 }
202             }
203             }
204              
205             =head2 C<< $ar->open >>
206              
207             my @entries = $ar->open;
208             for my $entry (@entries) {
209             print $entry->fileName, "\n";
210             };
211              
212             Lists the entries in the archive. A fresh archive which does not
213             exist on disk yet has no entries. The returned entries
214             are L instances.
215              
216             This method will one day move to the Path::Class-compatibility
217             API.
218              
219             =cut
220             # Iterate over the entries in the archive
221             # Path::Class API
222             sub open {
223 0     0 1 0 my( $self )= @_;
224 0         0 my @contents = $self->list();
225             }
226              
227             =head2 C<< $ar->memberNamed >>
228              
229             my $entry = $ar->memberNamed('hello_world.txt');
230             print $entry->fileName, "\n";
231              
232             The path separator must be a forward slash ("/")
233              
234             This method will one day move to the Archive::Zip-compatibility
235             API.
236              
237             =cut
238              
239             # Archive::Zip API
240             sub memberNamed {
241 0     0 1 0 my( $self, $name, %options )= @_;
242              
243 0         0 my( $entry ) = grep { $_->fileName eq $name } $self->members( %options );
  0         0  
244 0         0 $entry
245             }
246              
247             # Archive::Zip API
248             sub list {
249 0     0 0 0 my( $self, %options )= @_;
250              
251 0 0       0 if( ! grep { defined $_ } $options{archivename}, $self->{archivename}) {
  0         0  
252             # We are an archive that does not exist on disk yet
253             return
254 0         0 };
255 0         0 my $cmd = $self->get_command( command => "l", options => ["-slt"], %options );
256              
257             my $fh = $self->run($cmd,
258             encoding => $options{ fs_encoding },
259             stdin_fh => $options{ fh },
260 0         0 );
261 0         0 my @output = <$fh>;
262 0         0 my %results = (
263             header => [],
264             archive => [],
265             );
266              
267             # Get/skip header
268 0   0     0 while( @output and $output[0] !~ /^--\s*$/ ) {
269 0         0 my $line = shift @output;
270 0         0 $line =~ s!\s+$!!;
271 0         0 push @{ $results{ header }}, $line;
  0         0  
272             };
273              
274             # Get/skip archive information
275 0   0     0 while( @output and $output[0] !~ /^----------\s*$/ ) {
276 0         0 my $line = shift @output;
277 0         0 $line =~ s!\s+$!!;
278 0         0 push @{ $results{ archive }}, $line;
  0         0  
279             };
280              
281 0 0       0 if( $output[0] =~ /^----------\s*$/ ) {
282 0         0 shift @output;
283             } else {
284 0         0 warn "Unexpected line in 7zip output, hope that's OK: [$output[0]]";
285             };
286              
287 0         0 my @members;
288              
289             # Split entries
290             my %entry_info;
291 0         0 for my $line (@output ) {
292 0 0       0 if( $line =~ /^([\w ]+) =(?: (.*?)|)\s*$/ ) {
    0          
    0          
293 0         0 $entry_info{ $1 } = $2;
294             } elsif($line =~ /^\s*$/) {
295 0 0       0 if( $entry_info{ 'Path' }) {
296 0         0 push @members, Archive::SevenZip::Entry->new(
297             %entry_info,
298             _Container => $self,
299             );
300             };
301 0         0 %entry_info = ();
302             } elsif( $line =~ /^Warnings: \d+\s+/) {
303             # ignore
304             # use Data::Dumper; warn Dumper \@output;
305             # croak "Unknown file entry [$line]";
306             } else {
307 0         0 croak "Unknown file entry [$line]";
308             };
309             };
310              
311             return @members
312 0         0 }
313 10     10   106 { no warnings 'once';
  10         28  
  10         22933  
314             *members = \&list;
315             }
316              
317             =head2 C<< $ar->openMemberFH >>
318              
319             my $fh = $ar->openMemberFH('test.txt');
320             while( <$fh> ) {
321             print "test.txt: $_";
322             };
323              
324             Reads the uncompressed content of the member from the archive.
325              
326             This method will one day move to the Archive::Zip-compatibility
327             API.
328              
329             =cut
330              
331             sub openMemberFH {
332 0     0 1 0 my( $self, %options );
333 0 0       0 if( @_ == 2 ) {
334 0         0 ($self,$options{ membername })= @_;
335             } else {
336 0         0 ($self,%options) = @_;
337             };
338 0 0       0 defined $options{ membername } or croak "Need member name to extract";
339              
340 0         0 my $cmd = $self->get_command( command => "e", options => ["-so"], members => [$options{membername}] );
341 0         0 my $fh = $self->run($cmd, encoding => $options{ encoding }, binmode => $options{ binmode });
342 0         0 return $fh
343             }
344              
345             sub content {
346 0     0 0 0 my( $self, %options ) = @_;
347 0         0 my $fh = $self->openMemberFH( %options );
348 0         0 binmode $fh;
349 0         0 local $/;
350             <$fh>
351 0         0 }
352             =head2 C<< $ar->extractMember >>
353              
354             $ar->extractMember('test.txt' => 'extracted_test.txt');
355              
356             Extracts the uncompressed content of the member from the archive.
357              
358             This method will one day move to the Archive::Zip-compatibility
359             API.
360              
361             =cut
362              
363             # Archive::Zip API
364             sub extractMember {
365 0     0 0 0 my( $self, $memberOrName, $extractedName, %_options ) = @_;
366 0 0       0 $extractedName = $memberOrName
367             unless defined $extractedName;
368              
369 0         0 my %options = (%$self, %_options);
370              
371 0         0 my $target_dir = dirname $extractedName;
372 0         0 my $target_name = basename $extractedName;
373             my $cmd = $self->get_command(
374             command => "e",
375             archivename => $options{ archivename },
376 0         0 members => [ $memberOrName ],
377             options => [ "-o$target_dir" ],
378             );
379 0         0 my $fh = $self->run($cmd, encoding => $options{ encoding });
380              
381 0         0 while( <$fh>) {
382 0 0       0 warn $_ if $options{ verbose };
383             };
384 0 0       0 if( basename $memberOrName ne $target_name ) {
385 0         0 my $org = basename($memberOrName);
386              
387             # Maybe, _maybe_, we need to look for the file using UTF-8
388             # encoded filenames, but also, maybe not. So let's try both...
389 0         0 my $src = "$target_dir/$org";
390 0 0       0 if( ! -f $src ) {
391 0         0 $src = "$target_dir/" . encode('UTF-8', $org);
392             }
393 0 0       0 rename $src => $extractedName
394             or croak "Couldn't move '$src' ('$memberOrName') to '$extractedName': $!";
395             };
396              
397 0         0 return AZ_OK;
398             };
399              
400             =head2 C<< $ar->removeMember >>
401              
402             $ar->removeMember('test.txt');
403              
404             Removes the member from the archive.
405              
406             =cut
407              
408             # strikingly similar to Archive::Zip API
409             sub removeMember {
410 0     0 1 0 my( $self, $name, %_options ) = @_;
411              
412 0         0 my %options = (%$self, %_options);
413              
414 0 0       0 if( $^O =~ /MSWin/ ) {
415 0         0 $name =~ s!/!\\!g;
416             }
417              
418             my $cmd = $self->get_command(
419             command => "d",
420             archivename => $options{ archivename },
421 0         0 members => [ $name ],
422             );
423 0         0 my $fh = $self->run($cmd, encoding => $options{ encoding } );
424 0         0 $self->wait($fh, %options);
425              
426 0         0 return AZ_OK;
427             };
428              
429             sub add_quotes {
430 36     36 0 76 my $quote = shift;
431              
432             $quote ?
433             map {
434 36 0 0     102 defined $_ && /\s/ ? qq{"$_"} : $_
  0 50       0  
435             } @_
436             : @_
437             };
438              
439             sub get_command {
440 9     9 0 70 my( $self, %options )= @_;
441 9   50     94 $options{ members } ||= [];
442             $options{ archivename } = $self->{ archivename }
443 9 50       95 unless defined $options{ archivename };
444 9 50       36 if( ! exists $options{ fs_encoding }) {
445 0 0       0 $options{ fs_encoding } = defined $self->{ fs_encoding } ? $self->{ fs_encoding } : $class_defaults{ fs_encoding };
446             };
447 9 50       38 if( ! defined $options{ default_options }) {
448 0 0       0 $options{ default_options } = defined $self->{ default_options } ? $self->{ default_options } : $class_defaults{ default_options };
449             };
450              
451 9         22 my @charset;
452 9 50       38 if( defined $options{ fs_encoding }) {
453             exists $sevenzip_charsetname{ $options{ fs_encoding }}
454 0 0       0 or croak "Unknown filesystem encoding '$options{ fs_encoding }'";
455 0 0       0 if( my $charset = $sevenzip_charsetname{ $options{ fs_encoding }}) {
456 0         0 push @charset, "-scs" . $sevenzip_charsetname{ $options{ fs_encoding }};
457             };
458             };
459 9         19 for(@{ $options{ members }}) {
  9         36  
460 0         0 $_ = encode $options{ fs_encoding }, $_;
461             };
462              
463 9         27 my $add_quote = $self->{system_needs_quotes};
464 27         95 return [grep {defined $_}
465             add_quotes($add_quote, $self->{ '7zip' }),
466 9         46 @{ $options{ default_options }},
467             $options{ command },
468             @charset,
469 9         30 add_quotes($add_quote, @{ $options{ options }} ),
470             # "--",
471             add_quotes($add_quote, $options{ archivename } ),
472 9         53 add_quotes($add_quote, @{ $options{ members }} ),
  9         25  
473             ];
474             }
475              
476             sub run {
477 9     9 0 43 my( $self, $cmd, %options )= @_;
478              
479 9         25 my $mode = '-|';
480 9 50 33     92 if( defined $options{ stdin } || defined $options{ stdin_fh }) {
481 0         0 $mode = '|-';
482             };
483              
484 9         23 my $fh;
485             warn "Opening [@$cmd]"
486 9 50 33     65 if $options{ verbose } || $self->{verbose};
487              
488 9 50       37 if( $self->{verbose} ) {
489 0 0       0 CORE::open( $fh, $mode, @$cmd)
490             or croak "Couldn't launch [$mode @$cmd]: $!/$?";
491             } else {
492 9 50       562 CORE::open( my $fh_err, '>', File::Spec->devnull )
493             or warn "Couldn't redirect child STDERR";
494 9         52 my $errh = fileno $fh_err;
495 9         28 my $fh_in = $options{ stdin_fh };
496             # We accumulate zombie PIDs here, ah well.
497 9         297 $SIG{'CHLD'} = 'IGNORE';
498 9 0       100 my $pid = open3( $fh_in, my $fh_out, '>&' . $errh, @$cmd)
499             or croak "Couldn't launch [$mode @$cmd]: $!/$?";
500 0 0         if( $mode eq '|-' ) {
501 0           $fh = $fh_in;
502             } else {
503 0           $fh = $fh_out
504             };
505             }
506 0 0         if( $options{ encoding }) {
    0          
507 0           binmode $fh, ":encoding($options{ encoding })";
508             } elsif( $options{ binmode } ) {
509 0           binmode $fh, $options{ binmode };
510             };
511              
512 0 0         if( $options{ stdin }) {
    0          
    0          
513 0           print {$fh} $options{ stdin };
  0            
514 0           close $fh;
515              
516             } elsif( $options{ stdin_fh } ) {
517 0           close $fh;
518              
519             } elsif( $options{ skip }) {
520 0           for( 1..$options{ skip }) {
521             # Read that many lines
522 0           local $/ = "\n";
523 0           scalar <$fh>;
524             };
525             };
526              
527 0           $fh;
528             }
529              
530             sub archive_or_temp {
531 0     0 0   my( $self ) = @_;
532 0 0         if( ! defined $self->{archivename} ) {
533 0           $self->{is_tempfile} = 1;
534 0           (my( $fh ),$self->{archivename}) = tempfile( SUFFIX => ".$self->{type}" );
535 0           close $fh;
536 0           unlink $self->{archivename};
537             };
538             $self->{archivename}
539 0           };
540              
541             sub wait {
542 0     0 0   my( $self, $fh, %options ) = @_;
543 0           while( <$fh>) {
544             warn $_ if ($options{ verbose } || $self->{verbose})
545 0 0 0       };
546 0           wait; # reap that child
547             }
548              
549             =head2 C<< $ar->add_scalar >>
550              
551             $ar->add_scalar( "Some name.txt", "This is the content" );
552              
553             Adds a scalar as an archive member.
554              
555             Unfortunately, 7zip only reads archive members from STDIN
556             for xz, lzma, tar, gzip and bzip2 archives.
557             In the other cases, the scalar will be written to a tempfile, added to the
558             archive and then renamed in the archive.
559              
560             This requires 7zip version 9.30+
561              
562             =cut
563              
564             sub add_scalar {
565 0     0 1   my( $self, $name, $scalar )= @_;
566              
567 0 0         if( $sevenzip_stdin_support{ $self->{type} } ) {
568 0           my $cmd = $self->get_command(
569             command => 'a',
570             archivename => $self->archive_or_temp,
571             members => ["-si$name"],
572             );
573 0           my $fh = $self->run( $cmd,
574             binmode => ':raw',
575             stdin => $scalar,
576             verbose => 1,
577             );
578              
579             } else {
580              
581             # 7zip doesn't really support reading archive members from STDIN :-(
582 0           my($fh, $tempname) = tempfile;
583 0           binmode $fh, ':raw';
584 0           print {$fh} $scalar;
  0            
585 0           close $fh;
586              
587             # Only supports 7z archive type?!
588             # 7zip will magically append .7z to the filename :-(
589 0           my $cmd = $self->get_command(
590             command => 'a',
591             archivename => $self->archive_or_temp,
592             members => [$tempname],
593             #options => ],
594             );
595 0           $fh = $self->run( $cmd );
596 0           $self->wait($fh);
597              
598 0 0         unlink $tempname
599             or warn "Couldn't unlink '$tempname': $!";
600              
601             # Hopefully your version of 7zip can rename members (9.30+):
602 0           $cmd = $self->get_command(
603             command => 'rn',
604             archivename => $self->archive_or_temp,
605             members => [basename($tempname), $name],
606             #options => ],
607             );
608 0           $fh = $self->run( $cmd );
609 0           $self->wait($fh);
610             };
611             };
612              
613             =head2 C<< $ar->add_directory >>
614              
615             $ar->add_directory( "real_etc", "etc" );
616              
617             Adds an empty directory
618              
619             This currently ignores the directory date and time if the directory
620             exists
621              
622             =cut
623              
624             sub add_directory {
625 0     0 1   my( $self, $localname, $target )= @_;
626              
627 0   0       $target ||= $localname;
628              
629             # Create an empty directory, add it to the archive,
630             # then rename that temp name to the wanted name:
631 0           my $tempname = tempdir;
632              
633 0           my $cmd = $self->get_command(
634             command => 'a',
635             archivename => $self->archive_or_temp,
636             members => [$tempname],
637             options => ['-r0'],
638             );
639 0           my $fh = $self->run( $cmd );
640 0           $self->wait($fh);
641              
642             # Hopefully your version of 7zip can rename members (9.30+):
643 0           $cmd = $self->get_command(
644             command => 'rn',
645             archivename => $self->archive_or_temp,
646             members => [basename($tempname), $target],
647             );
648 0           $fh = $self->run( $cmd );
649 0           $self->wait($fh);
650              
651             # Once 7zip supports reading from stdin, this will work again:
652             #my $fh = $self->run( $cmd,
653             # binmode => ':raw',
654             # stdin => $scalar,
655             # verbose => 1,
656             #);
657             };
658              
659             =head2 C<< $ar->add >>
660              
661             $ar->add( items => ["real_etc" => "name_in_archive" ] );
662              
663             Adds elements to an archive
664              
665             This currently ignores the directory date and time if the directory
666             exists
667              
668             =cut
669              
670             sub add {
671 0     0 1   my( $self, %options )= @_;
672              
673 0 0         my @items = @{ delete $options{ items } || [] };
  0            
674              
675             # Split up the list into one batch for the listfiles
676             # and the list of files we need to rename
677              
678 0           my @filelist;
679 0           for my $item (@items) {
680 0 0         if( ! ref $item ) {
681 0           $item = [ $item, $item ];
682             };
683 0           my( $name, $storedName ) = @$item;
684              
685 0 0         if( $name ne $storedName ) {
686             # We need to pipe to 7zip from stdin (no, we don't, we can rename afterwards)
687             # This still means we might overwrite an already existing file in the archive...
688             # But 7-zip seems to not like duplicate filenames anyway in "@"-listfiles...
689 0           my $cmd = $self->get_command(
690             command => 'a',
691             archivename => $self->archive_or_temp,
692             members => [$name],
693             #options => ],
694             );
695 0           my $fh = $self->run( $cmd );
696 0           $self->wait($fh, %options );
697 0           $cmd = $self->get_command(
698             command => 'rn',
699             archivename => $self->archive_or_temp,
700             members => [$name, $storedName],
701             #options => ],
702             );
703 0           $fh = $self->run( $cmd );
704 0           $self->wait($fh, %options );
705              
706             } else {
707             # 7zip can read the file from disk
708             # Write the name to a tempfile to be read by 7zip for batching
709 0           push @filelist, $name;
710             };
711             };
712              
713 0 0         if( @filelist ) {
714 0           my( $fh, $name) = tempfile;
715 0           binmode $fh, ':raw';
716 0           print {$fh} join "\r\n", @filelist;
  0            
717 0           close $fh;
718              
719 0           my @options;
720 0 0         if( $options{ recursive }) {
721 0           push @options, '-r';
722             };
723 0           my $cmd = $self->get_command(
724             command => 'a',
725             archivename => $self->archive_or_temp,
726             members => ['@'.$name],
727             options => \@options
728             );
729 0           $fh = $self->run( $cmd );
730 0           $self->wait($fh, %options);
731             };
732             };
733              
734             =head2 C<< ->archiveZipApi >>
735              
736             my $ar = Archive::SevenZip->archiveZipApi(
737             find => 1,
738             archivename => $archivename,
739             verbose => $verbose,
740             );
741             print "$_\n" for $ar->list_files;
742              
743             This is an alternative constructor that gives you an API
744             that is somewhat compatible with the API of L.
745             See also L.
746              
747             =cut
748              
749             sub archiveZipApi {
750 0     0 1   my( $class, %options ) = @_;
751 0           require Archive::SevenZip::API::ArchiveZip;
752 0           Archive::SevenZip::API::ArchiveZip->new( %options )
753             }
754              
755             =head2 C<< ->archiveTarApi >>
756              
757             my $ar = Archive::SevenZip->archiveTarApi(
758             find => 1,
759             archivename => $archivename,
760             verbose => $verbose,
761             );
762             print "$_\n" for $ar->list_files;
763              
764             This is an alternative constructor that gives you an API
765             that is somewhat compatible with the API of L.
766             See also L.
767              
768             =cut
769              
770             sub archiveTarApi {
771 0     0 1   my( $class, %options ) = @_;
772 0           require Archive::SevenZip::API::ArchiveTar;
773 0           Archive::SevenZip::API::ArchiveTar->new( %options )
774             }
775              
776             package Path::Class::Archive::Handle;
777 10     10   202 use strict;
  10         76  
  10         601  
778              
779             =head1 NAME
780              
781             Path::Class::Archive::Handle - treat archives as directories
782              
783             =cut
784              
785             package Path::Class::Archive;
786              
787             1;
788              
789             __END__