/* This code is part of Freenet. It is distributed under the GNU General
* Public License, version 2 (or at your option any later version). See
* http://www.gnu.org/ for further details of the GPL. */
package freenet.io.xfer;
import java.io.IOException;
import java.util.Arrays;
import freenet.io.comm.MessageCore;
import freenet.io.comm.RetrievalException;
import freenet.support.BitArray;
import freenet.support.LogThresholdCallback;
import freenet.support.Logger;
import freenet.support.Logger.LogLevel;
import freenet.support.api.RandomAccessBuffer;
/**
* Equivalent of PartiallyReceivedBlock, for large(ish) file transfers.
* As presently implemented, we keep a bitmap in RAM of blocks received, so it should be adequate
* for fairly large files (128kB for a 1GB file e.g.). We can compress this structure later on if
* need be.
* @author toad
*/
public class PartiallyReceivedBulk {
/** The size of the data being received. Does *not* have to be a multiple of blockSize. */
final long size;
/** The size of the blocks sent as packets. */
final int blockSize;
private final RandomAccessBuffer raf;
/** Which blocks have been received and written? */
private final BitArray blocksReceived;
final int blocks;
private BulkTransmitter[] transmitters;
final MessageCore usm;
/** The one and only BulkReceiver */
BulkReceiver recv;
private int blocksReceivedCount;
// Abort status
boolean _aborted;
int _abortReason;
String _abortDescription;
private static volatile boolean logMINOR;
static {
Logger.registerLogThresholdCallback(new LogThresholdCallback(){
@Override
public void shouldUpdate(){
logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
}
});
}
/**
* Construct a PartiallyReceivedBulk.
* @param size Size of the file, does not have to be a multiple of blockSize.
* @param blockSize Block size.
* @param raf Where to store the data.
* @param initialState If true, assume all blocks have been received. If false, assume no blocks have
* been received.
*/
public PartiallyReceivedBulk(MessageCore usm, long size, int blockSize, RandomAccessBuffer raf, boolean initialState) {
this.size = size;
this.blockSize = blockSize;
this.raf = raf;
this.usm = usm;
long blocks = (size + blockSize - 1) / blockSize;
if(blocks > Integer.MAX_VALUE)
throw new IllegalArgumentException("Too big");
this.blocks = (int)blocks;
blocksReceived = new BitArray(this.blocks);
if(initialState) {
blocksReceived.setAllOnes();
blocksReceivedCount = this.blocks;
}
assert(raf.size() >= size);
}
/**
* Clone the blocksReceived BitArray. Used by BulkTransmitter to find what blocks are available on
* creation. BulkTransmitter will have already taken the lock and will keep it over the add() also.
* @return A copy of blocksReceived.
*/
synchronized BitArray cloneBlocksReceived() {
return new BitArray(blocksReceived);
}
/**
* Add a BulkTransmitter to the list of BulkTransmitters. When a block comes in, we will tell each
* BulkTransmitter about it.
* @param bt The BulkTransmitter to register.
*/
synchronized void add(BulkTransmitter bt) {
if(transmitters == null)
transmitters = new BulkTransmitter[] { bt };
else {
transmitters = Arrays.copyOf(transmitters, transmitters.length+1);
transmitters[transmitters.length-1] = bt;
}
}
/**
* Called when a block has been received. Will copy the data from the provided buffer and store it.
* @param blockNum The block number.
* @param data The byte array from which to read the data.
* @param offset The start of the data in the buffer.
*/
void received(int blockNum, byte[] data, int offset, int length) {
if(blockNum > blocks) {
Logger.error(this, "Received block "+blockNum+" of "+blocks+" !");
return;
}
if(logMINOR)
Logger.minor(this, "Received block "+blockNum);
BulkTransmitter[] notifyBTs;
long fileOffset = (long)blockNum * (long)blockSize;
int bs = (int) Math.min(blockSize, size - fileOffset);
if(length < bs) {
String err = "Data too short! Should be "+bs+" actually "+length;
Logger.error(this, err+" for "+this);
abort(RetrievalException.PREMATURE_EOF, err);
return;
}
synchronized(this) {
if(blocksReceived.bitAt(blockNum)) return; // ignore
blocksReceived.setBit(blockNum, true); // assume the rest of the function succeeds
blocksReceivedCount++;
notifyBTs = transmitters;
}
try {
raf.pwrite(fileOffset, data, offset, bs);
} catch (Throwable t) {
Logger.error(this, "Failed to store received block "+blockNum+" on "+this+" : "+t, t);
abort(RetrievalException.IO_ERROR, t.toString());
}
if(notifyBTs == null) return;
for(BulkTransmitter notifyBT: notifyBTs) {
// Not a generic callback, so no catch{} guard
notifyBT.blockReceived(blockNum);
}
}
public void abort(int errCode, String why) {
if(logMINOR)
Logger.normal(this, "Aborting "+this+": "+errCode+" : "+why+" first missing is "+blocksReceived.firstZero(0), new Exception("debug"));
BulkTransmitter[] notifyBTs;
BulkReceiver notifyBR;
synchronized(this) {
_aborted = true;
_abortReason = errCode;
_abortDescription = why;
notifyBTs = transmitters;
notifyBR = recv;
}
if(notifyBTs != null) {
for(BulkTransmitter notifyBT: notifyBTs) {
notifyBT.onAborted();
}
}
if(notifyBR != null)
notifyBR.onAborted();
raf.close();
}
public synchronized boolean isAborted() {
return _aborted;
}
public boolean hasWholeFile() {
return blocksReceivedCount >= blocks;
}
public byte[] getBlockData(int blockNum) {
long fileOffset = (long)blockNum * (long)blockSize;
int bs = (int) Math.min(blockSize, size - fileOffset);
byte[] data = new byte[bs];
try {
raf.pread(fileOffset, data, 0, bs);
} catch (IOException e) {
Logger.error(this, "Failed to read stored block "+blockNum+" on "+this+" : "+e, e);
abort(RetrievalException.IO_ERROR, e.toString());
return null;
}
return data;
}
public synchronized void remove(BulkTransmitter remove) {
boolean found = false;
for(BulkTransmitter t: transmitters) {
if(t == remove) found = true;
}
if(!found) return;
BulkTransmitter[] newTrans = new BulkTransmitter[transmitters.length-1];
int j = 0;
for(BulkTransmitter t: transmitters) {
if(t == remove) continue;
newTrans[j++] = t;
}
transmitters = newTrans;
}
public int getAbortReason() {
return _abortReason;
}
public String getAbortDescription() {
return _abortDescription;
}
}