using Archive::Zip::MemberRead fails to find "opened" method


D

David Karr

I'm working on a script that can recursively examine archive files, so I'm using "Archive::Zip" along with "Archive::Zip::MemberRead". I had originally written it with just "Archive::Zip", and it was working fine, but I tried to extend it to recursively examine the archive by adding "MemberRead".

I'm seeing the following error now:

Can't locate object method "opened" via package "Archive::Zip::MemberRead" at /usr/local/share/perl/5.14.2/Archive/Zip/Archive.pm line 570.

I tried adding in my script the various "File::" imports that are in "Archive.pm", but that made no difference.
 
Ad

Advertisements

D

David Karr

That implies you are calling ->readFromFileHandle on an Archive. You
need to be calling ->readFileHandle on a Member; but this method won't
exist unless you

use Archive::Zip::MemberRead;

Ok, based on that input, this is what I have so far. It's not failing, but I don't think it's working properly either. I ran this on an archive that has an archive which has an element I'm searching for, but it didn't find it.

sub processArchive($$$) {
my ($zip, $match, $zipName) = @_;
my @matchingList = $zip->membersMatching($match);
my $len = @matchingList;
if ($len > 0) {
print $zipName . ":\n";
if ($opt_list == 0) {
for my $member (@matchingList) {
if ($opt_cat == 0) {
print $member->fileName() . getDetail($member) . "\n";
}
else {
print $member->fileName() . getDetail($member) . ":\n";
print $member->contents();
}
}
}
}
my @matchingArchiveList = $zip->membersMatching("\.jar");
my $archivesLen = @matchingArchiveList;
#print "archivesLen[" . $archivesLen . "]\n";
if ($archivesLen > 0) {
print "zipfile[" . $zipName . "]\n";
for my $archiveMember (@matchingArchiveList) {
print "archiveMember[" . $archiveMember->fileName() . "]\n";
my $zipfh = Archive::Zip::MemberRead->new($zip, $archiveMember);
my $archiveZip = Archive::Zip->new();
my $archiveStatus = $archiveMember->readFileHandle($zipfh);
print "archiveStatus[" . $archiveStatus . "]\n";
# if ($archiveStatus == AZ_OK) {
processArchive($archiveZip, $entry, $zipName . ":" . $archiveMember->fileName());
# }
}
}
}
Why on Earth would you do that?

Have you ever done anything that you knew didn't make any sense, but you didn't know what else to try?
 
R

Rainer Weikusat

[...]
I have, but it never helps. Without understanding the cause of the
problem you've got no way of knowing you've fixed it, which means it'll
probably come back.

You seem to live in a part of the universe where 'sales rep' "computer
experts" don't exist ...
 
D

David Karr

