/* 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); } } } }