Create a ZIP file in Kotlin
Asked Answered
C

8

27

I'm trying to create a zip file in Kotlin. this is the code:

fun main(args: Array<String>) {
var files: Array<String> = arrayOf("/home/matte/theres_no_place.png", "/home/matte/vladstudio_the_moon_and_the_ocean_1920x1440_signed.jpg")
var out = ZipOutputStream(BufferedOutputStream(FileOutputStream("/home/matte/Desktop/test.zip")))
var data = ByteArray(1024)
for (file in files) {
    var fi = FileInputStream(file)
    var origin = BufferedInputStream(fi)
    var entry = ZipEntry(file.substring(file.lastIndexOf("/")))
    out.putNextEntry(entry)
    origin.buffered(1024).reader().forEachLine {
        out.write(data)
    }
    origin.close()
}
out.close()}

the zip file is created, but the files inside are corrupt!

Committee answered 14/9, 2017 at 14:44 Comment(0)
S
3

I'm not sure if you want to do it manually but I found this nice library that works perfectly:

https://github.com/zeroturnaround/zt-zip

This library is a nice wrapper of the Java Zip Utils library that include methods for zipping/unzipping both files and directories with a single function.

For zipping a single file you just need to use the packEntry method:

ZipUtil.packEntry(File("/tmp/demo.txt"), File("/tmp/demo.zip"))

For the case of zipping a directory and its sub-directories you can use the pack method:

val dirToCompress = Paths.get("/path/to/my/dir").toFile()
val targetOutput = Paths.get("/output/path/dir.zip").toFile()

ZipUtil.pack(dirToCompress, targetOutput)

The zip file should have been created in the specified target output.

You can find more details and examples in the library's documentation.

Hope this helps =)

Sized answered 8/4, 2022 at 9:47 Comment(0)
C
19

I did a mix:

fun main(args: Array<String>) {
    val files: Array<String> = arrayOf("/home/matte/theres_no_place.png", "/home/matte/vladstudio_the_moon_and_the_ocean_1920x1440_signed.jpg")
    ZipOutputStream(BufferedOutputStream(FileOutputStream("/home/matte/Desktop/test.zip"))).use { out ->
        for (file in files) {
            FileInputStream(file).use { fi ->
                BufferedInputStream(fi).use { origin ->
                    val entry = ZipEntry(file.substring(file.lastIndexOf("/")))
                    out.putNextEntry(entry)
                    origin.copyTo(out, 1024)
                }
            }
        }
    }
}

It works perfectly!

Committee answered 15/9, 2017 at 7:13 Comment(1)
I don't think you need to 'use' the BufferedInputStream. It will get closed when it's parent stream - FileInputStream gets closed.Vargo
V
16

If you use Kotlin's IOStreams.copyTo() extension, it will do the copying work for you, and that ended up working for me.

So replace this:

origin.buffered(1024).reader().forEachLine {
    out.write(data)
}

With this:

origin.copyTo(out, 1024)

I also had issues with the ZipEntry having a leading slash, but that could just be because I'm on Windows.

Note: I didn't end up needing to call closeEntry() to get this to work but it is recommended.

Vanya answered 14/9, 2017 at 15:20 Comment(0)
A
4

Here is a solution working with subfolders:

fun addFolderToZip(
    folder: String,
    destination: String,
    zipFileName: String = folder.substring(folder.lastIndexOf("/"))
) {

    val folderToZip = File(folder)
    var out: ZipOutputStream? = null
    try {
        out = ZipOutputStream(
            BufferedOutputStream(FileOutputStream("$destination/$zipFileName"))
        )
        recursivelyAddZipEntries(folderToZip, folderToZip.absolutePath, out)
    } catch (e: Exception) {
        Log.e("ZIP Err", e.message)
    } finally {
        out?.close()
    }

}


private fun recursivelyAddZipEntries(
    folder: File,
    basePath: String,
    out: ZipOutputStream
) {

    val files = folder.listFiles() ?: return
    for (file in files) {

        if (file.isDirectory) {
            recursivelyAddZipEntries(file, basePath, out)
        } else {
            val origin = BufferedInputStream(FileInputStream(file))
            origin.use {
                val entryName = file.path.substring(basePath.length)
                out.putNextEntry(ZipEntry(entryName))
                origin.copyTo(out, 1024)
            }
        }

    }

}
Arrangement answered 18/10, 2019 at 23:15 Comment(0)
S
3

I'm not sure if you want to do it manually but I found this nice library that works perfectly:

https://github.com/zeroturnaround/zt-zip

This library is a nice wrapper of the Java Zip Utils library that include methods for zipping/unzipping both files and directories with a single function.

For zipping a single file you just need to use the packEntry method:

ZipUtil.packEntry(File("/tmp/demo.txt"), File("/tmp/demo.zip"))

For the case of zipping a directory and its sub-directories you can use the pack method:

val dirToCompress = Paths.get("/path/to/my/dir").toFile()
val targetOutput = Paths.get("/output/path/dir.zip").toFile()

ZipUtil.pack(dirToCompress, targetOutput)

The zip file should have been created in the specified target output.

You can find more details and examples in the library's documentation.

Hope this helps =)

