Runtime Revolution
 
Articles Other News

A Utility to Create and Extract Zip Archives

by Marcus Van Houdt

 

This article demonstrates how you can use RevZip to produce a small utility to create zip archives, and also to extract files from zip archives.

To download the accompanying stack, click here.

We start off by creating some of the basic UI elements that we want for our utility. There are two buttons that allow the user to open an archive and to create a new archive. Also, there is a field that is used to list the contents of a zip archive, or to display the files and folders that are to be added to a new archive. Furthermore, we create three buttons to edit the file list by allowing the user to add files and folders and to remove items from the list. These buttons will only show when the user is creating a new archive, as we do not allow the user to edit an existing archive (it is easily possible to extend our program to allow for this capability). Finally we add a progress bar and two buttons "zip" and "extract".

The progress bar is just a group containing a border graphic and two graphics that are the actual progress bars. A larger one ("Bar") for the overall global progress, and a smaller one for item progress ("ItemBar"). Progress is managed by setting two (virtual) custom properties on the progress bar group: cProgress and cItemProgress. These are managed through two setProp handlers that set the rectangles of the progress graphics. The argument to these two handlers is a number between 0 and 1 being relative progress.

The addFiles button simply adds a files full path to the FileList field, if it is not already there. The addFolders button does the same thing, but adds a trailing "/" to indicate that this entry is a folder.

We will now discuss the two main functionalities of this program in more detail.

First, we take a look at extraction.

The user can open an archive by clicking the "Open Archive" button, which prompts the user to select a zip file to open. We then list the contents of the zip archive selected through the use of the following handler:

on listZipContents pArchive
revZipOpenArchive pArchive, "read"
local tZipContents
put revZipEnumerateItems(pArchive) into tZipContents
put tZipContents into field "FileList"
revZipCloseArchive pArchive

end listZipContents

This handler simply opens the zip archive for reading, enumerates its items through the use of revZipEnumerateItems and puts the result into the FileList field.
Depending on the program that was used to create the zip file, this may include folder items. We will display those, but they will be ignored upon extraction as revZip cannot handle folders. Also revZip can only extract items that have been compressed using the "deflate" type, it will still display files that have been compressed otherwise, but we will ignore them on extraction as we cannot handle them.
When the user clicks the extract button, the following code is executed:

-- extract archive pArchive to folder pWhere
on extractArchive pArchive, pWhere
put false into sZipCancelled
local tLocation
set the itemDel to "/"
put pWhere into tLocation
if char -1 of tLocation is not "/" then
put "/" after tLocation
end if
-- We assume that our archive has a filename of <archive>.zip
put char 1 to -5 of item -1 of pArchive after tLocation

-- Make sure that we can extract to a folder that does not exist:
set the itemDel to "."
if there is a folder tLocation then
local tCount
put 1 into tCount
put "." & tCount after tLocation
repeat until there is no folder tLocation
add 1 to tCount
put tCount into item -1 of tLocation

end repeat

end if
set the itemDel to comma

-- Open the archive for reading.

revZipOpenArchive pArchive, "read"

-- As the work happens in the revZipExtractItemToFile in this case,
we register the callback here.

set the label of button "Extract" to "Cancel"
revZipSetProgressCallback "zipProgressCallback"

repeat for each line tItem in field "FileList"
if char -1 of tItem is "/" then
-- we ignore extraction of folders, as libzip doesnt handle folders
else if item 6 of revZipDescribeItem(pArchive,tItem) is
"deflate" then
-- first make sure that the folder exists
set the itemDel to "/"
ensureFolder tLocation & "/" & item 1 to -2 of tItem
set the itemDel to ","
revZipExtractItemToFile pArchive, tItem, tLocation & "/" & tItem

else
-- ignore this item, possibly displaying a message to the user,
that this file cannot be extracted
-- because its compression type is not supported by this program

end if

end repeat

revZipCloseArchive pArchive

hide group "ProgressBar"
set the label of button "Extract" to "Extract"

end extractArchive

The first few lines make sure that the location folder does not exist, if it does it adds a period and a number to the end of the folder name until no such folder exists. The item is then opened, and for each line in the FileList field, we extract the item from the zip file. As mentioned above, we can only do this if deflate compressionn was used, thus we use the revZipDescribeItem function to find out what compression type was used. Item 6 of its result gives us the compression type.
We simply ignore files we cannot handle. Likewise, we also ignore folders.
When extracting an item from a zip archive to a file, it is important that we ensure that containing folder of that file exists. This is the purpose of the ensureFolder handler in the above code: it makes sure that the containing folder and all its parents exist.

