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();
}
}