package freenet.client.async;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.ArrayList;
import freenet.client.FailureCodeTracker;
import freenet.client.InsertContext;
import freenet.client.InsertException;
import freenet.client.InsertException.InsertExceptionMode;
import freenet.keys.CHKBlock;
import freenet.keys.ClientKey;
import freenet.keys.Key;
import freenet.keys.KeyBlock;
import freenet.keys.SSKBlock;
import freenet.node.LowLevelPutException;
import freenet.node.RequestClient;
import freenet.node.SendableRequestItem;
import freenet.node.SimpleSendableInsert;
import freenet.support.Logger;
import freenet.support.Logger.LogLevel;
import freenet.support.api.Bucket;
public class BinaryBlobInserter implements ClientPutState {
final ClientPutter parent;
final RequestClient clientContext;
final MySendableInsert[] inserters;
final FailureCodeTracker errors;
final int maxRetries;
final int consecutiveRNFsCountAsSuccess;
private boolean logMINOR;
private int completedBlocks;
private int succeededBlocks;
private boolean fatal;
final InsertContext ctx;
final boolean realTimeFlag;
BinaryBlobInserter(Bucket blob, ClientPutter parent, RequestClient clientContext, boolean tolerant, short prioClass, InsertContext ctx, ClientContext context)
throws IOException, BinaryBlobFormatException {
logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
this.ctx = ctx;
this.maxRetries = ctx.maxInsertRetries;
this.consecutiveRNFsCountAsSuccess = ctx.consecutiveRNFsCountAsSuccess;
this.parent = parent;
this.clientContext = clientContext;
this.errors = new FailureCodeTracker(true);
this.realTimeFlag = clientContext.realTimeFlag();
DataInputStream dis = new DataInputStream(blob.getInputStream());
BlockSet blocks = new SimpleBlockSet();
try {
BinaryBlob.readBinaryBlob(dis, blocks, tolerant);
} finally {
dis.close();
}
ArrayList<MySendableInsert> myInserters = new ArrayList<MySendableInsert>();
int x=0;
for(Key key: blocks.keys()) {
KeyBlock block = blocks.get(key);
MySendableInsert inserter =
new MySendableInsert(x++, block, prioClass, getScheduler(block, context), clientContext);
myInserters.add(inserter);
}
inserters = myInserters.toArray(new MySendableInsert[myInserters.size()]);
parent.addMustSucceedBlocks(inserters.length);
parent.notifyClients(context);
}
private ClientRequestScheduler getScheduler(KeyBlock block, ClientContext context) {
if(block instanceof CHKBlock)
return context.getChkInsertScheduler(realTimeFlag);
else if(block instanceof SSKBlock)
return context.getSskInsertScheduler(realTimeFlag);
else throw new IllegalArgumentException("Unknown block type "+block.getClass()+" : "+block);
}
@Override
public void cancel(ClientContext context) {
for(MySendableInsert inserter: inserters) {
if(inserter != null)
inserter.cancel(context);
}
parent.onFailure(new InsertException(InsertExceptionMode.CANCELLED), this, context);
}
@Override
public BaseClientPutter getParent() {
return parent;
}
@Override
public Object getToken() {
return clientContext;
}
@Override
public void schedule(ClientContext context) throws InsertException {
for(MySendableInsert inserter: inserters) {
inserter.schedule();
}
}
class MySendableInsert extends SimpleSendableInsert {
private static final long serialVersionUID = 1L;
final int blockNum;
private int consecutiveRNFs;
private int retries;
public MySendableInsert(int i, KeyBlock block, short prioClass, ClientRequestScheduler scheduler, RequestClient client) {
super(block, prioClass, client, scheduler);
this.blockNum = i;
}
@Override
public void onSuccess(SendableRequestItem keyNum, ClientKey key, ClientContext context) {
synchronized(this) {
if(inserters[blockNum] == null) return;
inserters[blockNum] = null;
completedBlocks++;
succeededBlocks++;
}
parent.completedBlock(false, context);
maybeFinish(context);
}
// FIXME duplicated code from SingleBlockInserter
// FIXME combine it somehow
@Override
public void onFailure(LowLevelPutException e, SendableRequestItem keyNum, ClientContext context) {
synchronized(BinaryBlobInserter.this) {
if(inserters[blockNum] == null) return;
}
if(parent.isCancelled()) {
fail(new InsertException(InsertExceptionMode.CANCELLED), true, context);
return;
}
logMINOR = Logger.shouldLog(LogLevel.MINOR, BinaryBlobInserter.this);
switch(e.code) {
case LowLevelPutException.COLLISION:
fail(new InsertException(InsertExceptionMode.COLLISION), false, context);
break;
case LowLevelPutException.INTERNAL_ERROR:
errors.inc(InsertExceptionMode.INTERNAL_ERROR);
break;
case LowLevelPutException.REJECTED_OVERLOAD:
errors.inc(InsertExceptionMode.REJECTED_OVERLOAD);
break;
case LowLevelPutException.ROUTE_NOT_FOUND:
errors.inc(InsertExceptionMode.ROUTE_NOT_FOUND);
break;
case LowLevelPutException.ROUTE_REALLY_NOT_FOUND:
errors.inc(InsertExceptionMode.ROUTE_REALLY_NOT_FOUND);
break;
default:
Logger.error(this, "Unknown LowLevelPutException code: "+e.code);
errors.inc(InsertExceptionMode.INTERNAL_ERROR);
}
if(e.code == LowLevelPutException.ROUTE_NOT_FOUND) {
consecutiveRNFs++;
if(logMINOR) Logger.minor(this, "Consecutive RNFs: "+consecutiveRNFs+" / "+consecutiveRNFsCountAsSuccess);
if(consecutiveRNFs == consecutiveRNFsCountAsSuccess) {
if(logMINOR) Logger.minor(this, "Consecutive RNFs: "+consecutiveRNFs+" - counting as success");
onSuccess(keyNum, null, context);
return;
}
} else
consecutiveRNFs = 0;
if(logMINOR) Logger.minor(this, "Failed: "+e);
retries++;
if((retries > maxRetries) && (maxRetries != -1)) {
fail(InsertException.construct(errors), false, context);
return;
}
this.clearWakeupTime(context);
// Retry *this block*
this.schedule();
}
private void fail(InsertException e, boolean fatal, ClientContext context) {
synchronized(BinaryBlobInserter.this) {
if(inserters[blockNum] == null) return;
inserters[blockNum] = null;
completedBlocks++;
if(fatal) BinaryBlobInserter.this.fatal = true;
}
if(fatal)
parent.fatallyFailedBlock(context);
else
parent.failedBlock(context);
maybeFinish(context);
}
}
public void maybeFinish(ClientContext context) {
boolean success;
boolean wasFatal;
synchronized(this) {
if(completedBlocks != inserters.length)
return;
success = completedBlocks == succeededBlocks;
wasFatal = fatal;
}
if(success) {
parent.onSuccess(this, context);
} else if(wasFatal)
parent.onFailure(new InsertException(InsertExceptionMode.FATAL_ERRORS_IN_BLOCKS, errors, null), this, context);
else
parent.onFailure(new InsertException(InsertExceptionMode.TOO_MANY_RETRIES_IN_BLOCKS, errors, null), this, context);
}
@Override
public void onResume(ClientContext context) throws InsertException {
// TODO binary blob inserter isn't persistent yet, right?
throw new InsertException(InsertExceptionMode.INTERNAL_ERROR, "Persistence not supported yet", null);
}
@Override
public void onShutdown(ClientContext context) {
// Ignore.
}
}