/* 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 static java.util.concurrent.TimeUnit.SECONDS; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Map; import java.util.TreeMap; import java.util.WeakHashMap; import freenet.client.FetchContext; import freenet.client.FetchException; import freenet.client.FetchResult; import freenet.client.HighLevelSimpleClient; import freenet.client.NullClientCallback; import freenet.clients.http.FProxyToadlet; import freenet.keys.FreenetURI; import freenet.keys.USK; import freenet.node.NodeClientCore; import freenet.node.RequestClient; import freenet.node.RequestClientBuilder; import freenet.node.RequestStarter; import freenet.support.Executor; import freenet.support.LRUMap; import freenet.support.LogThresholdCallback; import freenet.support.Logger; import freenet.support.Logger.LogLevel; import freenet.support.io.NullBucket; /** * Tracks the latest version of every known USK. * Also does auto-updates. * * Note that this is a transient class. It is not stored in the database. All fetchers and subscriptions are likewise transient. * * Plugin authors: Don't construct it yourself, get it from ClientContext from NodeClientCore. */ public class USKManager { private static volatile boolean logDEBUG; private static volatile boolean logMINOR; static RequestClient rcRT = new RequestClientBuilder().realTime().build(); static RequestClient rcBulk = new RequestClientBuilder().build(); static { Logger.registerLogThresholdCallback(new LogThresholdCallback() { @Override public void shouldUpdate() { logMINOR = Logger.shouldLog(LogLevel.MINOR, this); logDEBUG = Logger.shouldLog(LogLevel.DEBUG, this); } }); } /** Latest version successfully fetched by blanked-edition-number USK */ final Map<USK, Long> latestKnownGoodByClearUSK; /** Latest SSK slot known to be by the author by blanked-edition-number USK */ final Map<USK, Long> latestSlotByClearUSK; /** Subscribers by clear USK */ final Map<USK, USKCallback[]> subscribersByClearUSK; /** Backgrounded USKFetchers by USK. These have pollForever=true and are only * created when subscribe(,true) is called. */ final Map<USK, USKFetcher> backgroundFetchersByClearUSK; /** Temporary fetchers, started when a USK (with a positive edition number) is * fetched. These have pollForever=false. Keyed by the clear USK, i.e. one per * USK, not one per {USK, start edition}, unlike fetchersByUSK. */ final LRUMap<USK, USKFetcher> temporaryBackgroundFetchersLRU; /** Temporary fetchers where we have been asked to prefetch content. We track * the time we last had a new last-slot, so that if there is no new last-slot * found in 60 seconds, we start prefetching. We delete the entry when the * fetcher finishes. * FIXME this should be TreeMap-based to prevent hash collision DoS'es. * But we also need it to be weak ... how to implement? */ final WeakHashMap<USK, Long> temporaryBackgroundFetchersPrefetch; final FetchContext backgroundFetchContext; final FetchContext backgroundFetchContextIgnoreDBR; /** This one actually fetches data */ final FetchContext realFetchContext; final Executor executor; private ClientContext context; public USKManager(NodeClientCore core) { HighLevelSimpleClient client = core.makeClient(RequestStarter.UPDATE_PRIORITY_CLASS, false, false); client.setMaxIntermediateLength(FProxyToadlet.MAX_LENGTH_NO_PROGRESS); client.setMaxLength(FProxyToadlet.MAX_LENGTH_NO_PROGRESS); backgroundFetchContext = client.getFetchContext(); backgroundFetchContext.followRedirects = false; backgroundFetchContextIgnoreDBR = backgroundFetchContext.clone(); backgroundFetchContextIgnoreDBR.ignoreUSKDatehints = true; realFetchContext = client.getFetchContext(); // Performance: I'm pretty sure there is no spatial locality in the underlying data, so it's okay to use the FAST_COMPARATOR here. // That is, even if two USKs are by the same author, they won't necessarily be updated or polled at the same time. latestKnownGoodByClearUSK = new TreeMap<USK, Long>(USK.FAST_COMPARATOR); latestSlotByClearUSK = new TreeMap<USK, Long>(USK.FAST_COMPARATOR); subscribersByClearUSK = new TreeMap<USK, USKCallback[]>(USK.FAST_COMPARATOR); backgroundFetchersByClearUSK = new TreeMap<USK, USKFetcher>(USK.FAST_COMPARATOR); temporaryBackgroundFetchersLRU = LRUMap.createSafeMap(USK.FAST_COMPARATOR); temporaryBackgroundFetchersPrefetch = new WeakHashMap<USK, Long>(); executor = core.getExecutor(); } public void init(ClientContext context) { this.context = context; } /** * Look up the latest known working version of the given USK. * @return The latest known edition number, or -1. */ public synchronized long lookupKnownGood(USK usk) { Long l = latestKnownGoodByClearUSK.get(usk.clearCopy()); if(l != null) return l.longValue(); else return -1; } /** * Look up the latest SSK slot, whether the data it links to has been successfully * fetched or not, of the given USK. * @return The latest known edition number, or -1. */ public synchronized long lookupLatestSlot(USK usk) { Long l = latestSlotByClearUSK.get(usk.clearCopy()); if(l != null) return l.longValue(); else return -1; } public USKFetcherTag getFetcher(USK usk, FetchContext ctx, boolean keepLast, boolean persistent, boolean realTime, USKFetcherCallback callback, boolean ownFetchContext, ClientContext context, boolean checkStoreOnly) { return USKFetcherTag.create(usk, callback, persistent, realTime, ctx, keepLast, 0, ownFetchContext, checkStoreOnly || ctx.localRequestOnly); } USKFetcher getFetcher(USK usk, FetchContext ctx, ClientRequester requester, boolean keepLastData, boolean checkStoreOnly) { return new USKFetcher(usk, this, ctx, requester, 3, false, keepLastData, checkStoreOnly); } public USKFetcherTag getFetcherForInsertDontSchedule(USK usk, short prioClass, USKFetcherCallback cb, RequestClient client, ClientContext context, boolean persistent, boolean ignoreUSKDatehints) { FetchContext fctx = ignoreUSKDatehints ? backgroundFetchContextIgnoreDBR : backgroundFetchContext; return getFetcher(usk, persistent ? new FetchContext(fctx, FetchContext.IDENTICAL_MASK) : fctx, true, client.persistent(), client.realTimeFlag(), cb, true, context, false); } /** * A non-authoritative hint that a specific edition *might* exist. At the moment, * we just fetch the block. We do not fetch the contents, and it is possible that * USKFetcher's are also fetching the block. FIXME would it be more efficient to * pass it along to a USKFetcher? * @param usk * @param edition * @param context */ public void hintUpdate(USK usk, long edition, ClientContext context) { if(edition < lookupLatestSlot(usk)) return; FreenetURI uri = usk.copy(edition).getURI().sskForUSK(); final ClientGetter get = new ClientGetter(new NullClientCallback(rcBulk), uri, new FetchContext(backgroundFetchContext, FetchContext.IDENTICAL_MASK), RequestStarter.UPDATE_PRIORITY_CLASS, new NullBucket(), null, null); try { get.start(context); } catch (FetchException e) { // Ignore } } public void hintUpdate(FreenetURI uri, ClientContext context) throws MalformedURLException { hintUpdate(uri, context, RequestStarter.UPDATE_PRIORITY_CLASS); } /** * A non-authoritative hint that a specific edition *might* exist. At the moment, * we just fetch the block. We do not fetch the contents, and it is possible that * USKFetcher's are also fetching the block. FIXME would it be more efficient to * pass it along to a USKFetcher? * @param context * @throws MalformedURLException If the uri passed in is not a USK. */ public void hintUpdate(FreenetURI uri, ClientContext context, short priority) throws MalformedURLException { if(uri.getSuggestedEdition() < lookupLatestSlot(USK.create(uri))) { if(logMINOR) Logger.minor(this, "Ignoring hint because edition is "+uri.getSuggestedEdition()+" but latest is "+lookupLatestSlot(USK.create(uri))); return; } uri = uri.sskForUSK(); if(logMINOR) Logger.minor(this, "Doing hint fetch for "+uri); final ClientGetter get = new ClientGetter(new NullClientCallback(rcBulk), uri, new FetchContext(backgroundFetchContext, FetchContext.IDENTICAL_MASK), priority, new NullBucket(), null, null); try { get.start(context); } catch (FetchException e) { if(logMINOR) Logger.minor(this, "Cannot start hint fetch for "+uri+" : "+e, e); // Ignore } } public interface HintCallback { /** The SSK block exists. The USK tracker will have been updated. We * did not try to fetch the rest of the key. * @param origURI The original FreenetURI object. * @param token The token object passed in by the caller. */ void success(FreenetURI origURI, Object token); /** The SSK block does not exist. We got a DNF, DNF with RecentlyFailed, * check store only and it wasn't in the datastore etc. * @param origURI The original FreenetURI object. * @param token The token object passed in by the caller. * @param e The exception. */ void dnf(FreenetURI origURI, Object token, FetchException e); /** Some other error. We don't necessarily know that it doesn't exist. * @param origURI The original FreenetURI object. * @param token The token object passed in by the caller. * @param e The exception. */ void failed(FreenetURI origURI, Object token, FetchException e); } /** Simply check whether the block exists, in such a way that we don't fetch * the full content. If it does exist then the USK tracker, and therefore * any fetchers, will be updated. You can pass either an SSK or a USK. */ public void hintCheck(FreenetURI uri, final Object token, ClientContext context, short priority, final HintCallback cb) throws MalformedURLException { final FreenetURI origURI = uri; if(uri.isUSK()) uri = uri.sskForUSK(); if(logMINOR) Logger.minor(this, "Doing hint fetch for "+uri); final ClientGetter get = new ClientGetter(new ClientGetCallback() { @Override public void onSuccess(FetchResult result, ClientGetter state) { cb.success(origURI, token); } @Override public void onFailure(FetchException e, ClientGetter state) { if(e.isDataFound()) cb.success(origURI, token); else if(e.isDNF()) cb.dnf(origURI, token, e); else cb.failed(origURI, token, e); } @Override public void onResume(ClientContext context) { // Do nothing. } @Override public RequestClient getRequestClient() { return rcBulk; } }, uri, new FetchContext(backgroundFetchContext, FetchContext.IDENTICAL_MASK), priority, new NullBucket(), null, null); try { get.start(context); } catch (FetchException e) { if(logMINOR) Logger.minor(this, "Cannot start hint fetch for "+uri+" : "+e, e); if(e.isDataFound()) cb.success(origURI, token); else if(e.isDNF()) cb.dnf(origURI, token, e); else cb.failed(origURI, token, e); } } public void startTemporaryBackgroundFetcher(USK usk, ClientContext context, final FetchContext fctx, boolean prefetchContent, boolean realTimeFlag) { final USK clear = usk.clearCopy(); USKFetcher sched = null; ArrayList<USKFetcher> toCancel = null; synchronized(this) { // int x = 0; // for(USK key: backgroundFetchersByClearUSK.keySet()) { // System.err.println("Fetcher "+x+": "+key); // x++; // } USKFetcher f = temporaryBackgroundFetchersLRU.get(clear); if(f == null) { f = new USKFetcher(usk, this, fctx.ignoreUSKDatehints ? backgroundFetchContextIgnoreDBR : backgroundFetchContext, new USKFetcherWrapper(usk, RequestStarter.UPDATE_PRIORITY_CLASS, realTimeFlag ? rcRT : rcBulk), 3, false, false, false); sched = f; temporaryBackgroundFetchersLRU.push(clear, f); } else { f.addHintEdition(usk.suggestedEdition); } if(prefetchContent) { long fetchTime = -1; // If nothing in 60 seconds, try fetching the last known slot. long slot = lookupLatestSlot(clear); long good = lookupKnownGood(clear); if(slot > -1 && good != slot) fetchTime = System.currentTimeMillis(); temporaryBackgroundFetchersPrefetch.put(clear, fetchTime); if(logMINOR) Logger.minor(this, "Prefetch: set "+fetchTime+" for "+clear); schedulePrefetchChecker(); } temporaryBackgroundFetchersLRU.push(clear, f); while(temporaryBackgroundFetchersLRU.size() > NodeClientCore.getMaxBackgroundUSKFetchers()) { USKFetcher fetcher = temporaryBackgroundFetchersLRU.popValue(); temporaryBackgroundFetchersPrefetch.remove(fetcher.getOriginalUSK().clearCopy()); if(!fetcher.hasSubscribers()) { if(toCancel == null) toCancel = new ArrayList<USKFetcher>(2); toCancel.add(fetcher); } else { if(logMINOR) Logger.minor(this, "Allowing temporary background fetcher to continue as it has subscribers... "+fetcher); } } } final ArrayList<USKFetcher> cancelled = toCancel; final USKFetcher scheduleMe = sched; // This is just a prefetching method. so it should not unnecessarily delay the parent, nor should it take important locks. // So we should do the actual schedule/cancels off-thread. // However, the above is done on-thread because a lot of the time it will already be running. if(cancelled != null || sched != null) { executor.execute(new Runnable() { @Override public void run() { if(cancelled != null) { for(int i=0;i<cancelled.size();i++) { USKFetcher fetcher = cancelled.get(i); fetcher.cancel(USKManager.this.context); } } if(scheduleMe != null) scheduleMe.schedule(USKManager.this.context); } }); } } static final long PREFETCH_DELAY = SECONDS.toMillis(60); private void schedulePrefetchChecker() { context.ticker.queueTimedJob(prefetchChecker, "Check for USKs to prefetch", PREFETCH_DELAY, false, true); } private final Runnable prefetchChecker = new Runnable() { @Override public void run() { if(logDEBUG) Logger.debug(this, "Running prefetch checker..."); ArrayList<USK> toFetch = null; long now = System.currentTimeMillis(); boolean empty = true; synchronized(USKManager.this) { for(Map.Entry<USK, Long> entry : temporaryBackgroundFetchersPrefetch.entrySet()) { empty = false; if(entry.getValue() > 0 && now - entry.getValue() >= PREFETCH_DELAY) { if(toFetch == null) toFetch = new ArrayList<USK>(); USK clear = entry.getKey(); long l = lookupLatestSlot(clear); if(lookupKnownGood(clear) < l) toFetch.add(clear.copy(l)); entry.setValue(-1L); // Reset counter until new data comes in } else { if(logMINOR) Logger.minor(this, "Not prefetching: "+entry.getKey()+" : "+entry.getValue()); } } } if(toFetch == null) return; for(final USK key : toFetch) { final long l = key.suggestedEdition; if(logMINOR) Logger.minor(this, "Prefetching content for background fetch for edition "+l+" on "+key); FetchContext fctx = new FetchContext(realFetchContext, FetchContext.IDENTICAL_MASK); final ClientGetter get = new ClientGetter(new ClientGetCallback() { @Override public void onFailure(FetchException e, ClientGetter state) { if(e.newURI != null) { if(logMINOR) Logger.minor(this, "Prefetch succeeded with redirect for "+key); updateKnownGood(key, l, context); return; } else { if(logMINOR) Logger.minor(this, "Prefetch failed later: "+e+" for "+key, e); // Ignore } } @Override public void onSuccess(FetchResult result, ClientGetter state) { if(logMINOR) Logger.minor(this, "Prefetch succeeded for "+key); result.asBucket().free(); updateKnownGood(key, l, context); } @Override public void onResume(ClientContext context) { // Do nothing. Not persistent. } @Override public RequestClient getRequestClient() { return rcBulk; } }, key.getURI().sskForUSK() /* FIXME add getSSKURI() */, fctx, RequestStarter.UPDATE_PRIORITY_CLASS, new NullBucket(), null, null); try { get.start(context); } catch (FetchException e) { if(logMINOR) Logger.minor(this, "Prefetch failed: "+e, e); // Ignore } } if(!empty) schedulePrefetchChecker(); } }; void updateKnownGood(final USK origUSK, final long number, final ClientContext context) { if(logMINOR) Logger.minor(this, "Updating (known good) "+origUSK.getURI()+" : "+number); USK clear = origUSK.clearCopy(); final USKCallback[] callbacks; boolean newSlot = false; synchronized(this) { Long l = latestKnownGoodByClearUSK.get(clear); if(logMINOR) Logger.minor(this, "Old known good: "+l); if((l == null) || (number > l.longValue())) { l = Long.valueOf(number); latestKnownGoodByClearUSK.put(clear, l); if(logMINOR) Logger.minor(this, "Put "+number); } else return; // If it's in KnownGood, it will also be in Slot l = latestSlotByClearUSK.get(clear); if(logMINOR) Logger.minor(this, "Old slot: "+l); if((l == null) || (number > l.longValue())) { l = Long.valueOf(number); latestSlotByClearUSK.put(clear, l); if(logMINOR) Logger.minor(this, "Put "+number); newSlot = true; } callbacks = subscribersByClearUSK.get(clear); } if(callbacks != null) { // Run off-thread, because of locking, and because client callbacks may take some time final USK usk = origUSK.copy(number); final boolean newSlotToo = newSlot; for(final USKCallback callback : callbacks) context.mainExecutor.execute(new Runnable() { @Override public void run() { callback.onFoundEdition(number, usk, // non-persistent context, false, (short)-1, null, true, newSlotToo); } }, "USKManager callback executor for " +callback); } } void updateSlot(final USK origUSK, final long number, final ClientContext context) { if(logMINOR) Logger.minor(this, "Updating (slot) "+origUSK.getURI()+" : "+number); USK clear = origUSK.clearCopy(); final USKCallback[] callbacks; synchronized(this) { Long l = latestSlotByClearUSK.get(clear); if(logMINOR) Logger.minor(this, "Old slot: "+l); if((l == null) || (number > l.longValue())) { l = Long.valueOf(number); latestSlotByClearUSK.put(clear, l); if(logMINOR) Logger.minor(this, "Put "+number); } else return; callbacks = subscribersByClearUSK.get(clear); if(temporaryBackgroundFetchersPrefetch.containsKey(clear)) { temporaryBackgroundFetchersPrefetch.put(clear, System.currentTimeMillis()); schedulePrefetchChecker(); } } if(callbacks != null) { // Run off-thread, because of locking, and because client callbacks may take some time final USK usk = origUSK.copy(number); for(final USKCallback callback : callbacks) context.mainExecutor.execute(new Runnable() { @Override public void run() { callback.onFoundEdition(number, usk, // non-persistent context, false, (short)-1, null, false, false); } }, "USKManager callback executor for " +callback); } } /** Subscribe to a given USK, and poll it in the background, but only * report new editions when we've been through a round and are confident * that we won't find more in the near future. Note that it will ignore * KnownGood, it only cares about latest slot. * @return The proxy object which was actually subscribed. The caller MUST * record this and pass it in to unsubscribe() when unsubscribing. * */ public USKSparseProxyCallback subscribeSparse(USK origUSK, USKCallback cb, boolean ignoreUSKDatehints, RequestClient client) { USKSparseProxyCallback proxy = new USKSparseProxyCallback(cb, origUSK); subscribe(origUSK, proxy, true, ignoreUSKDatehints, client); return proxy; } public USKSparseProxyCallback subscribeSparse(USK origUSK, USKCallback cb, RequestClient client) { return subscribeSparse(origUSK, cb, false, client); } /** * Subscribe to a given USK. Callback will be notified when it is * updated. Note that this does not imply that the USK will be * checked on a regular basis, unless runBackgroundFetch=true. */ public void subscribe(USK origUSK, USKCallback cb, boolean runBackgroundFetch, boolean ignoreUSKDatehints, RequestClient client) { if(logMINOR) Logger.minor(this, "Subscribing to "+origUSK+" for "+cb); if(client.persistent()) throw new UnsupportedOperationException("USKManager subscriptions cannot be persistent"); USKFetcher sched = null; long ed = origUSK.suggestedEdition; if(ed < 0) { Logger.error(this, "Subscribing to USK with negative edition number: "+ed); ed = -ed; } long curEd; curEd = lookupLatestSlot(origUSK); long goodEd; goodEd = lookupKnownGood(origUSK); synchronized(this) { USK clear = origUSK.clearCopy(); USKCallback[] callbacks = subscribersByClearUSK.get(clear); if(callbacks == null) { callbacks = new USKCallback[] { cb }; } else { boolean mustAdd = true; for(USKCallback callback: callbacks) { if(callback == cb) { // Already subscribed. // But it may still be waiting for the callback. if(!(curEd > ed || goodEd > ed)) return; mustAdd = false; } } if(mustAdd) { callbacks = Arrays.copyOf(callbacks, callbacks.length+1); callbacks[callbacks.length-1] = cb; } } subscribersByClearUSK.put(clear, callbacks); if(runBackgroundFetch) { USKFetcher f = backgroundFetchersByClearUSK.get(clear); if(f == null) { f = new USKFetcher(origUSK, this, ignoreUSKDatehints ? backgroundFetchContextIgnoreDBR : backgroundFetchContext, new USKFetcherWrapper(origUSK, RequestStarter.UPDATE_PRIORITY_CLASS, client), 3, true, false, false); sched = f; backgroundFetchersByClearUSK.put(clear, f); } f.addSubscriber(cb, origUSK.suggestedEdition); } } if(goodEd > ed) cb.onFoundEdition(goodEd, origUSK.copy(curEd), context, false, (short)-1, null, true, curEd > ed); else if(curEd > ed) cb.onFoundEdition(curEd, origUSK.copy(curEd), context, false, (short)-1, null, false, false); final USKFetcher fetcher = sched; if(fetcher != null) { executor.execute(new Runnable() { @Override public void run() { if(logMINOR) Logger.minor(this, "Starting "+fetcher); fetcher.schedule(context); } }, "USKManager.schedule for "+fetcher); } } public void subscribe(USK origUSK, USKCallback cb, boolean runBackgroundFetch, RequestClient client) { subscribe(origUSK, cb, runBackgroundFetch, false, client); } public void unsubscribe(USK origUSK, USKCallback cb) { USKFetcher toCancel = null; synchronized(this) { USK clear = origUSK.clearCopy(); USKCallback[] callbacks = subscribersByClearUSK.get(clear); if(callbacks == null){ // maybe we should throw something ? shall we allow multiple unsubscriptions ? if(logMINOR) Logger.minor(this, "No longer subscribed"); return; } int j=0; for(USKCallback c: callbacks) { if((c != null) && (c != cb)) { callbacks[j++] = c; } } USKCallback[] newCallbacks = Arrays.copyOf(callbacks, j); if(newCallbacks.length > 0) subscribersByClearUSK.put(clear, newCallbacks); else{ subscribersByClearUSK.remove(clear); } USKFetcher f = backgroundFetchersByClearUSK.get(clear); if(f != null) { f.removeSubscriber(cb, context); if(!f.hasSubscribers()) { toCancel = f; backgroundFetchersByClearUSK.remove(clear); } } // Temporary background fetchers run once and then die. // They do not care about callbacks. } if(toCancel != null) { toCancel.cancel(context); } else { if(logMINOR) Logger.minor(this, "Not found unsubscribing: "+cb+" for "+origUSK); } } /** * Subscribe to a USK. When it is updated, the content will be fetched (subject to the limits in fctx), * and returned to the callback. If we are asked to do a background fetch, we will only fetch editions * when we are fairly confident there are no more to fetch. * @param origUSK The USK to poll. * @param cb Callback, called when we have downloaded a new key. * @param runBackgroundFetch If true, start a background fetcher for the key, which will run * forever until we unsubscribe. Note that internally we use subscribeSparse() in this case, * i.e. we will only download editions which we are confident about. * @param fctx Fetcher context for actually fetching the keys. Not used by the USK polling. * @param prio Priority for fetching the content (see constants in RequestScheduler). * @return */ public USKRetriever subscribeContent(USK origUSK, USKRetrieverCallback cb, boolean runBackgroundFetch, FetchContext fctx, short prio, RequestClient client) { USKRetriever ret = new USKRetriever(fctx, prio, client, cb, origUSK); USKCallback toSub = ret; if(logMINOR) Logger.minor(this, "Subscribing to "+origUSK+" for "+cb); if(runBackgroundFetch) { USKSparseProxyCallback proxy = new USKSparseProxyCallback(ret, origUSK); ret.setProxy(proxy); toSub = proxy; } subscribe(origUSK, toSub, runBackgroundFetch, fctx.ignoreUSKDatehints, client); return ret; } /** * Subscribe to a USK with a custom FetchContext. This is "off the books", * i.e. the background fetcher isn't started by subscribe(). */ public USKRetriever subscribeContentCustom(USK origUSK, USKRetrieverCallback cb, FetchContext fctx, short prio, RequestClient client) { USKRetriever ret = new USKRetriever(fctx, prio, client, cb, origUSK); USKCallback toSub = ret; if(logMINOR) Logger.minor(this, "Subscribing to "+origUSK+" for "+cb); USKSparseProxyCallback proxy = new USKSparseProxyCallback(ret, origUSK); ret.setProxy(proxy); toSub = proxy; /* runBackgroundFetch=false -> ignoreUSKDatehints unused */ subscribe(origUSK, toSub, false, client); USKFetcher f = new USKFetcher(origUSK, this, fctx, new USKFetcherWrapper(origUSK, prio, client), 3, true, false, false); ret.setFetcher(f); return ret; } public void unsubscribeContent(USK origUSK, USKRetriever ret, boolean runBackgroundFetch) { ret.unsubscribe(this); } // REMOVE: DO NOT Synchronize! ... debugging only. /** * The result of that method will be displayed on the Statistic Toadlet : it will help catching #1147 * Afterwards it should be removed: it's not usefull :) * @return the number of BackgroundFetchers started by USKManager */ public int getBackgroundFetcherByUSKSize(){ return backgroundFetchersByClearUSK.size(); } /** * The result of that method will be displayed on the Statistic Toadlet : it will help catching #1147 * Afterwards it should be removed: it's not usefull :) * @return the size of temporaryBackgroundFetchersLRU */ public int getTemporaryBackgroundFetchersLRU(){ return temporaryBackgroundFetchersLRU.size(); } public void onFinished(USKFetcher fetcher) { onFinished(fetcher, false); } public void onFinished(USKFetcher fetcher, boolean ignoreError) { USK orig = fetcher.getOriginalUSK(); USK clear = orig.clearCopy(); synchronized(this) { if(backgroundFetchersByClearUSK.get(clear) == fetcher) { backgroundFetchersByClearUSK.remove(clear); if(!ignoreError) { // This shouldn't happen, it's a sanity check: the only way we get cancelled is from USKManager, which removes us before calling cancel(). Logger.error(this, "onCancelled for "+fetcher+" - was still registered, how did this happen??", new Exception("debug")); } } if(temporaryBackgroundFetchersLRU.get(clear) == fetcher) { temporaryBackgroundFetchersLRU.removeKey(clear); temporaryBackgroundFetchersPrefetch.remove(clear); } } } public boolean persistent() { return false; } ClientContext getContext() { return context; } public void checkUSK(FreenetURI uri, boolean persistent, boolean isMetadata) { try { FreenetURI uu; if(uri.isSSK() && uri.isSSKForUSK()) { uu = uri.setMetaString(null).uskForSSK(); } else if(uri.isUSK()) { uu = uri; } else { return; } USK usk = USK.create(uu); if(!isMetadata) context.uskManager.updateKnownGood(usk, uu.getSuggestedEdition(), context); else // We don't know whether the metadata is fetchable. // FIXME add a callback so if the rest of the request completes we updateKnownGood(). context.uskManager.updateSlot(usk, uu.getSuggestedEdition(), context); } catch (MalformedURLException e) { Logger.error(this, "Caught "+e, e); } catch (Throwable t) { // Don't let the USK hint cause us to not succeed on the block. Logger.error(this, "Caught "+t, t); } } }