Learning ColdFusion 8: CFZip Part II - Zipping Files And Directories With CFZipParam
In the first part of this tutorial, we explored the ways in which the ColdFusion 8 CFZip tag, when used alone, can zip both files and entire directories into archives. Now, let's take a look at the ColdFusion 8 CFZipParam tag in the context of archive creation. The CFZipParam tag is a child of the CFZip tag and gives us even more power over what kind of data is added to the archive and how those entries are stored.
In Part I, we started off zipping a single file with CFZip; now, let's start off Part II zipping a single file with CFZipParam:
<!---
Create a zip archive that contains a single
file. We are going to overwrite any previously
existing archive of the same name.
--->
<cfzip
action="zip"
file="#ExpandPath( './data.zip' )#"
overwrite="true">
<!---
Add a single file. The file will be stored in
the archive as the root directory with the same
file name it already has (readme.txt).
--->
<cfzipparam
source="#ExpandPath( './data/documents/readme.txt' )#"
/>
</cfzip>
Here, the CFZipParam tag is the only child tag of the CFZip tag. Notice that, instead of providing the source attribute of the CFZip tag, as we would have done previously, we are providing the source attribute through the CFZipParam tag. By default, ColdFusion will store this file in the root directory of the archive with the same name as the file as it exists. This will result in a zip directory structure that looks like this:
./readme.txt
Both the CFZip and the CFZipParam tags have a source attribute. And, when it comes to using both tags in conjunction, it's not one or the other - the Source attributes of the two tags also work in conjunction. If the CFZip tag defines a source directory, then the Source attributes of the nested CFZipParam tags are relative to that parent directory. Running the following code:
<!---
Create a zip archive that contains a single
file. We are going to overwrite any previously
existing archive of the same name.
--->
<cfzip
action="zip"
file="#ExpandPath( './data.zip' )#"
source="#ExpandPath( './data/documents/' )#"
overwrite="true">
<!---
Add a single file. The file will be stored in
the archive as the root directory with the same
file name it already has (readme.txt). Since the
source attribute was defined in the CFZip tag,
the source attribute of the CFZipParam tag is
relative the previous source value.
--->
<cfzipparam
source="readme.txt"
/>
</cfzip>
... will result in a zip directory structure that looks like this:
./readme.txt
In the above example, ColdFusion took the Source attribute of the CFZip tag, "./data/documents/", and the Source attribute of the CFZip tag, "readme.txt", and created a combined path of "./data/documents/readme.txt." Now, in Part I of our tutorial, we demonstrated that if you provide a directory as the Source attribute, ColdFusion will archive the entire directory (by default). Notice, that in the above example, ColdFusion did not archive the directory, but rather just the CFZipParam file.
If you choose to use the Source attribute of the CFZip tag, all of your CFZipParam tags must use relative source attributes. You cannot mix and match. If you try to use a CFZip source and an absolute path for the CFZipParam tag, ColdFusion will throw an error. Either you use both, or you use just the CFZipParam source attribute.
We don't have to let the file be archived with the same name. We can rename it using the EntryPath attribute of the CFZipParam tag. Running this code:
<!---
Create a zip archive that contains a single
file. We are going to overwrite any previously
existing archive of the same name.
--->
<cfzip
action="zip"
file="#ExpandPath( './data.zip' )#"
overwrite="true">
<!---
Add a single file. The file will be stored in
the archive as the root directory but, instead of
using the existing file name, the file will be
stored as read_me_first.txt.
--->
<cfzipparam
source="#ExpandPath( './data/documents/readme.txt' )#"
entrypath="read_me_first.txt"
/>
</cfzip>
... will result in a zip directory structure that looks like this:
./read_me_first.txt
In the above, all we defined was the new file name; however, like the prefix attribute of the CFZip tag, the EntryPath attribute can also define a directory structure. By adding slashes to the attribute value, ColdFusion will create directories in which to save the file. Running this code:
<!---
Create a zip archive that contains a single
file. We are going to overwrite any previously
existing archive of the same name.
--->
<cfzip
action="zip"
file="#ExpandPath( './data.zip' )#"
overwrite="true">
<!---
Add a single file. Instead of storing it in the
root directory, we are going to store it in a
new, nested directory.
--->
<cfzipparam
source="#ExpandPath( './data/documents/readme.txt' )#"
entrypath="manuals/_LOOK_HERE_FIRST_/readme.txt"
/>
</cfzip>
... will result in a zip directory structure that looks like this:
./manuals/_LOOK_HERE_FIRST_/readme.txt
Notice that we are not only setting the file name, we are also defining which directory that file is stored in.
I mentioned above that the ColdFusion 8 CFZip tag has a prefix attribute; well, the CFZipParam tag also has a Prefix attribute. And, just like the CFZip tag, the Prefix attribute of this CFZipParam tag defines the directory in which the file is stored. We can reproduce the above structure using the Prefix attribute. Running the code:
<!---
Create a zip archive that contains a single
file. We are going to overwrite any previously
existing archive of the same name.
--->
<cfzip
action="zip"
file="#ExpandPath( './data.zip' )#"
overwrite="true">
<!---
Add a single file. Instead of storing it in the
root directory, we are going to store it in a
new, nested directory at the given prefix.
--->
<cfzipparam
source="#ExpandPath( './data/documents/readme.txt' )#"
prefix="manuals/_LOOK_HERE_FIRST_"
/>
</cfzip>
... will result in a zip directory structure that looks like this:
./manuals/_LOOK_HERE_FIRST_/readme.txt
Notice that since we did not include any EntryPath information, the file is stored using its original file name. You might be tempted to try and use the EntryPath to define the file name in conjunction with the Prefix to define the storage directory, as in this example:
<!---
Create a zip archive that contains a single
file. We are going to overwrite any previously
existing archive of the same name.
--->
<cfzip
action="zip"
file="#ExpandPath( './data.zip' )#"
overwrite="true">
<!---
Add a single file. Instead of storing it in the
root directory, we are going to store it in a
new, nested directory at the given prefix stored
at the given entry path file name.
--->
<cfzipparam
source="#ExpandPath( './data/documents/readme.txt' )#"
prefix="manuals/_LOOK_HERE_FIRST_"
entrypath="read_me_text.txt"
/>
</cfzip>
ColdFusion will not throw any sort of error here, but it will not work the way you expect it to. EntryPath takes precedence over the Prefix attribute. In fact, when the EntryPath is present, the Prefix value is completely ignored. Therefore, running the code above will result in a zip directory structure that looks like this:
./read_me_text.txt
Notice that since the EntryPath did not include any sub directory information, the file entry is stored in the root directory of the zip archive (even though the Prefix defined some directory nesting). The EntryPath could have, just as we demonstrated earlier, included the sub directory definition.
One of the coolest things about the CFZipParam tag is that it allows us to archive more than just files off of the server; we can also archive text and binary data using the Content attribute. In this next example, we are going to store a string directly into a text entry of the archive:
<!---
Create a zip archive that contains a single
entry. This entry will be created from text
data, not from a file on the server. We are
going to overwrite any previously existing
archive of the same name.
--->
<cfzip
action="zip"
file="#ExpandPath( './data.zip' )#"
overwrite="true">
<!---
Add a single entry using the given text
data into the given entry path.
--->
<cfzipparam
entrypath="text_entry.txt"
content="This is my text entry!"
/>
</cfzip>
Running that code will result in a zip directory structure that looks like this:
./text_entry.txt
... and that text file contains the text data from the Content attribute above. When storing text data, ColdFusion uses the CharSet attribute the string into binary data. This attribute defaults to whatever the default encoding of the machine is. I don't full understand this, but not including the attribute seems to work nicely.
Binary data can be stored in exactly the same way. Running this code:
<!--- Read in the image file as binary data. --->
<cffile
action="readbinary"
file="#ExpandPath( './data/images/mud_monster.jpg' )#"
variable="binImage"
/>
<!---
Create a zip archive that contains a single
entry. This entry will be created from binary
image data, not from a file on the server. We
are going to overwrite any previously existing
archive of the same name.
--->
<cfzip
action="zip"
file="#ExpandPath( './data.zip' )#"
overwrite="true">
<!---
Add a single entry using the given image binary
data into the given entry path.
--->
<cfzipparam
entrypath="mud_monster.jpg"
content="#binImage#"
/>
</cfzip>
... will result in a zip directory structure that looks like this:
./mud_monster.jpg
When you use the Content attribute, there is no original file on which to base the entry's file name. Therefore, if you do store string or binary data, you must use the EntryPath attribute so that ColdFusion knows how to store the target file.
Ok, so now let's talk about directories. CFZipParam can archive full or partial directories as well as single files. In the following examples, we are going to assume we have the following data directory:
./data/documents/manual.txt
./data/documents/readme.txt
./data/images/funny.jpg
./data/images/mud_monster.jpg
./data/images/red_face.jpg
./data/images/smile.jpg
Now, we can archive the directory using CFZipParam:
<!---
Create a zip archive that contains a multiple
entries. We are going to overwrite any previously
existing archive of the same name.
--->
<cfzip
action="zip"
file="#ExpandPath( './data.zip' )#"
overwrite="true">
<!---
Zip the entire images directory at the given
source path.
--->
<cfzipparam
source="#ExpandPath( './data/images/' )#"
/>
</cfzip>
Running the above will result in a zip directory structure that looks like this:
./funny.jpg
./mud_monster.jpg
./red_face.jpg
./smile.jpg
In this case, we were using an expanded path to point the directory. However, just as with the single file example way above, the Source attribute of the CFZip and CFZipParams work together when zipping directories. In the next example, we will define the CFZipParam's Source directory as being relative to the CFZip's Source attribute:
<!---
Create a zip archive that contains a multiple
entries. We are going to overwrite any previously
existing archive of the same name.
--->
<cfzip
action="zip"
file="#ExpandPath( './data.zip' )#"
source="#ExpandPath( './data/images/' )#"
overwrite="true">
<!---
Zip the entire directory. Since the CFZip tag defined
a source attribute, that means the source attribute
of the CFZipParam tag will relative to it. Since we
want to zip the entire directory, just leave the
source attribute empty.
--->
<cfzipparam
source=""
/>
</cfzip>
Notice that since the CFZip tag defines the actual directory we are trying to archive, the CFZipParam tag's Source attribute doesn't need a value at all. Running the above will result in a zip directory structure that looks like this:
./funny.jpg
./mud_monster.jpg
./red_face.jpg
./smile.jpg
Because the Source attribute of the CFZipParam tag is relative, we could have also used the value:
"./"
This would put us into the same directory. If you are familiar with this type of path notation, then you would also know that:
"../"
... moves you up a directory. The CFZipParam tag recognizes this as a valid relative path directive. In fact, if we re-ran the above code example using "../" instead of "./", we would end up with an archive that contained both the documents and images directories. The "../" would have taken us out of the images directory and into the root Data directory. Then, since CFZipParam recurses through directories by default, it would have zipped both the nested images and documents sub directories.
Just like the CFZip tag, when dealing with directories, the CFZipParam tag has the Filter, Recurse, and Prefix attributes. These act in exactly the same way as they did in the CFZip tag. I am not going to into detail here since the filter and recurse features were covered extensively in Part I of this tutorial and the Prefix attribute was covered in both Part I as well as above.
ColdFusion 8's new CFZipParam works with both files and directories, but this doesn't mean you have to use one or the other. The CFZip tag can have multiple CFZipParam child tags, each of which may deal with a file or a directory. In our next example, we will zip up the entire Data directory, but using multiple CFZipParam tags:
<!---
Create a zip archive that contains the entire Data
directory. We are going to use the CFZip's source
attribute to put us in the correct base directory
and then each child CFZipParam tag will use a source
relative to that. We are going to overwrite any
previously existing archive of the same name.
--->
<cfzip
action="zip"
file="#ExpandPath( './data.zip' )#"
source="#ExpandPath( './data/' )#"
overwrite="true">
<!---
Zip the entire documents directory. This
will create a documents directory in the
root of the archive.
--->
<cfzipparam
source="./documents/"
/>
<!--- Zip one of the images into the archive root. --->
<cfzipparam
source="./images/funny.jpg"
/>
<!--- Zip one of the images into the archive root. --->
<cfzipparam
source="./images/mud_monster.jpg"
/>
<!--- Zip one of the images into the archive root. --->
<cfzipparam
source="./images/red_face.jpg"
/>
<!--- Zip one of the images into the archive root. --->
<cfzipparam
source="./images/smile.jpg"
/>
</cfzip>
Running the above code will result in a zip directory structure that looks like this:
./documents/manual.txt
./documents/readme.txt
./funny.jpg
./mud_monster.jpg
./red_face.jpg
./smile.jpg
Notice that the documents directory gets created as a root-level directory - it's relative source path was maintained within the archive - but the images were zipped directly into the root of the archive - their relative source paths were not maintained. This is slightly different behavior than the zipping of a directory using just the CFZip tag. If all we used was the CFZip tag with a source directory, the files of the source directory would be zipped directly into the archive root.
Since the CFZipParam tag doesn't have a StorePath attribute (like the CFZip tag), if we wanted to put all of the files into the archive root, we have two options (not including the one that doesn't use the CFZipParam tag at all): we could change the source attribute of the CFZip tag to start in the documents folder:
<!---
Create a zip archive that contains the entire Data
directory. We are going to use the CFZip's source
attribute to put us in the documents base directory
and then each child CFZipParam tag will use a source
relative to that. We are going to overwrite any
previously existing archive of the same name.
--->
<cfzip
action="zip"
file="#ExpandPath( './data.zip' )#"
source="#ExpandPath( './data/documents/' )#"
overwrite="true">
<!---
Zip the entire documents directory.
--->
<cfzipparam
source="./"
/>
<!--- Zip one of the images into the archive root. --->
<cfzipparam
source="../images/funny.jpg"
/>
<!--- Zip one of the images into the archive root. --->
<cfzipparam
source="../images/mud_monster.jpg"
/>
<!--- Zip one of the images into the archive root. --->
<cfzipparam
source="../images/red_face.jpg"
/>
<!--- Zip one of the images into the archive root. --->
<cfzipparam
source="../images/smile.jpg"
/>
</cfzip>
Notice that this time, the Source attribute of the CFZip tag puts us into the Documents sub directory. The CFZipParam tag that zips the documents directory now just has "./" to denote the current directory. The CFZipParam tags that zip the images now just have to go up a directory to be able to access the images folder. This will put all files into the root archive since the file-only CFZipParam tags do that by default and the directory CFZipParam tag doesn't really have a path, so its contents get put in the archive root.
Our other option is to break the zip archive creation into two CFZip tags:
<!---
Create a zip archive that contains the entire Data
directory. We are going to use the CFZip's source
attribute to put us in the images base directory
and then each child CFZipParam tag will use a source
relative to that. We are going to overwrite any
previously existing archive of the same name.
--->
<cfzip
action="zip"
file="#ExpandPath( './data.zip' )#"
source="#ExpandPath( './data/images/' )#"
overwrite="true">
<!--- Zip one of the images into the archive root. --->
<cfzipparam
source="./funny.jpg"
/>
<!--- Zip one of the images into the archive root. --->
<cfzipparam
source="./mud_monster.jpg"
/>
<!--- Zip one of the images into the archive root. --->
<cfzipparam
source="./red_face.jpg"
/>
<!--- Zip one of the images into the archive root. --->
<cfzipparam
source="./smile.jpg"
/>
</cfzip>
<!---
Now that we have a zip archive that has the images,
let's add the contents of the documents folder to the
exisitng zip archive root. This time, we have no need
to overwrite the existing archive.
--->
<cfzip
action="zip"
file="#ExpandPath( './data.zip' )#"
source="#ExpandPath( './data/documents/' )#"
/>
Notice that this time, the first CFZip tag's Source attribute puts us into the Images directory. Each of the child CFZipParam tags then provides a directory-relative path to the image. The second CFZip tag then zips the documents directory into the existing archive created by the first CFZip tag. Pretty cool stuff. Running either code examples will result in a zip directory structure that looks like this:
./funny.jpg
./manual.txt
./mud_monster.jpg
./readme.txt
./red_face.jpg
./smile.jpg
The ColdFusion 8 CFZip tag gives us a lot of functionality. The CFZipParam tag gives us even more functionality with the ability to store text entries and binary entries and run multiple actions on a single archive. Used together, you can really see how easy ColdFusion 8 is making it for us developers to create zip archives.
Want to use code from this post? Check out the license.
Reader Comments
Hello Ben, good post!!!!
Now, is it possible to manually select the files I want to zip? Let's say I list all the files in a directory. So I need to zip, from that directory file1.eps, file2.eps and file.ai
Can I do that by using cfselect multiple and then passing the values to CFZipParam with the selection from my form?
Thank you much!
@Dani,
Yeah, that's what the CFZipParam tag is for - selecting specific files to zip.
how do i know it is zipped?
like what if i zip more 10mg then it takes time...
so is there any way to find out it's zipping or zipped?
@Sangyong,
CFZip doesn't execute asynchronously; when you call the CFZip tag, your thread of execution will halt at that command until the files are zipped. You don't have to add any additional logic to detect when something is done zipping.
@Ben,
Thank you for reply.
the thing is people download zip file during it's zipping.