/* 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.clients.fcp;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.util.HashMap;
import java.util.Map;
import freenet.clients.fcp.ClientGet.ReturnType;
import freenet.clients.fcp.ClientRequest.Persistence;
import freenet.keys.FreenetURI;
import freenet.node.Node;
import freenet.node.RequestStarter;
import freenet.support.LogThresholdCallback;
import freenet.support.Logger;
import freenet.support.Logger.LogLevel;
import freenet.support.SimpleFieldSet;
import freenet.support.api.Bucket;
import freenet.support.api.BucketFactory;
import freenet.support.io.BucketTools;
/**
* ClientGet message.
*
* Example:
*
* ClientGet
* IgnoreDS=false // true = ignore the datastore
* DSOnly=false // true = only check the datastore, don't route (~= htl 0)
* URI=KSK@sample.txt
* Identifier=Request Number One
* Verbosity=0 // no status, just tell us when it's done
* ReturnType=direct // return all at once over the FCP connection
* MaxSize=100 // maximum size of returned data
* MaxTempSize=1000 // maximum size of intermediary data
* MaxRetries=100 // automatic retry supported as an option
* PriorityClass=1 // priority class 1 = interactive
* Persistence=reboot // continue until node is restarted; report progress while client is
* connected, including if it reconnects after losing connection
* ClientToken=hello // returned in PersistentGet, a hint to the client, so the client
* doesn't need to maintain its own state
* IgnoreUSKDatehints=false // true = don't use USK datehints
* EndMessage
*/
public class ClientGetMessage extends BaseDataCarryingMessage {
public final static String NAME = "ClientGet";
final boolean ignoreDS;
final boolean dsOnly;
final FreenetURI uri;
final String identifier;
final int verbosity;
final ReturnType returnType;
final Persistence persistence;
final long maxSize;
final long maxTempSize;
final int maxRetries;
final short priorityClass;
final File diskFile;
final String clientToken;
final boolean global;
final boolean binaryBlob;
final String[] allowedMIMETypes;
public boolean writeToClientCache;
final String charset;
final boolean filterData;
final boolean realTimeFlag;
final boolean ignoreUSKDatehints;
private Bucket initialMetadata;
private final long initialMetadataLength;
private static volatile boolean logMINOR;
static {
Logger.registerLogThresholdCallback(new LogThresholdCallback(){
@Override
public void shouldUpdate(){
logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
}
});
}
public ClientGetMessage(SimpleFieldSet fs) throws MessageInvalidException {
short defaultPriority;
clientToken = fs.get("ClientToken");
global = fs.getBoolean("Global", false);
ignoreDS = fs.getBoolean("IgnoreDS", false);
dsOnly = fs.getBoolean("DSOnly", false);
identifier = fs.get("Identifier");
allowedMIMETypes = fs.getAll("AllowedMIMETypes");
filterData = fs.getBoolean("FilterData", false);
charset = fs.get("Charset");
if(identifier == null)
throw new MessageInvalidException(ProtocolErrorMessage.MISSING_FIELD, "No Identifier", null, global);
try {
uri = new FreenetURI(fs.get("URI"));
} catch (MalformedURLException e) {
throw new MessageInvalidException(ProtocolErrorMessage.FREENET_URI_PARSE_ERROR, e.getMessage(), identifier, global);
}
String verbosityString = fs.get("Verbosity");
if(verbosityString == null)
verbosity = 0;
else {
try {
verbosity = Integer.parseInt(verbosityString, 10);
} catch (NumberFormatException e) {
throw new MessageInvalidException(ProtocolErrorMessage.ERROR_PARSING_NUMBER, "Error parsing Verbosity field: "+e.getMessage(), identifier, global);
}
}
String returnTypeString = fs.get("ReturnType");
returnType = parseReturnTypeFCP(returnTypeString);
if(returnType == ReturnType.DIRECT) {
diskFile = null;
// default just below FProxy
defaultPriority = RequestStarter.IMMEDIATE_SPLITFILE_PRIORITY_CLASS;
} else if(returnType == ReturnType.NONE) {
diskFile = null;
defaultPriority = RequestStarter.PREFETCH_PRIORITY_CLASS;
} else if(returnType == ReturnType.DISK) {
defaultPriority = RequestStarter.BULK_SPLITFILE_PRIORITY_CLASS;
String filename = fs.get("Filename");
if(filename == null)
throw new MessageInvalidException(ProtocolErrorMessage.MISSING_FIELD, "Missing Filename", identifier, global);
diskFile = new File(filename);
if(diskFile.exists())
throw new MessageInvalidException(ProtocolErrorMessage.DISK_TARGET_EXISTS, null, identifier, global);
try {
// Check whether we can create a temp file in the target directory.
File temp = File.createTempFile(diskFile.getName(), ".freenet-tmp", diskFile.getParentFile());
temp.delete();
} catch (IOException e) {
throw new MessageInvalidException(ProtocolErrorMessage.COULD_NOT_CREATE_FILE, e.getMessage(), identifier, global);
}
} else
throw new MessageInvalidException(ProtocolErrorMessage.MESSAGE_PARSE_ERROR, "Unknown return-type", identifier, global);
String maxSizeString = fs.get("MaxSize");
if(maxSizeString == null)
// default to unlimited
maxSize = Long.MAX_VALUE;
else {
try {
maxSize = Long.parseLong(maxSizeString, 10);
if(maxSize < 0)
throw new MessageInvalidException(ProtocolErrorMessage.INVALID_FIELD, "Maximum size must be positive", identifier, global);
} catch (NumberFormatException e) {
throw new MessageInvalidException(ProtocolErrorMessage.ERROR_PARSING_NUMBER, "Error parsing MaxSize field: "+e.getMessage(), identifier, global);
}
}
String maxTempSizeString = fs.get("MaxTempSize");
if(maxTempSizeString == null)
// default to unlimited
maxTempSize = Long.MAX_VALUE;
else {
try {
maxTempSize = Long.parseLong(maxTempSizeString, 10);
if(maxTempSize < 0)
throw new MessageInvalidException(ProtocolErrorMessage.INVALID_FIELD, "Maximum temp size must be positive", identifier, global);
} catch (NumberFormatException e) {
throw new MessageInvalidException(ProtocolErrorMessage.ERROR_PARSING_NUMBER, "Error parsing MaxSize field: "+e.getMessage(), identifier, global);
}
}
String maxRetriesString = fs.get("MaxRetries");
if(maxRetriesString == null)
// default to 0
maxRetries = 0;
else {
try {
maxRetries = Integer.parseInt(maxRetriesString, 10);
if(maxRetries < -1) throw new MessageInvalidException(ProtocolErrorMessage.INVALID_FIELD, "Max retries must be -1 or larger", identifier, global);
} catch (NumberFormatException e) {
throw new MessageInvalidException(ProtocolErrorMessage.ERROR_PARSING_NUMBER, "Error parsing MaxSize field: "+e.getMessage(), identifier, global);
}
}
if(logMINOR)
Logger.minor(this, "max retries="+maxRetries);
String priorityString = fs.get("PriorityClass");
if(priorityString == null) {
// defaults to the one just below FProxy
priorityClass = defaultPriority;
} else {
try {
priorityClass = Short.parseShort(priorityString);
if(!RequestStarter.isValidPriorityClass(priorityClass))
throw new MessageInvalidException(ProtocolErrorMessage.INVALID_FIELD, "Invalid priority class "+priorityClass+" - range is "+RequestStarter.PAUSED_PRIORITY_CLASS+" to "+RequestStarter.MAXIMUM_PRIORITY_CLASS, identifier, global);
} catch (NumberFormatException e) {
throw new MessageInvalidException(ProtocolErrorMessage.ERROR_PARSING_NUMBER, "Error parsing PriorityClass field: "+e.getMessage(), identifier, global);
}
}
String persistenceString = fs.get("Persistence");
persistence = Persistence.parseOrThrow(persistenceString, identifier, global);
if(global && (persistence == Persistence.CONNECTION)) {
throw new MessageInvalidException(ProtocolErrorMessage.NOT_SUPPORTED, "Global requests must be persistent", identifier, global);
}
writeToClientCache = fs.getBoolean("WriteToClientCache", persistence == Persistence.CONNECTION);
binaryBlob = fs.getBoolean("BinaryBlob", false);
realTimeFlag = fs.getBoolean("RealTimeFlag", false);
initialMetadataLength = fs.getLong("InitialMetadata.DataLength", 0);
if(initialMetadataLength < 0)
throw new MessageInvalidException(ProtocolErrorMessage.INVALID_FIELD, "Invalid data length for initial metadata", identifier, global);
ignoreUSKDatehints = fs.getBoolean("IgnoreUSKDatehints", false);
}
@Override
public SimpleFieldSet getFieldSet() {
SimpleFieldSet fs = new SimpleFieldSet(true);
fs.put("IgnoreDS", ignoreDS);
fs.putSingle("URI", uri.toString(false, false));
fs.put("FilterData", filterData);
fs.putSingle("Charset", charset);
fs.putSingle("Identifier", identifier);
fs.put("Verbosity", verbosity);
fs.putSingle("ReturnType", getReturnTypeString());
fs.put("MaxSize", maxSize);
fs.put("MaxTempSize", maxTempSize);
fs.put("MaxRetries", maxRetries);
fs.put("BinaryBlob", binaryBlob);
return fs;
}
private String getReturnTypeString() {
return returnType.toString();
}
@Override
public String getName() {
return NAME;
}
@Override
public void run(FCPConnectionHandler handler, Node node) {
handler.startClientGet(this);
}
ReturnType parseReturnTypeFCP(String string) throws MessageInvalidException {
try {
if(string == null) return ReturnType.DIRECT;
return ReturnType.valueOf(string.toUpperCase());
} catch (IllegalArgumentException e) {
throw new MessageInvalidException(ProtocolErrorMessage.INVALID_FIELD, "Unable to parse ReturnType "+string+" : "+e, identifier, global);
}
}
@Override
long dataLength() {
return initialMetadataLength;
}
@Override
public void readFrom(InputStream is, BucketFactory bf, FCPServer server)
throws IOException, MessageInvalidException {
if(initialMetadataLength == 0) return;
Bucket data;
data = bf.makeBucket(initialMetadataLength);
BucketTools.copyFrom(data, is, initialMetadataLength);
// No need for synchronization here.
initialMetadata = data;
}
@Override
protected void writeData(OutputStream os) throws IOException {
throw new UnsupportedOperationException();
}
public Bucket getInitialMetadata() {
return initialMetadata;
}
}