Thread overview
Is there a std.zip.ZipArchive isDir or isFile method?
Feb 03, 2020
mark
Feb 03, 2020
JN
Feb 12, 2020
cc
Feb 12, 2020
mark
Feb 12, 2020
cc
February 03, 2020
I'm using std.zip.ZipArchive to read zip files, e.g.:

    auto zip = new ZipArchive(read(filename));
    // ...
    foreach (name, member; zip.directory) {
	if (name.endsWith('/')) // skip dirs
	    continue;
	mkdirRecurse(dirName(name));
	zip.expand(member);
	write(name, member.expandedData());
    }

As you can see, I am detecting directories with a crude test.

I really wish there was a method for this: and if there is, could you give me the link 'cos I can't see one in the docs?

(BTW The code above is slightly simplified: the real code won't unzip if there's an absolute path or .. present and also ensures that all members are unzipped into a subdir even if the zip has top-level names.)
February 03, 2020
On Monday, 3 February 2020 at 13:26:38 UTC, mark wrote:
> I'm using std.zip.ZipArchive to read zip files, e.g.:
>
>     auto zip = new ZipArchive(read(filename));
>     // ...
>     foreach (name, member; zip.directory) {
> 	if (name.endsWith('/')) // skip dirs
> 	    continue;
> 	mkdirRecurse(dirName(name));
> 	zip.expand(member);
> 	write(name, member.expandedData());
>     }
>
> As you can see, I am detecting directories with a crude test.
>
> I really wish there was a method for this: and if there is, could you give me the link 'cos I can't see one in the docs?
>
> (BTW The code above is slightly simplified: the real code won't unzip if there's an absolute path or .. present and also ensures that all members are unzipped into a subdir even if the zip has top-level names.)

ArchiveMember has "flags" field, perhaps it stores if it's a directory?

If not, fileAttributes will have it but it looks it's OS specific.
February 12, 2020
On Monday, 3 February 2020 at 13:26:38 UTC, mark wrote:
> I'm using std.zip.ZipArchive to read zip files, e.g.:
>
>     auto zip = new ZipArchive(read(filename));
>     // ...
>     foreach (name, member; zip.directory) {
> 	if (name.endsWith('/')) // skip dirs
> 	    continue;
> 	mkdirRecurse(dirName(name));
> 	zip.expand(member);
> 	write(name, member.expandedData());
>     }
>
> As you can see, I am detecting directories with a crude test.
>
> I really wish there was a method for this: and if there is, could you give me the link 'cos I can't see one in the docs?
>
> (BTW The code above is slightly simplified: the real code won't unzip if there's an absolute path or .. present and also ensures that all members are unzipped into a subdir even if the zip has top-level names.)

I couldn't find one either, I had to do this:

version(Windows) {
	enum uint FILE_ATTRIBUTE_DIRECTORY = 0x10;
}
auto zip = new ZipArchive(buffer);
foreach (fn, am; zip.directory) {
	if (am.fileAttributes & FILE_ATTRIBUTE_DIRECTORY)
		... is directory
	else
		... is file
}

As I'm looking at my code for this I'm also reminded that different zip files can internally store path separators as either \ or / depending on the platform that created them so you may need to be careful about that too.  I have a bit for this that simply does:
version(StandardizePathSeparators) {
	string filename = fn.replace("\\", "/");
} else {
	string filename = fn;
}


February 12, 2020
On Wednesday, 12 February 2020 at 05:59:53 UTC, cc wrote:
> On Monday, 3 February 2020 at 13:26:38 UTC, mark wrote:
>> I'm using std.zip.ZipArchive to read zip files, e.g.:
[snip]
> I couldn't find one either, I had to do this:
>
> version(Windows) {
> 	enum uint FILE_ATTRIBUTE_DIRECTORY = 0x10;
> }
> auto zip = new ZipArchive(buffer);
> foreach (fn, am; zip.directory) {
> 	if (am.fileAttributes & FILE_ATTRIBUTE_DIRECTORY)
> 		... is directory
> 	else
> 		... is file
> }

I need to work on both Linux and Windows, and on Linux am.fileAttributes seems to be 0 for both files and directories.

The lack of a cross-platform way of distinguishing whether an archive member is a directory or file does seem to be a missing piece of the API.

> As I'm looking at my code for this I'm also reminded that different zip files can internally store path separators as either \ or / depending on the platform that created them so you may need to be careful about that too.  I have a bit for this that simply does:
> version(StandardizePathSeparators) {
> 	string filename = fn.replace("\\", "/");
> } else {
> 	string filename = fn;
> }

Yes, I was aware of that, but I use:

    string filename = fn.tr("\\", "/"); // tr is from std.string
February 12, 2020
It looks like 0040000 (octal) is the flag for directories on linux, but it does seem that std.zip is explicitly returning 0 if the file was created on the opposite platform re: Posix vs Windows, which is... odd.

@property @nogc nothrow uint fileAttributes() const
{
    version (Posix)
    {
        if ((_madeVersion & 0xFF00) == 0x0300)
            return _externalAttributes >> 16;
        return 0;
    }
    else version (Windows)
    {
        if ((_madeVersion & 0xFF00) == 0x0000)
            return _externalAttributes;
        return 0;
    }
    else
    {
        static assert(0, "Unimplemented platform");
    }
}

Looks like the only way around it is modifying std.zip?  Adding something like:

@property bool isDir() const {
	enum uint FILE_ATTRIBUTE_DIRECTORY = 0x10; // WINNT.h
	enum uint S_IFDIR = 0x4000; // sys/stat.h
	version(Windows) {
		if ((_madeVersion & 0xFF00) == 0x0300) // Archive made on Posix
			return cast(bool) (_externalAttributes & (S_IFDIR << 16));
		return cast(bool) (_externalAttributes & FILE_ATTRIBUTE_DIRECTORY);
	} else version(Posix) {
		if ((_madeVersion & 0xFF00) == 0x0300) // Archive made on Posix
			return cast(bool) (_externalAttributes & (S_IFDIR << 16));
		return cast(bool) ((_externalAttributes) & FILE_ATTRIBUTE_DIRECTORY);
	} else {
		static assert(0, "Unimplemented platform");
	}
}

will let me do this:

void main() {
	foreach (zipfile; ["windowstest.zip", "linuxtest.zip"]) {
		writeln(zipfile);
		auto zip = new ZipArchive(std.file.read(zipfile));
		foreach (fn, am; zip.directory) {
			writefln("%24s  %5s  %s", fn, am.isDir, am.fileAttributes);
		}
	}
}

Results on Windows:
windowstest.zip
                   a.txt  false  32
                testdir/   true  16
           testdir/b.txt  false  32
linuxtest.zip
                   a.txt  false  0
                testdir/   true  0
           testdir/b.txt  false  0

Results on Linux:
windowstest.zip
                testdir/   true  0
           testdir/b.txt  false  0
                   a.txt  false  0
linuxtest.zip
                testdir/   true  16893
           testdir/b.txt  false  33204
                   a.txt  false  33204