Secondly, we also wish to create a zip archive. The user can click the new archive button, to create a new archive. All this does is emptying the FileList field, and hiding buttons such as the Extract button and showing the Zip button.
The user can now add files and folders to the FileList field by clicking the appropriate buttons.
After hitting the Zip button, and typing a filename for our target zip archive, the following code is executed:

on createZipFile pZipFile
put false into sZipCancelled
set
the itemDel to "/"
revZipOpenArchive pZipFile, "write"

repeat for each line tFile in field "FileList"
if char -1 of tFile is "/" then
addFolderToArchive pZipFile, tFile
else
addFileToArchive pZipFile, item -1 of tFile, tFile
end if
end repeat

set the label of button "Zip" to "Cancel"
revZipSetProgressCallback "zipProgressCallback"

revZipCloseArchive pZipFile

hide group "ProgressBar"

set the label of button "Zip" to "Zip"
end createZipFile

This simply opens the archive for writing, and for each line in the FileList field, depending on whether the last character is a slash or not, either adds a file or a folder (and all its contents) to the archive. Finally, it closes the archive. This is where all the work happens, thus this is where we have set up the progress callback. We also set the label of the Zip button to Cancel, allowing the user to cancel the zip operation. More on how this is done below.

The above code depends on two handlers that implement the addition of files and folders to the archive respectively.
These are implemented as follows:

on addFileToArchive pArchive, pName, pFile
revZipAddItemWithFile pArchive, pName, pFile
end addFileToArchive

on addFolderToArchive pArchive, pFolder, pPrefix
if char -1 of pFolder is "/" then
delete char -1 of pFolder
end if
local tOldFolder
set the itemDel to "/"
put the defaultFolder into tOldFolder
set the defaultFolder to pFolder

repeat for each line tFile the files
addFileToArchive pArchive, pPrefix & item -1 of pFolder & "/" &
tFile, pFolder & "/" & tFile

end repeat

repeat for each line tFolder in the folders
if tFolder is among the items of "./.." then next repeat
addFolderToArchive pArchive, pFolder & "/" & tFolder,
pPrefix & item -1 of pFolder & "/"

end repeat

end addFolderToArchive

The addFileToArchive handler simply calls revZipAddItemWithfile to add the file to the ziparchive. The addFolderToArchive handler simply adds all the files of the folder and the files of all the folders within the folder recursively.

Finally, to finish off, we want to implement a progress callback, and add cancellation to this.
As you can read in the above code, we have defined the progrcesscallback to revZip to be "zipProgressCallback". This handler is implemented as follows:

on zipProgressCallback pArchive, pItem, pType, pItemProgress,
pItemtotal, pGlobalProgress, pGlobalTotal
if not sZipCancelled then
if the visible of group "ProgressBar" is false then
set the visible of group "ProgressBar" to true
end if
-- set global progress (of the entire zip file)
set the cProgress of group "ProgressBar" to
pGlobalProgress/pGlobalTotal
-- set item progress (of the item being written to the zip file)
set the cItemProgress of group "ProgressBar" to
pItemProgress/pItemTotal
-- allow the user to cancel
wait 1 milliseconds with messages

else
-- The user has cancelled, so cancel the operation and
hide the progress bar.

revZipCancel
hide group "ProgressBar"

end if

end zipProgressCallback

If the zip creation/extraction was not cancelled, we can proceed and set the progress of the progress bar. Notice the wait statement in the above code. This is vital to make cancellation work: it causes revolution to check the events queue and will thus catch an instance of the user clicking on the cancel button. Otherwise, you will notice that you cannot click on anything and the whole program will be locked until the operation has finished. Finally, you can only cancel zipcreation from within the progresscallback, which is why we do it here (its a good place to do it anyway).

All our cancel button has to do is set the flag sZipCancelled to true, which will cause the progressCallback to execute revZipCancel which cancels the whole process, and hides the progressbar.

I have chosen not to hide the itemProgress bar in case of extraction, so that it demonstrates how itemProgress is equal to globalProgress in the case of extraction. In the case of creation you will notice that both bars differ.

Thus, in no time at all, we have written a zip utility that allows us to create and extract from zip files easily. We can easily extend our program to be more user friendly by adding drag drop to the FileList for example. Also, we can use revZip to modify existing archives by allowing users to remove files from them or even to add files or folders (content) to them.

©2005 Runtime Revolution Ltd, 15-19 York Place, Edinburgh, Scotland, UK, EH1 3EB.
Questions? Email info@runrev.com for answers.