/** * Copyright (c) 2011-2012, Thilo Planz. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package v7db.files; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.RandomAccessFile; import java.util.HashMap; import java.util.List; import java.util.Map; import net.lingala.zip4j.core.HeaderReader; import net.lingala.zip4j.exception.ZipException; import net.lingala.zip4j.model.FileHeader; import net.lingala.zip4j.model.LocalFileHeader; import net.lingala.zip4j.model.ZipModel; import net.lingala.zip4j.unzip.UnzipEngine; import org.apache.commons.io.IOUtils; import v7db.files.spi.Content; import v7db.files.spi.ContentPointer; import v7db.files.spi.ContentStorage; import v7db.files.spi.InlineContent; import v7db.files.spi.StorageScheme; /** * Utility to store and index a ZipFile. * <p> * In addition to just storing the ZipFile as a whole, references to all the * contents of all its files are also added to the database (so that if one of * those is uploaded later separately, it will not take up extra storage). * <p> * Cannot be used for files protected by password or otherwise encrypted. * * */ public class ZipFile { public static final class ContentFromZipFile implements StorageScheme { public Content getContent(ContentStorage storage, Map<String, Object> data) throws IOException { byte[] base = MapUtils.getRequiredBytes(data, "base.sha"); long offset = MapUtils.getRequiredLong(data, "off"); long length = MapUtils.getRequiredLong(data, "end") - offset; byte[] unzipped = IOUtils.toByteArray(Compression.unzip(storage .getContent(base).getInputStream(offset, length))); return new InlineContent(unzipped); } public String getId() { return "zip"; } } /** * index all individual files found in a zip archive already in storage * * @throws IOException */ public static final void index(ContentStorage storage, ContentPointer zipFile) throws IOException { Content zip = storage.getContent(zipFile); if (zip == null) throw new IllegalArgumentException("invalid ContentPointer " + zipFile); File tmp = File.createTempFile("v7files_zipfile_extractfile_", ".zip"); try { OutputStream f = new FileOutputStream(tmp); IOUtils.copy(zip.getInputStream(), f); f.close(); // open up the zip file HeaderReader r = new HeaderReader(new RandomAccessFile(tmp, "r")); ZipModel model = r.readAllHeaders(); model.setZipFile(tmp.getAbsolutePath()); Map<String, Object> map = zipFile.serialize(); List<?> fhs = model.getCentralDirectory().getFileHeaders(); for (Object _fh : fhs) { FileHeader fh = (FileHeader) _fh; UnzipEngine en = new UnzipEngine(model, fh); // this will read the local file header en.getInputStream(); LocalFileHeader lh = en.getLocalFileHeader(); store(storage, map, fh, lh); } } catch (ZipException e) { throw new IllegalArgumentException( "ContentPointer does not refer to a zip file: " + zipFile, e); } finally { tmp.delete(); } } /** * find the data indicated by the ContentPointer, treats it as a zip * archive, extracts the named file inside the archive, stores a reference * to it in the ContentStorage and returns a ContentPointer to it. * * @throws FileNotFoundException * if the archive exists, but does not contain the named file * @throws IllegalArgumentException * if the ContentPointer does not refer to a zip archive * */ public static final ContentPointer extractFile(ContentStorage storage, ContentPointer zipFile, String fileName) throws IOException { Content zip = storage.getContent(zipFile); if (zip == null) throw new IllegalArgumentException("invalid ContentPointer " + zipFile); File tmp = File.createTempFile("v7files_zipfile_extractfile_", ".zip"); try { OutputStream f = new FileOutputStream(tmp); IOUtils.copy(zip.getInputStream(), f); f.close(); // open up the zip file HeaderReader r = new HeaderReader(new RandomAccessFile(tmp, "r")); ZipModel model = r.readAllHeaders(); model.setZipFile(tmp.getAbsolutePath()); List<?> fhs = model.getCentralDirectory().getFileHeaders(); for (Object _fh : fhs) { FileHeader fh = (FileHeader) _fh; if (fileName.equals(fh.getFileName())) { UnzipEngine en = new UnzipEngine(model, fh); // this will read the local file header en.getInputStream(); LocalFileHeader lh = en.getLocalFileHeader(); return store(storage, zipFile.serialize(), fh, lh); } } } catch (ZipException e) { throw new IllegalArgumentException( "ContentPointer does not refer to a zip file: " + zipFile, e); } finally { tmp.delete(); } throw new FileNotFoundException("ContentPointer does not contain " + fileName + ": " + zipFile); } private static ContentPointer store(ContentStorage storage, Map<String, Object> zipFile, FileHeader fh, LocalFileHeader lh) throws IOException { Map<String, Object> cat = new HashMap<String, Object>(); cat.put("store", "zip"); cat.put("base", zipFile); cat.put("off", fh.getOffsetLocalHeader()); cat.put("length", fh.getUncompressedSize()); cat.put("end", lh.getOffsetStartOfData() + lh.getCompressedSize()); return storage.storeContent(cat); } }