About TT's Zip Archive Package for Xojo and Real Studio (REALbasic)

Version: 3.4.2 (27 Jul 2017)

This is a collection of REALbasic Classes to extract and create ZIP archives (known as PKZIP and Info-ZIP formats), for Mac OS X, Windows and Linux.

Written and improved since March 2003 by Thomas Tempelmann

For updates see http://www.tempel.org/RB/ZipPackage
For feedback and questions, write to: tt@tempel.org

License, costs

Most of this code is free and open sourced. This includes all code included before version 3.0.

However, recent additions to the code, in particular the ZipFolderItem class, require a license purchase. You can get a license code from me for US $35. Note that you can still use the restricted classes without a license code within Real Studio's debugger. This means that you can fully test the features of this package. You require the code only once you want to use the new features in a standalone (built) application.

To purchase a license, go to http://tip.tempel.org/ and enter to pay $35. (Note: If you have already sent me money for these classes in the past, e-mail me and I'll send you the code for free.)

Requirements

Xojo or Real Studio (2011r1 or later). The Einhugur e-CryptIt plugin is optional, and must be acquired separately. Alternatively, and only for Microsoft Windows, the "zlib" DLL needs to be acquired, e.g. from http://www.zlib.net/ and placed along the built app.

Features

Restrictions

It implements only a subset of the entire ZIP archive definition, though:

The only thing to be sure about is that files created by this class can be read by any modern ZIP tool, such as ZipIt (for Mac OS), OS X's Archives, WinZIP (for Windows), Stuffit Expander (Mac and Windows) and, of course, by itself.

Programming information

To actually be able to create and read compressed items in and archive you will also need a plugin providing the so-called "ZLib" compression functions. You have two choices:

Quick Demo

If you are using Windows, first download the "zlib compiled DLL" from http://www.zlib.net/ and place the contained file zlib1.dll into C:\Windows\system\. If you are using Mac OS X or Linux, you do not need to do this because there is already a zlib library installed on those systems.

Open the project called "TT's ZipArchiver.rbp" and run it. The program opens a window into which you can drop files and folders to compress or drop zip files to decompress instantly. It also allows you to choose options such as the encoding for the file names.

Overview of the classes

The only classes you usually need to look at are:

Or, if you only want to uncompress .zip files easily, look at:

The files inside the ZipSupport folder are not of much interest to you, while the files in the StreamSupport folder may be of general use for you in other projects, as they provide generalized read and write functions for both data and resource forks.

There is a folder called RemoveWhenUsingEinhugurPlugin. As its name suggests, you need to remove it (and the clasess therein) from your project if you have the Einhugur eCrypt-It plugin installed (you then also need to change the value of the constant "HaveEinhugurPlugin" to true).

Creating an archive and adding files to it

First, create a new ZipArchive instance, which is used to reference a zip file. Then you Open the archive by specifying a FolderItem along with a Boolean that is true to signal that you want to write to the archive. If the FolderItem exists, it will be opened as an archive (provided it's a valid archive file), and if no file exists yet, a new archive file will be created. (To always start with a new archive, call the FolderItem's Delete method before calling ZipArchive's Open function.)

  dim zar as new ZipArchive
  if not zar.Open (aZipFile, true) then
    MsgBox "Error: " + zar.ErrorMessage
    return
  end

To access the contents, get its root folder:

  dim root as ZipFolderItem
  root = zar.Tree

Now you can either add single files or entire folders to the archive:

  dim addedItem as ZipFolderItem
  addedItem = root.StoreFolderItem (aFolderItem)
  if addedItem = nil then
    MsgBox "Error: " + root.LastErrorMessage
    return
  end

There are other Add... functions in the ZipFolderItem class - just take a look at it. They all replace any existing files or folders. If that happens, it'll leave gaps inside the archive, though. Therefore, unless you have checked first that there are no items in archive that get replaced (you could call ZipFolderItem's HasChild function for this) it's prudent to always call zar.Compact at the end to remove any gaps left in the archive.

Finally, close the archive:

  if not zar.Close then
    MsgBox "Error: " + zar.ErrorMessage
    return
  end

Extracting items from an archive

You can choose from three ways, with different levels of complexity.

1. Extracting entire zip archive to a folder

This is very easy by using the ZipExtractor class:

  dim extractor as new ZipExtractor
  if not extractor.ExtractAllSilently (aZipFile, aDestinationFolderItem) then
    MsgBox "Error: " + extractor.LastErrMsg
    return
  end

2. Choosing items from the zip's directory tree

Here, you access the items inside the zip via a hierarchical tree with folders and files.

Create a new ZipArchive instance, then call Open with passing a FolderItem identifying the archive you want to extract from, and false for reading from the archive (instead of true for writing to it):

  dim zar as new ZipArchive
  if not zar.Open (aZipFile, false) then
    MsgBox "Error: " + zar.ErrorMessage
    return
  end

Get the root dir of the archive's contents (similar to calling Volume() to get a disk's root dir):

  dim root as ZipFolderItem
  root = zar.Tree

You could now extract all items in the root to a new folder on the desktop:

  dim destFolder = SpecialFolder.Desktop.Child("Extracted Archive")
  if not root.ExtractInto (destFolder) then
    MsgBox "Error: " + root.LastErrorMessage
  end

Or, you could loop over all root items and extract all whose name end in ".jpg":

  for each item as ZipFolderItem in root.Children
    if item.Name.Right(4) = ".jpg" then
      if not item.ExtractInto (destFolder) then
        MsgBox "Error: " + item.LastErrorMessage
        exit
      end
    end
  next

3. Using the low level functions to have access even to duplicates

At this lowest of all access levels, you get to look at the zip archive entries the way they are actually stored inside the zip file: As a sequental list, each item having a path in the form "TopFolderName/DeeperFolderName/FileName".

Create a new ZipArchive instance, then call Open with passing a FolderItem identifying the archive you want to extract from, and false for reading from the archive (instead of true for writing to it):

  dim zar as new ZipArchive
  if not zar.Open (aZipFile, false) then
    MsgBox "Error: " + zar.ErrorMessage
    return
  end

