📄 archive.pm
字号:
package Archive::Zip::Archive;# Represents a generic ZIP archiveuse strict;use File::Path;use File::Find ();use File::Spec ();use File::Copy ();use File::Basename;use Cwd;use vars qw( $VERSION @ISA );BEGIN { $VERSION = '1.23'; @ISA = qw( Archive::Zip );}use Archive::Zip qw( :CONSTANTS :ERROR_CODES :PKZIP_CONSTANTS :UTILITY_METHODS);# Note that this returns undef on read errors, else new zip object.sub new { my $class = shift; my $self = bless( { 'diskNumber' => 0, 'diskNumberWithStartOfCentralDirectory' => 0, 'numberOfCentralDirectoriesOnThisDisk' => 0, # shld be # of members 'numberOfCentralDirectories' => 0, # shld be # of members 'centralDirectorySize' => 0, # must re-compute on write 'centralDirectoryOffsetWRTStartingDiskNumber' => 0, # must re-compute 'writeEOCDOffset' => 0, 'writeCentralDirectoryOffset' => 0, 'zipfileComment' => '', 'eocdOffset' => 0, 'fileName' => '' }, $class ); $self->{'members'} = []; if (@_) { my $status = $self->read(@_); return $status == AZ_OK ? $self : undef; } return $self;}sub members { @{ shift->{'members'} };}sub numberOfMembers { scalar( shift->members() );}sub memberNames { my $self = shift; return map { $_->fileName() } $self->members();}# return ref to member with given name or undefsub memberNamed { my ( $self, $fileName ) = @_; foreach my $member ( $self->members() ) { return $member if $member->fileName() eq $fileName; } return undef;}sub membersMatching { my ( $self, $pattern ) = @_; return grep { $_->fileName() =~ /$pattern/ } $self->members();}sub diskNumber { shift->{'diskNumber'};}sub diskNumberWithStartOfCentralDirectory { shift->{'diskNumberWithStartOfCentralDirectory'};}sub numberOfCentralDirectoriesOnThisDisk { shift->{'numberOfCentralDirectoriesOnThisDisk'};}sub numberOfCentralDirectories { shift->{'numberOfCentralDirectories'};}sub centralDirectorySize { shift->{'centralDirectorySize'};}sub centralDirectoryOffsetWRTStartingDiskNumber { shift->{'centralDirectoryOffsetWRTStartingDiskNumber'};}sub zipfileComment { my $self = shift; my $comment = $self->{'zipfileComment'}; if (@_) { $self->{'zipfileComment'} = pack( 'C0a*', shift() ); # avoid unicode } return $comment;}sub eocdOffset { shift->{'eocdOffset'};}# Return the name of the file last read.sub fileName { shift->{'fileName'};}sub removeMember { my ( $self, $member ) = @_; $member = $self->memberNamed($member) unless ref($member); return undef unless $member; my @newMembers = grep { $_ != $member } $self->members(); $self->{'members'} = \@newMembers; return $member;}sub replaceMember { my ( $self, $oldMember, $newMember ) = @_; $oldMember = $self->memberNamed($oldMember) unless ref($oldMember); return undef unless $oldMember; return undef unless $newMember; my @newMembers = map { ( $_ == $oldMember ) ? $newMember : $_ } $self->members(); $self->{'members'} = \@newMembers; return $oldMember;}sub extractMember { my $self = shift; my $member = shift; $member = $self->memberNamed($member) unless ref($member); return _error('member not found') unless $member; my $originalSize = $member->compressedSize(); my $name = shift; # local FS name if given my ( $volumeName, $dirName, $fileName ); if ( defined($name) ) { ( $volumeName, $dirName, $fileName ) = File::Spec->splitpath($name); $dirName = File::Spec->catpath( $volumeName, $dirName, '' ); } else { $name = $member->fileName(); ( $dirName = $name ) =~ s{[^/]*$}{}; $dirName = Archive::Zip::_asLocalName($dirName); $name = Archive::Zip::_asLocalName($name); } if ( $dirName && !-d $dirName ) { mkpath($dirName); return _ioError("can't create dir $dirName") if ( !-d $dirName ); } my $rc = $member->extractToFileNamed( $name, @_ ); # TODO refactor this fix into extractToFileNamed() $member->{'compressedSize'} = $originalSize; return $rc;}sub extractMemberWithoutPaths { my $self = shift; my $member = shift; $member = $self->memberNamed($member) unless ref($member); return _error('member not found') unless $member; my $originalSize = $member->compressedSize(); return AZ_OK if $member->isDirectory(); my $name = shift; unless ($name) { $name = $member->fileName(); $name =~ s{.*/}{}; # strip off directories, if any $name = Archive::Zip::_asLocalName($name); } my $rc = $member->extractToFileNamed( $name, @_ ); $member->{'compressedSize'} = $originalSize; return $rc;}sub addMember { my ( $self, $newMember ) = @_; push( @{ $self->{'members'} }, $newMember ) if $newMember; return $newMember;}sub addFile { my $self = shift; my $fileName = shift; my $newName = shift; my $newMember = $self->ZIPMEMBERCLASS->newFromFile( $fileName, $newName ); $self->addMember($newMember) if defined($newMember); return $newMember;}sub addString { my $self = shift; my $newMember = $self->ZIPMEMBERCLASS->newFromString(@_); return $self->addMember($newMember);}sub addDirectory { my ( $self, $name, $newName ) = @_; my $newMember = $self->ZIPMEMBERCLASS->newDirectoryNamed( $name, $newName ); $self->addMember($newMember); return $newMember;}# add either a file or a directory.sub addFileOrDirectory { my ( $self, $name, $newName ) = @_; if ( -f $name ) { ( $newName =~ s{/$}{} ) if $newName; return $self->addFile( $name, $newName ); } elsif ( -d $name ) { ( $newName =~ s{[^/]$}{&/} ) if $newName; return $self->addDirectory( $name, $newName ); } else { return _error("$name is neither a file nor a directory"); }}sub contents { my ( $self, $member, $newContents ) = @_; return _error('No member name given') unless $member; $member = $self->memberNamed($member) unless ref($member); return undef unless $member; return $member->contents($newContents);}sub writeToFileNamed { my $self = shift; my $fileName = shift; # local FS format foreach my $member ( $self->members() ) { if ( $member->_usesFileNamed($fileName) ) { return _error( "$fileName is needed by member " . $member->fileName() . "; consider using overwrite() or overwriteAs() instead." ); } } my ( $status, $fh ) = _newFileHandle( $fileName, 'w' ); return _ioError("Can't open $fileName for write") unless $status; my $retval = $self->writeToFileHandle( $fh, 1 ); $fh->close(); $fh = undef; return $retval;}# It is possible to write data to the FH before calling this,# perhaps to make a self-extracting archive.sub writeToFileHandle { my $self = shift; my $fh = shift; return _error('No filehandle given') unless $fh; return _ioError('filehandle not open') unless $fh->opened(); my $fhIsSeekable = @_ ? shift: _isSeekable($fh); _binmode($fh); # Find out where the current position is. my $offset = $fhIsSeekable ? $fh->tell() : 0; $offset = 0 if $offset < 0; foreach my $member ( $self->members() ) { my $retval = $member->_writeToFileHandle( $fh, $fhIsSeekable, $offset ); $member->endRead(); return $retval if $retval != AZ_OK; $offset += $member->_localHeaderSize() + $member->_writeOffset(); $offset += $member->hasDataDescriptor() ? DATA_DESCRIPTOR_LENGTH + SIGNATURE_LENGTH : 0; # changed this so it reflects the last successful position $self->{'writeCentralDirectoryOffset'} = $offset; } return $self->writeCentralDirectory($fh);}# Write zip back to the original file,# as safely as possible.# Returns AZ_OK if successful.sub overwrite { my $self = shift; return $self->overwriteAs( $self->{'fileName'} );}# Write zip to the specified file,# as safely as possible.# Returns AZ_OK if successful.sub overwriteAs { my $self = shift; my $zipName = shift; return _error("no filename in overwriteAs()") unless defined($zipName); my ( $fh, $tempName ) = Archive::Zip::tempFile(); return _error( "Can't open temp file", $! ) unless $fh; ( my $backupName = $zipName ) =~ s{(\.[^.]*)?$}{.zbk}; my $status = $self->writeToFileHandle($fh); $fh->close(); $fh = undef; if ( $status != AZ_OK ) { unlink($tempName); _printError("Can't write to $tempName"); return $status; } my $err; # rename the zip if ( -f $zipName && !rename( $zipName, $backupName ) ) { $err = $!; unlink($tempName); return _error( "Can't rename $zipName as $backupName", $err ); } # move the temp to the original name (possibly copying) unless ( File::Copy::move( $tempName, $zipName ) ) { $err = $!; rename( $backupName, $zipName ); unlink($tempName); return _error( "Can't move $tempName to $zipName", $err ); } # unlink the backup if ( -f $backupName && !unlink($backupName) ) { $err = $!; return _error( "Can't unlink $backupName", $err ); } return AZ_OK;}# Used only during writingsub _writeCentralDirectoryOffset { shift->{'writeCentralDirectoryOffset'};}sub _writeEOCDOffset { shift->{'writeEOCDOffset'};}# Expects to have _writeEOCDOffset() setsub _writeEndOfCentralDirectory { my ( $self, $fh ) = @_; $fh->print(END_OF_CENTRAL_DIRECTORY_SIGNATURE_STRING) or return _ioError('writing EOCD Signature'); my $zipfileCommentLength = length( $self->zipfileComment() ); my $header = pack( END_OF_CENTRAL_DIRECTORY_FORMAT, 0, # {'diskNumber'}, 0, # {'diskNumberWithStartOfCentralDirectory'}, $self->numberOfMembers(), # {'numberOfCentralDirectoriesOnThisDisk'}, $self->numberOfMembers(), # {'numberOfCentralDirectories'}, $self->_writeEOCDOffset() - $self->_writeCentralDirectoryOffset(), $self->_writeCentralDirectoryOffset(), $zipfileCommentLength ); $fh->print($header) or return _ioError('writing EOCD header'); if ($zipfileCommentLength) { $fh->print( $self->zipfileComment() ) or return _ioError('writing zipfile comment'); } return AZ_OK;}# $offset can be specified to truncate a zip file.sub writeCentralDirectory { my ( $self, $fh, $offset ) = @_; if ( defined($offset) ) { $self->{'writeCentralDirectoryOffset'} = $offset; $fh->seek( $offset, IO::Seekable::SEEK_SET )
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -