#2444 Zip / Unzip files and directories

SlimerDude Tue 25 Aug 2015

Fantom has a pretty cool API for inspecting zip files and streams, but sometimes you just want to zip up a directory, or unzip a file. These handy little methods let you do just that.

Code is also available as a BitBucket Snippet.

** A collection of Zip / File utilities.
class ZipUtils {
    
    ** Compresses the given file and returns the compressed .zip file.
    ** 'toCompress' may be a directory.
    ** 
    ** If 'destFile' is null, it defaults to '${toCompress.basename}.zip' 
    ** 
    ** The options map is used to customise zipping:
    **  - bufferSize: an 'Int' that defines the stream buffer size. Defaults to 16Kb.
    **  - incFolderName: set to 'true' to include the containing folder name in the zip path
    **
    static File zip(File toCompress, File? destFile := null, [Str:Obj]? options := null) {
        if (destFile != null && destFile.isDir)
            throw ArgErr("Destination can not be a directory - ${destFile}")

        bufferSize  := options?.get("bufferSize") ?: 16*1024
        dstFile     := destFile ?: toCompress.parent + `${toCompress.basename}.zip` 
        zip         := Zip.write(dstFile.out(false, bufferSize))        
        parentUri   := toCompress.isDir && options?.get("incFolderName") == true ? toCompress.parent.uri : toCompress.uri
        
        try {
            toCompress.walk |src| {
                if (src.isDir) return
                path := src.uri.relTo(parentUri)
                out  := zip.writeNext(path)
                try {
                    src.in(bufferSize).pipe(out)
                } finally
                    out.close
            }
        } finally
            zip.close

        return dstFile
    }

    ** Decompresses the given file and returns the directory it was unzipped to.
    ** 
    ** The options map is used to customise zipping:
    **  - bufferSize: an 'Int' that defines the stream buffer size. Defaults to 16Kb.
    **
    static File unzip(File toDecompress, File? destDir := null, [Str:Obj]? options := null) {
        if (toDecompress.isDir)
            throw ArgErr("Destination can not be a directory - ${toDecompress}")
        if (destDir != null && !destDir.isDir)
            throw ArgErr("Destination must be a directory - ${destDir}")
        
        bufferSize  := options?.get("bufferSize") ?: 16*1024
        dstDir      := destDir ?: toDecompress.parent
        zip         := Zip.read(toDecompress.in(bufferSize))
        try {
            File? entry
            while ((entry = zip.readNext) != null) {
                entry.copyTo(dstDir + entry.uri.relTo(`/`), ["overwrite":true])
            }
        } finally {
            zip.close
        }
        
        return dstDir
    }
    
    ** Create a temporary directory which is guaranteed to be a new, empty
    ** directory with a unique name.  The dir name will be generated using
    ** the specified prefix and suffix.  
    ** 
    ** If dir is non-null then it is used as the file's parent directory,
    ** otherwise the system's default temporary directory is used.
    ** 
    ** Examples:
    **   File.createTemp("x", "-etc") => `/tmp/x67392-etc/`
    **   File.createTemp.deleteOnExit => `/tmp/fan-5284/`
    **
    ** See the Fantom forum topic [File.createTempDir()]`http://fantom.org/forum/topic/2424`.
    **
    static File createTempDir(Str prefix := "fan-", Str suffix := "", File? dir := null) {
        tempFile := File.createTemp(prefix, suffix, dir ?: Env.cur.tempDir)
        dirName  := tempFile.name
        tempFile.delete
        tempDir  := tempFile.parent.createDir(dirName)
        return tempDir
    }
}

I've used them myself a couple of times... thought it maybe useful to share.

brian Thu 27 Aug 2015

If you are working a build script, you can use build::CreateZip task

Login or Signup to reply.