package org.basex.io; import java.io.*; import java.util.zip.*; import org.basex.core.jobs.*; import org.basex.util.*; import org.basex.util.list.*; /** * Contains methods for zipping and unzipping archives. * * @author BaseX Team 2005-17, BSD License * @author Christian Gruen */ public final class Zip extends Job { /** Archive. */ private final IO file; /** Total files in a zip operation. */ private int total; /** Current file in a zip operation. */ private int curr; /** * Constructor. * @param file archive file */ public Zip(final IO file) { this.file = file; } /** * Returns the number of entries in a zip archive. * @return number of entries * @throws IOException I/O exception */ private int size() throws IOException { try(ZipInputStream in = new ZipInputStream(file.inputStream())) { int c = 0; while(in.getNextEntry() != null) c++; return c; } } /** * Returns the contents of a zip file entry. * @param path file to be read * @return resulting byte array * @throws IOException I/O exception */ public byte[] read(final String path) throws IOException { try(ZipInputStream in = new ZipInputStream(file.inputStream())) { final byte[] cont = getEntry(in, path); if(cont == null) throw new FileNotFoundException(path); return cont; } } /** * Unzips the archive to the specified directory. * @param target target path * @throws IOException I/O exception */ public void unzip(final IOFile target) throws IOException { total = size(); curr = 0; try(ZipInputStream in = new ZipInputStream(file.inputStream())) { final byte[] data = new byte[IO.BLOCKSIZE]; for(ZipEntry ze; (ze = in.getNextEntry()) != null;) { curr++; final IOFile trg = new IOFile(target, ze.getName()); if(ze.isDirectory()) { trg.md(); } else { trg.parent().md(); try(OutputStream out = new FileOutputStream(trg.path())) { for(int c; (c = in.read(data)) != -1;) out.write(data, 0, c); } } } } } /** * Zips the specified files. * @param root root directory * @param files files to add * @throws IOException I/O exception */ public void zip(final IOFile root, final StringList files) throws IOException { if(!(file instanceof IOFile)) throw new FileNotFoundException(file.path()); curr = 0; try(ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream( new FileOutputStream(file.path())))) { // use simple, fast compression out.setLevel(1); // loop through all files total = files.size(); final byte[] data = new byte[IO.BLOCKSIZE]; for(final String f : files) { curr++; try(FileInputStream in = new FileInputStream(new File(root.file(), f))) { final String fl = Prop.WIN ? f.replace('\\', '/') : f; out.putNextEntry(new ZipEntry(root.name() + '/' + fl)); for(int c; (c = in.read(data)) != -1;) out.write(data, 0, c); out.closeEntry(); } } } } @Override public double progressInfo() { return (double) curr / total; } /** * Returns the contents of the specified entry. * @param in input stream * @param entry entry to be found * @return entry, or {@code null} if it is not found * @throws IOException I/O exception */ private static byte[] getEntry(final ZipInputStream in, final String entry) throws IOException { for(ZipEntry ze; (ze = in.getNextEntry()) != null;) { if(!entry.equals(ze.getName())) continue; final int s = (int) ze.getSize(); if(s >= 0) { // known size: pre-allocate and fill array final byte[] data = new byte[s]; int c, o = 0; while(s - o != 0 && (c = in.read(data, o, s - o)) != -1) o += c; return data; } // unknown size: use byte list final byte[] data = new byte[IO.BLOCKSIZE]; final ByteList bl = new ByteList(); for(int c; (c = in.read(data)) != -1;) bl.add(data, 0, c); return bl.finish(); } return null; } }