Thread overview
Is there a std.zip.ZipArchive isDir or isFile method?
Feb 03
mark
Feb 03
JN
Feb 12
cc
Feb 12
mark
Feb 12
cc
February 03
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
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
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
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
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