Zip Tool

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.

You can download the stack that accompanies this lesson from: https://tinyurl.com/ycyrwmqv

Creating the UI

Creating the UI

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.

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.

The extractArchive handler

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 compression 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.

The extractArchive command code

When the user clicks the extract button, the following code is executed:

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

Creating a zip archive

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.

Adding files and folders to the archive

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 in 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.

Progress callback

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 progress callback 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 LiveCode 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).

Cancelling

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 progress bar.

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.

4 Comments

Mike Felker

How would we add multiselect (shift or ctrl click) to this so we could add more than one file at a time but not a entire folder? How would we program an exact set of files to load?

Matthias Rebbe

Mike,
changing
answer file
to
answer files
in the script of button 'add file'
will allow to select multiple files.

Regards,
Matthias

Philippe Coenen

The zip tool looks awesome, I'm now trying to use it.

However, I have a critic for the code you have created here. It is really a pity that you refer to a field inside it. The same code using variables would be more modular. A higher level lib, so to speak.

A simple example that will request a path to a folder and a path to a destination would easier to understand because it will not require understanding buttons, and UI which are specific for a single use case.

Then, when the example without interface would be understood, you could make a lesson where you use the created lib to create a UI using simple code.

Panos Merakos

Hello Philippe,
Thank you for the feedback. We might revisit this lesson in the future and do as you suggest. If you get there first, feel free to post your code in a comment.
Kind regards,
Panos
--

Add your comment

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.