package org.basex.io;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import org.basex.core.Progress;
import org.basex.util.list.ByteList;
import org.basex.util.list.StringList;
/**
* Contains methods for zipping and unzipping archives.
*
* @author BaseX Team 2005-12, BSD License
* @author Christian Gruen
*/
public final class Zip extends Progress {
/** Archive. */
private final IO archive;
/** 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) {
archive = file;
}
/**
* Returns the number of entries in a zip archive.
* @return number of entries
* @throws IOException I/O exception
*/
private int size() throws IOException {
int c = 0;
ZipInputStream in = null;
try {
in = new ZipInputStream(archive.buffer());
while(in.getNextEntry() != null) c++;
return c;
} finally {
if(in != null) try { in.close(); } catch(final IOException e) { }
}
}
/**
* 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 {
ZipInputStream in = null;
try {
in = new ZipInputStream(archive.buffer());
for(ZipEntry ze; (ze = in.getNextEntry()) != null;) {
if(!path.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.toArray();
}
} finally {
if(in != null) try { in.close(); } catch(final IOException e) { }
}
throw new FileNotFoundException(path);
}
/**
* Unzips the archive to the specified directory.
* @param target target path
* @throws IOException I/O exception
*/
public void unzip(final IOFile target) throws IOException {
final byte[] data = new byte[IO.BLOCKSIZE];
ZipInputStream in = null;
total = size();
curr = 0;
try {
in = new ZipInputStream(archive.buffer());
for(ZipEntry ze; (ze = in.getNextEntry()) != null;) {
curr++;
final IOFile trg = new IOFile(target, ze.getName());
if(ze.isDirectory()) {
trg.md();
} else {
new IOFile(trg.dir()).md();
OutputStream out = null;
try {
out = new FileOutputStream(trg.path());
for(int c; (c = in.read(data)) != -1;) out.write(data, 0, c);
} finally {
if(out != null) try { out.close(); } catch(final IOException e) { }
}
}
}
} finally {
if(in != null) try { in.close(); } catch(final IOException e) { }
}
}
/**
* Zips the specified source directory.
* @param source source directory to be zipped
* @throws IOException I/O exception
*/
public void zip(final IOFile source) throws IOException {
if(!(archive instanceof IOFile))
throw new FileNotFoundException(archive.path());
final byte[] data = new byte[IO.BLOCKSIZE];
ZipOutputStream out = null;
curr = 0;
try {
// create output stream for zipping; use fast compression
out = new ZipOutputStream(new BufferedOutputStream(
new FileOutputStream(archive.path())));
out.setLevel(1);
out.putNextEntry(new ZipEntry(source.name() + '/'));
out.closeEntry();
// loop through all files
final StringList files = source.descendants();
total = files.size();
for(final String io : files) {
curr++;
FileInputStream in = null;
try {
in = new FileInputStream(new File(source.file(), io));
out.putNextEntry(new ZipEntry(source.name() + '/' + io));
for(int c; (c = in.read(data)) != -1;) out.write(data, 0, c);
out.closeEntry();
} finally {
if(in != null) try { in.close(); } catch(final IOException e) { }
}
}
} finally {
if(out != null) try { out.close(); } catch(final IOException e) { }
}
}
@Override
protected double prog() {
return (double) curr / total;
}
}