/* ************************************************************************ # # 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.io.IOException; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; import divconq.ctp.f.FileDescriptor; import divconq.hub.Hub; import divconq.io.CyclingByteBufferOutputStream; import divconq.lang.op.OperationContext; import divconq.script.StackEntry; import divconq.util.FileUtil; import divconq.util.StringUtil; import divconq.xml.XElement; public class TarStream extends BaseStream implements IStreamSource { protected CyclingByteBufferOutputStream bstream = null; protected TarArchiveOutputStream tstream = null; protected boolean archiveopenflag = false; protected boolean finalflag = false; protected String nameHint = null; protected String lastpath = null; public TarStream() { } public TarStream withNameHint(String v) { this.nameHint = v; return this; } @Override public void init(StackEntry stack, XElement el) { this.nameHint = stack.stringFromElement(el, "NameHint"); } @Override public void close() { //System.out.println("Tar killed"); // TODO if (this.tstream != null) try { this.tstream.close(); } catch (IOException x) { } this.bstream = null; this.tstream = 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) { if (this.tstream == null) return this.downstream.handle(file, data); this.finalflag = true; } // I don't think tar cares about folder entries at this stage - tar is for file content only // folder scanning is upstream in the FileSourceStream and partners // TODO try with ending / to file name if (file.isFolder()) return ReturnOption.CONTINUE; // init if not set for this round of processing if (this.tstream == null) { this.bstream = new CyclingByteBufferOutputStream(); this.tstream = new TarArchiveOutputStream(this.bstream); this.tstream.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU); } ByteBuf in = data; ByteBuf out = null; // always allow for a header (512) and/or footer (1024) in addition to content int sizeEstimate = (in != null) ? in.readableBytes() + 2048 : 2048; out = Hub.instance.getBufferAllocator().heapBuffer(sizeEstimate); this.bstream.installBuffer(out); // TODO if there is no output available to send and not EOF then just request more, // no need to send a message that is empty and not EOF FileDescriptor blk = new FileDescriptor(); if (StringUtil.isNotEmpty(this.lastpath)) { blk.setPath(this.lastpath); } else { if (file.hasPath()) this.lastpath = "/" + (StringUtil.isNotEmpty(this.nameHint) ? this.nameHint : file.path().getFileName()) + ".tar"; else if (StringUtil.isNotEmpty(this.nameHint)) this.lastpath = "/" + this.nameHint + ".tar"; else this.lastpath = "/" + FileUtil.randomFilename() + ".tar"; blk.setPath(this.lastpath); } blk.setModTime(System.currentTimeMillis()); if (!this.archiveopenflag && !this.finalflag) { TarArchiveEntry tentry = new TarArchiveEntry(file.getPath().toString().substring(1), true); tentry.setSize(file.getSize()); tentry.setModTime(file.getModTime()); try { this.tstream.putArchiveEntry(tentry); } catch (IOException x) { if (in != null) in.release(); out.release(); OperationContext.get().getTaskRun().kill("Problem writing tar entry: " + x); return ReturnOption.DONE; } this.archiveopenflag = true; } if (in != null) try { this.tstream.write(in.array(), in.arrayOffset(), in.writerIndex()); } catch (IOException x) { in.release(); out.release(); OperationContext.get().getTaskRun().kill("Problem writing tar body: " + x); return ReturnOption.DONE; } if (file.isEof()) { try { this.tstream.closeArchiveEntry(); } catch (IOException x) { if (in != null) in.release(); out.release(); OperationContext.get().getTaskRun().kill("Problem closing tar entry: " + x); return ReturnOption.DONE; } this.archiveopenflag = false; } if (in != null) in.release(); if (file == FileDescriptor.FINAL) { blk.setEof(true); try { this.tstream.close(); } catch (IOException x) { //in.release(); out.release(); OperationContext.get().getTaskRun().kill("Problem closing tar stream: " + x); return ReturnOption.DONE; } this.tstream = null; this.bstream = null; } else this.bstream.uninstallBuffer(); // we are done with out forever, don't reference it System.out.println("tar sending: " + out.readableBytes()); ReturnOption v = this.downstream.handle(blk, out); if (!this.finalflag) return v; if (v == ReturnOption.CONTINUE) return this.downstream.handle(FileDescriptor.FINAL, null); return ReturnOption.DONE; } @Override public void read() { if (this.finalflag) { this.downstream.handle(FileDescriptor.FINAL, null); return; } this.upstream.read(); } }