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
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.
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.
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).
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.
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?
in the script of button 'add file'
will allow to select multiple files.
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.
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.