/* ************************************************************************
#
# DivConq
#
# http://divconq.com/
#
# Copyright:
# Copyright 2014 eTimeline, LLC. All rights reserved.
#
# License:
# See the license.txt file in the project's top-level directory for details.
#
# Authors:
# * Andy White
#
************************************************************************ */
package divconq.ctp.stream;
import io.netty.buffer.ByteBuf;
import java.util.zip.CRC32;
import java.util.zip.Deflater;
import org.apache.commons.compress.compressors.gzip.GzipUtils;
import divconq.ctp.f.FileDescriptor;
import divconq.hub.Hub;
import divconq.script.StackEntry;
import divconq.util.FileUtil;
import divconq.util.StringUtil;
import divconq.xml.XElement;
public class GzipStream extends BaseStream implements IStreamSource {
protected static final byte[] gzipHeader = {0x1f, (byte) 0x8b, Deflater.DEFLATED, 0, 0, 0, 0, 0, 0, 0};
protected int compressionLevel = 6;
protected Deflater deflater = null;
protected CRC32 crc = new CRC32();
protected boolean writeHeader = true;
protected String nameHint = null;
protected String lastpath = null;
public GzipStream() {
}
public GzipStream(int compressionLevel) {
this.compressionLevel = compressionLevel;
}
@Override
public void init(StackEntry stack, XElement el) {
this.nameHint = stack.stringFromElement(el, "NameHint");
}
@Override
public void close() {
//System.out.println("GZip killed"); // TODO
this.deflater = null;
super.close();
}
// make sure we don't return without first releasing the file reference content
@Override
public ReturnOption handle(FileDescriptor file, ByteBuf data) {
if (file == FileDescriptor.FINAL)
return this.downstream.handle(file, data);
// we don't know what to do with a folder at this stage - gzip is for file content only
// folder scanning is upstream in the FileSourceStream and partners
if (file.isFolder())
return ReturnOption.CONTINUE;
// init if not set for this round of processing
if (this.deflater == null) {
this.deflater = new Deflater(this.compressionLevel, true);
this.crc.reset();
this.writeHeader = true;
}
ByteBuf in = data;
ByteBuf out = null;
if (in != null) {
byte[] inAry = in.array();
// always allow for a header (10) plus footer (8) plus extra (12)
// in addition to content
int sizeEstimate = (int) Math.ceil(in.readableBytes() * 1.001) + 30;
out = Hub.instance.getBufferAllocator().heapBuffer(sizeEstimate);
if (this.writeHeader) {
this.writeHeader = false;
out.writeBytes(gzipHeader);
}
this.crc.update(inAry, in.arrayOffset(), in.writerIndex());
this.deflater.setInput(inAry, in.arrayOffset(), in.writerIndex());
while (!this.deflater.needsInput())
deflate(out);
}
else
out = Hub.instance.getBufferAllocator().heapBuffer(30);
FileDescriptor blk = new FileDescriptor();
if (StringUtil.isEmpty(this.lastpath)) {
if (StringUtil.isNotEmpty(this.nameHint))
this.lastpath = "/" + this.nameHint;
else if (file.getPath() != null)
this.lastpath = "/" + GzipUtils.getCompressedFilename(file.path().getFileName());
else
this.lastpath = "/" + FileUtil.randomFilename("gz");
}
blk.setPath(this.lastpath);
file.setModTime(System.currentTimeMillis());
if (file.isEof()) {
this.deflater.finish();
while (!this.deflater.finished())
deflate(out);
int crcValue = (int) this.crc.getValue();
out.writeByte(crcValue);
out.writeByte(crcValue >>> 8);
out.writeByte(crcValue >>> 16);
out.writeByte(crcValue >>> 24);
int uncBytes = this.deflater.getTotalIn();
out.writeByte(uncBytes);
out.writeByte(uncBytes >>> 8);
out.writeByte(uncBytes >>> 16);
out.writeByte(uncBytes >>> 24);
this.deflater.end();
this.deflater = null; // cause a reset for next time we use stream
blk.setEof(true);
}
if (in != null)
in.release();
return this.downstream.handle(blk, out);
}
protected void deflate(ByteBuf out) {
int numBytes = 0;
do {
byte[] o = out.array();
numBytes = this.deflater.deflate(o, out.arrayOffset() + out.writerIndex(), out.writableBytes(), Deflater.SYNC_FLUSH);
out.writerIndex(out.writerIndex() + numBytes);
} while (numBytes > 0);
}
@Override
public void read() {
this.upstream.read();
}
}