package com.sissi.server.exchange.impl; import io.netty.buffer.ByteBuf; import io.netty.buffer.PooledByteBufAllocator; import io.netty.util.ReferenceCountUtil; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.Closeable; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Iterator; import java.util.Map; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicLong; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.mongodb.BasicDBObjectBuilder; import com.sissi.commons.Trace; import com.sissi.commons.apache.IOUtil; import com.sissi.config.Dictionary; import com.sissi.config.impl.MongoUtils; import com.sissi.persistent.Persistent; import com.sissi.pipeline.TransferBuffer; import com.sissi.resource.ResourceCounter; import com.sissi.server.exchange.Delegation; import com.sissi.server.exchange.Exchanger; /** * 基于文件系统的离线文件代理. 索引策略:{"host":1} * * @author kim 2014年2月26日 */ public class FSDelegation implements Delegation { private final String transfer = this.getClass().getSimpleName() + "_transfer"; private final String chunk = this.getClass().getSimpleName() + "_chunk"; private final Log log = LogFactory.getLog(this.getClass()); private final File dir; private final int buffer; private final Persistent persistent; private final ResourceCounter resourceCounter; /** * @param dir * @param buffer Push缓冲区大小 * @param resourceCounter * @param persistent */ public FSDelegation(File dir, int buffer, ResourceCounter resourceCounter, Persistent persistent) { super(); this.resourceCounter = resourceCounter; this.persistent = persistent; this.buffer = buffer; this.dir = this.mkdir(dir); } private File mkdir(File dir) { if (!dir.exists()) { dir.mkdirs(); } return dir; } /* * BufferedOutputStream * * @see com.sissi.server.exchange.Delegation#allocate(java.lang.String) */ @Override public OutputStream allocate(String sid) { try { return new BufferedOutputStream(new FileOutputStream(new File(this.dir, sid))); } catch (Exception e) { this.log.warn(e.toString()); Trace.trace(this.log, e); throw new RuntimeException(e); } } @Override public Delegation push(Exchanger exchanger) { // {"host":Xxx} Map<String, Object> peek = this.persistent.peek(MongoUtils.asMap(BasicDBObjectBuilder.start(Dictionary.FIELD_HOST, exchanger.host()).get())); ByteTransferBuffer buffer = null; try { buffer = new ByteTransferBuffer(new BufferedInputStream(new FileInputStream(new File(FSDelegation.this.dir, peek.get(Dictionary.FIELD_SID).toString()))), Long.valueOf(peek.get(Dictionary.FIELD_SIZE).toString())).write(exchanger); return this; } catch (Exception e) { this.log.warn(e.toString()); Trace.trace(this.log, e); throw new RuntimeException(e); } finally { IOUtil.closeQuietly(buffer); } } private class ByteTransferBuffer implements TransferBuffer, Closeable, Iterator<ByteTransferBuffer> { private final BlockingQueue<ByteBuf> queue = new LinkedBlockingQueue<ByteBuf>(); /** * 已经读取字节 */ private final AtomicLong readable = new AtomicLong(); /** * 本次读取字节 */ private final AtomicLong current = new AtomicLong(); private final InputStream input; private final long total; private ByteBuf byteBuf; /** * @param input * @param total Si长度 */ public ByteTransferBuffer(InputStream input, long total) { super(); this.input = input; this.total = total; FSDelegation.this.resourceCounter.increment(FSDelegation.this.transfer); } /** * 写入Exchanger * * @param exchanger * @return */ public ByteTransferBuffer write(Exchanger exchanger) { while (this.hasNext()) { exchanger.write(this.next()); } return this; } @Override public Object getBuffer() { return this.byteBuf; } /** * 流读至末尾或已读取超出Si长度 * * @return */ @Override public boolean hasNext() { return this.current.get() != -1 && this.readable.get() < this.total; } @Override public ByteTransferBuffer next() { try { ByteBuf byteBuf = PooledByteBufAllocator.DEFAULT.buffer(FSDelegation.this.buffer); if (this.set(byteBuf.writeBytes(this.input, byteBuf.capacity())).get() > 0) { this.readable.addAndGet(current.get()); } this.queue.add(this.byteBuf = byteBuf); FSDelegation.this.resourceCounter.increment(FSDelegation.this.chunk); return this; } catch (Exception e) { throw new RuntimeException(e); } } private AtomicLong set(long current) { this.current.set(current); return this.current; } @Override public void remove() { } @Override public TransferBuffer release() { try { ByteBuf byteBuf = this.queue.take(); if (byteBuf.refCnt() > 0) { ReferenceCountUtil.release(byteBuf); } } catch (Exception e) { FSDelegation.this.log.warn(e.toString()); Trace.trace(FSDelegation.this.log, e); } finally { this.notify(); FSDelegation.this.resourceCounter.decrement(FSDelegation.this.chunk); } return this; } @Override public void close() throws IOException { this.current.set(-1); IOUtil.closeQuietly(this.input); FSDelegation.this.resourceCounter.decrement(FSDelegation.this.transfer); } } }