Read ZipInputStream in Kotlin
Asked Answered
E

3

8

I am trying to read a zipped file using Kotlin and ZipInputStream into a ByteArrayOutputStream()

val f = File("/path/to/zip/myFile.zip")
val zis = ZipInputStream(FileInputStream(f))

//loop through all entries in the zipped file
var entry = zis.nextEntry
while(entry != null) {
    val baos = ByteArrayOutputStream()

    //read the entry into a ByteArrayOutputStream
    zis.use{ it.copyTo(baos) }

    val bytes = baos.toByteArray()

    System.out.println(bytes[0])

    zis.closeEntry()  //error thrown here on first iteration
    entry = zis.nextEntry
}

The error I get is:

java.io.IOException: Stream closed
    at java.util.zip.ZipInputStream.ensureOpen(ZipInputStream.java:67)
    at java.util.zip.ZipInputStream.closeEntry(ZipInputStream.java:139)
    <the code above>

I thought maybe zis.use is closing the entry already after the contents of the entry are read, so I removed zis.closeEntry(), but then it produced the same error when trying to get the next entry

I know zis.use is safe and guarantees the input stream is closed, but I would expect it to close just the entry if anything rather than the entire stream.

Having printed the entire byte array, I know only the first file in the zip is being read during zis.use

Is there a good way to read all entries in a ZipInputStream in kotlin?

Exclamation answered 12/9, 2018 at 14:24 Comment(0)
C
4

The use function calls the close() method, which closes the entire stream, rather than closeEntry(), which closes just the current entry. I think you should wrap your entire while loop with zis.use { ... }, rather than calling it for each entry.

Corrigan answered 12/9, 2018 at 14:33 Comment(4)
thanks. I see where I went wrong. I had only seen examples of streams being read by wrapping it in the use. I didn't realize the copyTo method can actually be called on any input stream even outside of useExclamation
@Exclamation Can you please show what's the correct code, then?Webfooted
@androiddeveloper, instead of doing zis.use{ it.copyTo(baos) } just do zis.copyTo(baos)Exclamation
@Exclamation Oh ok. ThanksWebfooted
C
18

Is there a good way to read all entries in a ZipInputStream in kotlin?

Here's a function to extract the files from a Zip file, which you can use as a base and adjust to your own needs:

data class UnzippedFile(val filename: String, val content: ByteArray)

fun unzip(file: File): List<UnzippedFile> = ZipInputStream(FileInputStream(file))
    .use { zipInputStream ->
        generateSequence { zipInputStream.nextEntry }
            .filterNot { it.isDirectory }
            .map {
                UnzippedFile(
                    filename = it.name,
                    content = zipInputStream.readAllBytes()
                )
            }.toList()
    }

The key point is to use generateSequence to handle iterating over the entries until there are none left.

Example usage, extracing a zip that contains three text files in a directory:

fun main() {
    val unzipped = unzip(File("zipped.zip"))
    for ((filename, content) in unzipped) {
        println("Contents of $filename: '${content.toString(Charset.defaultCharset()).trim()}'")
    }
}

Output:

Contents of zipped/two.txt: 'contents of two'
Contents of zipped/three.txt: 'three!!!!'
Contents of zipped/one.txt: 'contents of one'
Campion answered 18/3, 2021 at 1:22 Comment(1)
That is exactly what I was looking for!Zack
C
4

The use function calls the close() method, which closes the entire stream, rather than closeEntry(), which closes just the current entry. I think you should wrap your entire while loop with zis.use { ... }, rather than calling it for each entry.

Corrigan answered 12/9, 2018 at 14:33 Comment(4)
thanks. I see where I went wrong. I had only seen examples of streams being read by wrapping it in the use. I didn't realize the copyTo method can actually be called on any input stream even outside of useExclamation
@Exclamation Can you please show what's the correct code, then?Webfooted
@androiddeveloper, instead of doing zis.use{ it.copyTo(baos) } just do zis.copyTo(baos)Exclamation
@Exclamation Oh ok. ThanksWebfooted
P
0

In Kotlin, use will close a resource that implements AutoCloseable. That means its close() method is called for you automatically. I think you are assuming that in ZipInputStream, that's been overridden to close just entries, but it has not.

According to the documentation:

Closes this input stream and releases any system resources associated with the stream. [Emphasis mine]

Prewitt answered 12/9, 2018 at 14:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.