package freenet.clients.http; import java.util.ArrayList; import java.util.Enumeration; import freenet.client.FetchContext; import freenet.client.FetchException; import freenet.client.async.ClientContext; import freenet.clients.http.FProxyFetchInProgress.REFILTER_POLICY; import freenet.keys.FreenetURI; import freenet.node.RequestClient; import freenet.support.LogThresholdCallback; import freenet.support.Logger; import freenet.support.MultiValueTable; import freenet.support.Logger.LogLevel; public class FProxyFetchTracker implements Runnable { private static volatile boolean logMINOR; static { Logger.registerLogThresholdCallback(new LogThresholdCallback() { @Override public void shouldUpdate() { logMINOR = Logger.shouldLog(LogLevel.MINOR, this); } }); } final MultiValueTable<FreenetURI, FProxyFetchInProgress> fetchers; final ClientContext context; private long fetchIdentifiers; private final FetchContext fctx; private final RequestClient rc; private boolean queuedJob; private boolean requeue; public FProxyFetchTracker(ClientContext context, FetchContext fctx, RequestClient rc) { fetchers = new MultiValueTable<FreenetURI, FProxyFetchInProgress>(); this.context = context; this.fctx = fctx; this.rc = rc; } public FProxyFetchWaiter makeFetcher(FreenetURI key, long maxSize, FetchContext fctx, REFILTER_POLICY refilterPolicy) throws FetchException { FProxyFetchInProgress progress; /* LOCKING: * Call getWaiter() inside the fetchers lock, since we will purge old * fetchers inside that lock, hence avoid a race condition. FetchInProgress * lock is always taken last. */ synchronized(fetchers) { FProxyFetchWaiter waiter=makeWaiterForFetchInProgress(key, maxSize, fctx != null ? fctx : this.fctx); if(waiter!=null){ return waiter; } progress = new FProxyFetchInProgress(this, key, maxSize, fetchIdentifiers++, context, fctx != null ? fctx : this.fctx, rc, refilterPolicy); fetchers.put(key, progress); } try { progress.start(context); } catch (FetchException e) { synchronized(fetchers) { fetchers.removeElement(key, progress); } throw e; } if(logMINOR) Logger.minor(this, "Created new fetcher: "+progress, new Exception()); return progress.getWaiter(); // FIXME promote a fetcher when it is re-used // FIXME get rid of fetchers over some age } void removeFetcher(FProxyFetchInProgress progress) { synchronized(fetchers) { fetchers.removeElement(progress.uri, progress); } } public FProxyFetchWaiter makeWaiterForFetchInProgress(FreenetURI key,long maxSize, FetchContext fctx){ FProxyFetchInProgress progress=getFetchInProgress(key, maxSize, fctx); if(progress!=null){ return progress.getWaiter(); } return null; } /** Gets an FProxyFetchInProgress identified by the URI and the maxsize. If no such FetchInProgress exists, then returns null. * @param key - The URI of the fetch * @param maxSize - The maxSize of the fetch * @param fctx TODO * @return The FetchInProgress if found, null otherwise*/ public FProxyFetchInProgress getFetchInProgress(FreenetURI key, long maxSize, FetchContext fctx){ synchronized (fetchers) { Object[] check = fetchers.getArray(key); if(check != null) { for(int i=0;i<check.length;i++) { FProxyFetchInProgress progress = (FProxyFetchInProgress) check[i]; if((progress.maxSize == maxSize && progress.notFinishedOrFatallyFinished()) || progress.hasData()){ if(logMINOR) Logger.minor(this, "Found "+progress); if(fctx != null && !progress.fetchContextEquivalent(fctx)) continue; if(logMINOR) Logger.minor(this, "Using "+progress); return progress; } else if(logMINOR) Logger.minor(this, "Skipping "+progress); } } } return null; } public void queueCancel(FProxyFetchInProgress progress) { if(logMINOR) Logger.minor(this, "Queueing removal of old FProxyFetchInProgress's"); synchronized(this) { if(queuedJob) { requeue = true; return; } queuedJob = true; } context.ticker.queueTimedJob(this, FProxyFetchInProgress.LIFETIME); } @Override public void run() { if(logMINOR) Logger.minor(this, "Removing old FProxyFetchInProgress's"); ArrayList<FProxyFetchInProgress> toRemove = null; boolean needRequeue = false; synchronized(fetchers) { if(requeue) { requeue = false; needRequeue = true; } else { queuedJob = false; } // Horrible hack, FIXME Enumeration<FreenetURI> e = fetchers.keys(); while(e.hasMoreElements()) { FreenetURI uri = (FreenetURI) e.nextElement(); // Really horrible hack, FIXME for(FProxyFetchInProgress f : fetchers.iterateAll(uri)) { // FIXME remove on the fly, although cancel must wait if(f.canCancel()) { if(toRemove == null) toRemove = new ArrayList<FProxyFetchInProgress>(); toRemove.add(f); } } } if(toRemove != null) for(FProxyFetchInProgress r : toRemove) { if(logMINOR){ Logger.minor(this,"Removed fetchinprogress:"+r); } fetchers.removeElement(r.uri, r); } } if(toRemove != null) for(FProxyFetchInProgress r : toRemove) { if(logMINOR) Logger.minor(this, "Cancelling for "+r); r.finishCancel(); } if(needRequeue) context.ticker.queueTimedJob(this, FProxyFetchInProgress.LIFETIME); } public int makeRandomElementID() { return context.fastWeakRandom.nextInt(); } }