/* 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.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import freenet.clients.fcp.ClientRequest.Persistence;
import freenet.node.Node;
import freenet.support.Logger;
import freenet.support.SimpleFieldSet;
import freenet.support.Logger.LogLevel;
import freenet.support.api.BucketFactory;
import freenet.support.api.ManifestElement;
import freenet.support.io.PersistentTempBucketFactory;
/**
* <pre>
* ClientPutComplexDir
* < ... standard ClientPutDir headers ... >
* Files.0.Name=hello.txt
* Files.0.UploadFrom=direct
* Files.0.Metadata.ContentType=text/plain
* Files.0.DataLength=6
* ( upload the 6 bytes following this message as hello.txt, type plain text)
* Files.1.Name=something.pdf
* Files.1.UploadFrom=disk
* Files.1.Filename=something.pdf
* ( upload something.pdf, guess the mime type from the filename )
* Files.2.Name=toad.jpeg
* Files.2.UploadFrom=redirect
* Files.2.TargetURI=CHK@...,...,...
* Files.2.Metadata.ContentType=image/jpeg
* ( not yet supported, but would be really useful! FIXME ! )
* (note that the Files.x must always be a decimal integer. We use these for sort
* order for UploadFrom=direct. they must be sequential and start at 0).
* ...
* End
* <data from above direct uploads, ***in alphabetical order***>
* </pre>
*/
public class ClientPutComplexDirMessage extends ClientPutDirMessage {
/** The files attached to this message, in a directory hierarchy */
private final HashMap<String, Object /* <HashMap || DirPutFile> */> filesByName;
/** Any files we want to read data from */
private final LinkedList<DirPutFile> filesToRead;
/** Total number of bytes of attached data */
private final long attachedBytes;
public ClientPutComplexDirMessage(SimpleFieldSet fs, BucketFactory bfTemp, PersistentTempBucketFactory bfPersistent) throws MessageInvalidException {
// Parse the standard ClientPutDir headers - URI, etc.
super(fs);
filesByName = new HashMap<String, Object>();
filesToRead = new LinkedList<DirPutFile>();
long totalBytes = 0;
// Now parse the meat
SimpleFieldSet files = fs.subset("Files");
if(files == null)
throw new MessageInvalidException(ProtocolErrorMessage.MISSING_FIELD, "Missing Files section", identifier, global);
boolean logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
for(int i=0;;i++) {
SimpleFieldSet subset = files.subset(Integer.toString(i));
if(subset == null) break;
DirPutFile f = DirPutFile.create(subset, identifier, global, (persistence == Persistence.FOREVER) ? bfPersistent : bfTemp);
addFile(f);
if(logMINOR) Logger.minor(this, "Adding "+f);
if(f instanceof DirectDirPutFile) {
totalBytes += ((DirectDirPutFile)f).bytesToRead();
filesToRead.addLast(f);
if(logMINOR) Logger.minor(this, "totalBytes now "+totalBytes);
}
}
attachedBytes = totalBytes;
}
/**
* Add a file to the filesByName.
* @throws MessageInvalidException
*/
private void addFile(DirPutFile f) throws MessageInvalidException {
addFile(filesByName, f.getName(), f);
}
@SuppressWarnings("unchecked")
private void addFile(HashMap<String, Object> byName, String name, DirPutFile f) throws MessageInvalidException {
int idx = name.indexOf('/');
if(idx == -1) {
byName.put(name, f);
} else {
String before = name.substring(0, idx);
String after = name.substring(idx+1);
Object o = byName.get(before);
if(o != null) {
if (o instanceof HashMap) {
addFile((HashMap<String, Object>) o, after, f);
return;
} else {
throw new MessageInvalidException(ProtocolErrorMessage.INVALID_MESSAGE, "Cannot be both a file and a directory: "+before, identifier, global);
}
} else {
o = new HashMap<Object, Object>();
byName.put(before, o);
addFile((HashMap<String, Object>) o, after, f);
}
}
}
static final String NAME = "ClientPutComplexDir";
@Override
public String getName() {
return NAME;
}
@Override
long dataLength() {
return attachedBytes;
}
String getIdentifier() {
return identifier;
}
@Override
public void readFrom(InputStream is, BucketFactory bf, FCPServer server) throws IOException, MessageInvalidException {
for(DirPutFile f: filesToRead) {
((DirectDirPutFile)f).read(is);
}
}
@Override
protected void writeData(OutputStream os) throws IOException {
for(DirPutFile f: filesToRead) {
((DirectDirPutFile)f).write(os);
}
}
@Override
public void run(FCPConnectionHandler handler, Node node) throws MessageInvalidException {
// Convert the hierarchical hashmap's of DirPutFile's to hierarchical hashmap's
// of ManifestElement's.
// Then simply create the ClientPutDir.
HashMap<String, Object> manifestElements = new HashMap<String, Object>();
convertFilesByNameToManifestElements(filesByName, manifestElements, node);
handler.startClientPutDir(this, manifestElements, false);
}
/**
* Convert a hierarchy of HashMap's containing DirPutFile's into a hierarchy of
* HashMap's containing ManifestElement's.
*/
@SuppressWarnings("unchecked")
private void convertFilesByNameToManifestElements(HashMap<String, Object> filesByName,
HashMap<String, Object> manifestElements, Node node) throws MessageInvalidException {
for (Map.Entry<String, Object> entry : filesByName.entrySet()) {
String tempName = entry.getKey();
Object val = entry.getValue();
if(val instanceof HashMap) {
HashMap<String, Object> h = (HashMap<String, Object>) val;
HashMap<String, Object> manifests = new HashMap<String, Object>();
manifestElements.put(tempName, manifests);
convertFilesByNameToManifestElements(h, manifests, node);
} else {
DirPutFile f = (DirPutFile) val;
if(f instanceof DiskDirPutFile && !node.clientCore.allowUploadFrom(((DiskDirPutFile)f).getFile()))
throw new MessageInvalidException(ProtocolErrorMessage.ACCESS_DENIED, "Not allowed to upload "+((DiskDirPutFile) f).getFile(), identifier, global);
ManifestElement e = f.getElement();
manifestElements.put(tempName, e);
}
}
}
}