for my $archiveMember (@matchingArchiveList) {
print "archiveMember[" . $archiveMember->fileName() . "]\n";
my $zipfh = Archive::Zip::MemberRead->new($zip, $archiveMember);
my $archiveZip = Archive::Zip->new();
my $archiveStatus = $archiveMember->readFileHandle($zipfh);

Oh, I see now what you are doing: you did want

my $archiveStatus = $archiveZip->readFromFileHandle($zipfh);

after all, but unfortunately it isn't going to work. AZ::MemberRead
doesn't give you a real filehandle: the object is neither a Perl
filehandle nor inherits from IO::Handle, so AZ can't read a zipfile from
it.

If you felt like it writing a tied-handle class (or a proper subclass of
IO::Handle) which read from an AZ::Member shouldn't be too difficult.
You would need to implement seeking by calling ->rewindData and then
reading forward, since AZ needs to be able to seek; you would also need
to count bytes read so as to return the correct value from tell. A tied
handle would probably be easiest: see perltie and Tie::Handle.

That sounds more complicated than my backup plan, which is just to extract the member to a tempfile and then reading it as an archive. I had already implemented that, as a result of responses to a related question I had asked. This mostly worked, but I noticed some situations where it didn't work,so I thought I would return back to the hopefully more efficient mechanismI asked about in this chain. Now that I see this is more complicated thanI'm comfortable, I returned to try to fix the tempfile strategy, and I gotthat working.

This is the key part of it:

for my $archiveMember (@matchingArchiveList) {
#print "archiveMember[" . $archiveMember->fileName() . "]\n";
my $tempArchiveName = prepareTempFile();
#print "tempArchiveName[" . $tempArchiveName . "]\n";
$zip->extractMemberWithoutPaths($archiveMember, $tempArchiveName);
my $archiveZip = Archive::Zip->new();
my $archiveStatus = $archiveZip->read($tempArchiveName);
if ($archiveStatus == AZ_OK) {
processArchive($archiveZip, $entry, $zipName . ":" . $archiveMember->fileName());
unlink($tempArchiveName);
}
}

Again, this appears to work. I'm sure this is likely a little slower than the other approach, but it's not noticeable for me, so it's good enough.

Do you see any problems with this approach?
 
D

David Karr

Quoth David Karr said:
This is the key part of it:

for my $archiveMember (@matchingArchiveList) {
#print "archiveMember[" . $archiveMember->fileName() . "]\n";
my $tempArchiveName = prepareTempFile();
#print "tempArchiveName[" . $tempArchiveName . "]\n";
$zip->extractMemberWithoutPaths($archiveMember, $tempArchiveName);
my $archiveZip = Archive::Zip->new();
my $archiveStatus = $archiveZip->read($tempArchiveName);
if ($archiveStatus == AZ_OK) {
processArchive($archiveZip, $entry, $zipName . ":" .
$archiveMember->fileName());
unlink($tempArchiveName);

This unlink probably wants to be outside the if. If (say) the internal
zipfile is corrupted, and can't be read, you still need to clean up the
tempfile.

I was thinking about that, but I assume that it would fail if the file didn't actually get created? I figured I would need some sort of exception handling to do that cleanly (of course, if that was really important, I probably should have already been doing that). I'm not very familiar with how Perl does exception handling.
 
Ad

Advertisements

J

Jim Gibson

David Karr said:
I was thinking about that, but I assume that it would fail if the file didn't
actually get created? I figured I would need some sort of exception handling
to do that cleanly (of course, if that was really important, I probably
should have already been doing that). I'm not very familiar with how Perl does exception handling.

It generally just keeps going. unlink will return false and set $! to
an error number and message. That is typical. You can ignore the error,
as you have done.
 
Ad

Advertisements

P

Paul Marquess

Quoth David Karr said:
This is the key part of it:

for my $archiveMember (@matchingArchiveList) {
#print "archiveMember[" . $archiveMember->fileName() . "]\n";
my $tempArchiveName = prepareTempFile();
#print "tempArchiveName[" . $tempArchiveName . "]\n";
$zip->extractMemberWithoutPaths($archiveMember, $tempArchiveName);
my $archiveZip = Archive::Zip->new();
my $archiveStatus = $archiveZip->read($tempArchiveName);
if ($archiveStatus == AZ_OK) {
processArchive($archiveZip, $entry, $zipName . ":" .
$archiveMember->fileName());
unlink($tempArchiveName);

This unlink probably wants to be outside the if. If (say) the internal
zipfile is corrupted, and can't be read, you still need to clean up the
tempfile.

I was thinking about that, but I assume that it would fail if the file didn't actually get created? I figured I would need some sort of exception handling to do that cleanly (of course, if that was really important, I probably should have already been doing that). I'm not very familiar with how Perl does exception handling.

Here is an alternative implementation that uses IO::Uncompress::Unzip to
walk a nested zip files to any depth. It just outputs the name of each
member in all the zip files it finds as a proof of concept.

No temp files needed.


#!/usr/bin/perl

use warnings;
use strict;
use IO::Uncompress::Unzip;

sub walk
{
my $name = shift;
my $fh = shift;
my $length = shift;
my $indent = shift ;

my $unzip = new IO::Uncompress::Unzip $fh,
InputLength => $length
or die "Cannot open zip\n" ;

my $status;
for ($status = 1; $status > 0; $status = $unzip->nextStream())
{

my $name = $unzip->getHeaderInfo()->{Name};
my $len = $unzip->getHeaderInfo()->{CompressedLength};
$len = $len->get32bit() if ref $len eq 'U64';
warn " " x $indent . "Processing member $name $len\n" ;

if ($name =~ /.zip$/)
{
walk($name, $unzip, $len, $indent + 2);
}
else
{
# Deal with the payload if necessary
# my $buff;
# while (($status = $unzip->read($buff)) > 0) {
# # Do something here
# }
}

last if $status < 0;
}

die "Error processing $name: $!\n"
if $status < 0 ;
}

my $file = shift;
my $length = -s $file ;
my $fh ;
open $fh, "<$file" ;

walk($file, $fh, $length, 0);
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top