/* 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.client.async;
import java.io.IOException;
import java.io.Serializable;
import freenet.client.ClientMetadata;
import freenet.client.FetchContext;
import freenet.client.FetchException;
import freenet.client.FetchException.FetchExceptionMode;
import freenet.client.FetchResult;
import freenet.keys.ClientKey;
import freenet.keys.ClientKeyBlock;
import freenet.keys.KeyDecodeException;
import freenet.keys.TooBigException;
import freenet.node.LowLevelGetException;
import freenet.node.SendableRequestItem;
import freenet.support.LogThresholdCallback;
import freenet.support.Logger;
import freenet.support.Logger.LogLevel;
import freenet.support.api.Bucket;
import freenet.support.io.InsufficientDiskSpaceException;
/**
* Fetch a single block file. Used directly for very simple fetches, but also base class for
* SingleFileFetcher.
*
* WARNING: Changing non-transient members on classes that are Serializable can result in
* restarting downloads or losing uploads.
*/
public class SimpleSingleFileFetcher extends BaseSingleFileFetcher implements ClientGetState, Serializable {
private static final long serialVersionUID = 1L;
SimpleSingleFileFetcher(ClientKey key, int maxRetries, FetchContext ctx, ClientRequester parent,
GetCompletionCallback rcb, boolean isEssential, boolean dontAdd, long l, ClientContext context, boolean deleteFetchContext, boolean realTimeFlag) {
super(key, maxRetries, ctx, parent, deleteFetchContext, realTimeFlag);
this.rcb = rcb;
this.token = l;
if(!dontAdd) {
if(isEssential)
parent.addMustSucceedBlocks(1);
else
parent.addBlock();
parent.notifyClients(context);
}
}
final GetCompletionCallback rcb;
final long token;
private static volatile boolean logMINOR;
static {
Logger.registerLogThresholdCallback(new LogThresholdCallback(){
@Override
public void shouldUpdate(){
logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
}
});
}
// Translate it, then call the real onFailure
@Override
public void onFailure(LowLevelGetException e, SendableRequestItem reqTokenIgnored, ClientContext context) {
onFailure(translateException(e), false, context);
}
// Real onFailure
protected void onFailure(FetchException e, boolean forceFatal, ClientContext context) {
if(logMINOR) Logger.minor(this, "onFailure( "+e+" , "+forceFatal+")", e);
if(parent.isCancelled() || cancelled) {
if(logMINOR) Logger.minor(this, "Failing: cancelled");
e = new FetchException(FetchExceptionMode.CANCELLED);
forceFatal = true;
}
if(!(e.isFatal() || forceFatal) ) {
if(retry(context)) {
if(logMINOR) Logger.minor(this, "Retrying");
return;
}
}
// :(
unregisterAll(context);
synchronized(this) {
finished = true;
}
if(e.isFatal() || forceFatal)
parent.fatallyFailedBlock(context);
else
parent.failedBlock(context);
rcb.onFailure(e, this, context);
}
/** Will be overridden by SingleFileFetcher */
protected void onSuccess(FetchResult data, ClientContext context) {
if(parent.isCancelled()) {
data.asBucket().free();
onFailure(new FetchException(FetchExceptionMode.CANCELLED), false, context);
return;
}
rcb.onSuccess(new SingleFileStreamGenerator(data.asBucket(), persistent), data.getMetadata(), null, this, context);
}
@Override
public void onSuccess(ClientKeyBlock block, boolean fromStore, Object reqTokenIgnored, ClientContext context) {
if(parent instanceof ClientGetter)
((ClientGetter)parent).addKeyToBinaryBlob(block, context);
Bucket data = extract(block, context);
if(data == null) return; // failed
context.uskManager.checkUSK(key.getURI(), fromStore, block.isMetadata());
if(!block.isMetadata()) {
onSuccess(new FetchResult(new ClientMetadata(null), data), context);
} else {
onFailure(new FetchException(FetchExceptionMode.INVALID_METADATA, "Metadata where expected data"), false, context);
}
}
/** Convert a ClientKeyBlock to a Bucket. If an error occurs, report it via onFailure
* and return null.
*/
protected Bucket extract(ClientKeyBlock block, ClientContext context) {
Bucket data;
try {
data = block.decode(context.getBucketFactory(parent.persistent()), (int)(Math.min(ctx.maxOutputLength, Integer.MAX_VALUE)), false);
} catch (KeyDecodeException e1) {
if(logMINOR)
Logger.minor(this, "Decode failure: "+e1, e1);
onFailure(new FetchException(FetchExceptionMode.BLOCK_DECODE_ERROR, e1.getMessage()), false, context);
return null;
} catch (TooBigException e) {
onFailure(new FetchException(FetchExceptionMode.TOO_BIG, e), false, context);
return null;
} catch (InsufficientDiskSpaceException e) {
onFailure(new FetchException(FetchExceptionMode.NOT_ENOUGH_DISK_SPACE), false, context);
return null;
} catch (IOException e) {
Logger.error(this, "Could not capture data - disk full?: "+e, e);
onFailure(new FetchException(FetchExceptionMode.BUCKET_ERROR, e), false, context);
return null;
}
return data;
}
/** getToken() is not supported */
@Override
public long getToken() {
return token;
}
@Override
public void onFailed(KeyListenerConstructionException e, ClientContext context) {
onFailure(e.getFetchException(), false, context);
}
@Override
public void cancel(ClientContext context) {
super.cancel(context);
rcb.onFailure(new FetchException(FetchExceptionMode.CANCELLED), this, context);
}
@Override
protected void notFoundInStore(ClientContext context) {
this.onFailure(new FetchException(FetchExceptionMode.DATA_NOT_FOUND), true, context);
}
@Override
protected void onBlockDecodeError(SendableRequestItem token, ClientContext context) {
onFailure(new FetchException(FetchExceptionMode.BLOCK_DECODE_ERROR, "Could not decode block with the URI given, probably invalid as inserted, possible the URI is wrong"), true, context);
}
@Override
public void onShutdown(ClientContext context) {
// Do nothing.
}
@Override
protected ClientGetState getClientGetState() {
return this;
}
}