/* ************************************************************************ # # 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 divconq.ctp.f.FileDescriptor; import divconq.script.StackEntry; import divconq.xml.XElement; public class FunnelStream extends BaseStream implements IStreamSource { protected int aperture = 1; protected FileDescriptor current = null; protected ByteBuf currbuf = null; protected boolean relayed = false; // TODO currently only small Aperture is supported well because we may not get large buffers from // source. we should accumulate small buffers into a large buffer so we always pass the correct size // down, except at EOF of course. see remnant in UngzipStream for an example of this sort of buffer gathering @Override public void init(StackEntry stack, XElement el) { this.aperture = (int) stack.intFromElement(el, "Aperture", this.aperture); } public boolean hasMore() { FileDescriptor curr = this.current; if (curr == null) return false; if (!this.relayed) // TODO what about EOF, we need to send that along, so even first is not enough? return true; ByteBuf payload = this.currbuf; return (payload != null) && payload.isReadable(); } public ReturnOption nextMessage() { FileDescriptor curr = this.current; if (curr == null) return ReturnOption.CONTINUE; FileDescriptor blk = new FileDescriptor(); blk.copyAttributes(curr); ByteBuf payload = this.currbuf; if ((payload != null) && payload.isReadable()) { int ramt = Math.min(this.aperture, payload.readableBytes()); ByteBuf pslice = payload.copy(payload.readerIndex(), ramt); payload.skipBytes(ramt); // TODO blk.payloadoffset = 0; blk.setEof(!payload.isReadable() && curr.isEof()); if (blk.isEof()) { payload.release(); this.current = null; this.currbuf = null; } payload = pslice; } else { blk.setEof(curr.isEof()); if (payload != null) payload.release(); payload = null; this.current = null; this.currbuf = null; } // current has been sent at least once this.relayed = true; return this.downstream.handle(blk, payload); } @Override public ReturnOption handle(FileDescriptor file, ByteBuf data) { if (file == FileDescriptor.FINAL) return this.downstream.handle(file, data); this.current = file; this.currbuf = data; this.relayed = false; while (this.hasMore()) { ReturnOption ret = this.nextMessage(); if (ret != ReturnOption.CONTINUE) return ret; } return ReturnOption.CONTINUE; } @Override public void read() { while (this.hasMore()) { ReturnOption ret = this.nextMessage(); if (ret != ReturnOption.CONTINUE) return; } this.upstream.read(); } @Override public void close() { ByteBuf curr = this.currbuf; if (curr != null) curr.release(); this.currbuf = null; this.current = null; super.close(); } }