/* 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 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; } }