How to zip folders & subfolders with files in it in Kotlin using ZipOutputStream?

Viewed 4672

I have list of files arranges in different directories, some directories will have sub-directories and files in it. I am not able to successfully put same directory structure within a zip file. Here is my code:

fun zipAll(directory: String, zipFile: String) {
val sourceFile = File(directory)

 ZipOutputStream(BufferedOutputStream(FileOutputStream(zipFile))).use {
       zipFiles(it, sourceFile)
   }
}

private fun zipFiles(zipOut: ZipOutputStream, directory: File) {

val data = ByteArray(1024)

zipOut.use {

    if (directory.isDirectory) {
        //Adding directory
        it.putNextEntry(ZipEntry(directory.name))
    } else {
        zipFiles(zipOut, directory)
    }

    for (f in directory.listFiles()) {

        if (!f.name.contains(".zip") && f.exists()) {

            //Adding file

            FileInputStream(f).use { fi ->
                BufferedInputStream(fi).use { origin ->
                    val entry = ZipEntry(f.name)
                    it.putNextEntry(entry)
                    while (true) {
                        val readBytes = origin.read(data)
                        if (readBytes == -1) {
                            break
                        }
                        it.write(data, 0, readBytes)
                    }
                }
            }
        }
    }
}
}
4 Answers

It's a bit old question, but I think I have a better solution, here you are:

    val inputDirectory = File("/home/fred")
    val outputZipFile = File.createTempFile("out", ".zip")
    ZipOutputStream(BufferedOutputStream(FileOutputStream(outputZipFile))).use { zos ->
        inputDirectory.walkTopDown().forEach { file ->
            val zipFileName = file.absolutePath.removePrefix(inputDirectory.absolutePath).removePrefix("/")
            val entry = ZipEntry( "$zipFileName${(if (file.isDirectory) "/" else "" )}")
            zos.putNextEntry(entry)
            if (file.isFile) {
                file.inputStream().copyTo(zos)
            }
        }
    }

You can filter out some files in forEach loop by test 'file' variable (for example filter out zip files to prevent it from compressing)

I solved it, here is complete solution:

fun zipAll(directory: String, zipFile: String) {
    val sourceFile = File(directory)

    ZipOutputStream(BufferedOutputStream(FileOutputStream(zipFile))).use {
        it.use {
            zipFiles(it, sourceFile, "")
        }
    }
}

private fun zipFiles(zipOut: ZipOutputStream, sourceFile: File, parentDirPath: String) {

    val data = ByteArray(2048)

    for (f in sourceFile.listFiles()) {

        if (f.isDirectory) {
            val entry = ZipEntry(f.name + File.separator)
            entry.time = f.lastModified()
            entry.isDirectory
            entry.size = f.length()

            Log.i("zip", "Adding Directory: " + f.name)
            zipOut.putNextEntry(entry)

            //Call recursively to add files within this directory
            zipFiles(zipOut, f, f.name)
        } else {

            if (!f.name.contains(".zip")) { //If folder contains a file with extension ".zip", skip it
                FileInputStream(f).use { fi ->
                    BufferedInputStream(fi).use { origin ->
                        val path = parentDirPath + File.separator + f.name
                        Log.i("zip", "Adding file: $path")
                        val entry = ZipEntry(path)
                        entry.time = f.lastModified()
                        entry.isDirectory
                        entry.size = f.length()
                        zipOut.putNextEntry(entry)
                        while (true) {
                            val readBytes = origin.read(data)
                            if (readBytes == -1) {
                                break
                            }
                            zipOut.write(data, 0, readBytes)
                        }
                    }
                }
            } else {
                zipOut.closeEntry()
                zipOut.close()
            }
        }
    }
}

Usage:

zipAll("Path of source files to Zip", "Path for Zip to Export")

This is my complete solution. It don't ignore zip unlike Umair Adil version and also respect folder hierarchy like Jeyhey patch

fun zipAll(directory: String, zipFile: String) {
    val sourceFile = File(directory)

    ZipOutputStream(BufferedOutputStream(FileOutputStream(zipFile))).use {
        zipFiles(it, sourceFile, "")
    }
}

private fun zipFiles(zipOut: ZipOutputStream, sourceFile: File, parentDirPath: String) {
    val data = ByteArray(2048)
    sourceFile.listFiles()?.forEach { f ->
        if (f.isDirectory) {
            val path = if (parentDirPath == "") {
                f.name
            } else {
                parentDirPath + File.separator + f.name
            }
            val entry = ZipEntry(path + File.separator)
            entry.time = f.lastModified()
            entry.isDirectory
            entry.size = f.length()
            zipOut.putNextEntry(entry)
            //Call recursively to add files within this directory
            zipFiles(zipOut, f, path)
        } else {
            FileInputStream(f).use { fi ->
                BufferedInputStream(fi).use { origin ->
                    val path = parentDirPath + File.separator + f.name
                    val entry = ZipEntry(path)
                    entry.time = f.lastModified()
                    entry.isDirectory
                    entry.size = f.length()
                    zipOut.putNextEntry(entry)
                    while (true) {
                        val readBytes = origin.read(data)
                        if (readBytes == -1) {
                            break
                        }
                        zipOut.write(data, 0, readBytes)
                    }
                }
            }
        }
    }
}

This zips nested folders into a flat directory structure. If you want to keep the nested structure, use the following code in the isDirectory-if-case:

if (f.isDirectory) {
  val path = if (parentDirPath == "") {
    f.name
  } else {
    parentDirPath + File.separator + f.name
  }
  val entry = ZipEntry(path + File.separator)
  entry.time = f.lastModified()
  entry.isDirectory
  entry.size = f.length()
  zipOut.putNextEntry(entry)
  //Call recursively to add files within this directory
  zipFiles(zipOut, f, path)
} else {
Related