You can now loop over all entries in the archive, get their "ZipEntry" instances, and then extract their files to a folder hierarchy of which you can provide the starting folder:

 dim f as FolderItem, e as ZipEntry, i as Integer
 
 for i = 1 to zar.EntryCount
   e = zar.Entry(i)
   f = e.MakeDestination(destFolder,false)
   if f.exists then
     // here you could ask the user whether to overwrite the
     // existing file (but it could be even a folder!) or
     // to skip the item or abort the entire process.
     f.Delete // this will not work if it's a non-empty folder, though!
   end
   if not e.Extract(f) then
     MsgBox "Extraction of """+e.RawPath+""" failed: "+e.ErrorMessage
     return
   end
 next

There is a little caveat here, though: Some operating systems (e.g. OS X) update a folder's modification time to the current time when a new file is added to the folder. While a zip archive usually contains entries for folders and their original timestamps, those usually come first, i.e. before their contents. Hence, even if the above loop extracts a folder entry first, settings its original timestamp, successive files extracted into the folder will modify its timestamp again. There is a work-around for this, fortunately: First, extract only the files, and then perform another extraction loop in which only the folders are extracting, thus setting their timestamps after all modifications to the folder are completed. See ZipExtractor.ExtractAllSilently() for an example.

Finally, close the archive again (no need to check for errors because we did only read from the archive):

 call zar.Close()

Hint: To delete a non-empty folder, use the included FolderItemSupport.DeleteFolder() function.

More examples

Look into the project's Demos module. It contains a few methods showing particular use cases:

More detailed programming information

There are many more options to store and retrieve items in a Zip archive:

To learn about all these, look up the functions in the classes (mainly ZipArchive, ZipFolderItem and ZipEntry) and see the comments at the top of their code. That's their documentation.

Caution: Archives may contain duplicate items!

Since Zip archives may contain multiple files with the same file name (and path), the methods in ZipArchive to add an item to the archive do not check if such an item already exists in the archive. If you want to avoid adding duplicates to your archive using the ZipArchive.Store... methods, you have to check if they already exist, remove them first, and also compact the archive thereafter, ideally (and since compacting takes a while, you should first remove all items, then compact, then add the new ones). Also note that removing a folder from the archive using ZipArchive's RemoveEntry method will not remove all the items that were added inside that folder. That's because a Zip file organizes the files as a simple sequence, each entry noting the path and file where it came belongs.

If you do not want to deal with this, I suggest you use the Add functions of the ZipFolderItem class instead, which take care of avoiding duplicates in the archive by always replacing existing items when you add new ones.

MacBinary information

The MacBinary option allows you to preserve Macintosh-specific file information, mainly that's the resource forks (it does also preserve some minor attributes such as the File Creation Date, because standard Zip format only preserves the Modification Date).

A Mac file's Type and Creator codes will always be preserved, even if no MacBinary is used. You may want to pass MacBinarySmart or MacBinaryAlways instead of MacBinaryNever in order to better preserve Macintosh specific information (when running on Windows, they make no difference).

The "Smart" version will encode files only as MacBinary if the file contains a Resource Fork.

Note that many non-Macintosh Zip tools can not handle MacBinary encoding, which means that they would decode such files as a MacBinary file, hiding the data fork in them along with other information (some tools can decode this properly, though, like Stuffit Expander for Windows - they extract the data fork only, ignoring the resource fork).

Building for Microsoft Windows

This code requires the zlib library. While this lib is already installed on OS X and Linux system, it's usually not present on Windows. You can install the "zlib1.dll" into the Windows\system folder during testing, but if you want to deliver your built program to other people, you should to copy the "zlib1.dll" next to the .exe of your built app, along with all the other .dll files that Xojo places there (older Xojo versions placed them into a Libs folder while never ones place them directly next to the .exe file).

If you're building a 32 bit application, get the compiled DLL from http://www.zlib.net/. For 64 bit apps, as long as the zlib site doesn't provide one, you may get a pre-built one from my own server: http://files.tempel.org/zlib/Windows_64bit (currently that's zlib1_v1.2.8.zip).

Bugs and Modifications to the code

If you find bugs or make enhancements to this software, please contact me about them (send me your changed version), and I'll see to incorporate them into my next release to have others benefit from it, too.

Known limitations and problems

Compatibility & interchangeability considerations

There are many different Zip archiver tools around, and many have interchange problems when it comes to these particularities:

Further reading

The "zip archive" standard is quite a loose one. There appear to be lots of zip programs around that do not follow the PKWARE-specs very well, and while I have tried to program this software as close to the specs as I could, you may run into compatibility problems when trying to open archives from or create archives for other zip implementations.

A universal, open-sourced, reference implementation that attempts to deal with all eventualities is the so-called info-zip code base, in C language, which is also used by Apple with OS X (even though Apple appears to use quite an outdated version of it). Here is the info-zip home page:

Info-Zip home

The original PKWARE specification for Zip archives is included with this package in the "Technical docs" folder, along with the MacBinary specs.

List of changes (Version History)

v1.0, 8 Apr 2003

v1.1, 25 Apr 2003

v1.1.1, 18 June 2003

v1.1.2, 17 July 2003

v1.1.3, 29 July 2004

v.1.2, 2004-2006

v1.3.1, 22 March 2007

2 August 2007

May 2008

v2.0.0, 18 October 2009

v2.0.1, 9 February 2010

v2.0.2, 25 February 2010

v2.0.3, 26 February 2010

v2.0.4, 26 February 2010

v2.1, 15 October 2010

v3.0, 8 December 2010

v3.0, 15 June 2011

v3.1, never officially released, I only sent interim versions to individual users

v3.2, 20 August 2011

v3.2.1, 4 September 2011

v3.2.2, 6 September 2011

v3.2.3, 16 May 2012

v3.2.4, 31 May 2012

v3.2.5, 4 June 2012

v3.3.0, 9 June 2013

v3.3.1, 26 June 2013

v3.3.2, 16 August 2013

v3.3.3, 16 August 2013

v3.3.4, 24 June 2014

v3.3.5, 17 March 2015

v3.3.6, 31 July 2015

v3.3.7, 8 September 2015

v3.4, 13 January 2016

v3.4.1, 7 April 2016

v3.4.2, 27 July 2017

Copyrights and Acknowledgements

e-CryptIt Engine copyright Björn Eiríksson (www.einhugur.com)

e-CryptIt Engine uses zlib code, copyright © 1995-2002 Jean-Loup Gailly and Mark Adler.

Original zip format REALbasic code was written by Carsten Friehe for the Mieze program (http://carsten-friehe.de/).

RB code improved and reorganized by Thomas Tempelmann (http://www.tempel.org/rb/) for public release.

Some of the design and error messages was influenced by Java 1.1's ZipFile and related classes.

My thanks go to Leonard Rosenthol (author of Stuffit Zip support and maintainer of MacBinary format) and Tom Brown (author of ZipIt) for providing helpful information.

Tom Buchler helped me a lot getting the 64 bit Windows version working.

Enjoy!

8 April 2003
Thomas Tempelmann