Logaholic.de

Avatar

queer as code!

Uncompressing Zip-files with subfolders in AIR applications via JavaScript

I am currently building an AIR application with JavaScript (jQuery + jQuery UI), one function downloads zipped files and has to extract the contents somewhere to the local filesystem. Since the files may contain subfolders, i didn’t find any working example supporting subfolders in the docs or via google.

I found an Adobe Tutorial for basic Zip files (see Sources), and after reading the zip specs there was just one small change to their code to get it working with subfolders.

The spec says that entries in the zip file which are directories just end with an ‘/’. Skipping them is the whole magic. (see line 48)

Thankfully, the air.FileStream autocreates director ies, so writing the files to the subfolders does not need any change to the code. I added a basepath as parameter to the outFile function, to complete the for me intuitive function unzipFile(sourceFile, targetPath).

Here is the code:

 function unzipFile(sourceFile, targetPath)
{
	var bytes = new air.ByteArray();
	var fileName = new String();
	var flNameLength;
	var xfldLength;
	var offset;
	var compSize;
	var uncompSize;
	var compMethod;
	var signature; 

	var zfile = air.File.applicationStorageDirectory.resolvePath(sourceFile);
	var zStream = new air.FileStream();
	zStream.open(zfile, air.FileMode.READ);
	bytes.endian = air.Endian.LITTLE_ENDIAN;

	while (zStream.position < zfile.size)
    {
		// read fixed metadata portion of local file header
        zStream.readBytes(bytes, 0, 30);

		bytes.position = 0;
        signature = bytes.readInt();
        // if no longer reading data files, quit
        if (signature != 0x04034b50)
        {
            break;
        }

		bytes.position = 8;
        compMethod = bytes.readByte();  // store compression method (8 == Deflate)

		offset = 0;    // stores length of variable portion of metadata
        bytes.position = 26;  // offset to file name length
        flNameLength = bytes.readShort();    // store file name
        offset += flNameLength;     // add length of file name
        bytes.position = 28;    // offset to extra field length
        xfldLength = bytes.readShort();
        offset += xfldLength;    // add length of extra field

		// read variable length bytes between fixed-length header and compressed file data
        zStream.readBytes(bytes, 30, offset);

		bytes.position = 30;
        fileName = bytes.readUTFBytes(flNameLength); // read file name 

		if (fileName.substr(fileName.length - 1, 1) != '/')
		{
			bytes.position = 18;
			compSize = bytes.readUnsignedInt(); // store size of compressed portion
			bytes.position = 22; // offset to uncompressed size
			uncompSize = bytes.readUnsignedInt(); // store uncompressed size 

			// read compressed file to offset 0 of bytes; for uncompressed files
			// the compressed and uncompressed size is the same
			zStream.readBytes(bytes, 0, compSize);

			if (compMethod == 8 ) // if file is compressed, uncompress
			{
				bytes.uncompress(air.CompressionAlgorithm.DEFLATE);
			}
			outFile(targetPath, fileName, bytes); // call outFile() to write out the file
		}
	}
}

This is the outFile function which writes the uncompressed files to the local file system.

 function outFile(baseDir, fileName, data)
{
    var outFile = air.File.applicationStorageDirectory;
    outFile = outFile.resolvePath(baseDir+air.File.separator+fileName);
    var outStream = new air.FileStream();
    outStream.open(outFile, air.FileMode.WRITE);
    outStream.writeBytes(data, 0, data.length);
    outStream.close();
}

Sources:

,