Sized answered 8/4, 2022 at 9:47 Comment(0)
C
2

1) You are writing an empty byte array to the out for each line of an input file.

2) There is no need in BufferedReader because it is enough to read and write bytes instead of lines (which would lead the unpacked content not to be matched with the original).

3) All streams should be closed in the case of exceptions. Use method use like try-with-resources in java.

4) val instead var there possible

5) Don't use absolute paths except for the quick test snippets.

6) This snippet is not in idiomatic way for Kotlin (see the Todd's answer)

So this is how it should work (though in the Java way):

fun main(args: Array<String>) {
    val files: Array<String> = arrayOf("/home/matte/theres_no_place.png", "/home/matte/vladstudio_the_moon_and_the_ocean_1920x1440_signed.jpg")
    ZipOutputStream(BufferedOutputStream(FileOutputStream("/home/matte/Desktop/test.zip"))).use { out ->
        val data = ByteArray(1024)
        for (file in files) {
            FileInputStream(file).use { fi ->
                BufferedInputStream(fi).use { origin ->
                    val entry = ZipEntry(file)
                    out.putNextEntry(entry)
                    while (true) {
                        val readBytes = origin.read(data)
                        if (readBytes == -1) {
                            break
                        }
                        out.write(data, 0, readBytes)
                    }
                }
            }
        }
    }
}

EDIT: I've ran this snippet with my files and it worked OK.

Chlorophyll answered 14/9, 2017 at 15:19 Comment(0)
P
2

The code can be cleaned up a bit to separate concerns and take advantage of use:

fun File.bufferedOutputStream(size: Int = 8192) = BufferedOutputStream(this.outputStream(), size)
fun File.zipOutputStream(size: Int = 8192) = ZipOutputStream(this.bufferedOutputStream(size))
fun File.bufferedInputStream(size: Int = 8192) = BufferedInputStream(this.inputStream(), size)
fun File.asZipEntry() = ZipEntry(this.name)

fun archive(files: List<File>, destination: File) =
    destination.zipOutputStream().use { zipOs ->
        files.forEach { file ->
            zipOs.putNextEntry(file.asZipEntry())
            file.bufferedInputStream().use { bIs -> bIs.copyTo(zipOs) }
        }
    }


fun main() {
    val files = listOf(
        File("/Users/xor/Downloads/Ghibli/kaguyahime006.jpg"),
        File("/Users/xor/Downloads/Ghibli/kaguyahime035.jpg")
    )

    val destination = File("/Users/xor/work/kotlin/scratchpad-kotlin-java/src/main/kotlin/main/archive.zip")

    archive(files, destination)
}
Proprietor answered 4/8, 2021 at 17:52 Comment(2)
Can you elaborate / explain the variable / object it which is used to perform operation it.putNextEntry(file.asZipEntry()) ? Where & How it is defined ?Lustrous
it is the zip output stream exposed by use. Similar to bIs -> bIs.copyTo(zipOs) , I amended the example for clarityProprietor
C
1

This is a simpler solution, also provided by https://mcmap.net/q/534306/-how-to-zip-folders-amp-subfolders-with-files-in-it-in-kotlin-using-zipoutputstream

fun test() {

        val fullPath: String = tempFolder.absolutePath // Folder to be zipped
        val zipFilePath = File(baseDirectory, "newTest.zip")// new zip file

        zipAll(fullPath, zipFilePath.absolutePath)

}




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

    println("directory: $directory")
    println("zipFile: $zipFile")

    val inputDirectory = sourceFile
    val outputZipFile = File(zipFile)

    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)
            }
        }
    }
}
Coat answered 4/8, 2021 at 10:26 Comment(1)
I tried this code and it is not adding anything to the zip file, i even debugged the part where if(file.isfile()) is being used and the code is antering the conditional, yet the zip is always ampty. was this working with any folder really?Holly
R
0

Combined two previously proposed options into a more universal solution that allows you to set arbitrary names in the archive for source files

private const val DEFAULT_BUFFER_SIZE: Int = 8 * 1024
fun File.bufferedOutputStream(size: Int = DEFAULT_BUFFER_SIZE) = BufferedOutputStream(this.outputStream(), size)
fun File.zipOutputStream(size: Int = DEFAULT_BUFFER_SIZE) = ZipOutputStream(this.bufferedOutputStream(size))
fun File.bufferedInputStream(size: Int = DEFAULT_BUFFER_SIZE) = BufferedInputStream(this.inputStream(), size)

fun archive(files: Map<String, File>, destination: File) {
    destination.zipOutputStream().use { zipStream ->
        files.forEach { entry ->
            val file = entry.value
            val name = if (file.isDirectory) "${entry.key}/" else entry.key
            zipStream.putNextEntry(ZipEntry(name))
            if (file.isFile) {
                file.bufferedInputStream().use { bis -> bis.copyTo(zipStream) }
            }
        }
    }
}

fun use(){
    val zip = mutableMapOf<String, File>()
    zip.put("internal/3.jpg", File("/home/qixi/3.jpg"))
    zip.put("4.jpg", File("/home/qixi/4.jpg"))
    archive(zip, File("/home/qixi/file.zip"))
}
Rome answered 13/6, 2023 at 16:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.