Question

I have a webapp that allows users to select images and then download them. For a single image, I use HTML5's anchor download and it works beautifully. Now I need to allow them to select multiple images, and download them as a .zip file. I'm using an api to get each image as an InputStream and returning a Jersey Response.

I'm new to zipping and I'm a bit confused with how zipping with InputStream should work.

For single images, it works like so:

try {
    InputStream imageInputStream = ImageStore.getImage(imageId);

    if (imageInputStream == null) {
        XLog.warnf("Unable to find image [%s].", imageId);
        return Response.status(HttpURLConnection.HTTP_GONE).build();
    }

    Response.ResponseBuilder response = Response.ok(imageInputStream);
    response.header("Content-Type", imageType.mimeType());
    response.header("Content-Disposition", "filename=image.jpg");

    return response.build();
}

It's not much, but here's the java I have so far for multiple images

public Response zipAndDownload(List<UUID> imageIds) {
    try {
        // TODO: instantiate zip file?

        for (UUID imageId : imageIds) {
            InputStream imageInputStream = ImageStore.getImage(imageId);
            // TODO: add image to zip file (ZipEntry?)
        }

        // TODO: return zip file
    }
    ...
}

I just don't know how to deal with multiple InputStreams, and it seems that I shouldn't have multiple, right?

Was it helpful?

Solution

An InputStream per image is ok. To zip the files you need to create a .zip file for them to live in and get a ZipOutputStream to write to it:

File zipFile = new File("/path/to/your/zipFile.zip");
ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile)));

For each image, create a new ZipEntry, add it to the ZipOutputSteam, then copy the bytes from your image's InputStream to the ZipOutputStream:

ZipEntry ze = new ZipEntry("PrettyPicture1.jpg");
zos.putNextEntry(ze);
byte[] bytes = new byte[1024];
int count = imageInputStream.read(bytes);
while (count > -1)
{
    zos.write(bytes, 0, count);
    count = imageInputStream.read(bytes);
}
imageInputStream.close();
zos.closeEntry();

After you add all the entries, close the ZipOutputStream:

zos.close();

Now your zipFile points to a zip file full of pictures you can do whatever you want with. You can return it like you do with a single image:

BufferedInputStream zipFileInputStream = new BufferedInputStream(new FileInputStream(zipFile));
Response.ResponseBuilder response = Response.ok(zipFileInputStream);

But the content type and disposition are different:

response.header("Content-Type", MediaType.APPLICATION_OCTET_STREAM_TYPE);
response.header("Content-Disposition", "attachment; filename=zipFile.zip");

Note: You can use the copy method from Guava's ByteStreams helper to copy the streams instead of copying the bytes manually. Simply replace the while loop and the 2 lines before it with this line:

ByteStreams.copy(imageInputStream, zos);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top