package freenet.clients.fcp;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import freenet.client.ClientMetadata;
import freenet.client.FetchException.FetchExceptionMode;
import freenet.client.InsertContext;
import freenet.client.InsertException.InsertExceptionMode;
import freenet.client.async.CacheFetchResult;
import freenet.client.events.SplitfileProgressEvent;
import freenet.clients.fcp.ClientPut.COMPRESS_STATE;
import freenet.keys.FreenetURI;
import freenet.support.Logger;
import freenet.support.MultiValueTable;
import freenet.support.api.Bucket;
import freenet.support.io.NoFreeBucket;
/** Per-PersistentRequestClient cache of status of requests. */
public class RequestStatusCache {
private static volatile boolean logMINOR;
static {
Logger.registerClass(RequestStatusCache.class);
}
private final ArrayList<RequestStatus> downloads;
private final ArrayList<RequestStatus> uploads;
private final HashMap<String, RequestStatus> requestsByIdentifier;
private final MultiValueTable<FreenetURI, RequestStatus> downloadsByURI;
private final MultiValueTable<FreenetURI, RequestStatus> uploadsByFinalURI;
RequestStatusCache() {
downloads = new ArrayList<RequestStatus>();
uploads = new ArrayList<RequestStatus>();
requestsByIdentifier = new HashMap<String, RequestStatus>();
downloadsByURI = new MultiValueTable<FreenetURI, RequestStatus>();
uploadsByFinalURI = new MultiValueTable<FreenetURI, RequestStatus>();
}
synchronized void addDownload(DownloadRequestStatus status) {
RequestStatus old =
requestsByIdentifier.put(status.getIdentifier(), status);
if(logMINOR) Logger.minor(this, "Starting download "+status.getIdentifier());
if(old == status) return;
if(old != null)
downloads.remove(old);
downloads.add(status);
downloadsByURI.put(status.getURI(), status);
}
synchronized void addUpload(UploadRequestStatus status) {
RequestStatus old =
requestsByIdentifier.put(status.getIdentifier(), status);
if(old == status) return;
if(logMINOR) Logger.minor(this, "Starting upload "+status.getIdentifier());
if(old != null)
uploads.remove(old);
uploads.add(status);
FreenetURI uri = status.getURI();
if(uri != null)
uploadsByFinalURI.put(uri, status);
}
synchronized void finishedDownload(String identifier, boolean success, long dataSize,
String mimeType, FetchExceptionMode failureCode, String failureReasonLong, String failureReasonShort, Bucket dataShadow, boolean filtered) {
DownloadRequestStatus status = (DownloadRequestStatus) requestsByIdentifier.get(identifier);
if(status == null) return; // Can happen during cancel etc.
status.setFinished(success, dataSize, mimeType, failureCode, failureReasonLong,
failureReasonShort, dataShadow, filtered);
}
synchronized void gotFinalURI(String identifier, FreenetURI finalURI) {
UploadRequestStatus status = (UploadRequestStatus) requestsByIdentifier.get(identifier);
if(status == null) return; // Can happen during cancel etc.
if(status.getFinalURI() == null)
// No final URI set yet, put into the index.
uploadsByFinalURI.put(finalURI, status);
status.setFinalURI(finalURI);
}
synchronized void finishedUpload(String identifier, boolean success,
FreenetURI finalURI, InsertExceptionMode failureCode, String failureReasonShort,
String failureReasonLong) {
UploadRequestStatus status = (UploadRequestStatus) requestsByIdentifier.get(identifier);
if(status == null) return; // Can happen during cancel etc.
if(status.getFinalURI() == null && finalURI != null)
// No final URI set yet, put into the index.
uploadsByFinalURI.put(finalURI, status);
status.setFinished(success, finalURI, failureCode, failureReasonShort, failureReasonLong);
}
synchronized void updateStatus(String identifier, SplitfileProgressEvent event) {
RequestStatus status = requestsByIdentifier.get(identifier);
if(status == null) return; // Can happen during cancel etc.
status.updateStatus(event);
}
synchronized void updateDetectedCompatModes(String identifier, InsertContext.CompatibilityMode[] compatModes, byte[] splitfileKey, boolean dontCompress) {
DownloadRequestStatus status = (DownloadRequestStatus) requestsByIdentifier.get(identifier);
if(status == null) return; // Can happen during cancel etc.
status.updateDetectedCompatModes(compatModes, dontCompress);
status.updateDetectedSplitfileKey(splitfileKey);
}
synchronized void removeByIdentifier(String identifier) {
RequestStatus status = requestsByIdentifier.remove(identifier);
if(status == null) return;
if(status instanceof DownloadRequestStatus) {
downloads.remove(status);
FreenetURI uri = status.getURI();
assert(uri != null);
downloadsByURI.removeElement(uri, status);
} else if(status instanceof UploadRequestStatus) {
uploads.remove(status);
FreenetURI uri = ((UploadRequestStatus) status).getFinalURI();
if(uri != null)
uploadsByFinalURI.removeElement(uri, status);
}
}
synchronized void clear() {
downloads.clear();
uploads.clear();
requestsByIdentifier.clear();
downloadsByURI.clear();
uploadsByFinalURI.clear();
}
public void updateCompressionStatus(String identifier,
COMPRESS_STATE compressing) {
UploadFileRequestStatus status = (UploadFileRequestStatus) requestsByIdentifier.get(identifier);
if(status == null) return; // Can happen during cancel etc.
status.updateCompressionStatus(compressing);
}
public synchronized void addTo(List<RequestStatus> status) {
// FIXME is it better to just synchronize on the RequestStatusCache when
// rendering the downloads page, and when updating? Ugly though ...
for(RequestStatus req : requestsByIdentifier.values())
status.add(req.clone());
}
public synchronized void updateExpectedMIME(String identifier, String foundDataMimeType) {
DownloadRequestStatus status = (DownloadRequestStatus) requestsByIdentifier.get(identifier);
if(status == null) return; // Can happen during cancel etc.
status.updateExpectedMIME(foundDataMimeType);
}
public synchronized void updateExpectedDataLength(String identifier, long expectedDataLength) {
DownloadRequestStatus status = (DownloadRequestStatus) requestsByIdentifier.get(identifier);
if(status == null) return; // Can happen during cancel etc.
status.updateExpectedDataLength(expectedDataLength);
}
public void setPriority(String identifier, short newPriorityClass) {
RequestStatus status = requestsByIdentifier.get(identifier);
if(status == null) return; // Can happen during cancel etc.
status.setPriority(newPriorityClass);
}
/** Restart a request. Caller should call ,false first, at which point we setStarted,
* and ,true when it has actually started (a race condition means we don't setStarted
* at that point since it's possible the success/failure callback might happen first). */
public synchronized void updateStarted(String identifier, boolean started) {
RequestStatus status = requestsByIdentifier.get(identifier);
if(status == null) return; // Can happen during cancel etc.
if(!started)
// Caller should call with false first, so we only need to unset finished when setting started=false.
status.restart(false);
else
// Already restarted, just set started = true.
status.setStarted(started);
}
/** Restart a download. Caller should call ,false first, at which point we setStarted,
* and ,true when it has actually started (a race condition means we don't setStarted
* at that point since it's possible the success/failure callback might happen first).
* @param redirect If non-null, the request followed a redirect. */
public synchronized void updateStarted(String identifier, FreenetURI redirect) {
DownloadRequestStatus status = (DownloadRequestStatus) requestsByIdentifier.get(identifier);
if(status == null) return; // Can happen during cancel etc.
status.restart(false);
if(redirect != null) {
downloadsByURI.remove(status.getURI());
status.redirect(redirect);
downloadsByURI.put(redirect, status);
}
}
public synchronized CacheFetchResult getShadowBucket(FreenetURI key, boolean noFilter) {
Object[] downloads = downloadsByURI.getArray(key);
if(downloads == null) return null;
for(Object o : downloads) {
DownloadRequestStatus download = (DownloadRequestStatus) o;
Bucket data = download.getDataShadow();
if(data == null) continue;
if(data.size() == 0) continue;
if(noFilter && download.filterData) continue;
// FIXME it probably *is* worth the effort to allow this when it is overridden on the fetcher, since the user changed the type???
if(download.overriddenDataType) continue;
return new CacheFetchResult(new ClientMetadata(download.getMIMEType()), new NoFreeBucket(data), download.filterData);
}
return null;
}
}