1

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();
    }
Toik95
  • 157
  • 1
  • 2
  • 13
  • 1
    Try adding the file extension to the file name: `file.getName() + ".zip"` instead of `file.getName()` – Ferrybig Apr 10 '19 at 08:43
  • This already points in the right direction. However, each folder created with ```new ZipEntry(Ids[count] + "/")``` now contains a zipfolder with the desired files. I just want the files there, not compiled in a zip. – Toik95 Apr 10 '19 at 08:57

3 Answers3

2

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
Toik95
  • 157
  • 1
  • 2
  • 13
Ferrybig
  • 18,194
  • 6
  • 57
  • 79
0

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()).

Lukas Novicky
  • 921
  • 1
  • 19
  • 44
  • The ```count``` is to create a seperate folder for each file. The first ```zos.putNextEntry(new ZipEntry())``` creates the folder, the next the file. Your solution does not work, it does the same as mine (writing just one file instead of all the files (shp, dbf, ...), except writing it to a folder. – Toik95 Apr 10 '19 at 08:39
  • No, that does create an invalid zip + it does not create the folder structure as I mentioned above. – Toik95 Apr 10 '19 at 08:59
0
 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();
    }
}