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);
}
}
}