Skip to content

Instantly share code, notes, and snippets.

@ato
Last active October 19, 2016 03:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ato/276ffe99444681fe70092f03087b46e0 to your computer and use it in GitHub Desktop.
Save ato/276ffe99444681fe70092f03087b46e0 to your computer and use it in GitHub Desktop.
On the fly unzipping for dl-repo

This is an example of how to modify dl-repo so it can serve a entry from a zip file on the fly without creating temporary files. It compiles but I haven't tested it. It also obviously needs error handling added, for when an entry with the given path doesn't exist.

The idea is you can make requests like:

GET /dl-repo/unzip/copy/nla.obj-12345/OEBPS/chapter-001-chapter-i.html

Note: The JVM's builtin ZipFile class kinda sucks because it can only take a file path not a channel so can only be used on local files not files in the repository. I suggest using an alternative implementation that abstracts the I/O calls through an interface. TrueZip is one such implementation, I'm sure there's others.

import de.schlichtherle.truezip.rof.AbstractReadOnlyFile;
import de.schlichtherle.truezip.zip.ZipEntry;
import de.schlichtherle.truezip.zip.ZipFile;
// ...
@RequestMapping(value = "/Repository/unzip/copy/{copyId:nla\\.obj-[^/]+}/{path:.+}", method = {RequestMethod.GET})
@ResponseBody
public void unzipCopy(@PathVariable String copyId,
@PathVariable String path,
HttpServletResponse response) throws IOException {
try (AmberSession session = amberDbService.get()) {
Copy copy = session.findModelObjectById(copyId, Copy.class);
try (SeekableByteChannel channel = copy.getFile().openChannel();
ZipFile zip = new ZipFile(new ChannelReadOnlyFile(channel))) {
ZipEntry entry = zip.getEntry(path);
response.addHeader("Content-Length", Long.toString(entry.getSize()));
// XXX; check that this map has the right content type mappings for your epub stuff
String contentType = MimetypesFileTypeMap.getDefaultFileTypeMap().getContentType(path);
response.setContentType(contentType);
IOUtils.copy(zip.getInputStream(entry), response.getOutputStream());
}
}
}
static class ChannelReadOnlyFile extends AbstractReadOnlyFile {
final SeekableByteChannel channel;
ChannelReadOnlyFile(SeekableByteChannel channel) {
this.channel = channel;
}
@Override
public long length() throws IOException {
return channel.size();
}
@Override
public long getFilePointer() throws IOException {
return channel.position();
}
@Override
public void seek(long position) throws IOException {
channel.position(position);
}
@Override
public int read() throws IOException {
ByteBuffer buf = ByteBuffer.allocate(1);
int nread = channel.read(buf);
return nread > 0 ? buf.get(0) : nread;
}
@Override
public int read(byte[] bytes, int off, int len) throws IOException {
ByteBuffer buf = ByteBuffer.wrap(bytes, off, len);
return channel.read(buf);
}
@Override
public void close() throws IOException {
channel.close();
}
}
<dependency>
<groupId>de.schlichtherle.truezip</groupId>
<artifactId>truezip-driver-zip</artifactId>
<version>7.7.10</version>
</dependency>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment