package uc.files.downloadqueue;
import helpers.GH;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.HashMap;
import java.util.Map;
import logger.LoggerFactory;
import org.apache.log4j.Logger;
import uc.crypto.HashValue;
import uc.crypto.IBlock;
import uc.crypto.IHashEngine.VerifyListener;
/**
* the smallest unit of verification in a file..
*
* size is determined by the amount of interleave hashes we have..
* each interleave represents one Block..
*
* @author Quicksilver
*
*/
public class Block implements IBlock {
private static final Logger logger = LoggerFactory.make();
public static class FileChannelManager {
/**
* holds FileChannels for reuse
*/
private Map<File,FileChannel> fcmap = new HashMap<File,FileChannel>();
/**
* holds Random Access Files for these channels for reuse
*/
private Map<File,RandomAccessFile> ramap = new HashMap<File,RandomAccessFile>();
/**
* counts how often a FileChannel is in use..
*/
private Map<File,Integer> fcCounter = new HashMap<File,Integer>();
private synchronized FileChannel getFC(File f) throws IOException {
FileChannel fc = fcmap.get(f);
if (fc == null || !fc.isOpen()) {
RandomAccessFile raf = new RandomAccessFile(f,"rw");
ramap.put(f, raf);
fc = raf.getChannel();
fcmap.put(f, fc);
}
changeFCCounter(f,1);
return fc;
}
private int changeFCCounter(File f,int howmuch) {
Integer count = fcCounter.get(f);
if (count == null) {
count = 0;
}
count += howmuch;
fcCounter.put(f, count);
return count;
}
private synchronized void returnFC(File f) throws IOException {
int count = changeFCCounter(f,-1);
if (count <= 0) {
if (count < 0) {
throw new IllegalStateException("counter can't be smaller than zero");
}
FileChannel fc = fcmap.remove(f);
fc.force(true);
fc.close();
RandomAccessFile raf = ramap.remove(f);
raf.close();
}
}
}
/**
* where this block belongs to
*/
private final FileDQE dqe;
/**
* the number in the file..
* position in the file starting from zero
*/
private final int blocknumber;
/**
* how many bytes this Block has.. so it doesn't has to be calculated on each
* request..
* unknown set from start..
*/
private long length = -1;
private volatile BlockState state = BlockState.UNVERIFIED;
private final HashValue hashOfBlock;
public Block(FileDQE dqe,int blocknumber,HashValue hashOfBlock,boolean finished) {
this.hashOfBlock = hashOfBlock;
this.blocknumber = blocknumber;
this.dqe = dqe;
if (hashOfBlock == null) {
logger.warn("Block not correctly loaded: "+dqe.toString()+ " BN: "+blocknumber,new Throwable());
}
//check for the existence and set empty if not existing.. else hash it
if (dqe.getTempPath().isFile() && dqe.getTempPath().length() >= getStartPosition() + getLength()) {
if (finished) {
state = BlockState.FINISHED;
} else {
verify();
}
} else {
state = BlockState.EMPTY;
}
}
public Block(FileDQE dqe,int blocknumber,HashValue hashOfBlock) {
this(dqe,blocknumber,hashOfBlock,false);
}
public HashValue getHashOfBlock() {
return hashOfBlock;
}
private long getStartPosition() {
return blocknumber * dqe.getBlocksize();
}
public long getLength() {
if (length == -1 ) {
if (getStartPosition()+ dqe.getBlocksize() > dqe.getSize()) {
length = dqe.getSize() - getStartPosition();
} else {
length = dqe.getBlocksize();
}
}
return length;
}
public ReadableByteChannel getReadChannel() throws IOException {
if (state == BlockState.EMPTY || state == BlockState.WRITEINPROGRESS ) {
throw new IllegalStateException("the block is not in a state that allows reading from it");
}
final FileChannel fc = dqe.getFileChannelManager().getFC(dqe.getTempPath());
return new ReadableByteChannel() {
private long currentpos = getStartPosition();
private long bytesleft = getLength();
private boolean open = true;
public void close() throws IOException {
if (open) {
dqe.getFileChannelManager().returnFC(dqe.getTempPath());
open = false;
}
}
public boolean isOpen() {
return open;
}
public int read(ByteBuffer dst) throws IOException {
if (bytesleft <= 0) {
if (bytesleft < 0) {
throw new IllegalStateException("read too much from the block");
}
return -1;
}
if (dst.remaining() > bytesleft) {
dst.limit((int)(dst.position()+bytesleft));
}
int read = fc.read(dst,currentpos);
if (read == -1) {
// return read(dst);
return -1;
} else {
bytesleft -= read;
currentpos += read;
return read;
}
}
};
}
public boolean isWritable() {
return state == BlockState.EMPTY;
}
public boolean isFinished() {
return state == BlockState.FINISHED;
}
private void setState(BlockState bs) {
state = bs;
dqe.internal_NotifyBlockChanged(this,bs);
}
/**
* returns a writable channel for this block.. that will write the bytes
* to the specified position in the file..
*
* when finished writing 0 will be returned..
* from the write operation
*
* when ever the channel is closed the Block will be verified..
*
* @return a channel
* @throws IOException
*/
public WritableByteChannel getWriteChannel() throws IOException {
if (state != BlockState.EMPTY) {
throw new IllegalStateException("current state: "+state+" this is not legal");
}
final FileChannel fc = dqe.getFileChannelManager().getFC(dqe.getTempPath());
setState(BlockState.WRITEINPROGRESS);
return new WritableByteChannel() {
private long currentpos = getStartPosition();
private long bytesleft = getLength();
boolean open = true;
public void close() throws IOException {
if (open) {
dqe.getFileChannelManager().returnFC(dqe.getTempPath());
open = false;
if (bytesleft == 0) {
verify();
} else {
setState(BlockState.EMPTY);
}
}
}
public boolean isOpen() {
return open;
}
public int write(ByteBuffer src) throws IOException {
if (bytesleft == 0) {
return 0;
}
boolean bufferduplicatedone = false; //signal if the following operation was done
//this is needed to restrict the possible amount of written bytes to the remaining bytes of the block
if (src.remaining() > bytesleft) {
ByteBuffer old = src;
src = src.duplicate();
//advance position of the old buffer to a value that means all possible byte where written
old.position((int)(old.position()+bytesleft));
//the copied buffer be set so only the really needed amount of bytes are written
src.limit((int)(src.position()+bytesleft));
bufferduplicatedone = true;
}
//now write all possible bytes
int written = fc.write(src, currentpos);
if (written == -1) {
return -1;
} else {
currentpos += written;
bytesleft -= written;
//just a check if this buffer copying did work..
if (bufferduplicatedone && bytesleft != 0) {
throw new IOException("buffer duplicate operation did not work Block.class is not working properly");
}
if (bytesleft < 0) {
throw new IOException("Too many bytes written");
}
return written;
}
}
};
}
/**
* asynchronous verification with the help of the
* HashEngine..
*/
private void verify() {
setState(BlockState.UNVERIFIED);
dqe.getDCC().getHashEngine().checkBlock(this, new VerifyListener() {
public void checked(boolean verified) {
setState(verified?BlockState.FINISHED:BlockState.EMPTY );
}
});
}
public static enum BlockState {
EMPTY,WRITEINPROGRESS,UNVERIFIED,FINISHED;
}
public int getBlocknumber() {
return blocknumber;
}
/**
* calculates the maximum size of an interval to write
* started by this block
*
* @return the IntervalSize from this to the first block that can't be written to
*/
public long getIntervalLength() {
//Block next = dqe.getBlock(blocknumber+1);
int count = 0;
long total = 0;
while (true) {
Block next = dqe.getBlock(blocknumber+count);
if (next != null && next.isWritable()) {
total += next.getLength();
count++;
} else {
return total;
}
}
}
public boolean isIntervalBeingWritten() {
for (int i = blocknumber; i >=0; i--) {
switch(dqe.getBlock(i).state) {
case EMPTY: continue;
case FINISHED: return false;
case WRITEINPROGRESS: return true;
case UNVERIFIED: return false;
}
}
return false;
}
public int getIntervalLengthInBlocks() {
//try {
int count = 1 ;
while (true) {
Block next = dqe.getBlock(blocknumber+count);
if (next != null && next.isWritable()) {
count++;
} else {
return count;
}
}
}
public BlockState getState() {
return state;
}
public String toString() {
return "BlockNr: "+ blocknumber ;
}
@Override
public int compareTo(IBlock o) {
if (o instanceof Block) {
Block b = (Block)o;
int i = dqe.getID().compareTo(b.dqe.getID());
if (i == 0) {
i = GH.compareTo(blocknumber, b.blocknumber);
}
return i;
}
return 0;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + blocknumber;
result = prime * result + ((dqe == null) ? 0 : dqe.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Block other = (Block) obj;
if (blocknumber != other.blocknumber)
return false;
if (dqe == null) {
if (other.dqe != null)
return false;
} else if (!dqe.equals(other.dqe))
return false;
return true;
}
}