I want to zip an array of File to a zipfile and send it to the browser. The Inputstream of each File is a shapefile, and actually consists of multiple files (.shp, .dbf, .shx, ...).

When I send only one File with the following code, it works properly and a zipfile is returned with all the desired files in it.

Code to send a single file

FileInputStream is = new FileInputStream(files.get(0));

response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment; filename=" + getCurrentUser(request).getNiscode() + ".zip");

while (is.available() > 0) {
    response.getOutputStream().write(is.read());
}

is.close();
if (response.getOutputStream() != null) {
    response.getOutputStream().flush();
    response.getOutputStream().close();
}

When I try to send all the files together, a zipfile is returned with the desired folders, but in each folder only one element with just a .file extension is present. It has something to do with the entries of the ZipOutputStream?

Code to send all the files

byte[] zip = this.zipFiles(files, Ids);

response.setContentType("application/zip");
response.setHeader("Content-Disposition", "attachment; filename="test.zip");

response.getOutputStream().write(zip);
response.flushBuffer();
private byte[] zipFiles(ArrayList<File> files, String[] Ids) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ZipOutputStream zos = new ZipOutputStream(baos);

        int count = 0;
        for (File file : files) {
            FileInputStream fis = new FileInputStream(file);

            zos.putNextEntry(new ZipEntry(Ids[count] + "/"));
            zos.putNextEntry(new ZipEntry(Ids[count] + "/" + file.getName()));

            while (fis.available() > 0) {
                zos.write(fis.read());
            }
            zos.closeEntry();
            fis.close();
            count ++;
        }
        zos.flush();
        baos.flush();
        zos.close();
        baos.close();

        return baos.toByteArray();
    }

3 Answers

2
Ferrybig On Best Solutions

Based on your code, it seems like every file inside your files array is already a zip file

When you then later do zipFiles, you are making a zipfile that contains more zipfiles in its folders. You obviously don't want this, but you want a zipfile that has folders that contain the contents of all could zipfiles.

Basing on an existing an existing answer of Thanador located at "Reading data from multiple zip files and combining them to one" I devised the following solution to also include directories and proper stream handling:

/**
 * Combine multiple zipfiles together
 * @param files List of file objects pointing to zipfiles
 * @param ids List of file names to use in the final output
 * @return The byte[] object representing the final output
 * @throws IOException When there was a problem reading a zipfile
 * @throws NullPointerException When there either input is or contains a null value
 */
private byte[] zipFiles(ArrayList<File> files, String[] ids) throws IOException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();

    // Buffer to copy multiple bytes at once, this is generally faster that just reading and writing 1 byte at a time like your previous code does
    byte[] buf = new byte[16 * 1024];
    int length = files.size();
    assert length == ids.length;
    try (ZipOutputStream zos = new ZipOutputStream(baos)) {
        for (int i = 0; i < length; i++) {
            try (ZipInputStream inStream = new ZipInputStream(new FileInputStream(files.get(i))) {
                ZipEntry entry;
                while ((entry = inStream.getNextEntry()) != null) {
                    zos.putNextEntry(new ZipEntry(ids[i] + "/" + entry.getName()));
                    int readLength;
                    while ((readLength = inStream.read(buf)) > 0) {
                        zos.write(buf, 0, readLength);
                    }
                }
            }
        }
    }

    return baos.toByteArray();
}
  • Technically, its faster and more memory efficient to directly write to the output stream you got from response.getOutputStream(), but I didn't do this in the above example, so you would have an easier time implementing the method in your code
  • If you close a stream, it automatically flushes it, I'm using try-with-resources to close them
0
Lukas Novicky On

try following solution:

private byte[] zipFiles(ArrayList<File> files, String[] Ids) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ZipOutputStream zos = new ZipOutputStream(baos);
    for (File file : files) {
        ZipEntry ze= new ZipEntry(file.getName());
        zos.putNextEntry(ze);

        FileInputStream fis = new FileInputStream(file);

        int len;
        while ((len = fis .read(buffer)) > 0) {
            zos.write(buffer, 0, len);
        }

        fis .close();
    }
    byte[] byteArray = baos.toByteArray();
    zos.flush();
    baos.flush();
    zos.close();
    baos.close();

    return byteArray;
}

IDK why you put count variable and why you put twice zos.putNextEntry(new ZipEntry()).

0
Nurlan Rysbaev On
 public static void compressListFiles(List<Pair<String, String>> filePairList, ZipOutputStream zipOut) throws Exception {
    for (Pair<String, String> pair : filePairList) {
        File fileToZip = new File(pair.getValue());
        String fileId = pair.getKey();
        FileInputStream fis = new FileInputStream(fileToZip);
        ZipEntry zipEntry = new ZipEntry(fileId + "/" + fileToZip.getName());
        zipOut.putNextEntry(zipEntry);
        byte[] bytes = new byte[1024];
        int length;
        while ((length = fis.read(bytes)) >= 0) {
            zipOut.write(bytes, 0, length);
        }
        fis.close();
    }
}