package freenet.client.async; import java.io.Serializable; import freenet.client.FetchContext; import freenet.keys.USK; import freenet.support.LogThresholdCallback; import freenet.support.Logger; import freenet.support.Logger.LogLevel; import freenet.support.io.NativeThread; /** * Not the actual fetcher. Just a tag associating a USK with the client that should be called when * the fetch has been done. Can be included in persistent requests. On startup, all USK fetches are * restarted, but this remains the same: the actual USKFetcher's are always transient. * * WARNING: Changing non-transient members on classes that are Serializable can result in * restarting downloads or losing uploads. * @author toad */ class USKFetcherTag implements ClientGetState, USKFetcherCallback, Serializable { private static final long serialVersionUID = 1L; /** The callback */ public final USKFetcherCallback callback; /** The original USK */ public final USK origUSK; /** The edition number found so far */ protected long edition; /** Persistent?? */ public final boolean persistent; /** Context */ public final FetchContext ctx; public final boolean keepLastData; /** Priority */ private short priority; private long token; private transient USKFetcher fetcher; private short pollingPriorityNormal; private short pollingPriorityProgress; private boolean finished; private final boolean ownFetchContext; private final boolean checkStoreOnly; private final int hashCode; private final boolean realTimeFlag; private USKFetcherTag(USK origUSK, USKFetcherCallback callback, boolean persistent, boolean realTime, FetchContext ctx, boolean keepLastData, long token, boolean hasOwnFetchContext, boolean checkStoreOnly) { this.callback = callback; this.origUSK = origUSK; this.edition = origUSK.suggestedEdition; this.persistent = persistent; this.ctx = ctx; this.keepLastData = keepLastData; this.token = token; this.ownFetchContext = hasOwnFetchContext; this.realTimeFlag = realTime; pollingPriorityNormal = callback.getPollingPriorityNormal(); pollingPriorityProgress = callback.getPollingPriorityProgress(); priority = pollingPriorityNormal; this.checkStoreOnly = checkStoreOnly; this.hashCode = super.hashCode(); if(logMINOR) Logger.minor(this, "Created tag for "+origUSK+" and "+callback+" : "+this); } @Override public int hashCode() { return hashCode; } /** * For a persistent request, the caller must call removeFromDatabase() when finished. Note that the caller is responsible for * deleting the USKFetcherCallback and the FetchContext. * @param usk * @param callback * @param persistent * @param container * @param ctx * @param keepLast * @param token * @return */ public static USKFetcherTag create(USK usk, USKFetcherCallback callback, boolean persistent, boolean realTime, FetchContext ctx, boolean keepLast, int token, boolean hasOwnFetchContext, boolean checkStoreOnly) { USKFetcherTag tag = new USKFetcherTag(usk, callback, persistent, realTime, ctx, keepLast, token, hasOwnFetchContext, checkStoreOnly); return tag; } synchronized void updatedEdition(long ed) { if(edition < ed) edition = ed; } public void start(USKManager manager, ClientContext context) { USK usk = origUSK; if(usk.suggestedEdition < edition) usk = usk.copy(edition); else if(persistent) // Copy it to avoid deactivation issues usk = usk.copy(); fetcher = manager.getFetcher(usk, ctx, new USKFetcherWrapper(usk, priority, realTimeFlag ? USKManager.rcRT : USKManager.rcBulk), keepLastData, checkStoreOnly); fetcher.addCallback(this); fetcher.schedule(context); // non-persistent if(logMINOR) Logger.minor(this, "Starting "+fetcher+" for "+this); } @Override public void cancel(ClientContext context) { USKFetcher f = fetcher; if(f != null) fetcher.cancel(context); synchronized(this) { if(finished) { if(logMINOR) Logger.minor(this, "Already cancelled "+this); return; } finished = true; } if(f != null) Logger.error(this, "cancel() for "+fetcher+" did not set finished on "+this+" ???"); } @Override public long getToken() { return token; } @Override public void schedule(ClientContext context) { start(context.uskManager, context); } @Override public void onCancelled(ClientContext context) { if(logMINOR) Logger.minor(this, "Cancelled on "+this); synchronized(this) { finished = true; } if(persistent) { // This can be called from USKFetcher, in which case we want to run on the // PersistentJobRunner. try { context.jobRunner.queue(new PersistentJob() { @Override public boolean run(ClientContext context) { if(callback instanceof USKFetcherTagCallback) ((USKFetcherTagCallback)callback).setTag(USKFetcherTag.this, context); callback.onCancelled(context); return false; } }, NativeThread.HIGH_PRIORITY); } catch (PersistenceDisabledException e) { // Impossible. } } else { if(callback instanceof USKFetcherTagCallback) ((USKFetcherTagCallback)callback).setTag(USKFetcherTag.this, context); callback.onCancelled(context); } } @Override public void onFailure(ClientContext context) { if(logMINOR) Logger.minor(this, "Failed on "+this); synchronized(this) { if(finished) { Logger.error(this, "onFailure called after finish on "+this, new Exception("error")); return; } finished = true; } if(persistent) { try { context.jobRunner.queue(new PersistentJob() { @Override public boolean run(ClientContext context) { if(callback instanceof USKFetcherTagCallback) ((USKFetcherTagCallback)callback).setTag(USKFetcherTag.this, context); callback.onFailure(context); return true; } }, NativeThread.HIGH_PRIORITY); } catch (PersistenceDisabledException e) { // Impossible. } } else { if(callback instanceof USKFetcherTagCallback) ((USKFetcherTagCallback)callback).setTag(USKFetcherTag.this, context); callback.onFailure(context); } } @Override public short getPollingPriorityNormal() { return pollingPriorityNormal; } @Override public short getPollingPriorityProgress() { return pollingPriorityProgress; } @Override public void onFoundEdition(final long l, final USK key, ClientContext context, final boolean metadata, final short codec, final byte[] data, final boolean newKnownGood, final boolean newSlotToo) { if(logMINOR) Logger.minor(this, "Found edition "+l+" on "+this); synchronized(this) { if(fetcher == null) { Logger.error(this, "onFoundEdition but fetcher is null - isn't onFoundEdition() terminal for USKFetcherCallback's??", new Exception("debug")); } if(finished) { Logger.error(this, "onFoundEdition called after finish on "+this, new Exception("error")); return; } finished = true; fetcher = null; } if(persistent) { try { context.jobRunner.queue(new PersistentJob() { @Override public boolean run(ClientContext context) { if(callback instanceof USKFetcherTagCallback) ((USKFetcherTagCallback)callback).setTag(USKFetcherTag.this, context); callback.onFoundEdition(l, key, context, metadata, codec, data, newKnownGood, newSlotToo); return false; } }, NativeThread.HIGH_PRIORITY); } catch (PersistenceDisabledException e) { // Impossible. } } else { if(callback instanceof USKFetcherTagCallback) ((USKFetcherTagCallback)callback).setTag(USKFetcherTag.this, context); callback.onFoundEdition(l, key, context, metadata, codec, data, newKnownGood, newSlotToo); } } public final boolean isFinished() { return finished; } private static volatile boolean logMINOR; // private static volatile boolean logDEBUG; static { Logger.registerLogThresholdCallback(new LogThresholdCallback() { @Override public void shouldUpdate() { logMINOR = Logger.shouldLog(LogLevel.MINOR, this); // logDEBUG = Logger.shouldLog(LogLevel.MINOR, this); } }); } @Override public void onResume(ClientContext context) { if(finished) return; start(context.uskManager, context); } @Override public void onShutdown(ClientContext context) { // Ignore. } }