Author: saces
Date: 2009-04-14 19:24:13 +0000 (Tue, 14 Apr 2009)
New Revision: 26799

Added:
   trunk/freenet/src/freenet/client/async/BaseManifestPutter.java
Log:
new ManifestPutter infrastructure
this class BaseManifestPutter contains all the code to insert the stuff and 
provide helpers functions to add elements,
but does not have any 'pack logic'. this needs to be implemented in sub classes.


Added: trunk/freenet/src/freenet/client/async/BaseManifestPutter.java
===================================================================
--- trunk/freenet/src/freenet/client/async/BaseManifestPutter.java              
                (rev 0)
+++ trunk/freenet/src/freenet/client/async/BaseManifestPutter.java      
2009-04-14 19:24:13 UTC (rev 26799)
@@ -0,0 +1,1864 @@
+package freenet.client.async;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+import java.util.Vector;
+
+import com.db4o.ObjectContainer;
+
+import freenet.client.ClientMetadata;
+import freenet.client.DefaultMIMETypes;
+import freenet.client.InsertBlock;
+import freenet.client.InsertContext;
+import freenet.client.InsertException;
+import freenet.client.Metadata;
+import freenet.client.MetadataUnresolvedException;
+import freenet.client.ArchiveManager.ARCHIVE_TYPE;
+import freenet.client.events.SplitfileProgressEvent;
+import freenet.keys.BaseClientKey;
+import freenet.keys.FreenetURI;
+import freenet.node.RequestClient;
+import freenet.support.LogThresholdCallback;
+import freenet.support.Logger;
+import freenet.support.api.Bucket;
+import freenet.support.io.BucketTools;
+import freenet.support.io.NativeThread;
+
+public abstract class BaseManifestPutter extends BaseClientPutter implements 
PutCompletionCallback {
+
+       private static volatile boolean logMINOR;
+       private static volatile boolean logDEBUG;
+       
+       static {
+               Logger.registerLogThresholdCallback(new LogThresholdCallback() {
+                       
+                       @Override
+                       public void shouldUpdate() {
+                               logMINOR = Logger.shouldLog(Logger.MINOR, this);
+                               logDEBUG = Logger.shouldLog(Logger.DEBUG, this);
+                       }
+               });
+       }
+       
+       // Only implements PutCompletionCallback for the final metadata insert
+       protected class PutHandler extends BaseClientPutter implements 
PutCompletionCallback {
+               
+               protected PutHandler(final BaseManifestPutter smp, String name, 
Bucket data, ClientMetadata cm, boolean getCHKOnly) {
+                       this(smp, null, name, data, cm, getCHKOnly);
+               }
+                       
+               protected PutHandler(final BaseManifestPutter smp, PutHandler 
parent, String name, Bucket data, ClientMetadata cm, boolean getCHKOnly) {
+                       super(smp.priorityClass, smp.client);
+                       this.persistent = BaseManifestPutter.this.persistent();
+                       this.cm = cm;
+                       this.name = name;
+                       InsertBlock block = 
+                               new InsertBlock(data, cm, persistent() ? 
FreenetURI.EMPTY_CHK_URI.clone() : FreenetURI.EMPTY_CHK_URI);
+                       this.origSFI =
+                               new SingleFileInserter(this, this, block, 
false, ctx, false, getCHKOnly, true, null, null, false, null, earlyEncode);
+                       metadata = null;
+                       parentContainerHandle = parent;
+                       isArchive = isContainer = false;
+               }
+               
+               //protected PutHandler(final SimpleManifestPutter smp, String 
name, HashMap<String, Object> data, ClientMetadata cm, boolean getCHKOnly) {
+               //      this(smp, null, name, data, cm, getCHKOnly);
+               //}
+                               
+               protected PutHandler(final BaseManifestPutter smp, PutHandler 
parent, String name, HashMap<String, Object> data, ClientMetadata cm, boolean 
getCHKOnly, boolean isArchive2) {
+                       super(smp.priorityClass, smp.client);
+                       this.persistent = BaseManifestPutter.this.persistent();
+                       this.cm = cm;
+                       this.name = name;
+                       this.origSFI =
+                               new ContainerInserter(this, this, data, 
(persistent ? FreenetURI.EMPTY_CHK_URI.clone() : FreenetURI.EMPTY_CHK_URI), 
ctx, false, getCHKOnly, !isArchive2, null, ARCHIVE_TYPE.TAR, false, 
earlyEncode);
+                       metadata = null;
+                       parentContainerHandle = parent;
+                       isContainer = true;
+                       isArchive = isArchive2;
+               }
+
+               protected PutHandler(final BaseManifestPutter smp, String name, 
FreenetURI target, ClientMetadata cm) {
+                       this(smp, null, name, name, target, cm);
+               }
+                               
+               protected PutHandler(final BaseManifestPutter smp, PutHandler 
parent, String name, String targetInArchive2, FreenetURI target, ClientMetadata 
cm) {
+                       super(smp.getPriorityClass(), smp.client);
+                       this.persistent = BaseManifestPutter.this.persistent();
+                       this.cm = cm;
+                       this.name = name;
+                       Metadata m = new Metadata(Metadata.SIMPLE_REDIRECT, 
null, null, target, cm);
+                       metadata = m;
+                       if(logMINOR) Logger.minor(this, "Simple redirect 
metadata: "+m);
+                       origSFI = null;
+                       parentContainerHandle = parent;
+                       isArchive = isContainer = false;
+                       targetInArchive = targetInArchive2;
+               }
+               
+               private ClientPutState origSFI;
+               private ClientPutState currentState;
+               private ClientMetadata cm;
+               private Metadata metadata;
+               private String targetInArchive;
+               private final String name;
+               private final boolean persistent;
+               private final PutHandler parentContainerHandle;
+               private final boolean isContainer;
+               private final boolean isArchive;
+               
+               public void start(ObjectContainer container, ClientContext 
context) throws InsertException {
+                       if (logDEBUG)
+                               Logger.debug(this, "Starting a PutHandler for 
'"+this.name+"' (isRootContainer="+ (this==rootContainerPutHandler)+", 
isArchive="+this.isArchive+") " + this);
+                       
+                       if (origSFI == null) {
+                                Logger.error(this, "origSFI is null on 
start(), should be impossible", new Exception("debug"));
+                                return;
+                       }
+                       if (metadata != null) {
+                               Logger.error(this, "metdata=" + metadata + " on 
start(), should be impossible", new Exception("debug"));
+                               return;
+                       }
+                       ClientPutState sfi;
+                       synchronized(this) {
+                               sfi = origSFI;
+                               currentState = sfi;
+                               origSFI = null;
+                       }
+                       if(persistent) {
+                               container.activate(sfi, 1);
+                               container.store(this);
+                       }
+                       sfi.schedule(container, context);
+                       if(persistent) {
+                               container.deactivate(sfi, 1);
+                       }
+               }
+               
+               @Override
+               public void cancel(ObjectContainer container, ClientContext 
context) {
+                       if(logMINOR) Logger.minor(this, "Cancelling "+this, new 
Exception("debug"));
+                       ClientPutState oldState = null;
+                       synchronized(this) {
+                               if(cancelled) return;
+                               super.cancel();
+                               oldState = currentState;
+                       }
+                       if(persistent()) {
+                               container.store(this);
+                               if(oldState != null)
+                                       container.activate(oldState, 1);
+                       }
+                       if(oldState != null) oldState.cancel(container, 
context);
+                       onFailure(new 
InsertException(InsertException.CANCELLED), null, container, context);
+               }
+               
+               @Override
+               public FreenetURI getURI() {
+                       return null;
+               }
+
+               @Override
+               public boolean isFinished() {
+                       if(logMINOR) Logger.minor(this, "Finished "+this, new 
Exception("debug"));
+                       return BaseManifestPutter.this.finished || cancelled || 
BaseManifestPutter.this.cancelled;
+               }
+
+               public void onSuccess(ClientPutState state, ObjectContainer 
container, ClientContext context) {
+                       if(logMINOR) Logger.minor(this, "Completed 
'"+this.name+"' "+this);
+                       
+                       
+                       if (isArchive) {
+                               
+                               Vector<PutHandler> phv = 
putHandlersArchiveTransformMap.get(this);
+                               for (PutHandler ph: phv) {
+//                                     
+                                       
perContainerPutHandlersWaitingForFetchable.get(ph.parentContainerHandle).remove(this);
+//                                     Metadata m = new 
Metadata(Metadata.SIMPLE_REDIRECT, null, null, 
archiveURI.setDocName(targetInArchive), cm);
+//                                     hm.put(ph.name, m);
+//                                     putHandlersTransformMap.remove(ph);
+                                       try {
+                                               
maybeStartParentContainer(ph.parentContainerHandle, container, context);
+                                       } catch (InsertException e) {
+                                               fail(new 
InsertException(InsertException.INTERNAL_ERROR, e, null), container, context);
+                                               return;
+                                       }
+                               }
+                               return;
+                       }
+                       
+                       if(persistent) {
+                               container.activate(BaseManifestPutter.this, 1);
+                               container.activate(runningPutHandlers, 2);
+                       }
+                       BaseManifestPutter.this.onFetchable(this, container);
+                       ClientPutState oldState;
+                       boolean insertedAllFiles = true;
+                       synchronized(this) {
+                               oldState = currentState;
+                               currentState = null;
+                       }
+                       synchronized(BaseManifestPutter.this) {
+                               if(persistent) container.store(this);
+                               runningPutHandlers.remove(this);
+                               if(persistent) {
+                                       
container.ext().store(runningPutHandlers, 2);
+                                       
container.activate(putHandlersWaitingForMetadata, 2);
+                               }
+                               
if(putHandlersWaitingForMetadata.contains(this)) {
+                                       
putHandlersWaitingForMetadata.remove(this);
+                                       
container.ext().store(putHandlersWaitingForMetadata, 2);
+                                       Logger.error(this, "PutHandler 
'"+this.name+"' was in waitingForMetadata in onSuccess() on "+this+" for 
"+BaseManifestPutter.this);
+                               }
+                               
+                               if(persistent) {
+                                       
container.deactivate(putHandlersWaitingForMetadata, 1);
+                                       container.activate(waitingForBlockSets, 
2);
+                               }
+                               if(waitingForBlockSets.contains(this)) {
+                                       waitingForBlockSets.remove(this);
+                                       container.store(waitingForBlockSets);
+                                       Logger.error(this, "PutHandler was in 
waitingForBlockSets in onSuccess() on "+this+" for "+BaseManifestPutter.this);
+                               }
+                               if(persistent) {
+                                       
container.deactivate(waitingForBlockSets, 1);
+                                       
container.deactivate(putHandlersWaitingForFetchable, 1);
+                                       
container.activate(putHandlersWaitingForFetchable, 2);
+                               }
+                               
if(putHandlersWaitingForFetchable.contains(this)) {
+                                       
putHandlersWaitingForFetchable.remove(this);
+                                       
container.ext().store(putHandlersWaitingForFetchable, 2);
+                                       // Not getting an onFetchable is not 
unusual, just ignore it.
+                                       if(logMINOR) Logger.minor(this, 
"PutHandler was in waitingForFetchable in onSuccess() on "+this+" for 
"+BaseManifestPutter.this);
+                               }
+                               if(persistent)
+                                       
container.deactivate(putHandlersWaitingForFetchable, 1);
+                               
+                               if(!runningPutHandlers.isEmpty()) {
+                                       if(logMINOR) {
+                                               Logger.minor(this, "Running put 
handlers: "+runningPutHandlers.size());
+                                               for(Object o : 
runningPutHandlers) {
+                                                       boolean activated = 
true;
+                                                       if(persistent) {
+                                                               activated = 
container.ext().isActive(o);
+                                                               if(!activated) 
container.activate(o, 1);
+                                                       }
+                                                       Logger.minor(this, 
"Still running: "+o);
+                                                       if(!activated)
+                                                               
container.deactivate(o, 1);
+                                               }
+                                       }
+                                       insertedAllFiles = false;
+                               }
+                       }
+                       if(oldState != null && oldState != state && persistent) 
{
+                               container.activate(oldState, 1);
+                               oldState.removeFrom(container, context);
+                       } else if(state != null && persistent) {
+                               state.removeFrom(container, context);
+                       }
+                       if(insertedAllFiles)
+                               insertedAllFiles(container, context);
+                       if(persistent) {
+                               container.deactivate(runningPutHandlers, 1);
+                               container.deactivate(BaseManifestPutter.this, 
1);
+                       }
+               }
+
+               public void onFailure(InsertException e, ClientPutState state, 
ObjectContainer container, ClientContext context) {
+                       ClientPutState oldState;
+                       synchronized(this) {
+                               oldState = currentState;
+                               currentState = null;
+                       }
+                       if(oldState != null && oldState != state && persistent) 
{
+                               container.activate(oldState, 1);
+                               oldState.removeFrom(container, context);
+                       } else if(state != null && persistent) {
+                               state.removeFrom(container, context);
+                       }
+                       if(logMINOR) Logger.minor(this, "Failed: "+this+" - 
"+e, e);
+                       if(persistent)
+                               container.activate(BaseManifestPutter.this, 1);
+                       fail(e, container, context);
+                       if(persistent)
+                               container.deactivate(BaseManifestPutter.this, 
1);
+               }
+
+               public void onEncode(BaseClientKey key, ClientPutState state, 
ObjectContainer container, ClientContext context) {
+                       if(logMINOR) Logger.minor(this, "onEncode("+key+") for 
"+this);
+                       System.out.println("Got a URI: 
"+key.getURI().toString(false, false) + " for "+this);
+                       if (isArchive) {
+                               
+                               FreenetURI archiveURI = key.getURI();
+                               
+                               Vector<PutHandler> phv = 
putHandlersArchiveTransformMap.get(this);
+                               for (PutHandler ph: phv) {
+                                       HashMap<String, Object> hm = 
putHandlersTransformMap.get(ph);
+                                       
+                                       
perContainerPutHandlersWaitingForFetchable.get(ph.parentContainerHandle).remove(this);
+                                       if (ph.targetInArchive == null) throw 
new NullPointerException();
+                                       Metadata m = new 
Metadata(Metadata.SIMPLE_REDIRECT, null, null, archiveURI.setMetaString(new 
String[]{ph.targetInArchive}), cm);
+                                       hm.put(ph.name, m);
+                                       putHandlersTransformMap.remove(ph);
+                               }
+                               
//perContainerPutHandlersWaitingForFetchable.get(parentContainerHandle).remove(this);
+                               //putHandlersArchiveTransformMap.remove(this);
+                               return;
+                       }
+
+                       if(metadata == null) {
+                               // The file was too small to have its own 
metadata, we get this instead.
+                               // So we make the key into metadata.
+                               if(persistent) {
+                                       container.activate(key, 5);
+                                       
container.activate(BaseManifestPutter.this, 1);
+                               }
+                               Metadata m =
+                                       new Metadata(Metadata.SIMPLE_REDIRECT, 
null, null, key.getURI(), cm);
+                               onMetadata(m, null, container, context);
+                               if(persistent) {
+                                       
container.deactivate(BaseManifestPutter.this, 1);
+                               }
+                       }
+               }
+
+               /**
+                * The caller of onTransition removes the old state, so we 
don't have to.
+                * However, in onSuccess or onFailure, we need to remove the 
new state, even if
+                * what is passed in is different (in which case we remove that 
too).
+                */
+               public void onTransition(ClientPutState oldState, 
ClientPutState newState, ObjectContainer container) {
+                       if(newState == null) throw new NullPointerException();
+                       
+                       // onTransition is *not* responsible for removing the 
old state, the caller is.
+                       synchronized (this) {
+                               if (currentState == oldState) {
+                                       currentState = newState;
+                                       if(persistent())
+                                               container.store(this);
+                                       if(logMINOR)
+                                               Logger.minor(this, 
"onTransition: cur=" + currentState + ", old=" + oldState + ", new=" + 
newState+" for "+this);
+                                       return;
+                               }
+                               Logger.error(this, "Ignoring onTransition: 
cur=" + currentState + ", old=" + oldState + ", new=" + newState+" for "+this);
+                       }
+               }
+
+               public void onMetadata(Metadata m, ClientPutState state, 
ObjectContainer container, ClientContext context) {
+                       if(logMINOR) Logger.minor(this, "Assigning metadata: 
"+m+" for '"+this.name+"' "+this+" from "+state+" persistent="+persistent);
+                       if(metadata != null) {
+                               Logger.error(this, "Reassigning metadata", new 
Exception("debug"));
+                               return;
+                       }
+                       metadata = m;
+                       
+                       if (parentContainerHandle != null) {
+                               // it is a subcontainer/archive, but not the 
root container
+                               
perContainerPutHandlersWaitingForMetadata.get(parentContainerHandle).remove(this);
+                               HashMap<String, Object> hm = 
putHandlersTransformMap.get(this);
+                               hm.put(name, m);
+                               putHandlersTransformMap.remove(this);
+                               try {
+                                       
maybeStartParentContainer(parentContainerHandle, container, context);
+                               } catch (InsertException e) {
+                                       fail(new 
InsertException(InsertException.INTERNAL_ERROR, e, null), container, context);
+                               }
+                               return;
+                       }
+                       
+                       if (this==rootContainerPutHandler) {
+                               baseMetadata = m;
+                               resolveAndStartBase(container, context);
+                               return;
+                       }
+                       
+                       if (containerMode) {
+                               try {
+                                       
rootContainerPutHandler.start(container, context);
+                               } catch (InsertException e) {
+                                       fail(new 
InsertException(InsertException.INTERNAL_ERROR, e, null), container, context);
+                               }
+                               return;
+                       }
+                       
+                       if(persistent) {
+                               container.activate(BaseManifestPutter.this, 1);
+                               
container.activate(putHandlersWaitingForMetadata, 2);
+                       }
+                       boolean allMetadatas = false;
+                       
+                       synchronized(BaseManifestPutter.this) {
+                               putHandlersWaitingForMetadata.remove(this);
+                               if(persistent) {
+                                       
container.ext().store(putHandlersWaitingForMetadata, 2);
+                                       container.store(this);
+                               }
+                               allMetadatas = 
putHandlersWaitingForMetadata.isEmpty();
+                               if(!allMetadatas) {
+                                       if(logMINOR)
+                                               Logger.minor(this, "Still 
waiting for metadata: "+putHandlersWaitingForMetadata.size());
+                               }
+                       }
+                       if(allMetadatas) {
+                               // Will resolve etc.
+                               gotAllMetadata(container, context);
+                       } else {
+                               // Resolve now to speed up the insert.
+                               try {
+                                       byte[] buf = m.writeToByteArray();
+                                       if(buf.length > 
Metadata.MAX_SIZE_IN_MANIFEST)
+                                               throw new 
MetadataUnresolvedException(new Metadata[] { m }, "Too big");
+                               } catch (MetadataUnresolvedException e) {
+                                       try {
+                                               resolve(e, container, context);
+                                       } catch (IOException e1) {
+                                               fail(new 
InsertException(InsertException.BUCKET_ERROR, e1, null), container, context);
+                                               return;
+                                       } catch (InsertException e1) {
+                                               fail(e1, container, context);
+                                       }
+                               }
+                       }
+                       if(persistent) {
+                               
container.deactivate(putHandlersWaitingForMetadata, 1);
+                               container.deactivate(BaseManifestPutter.this, 
1);
+                       }
+               }
+
+               @Override
+               public void addBlock(ObjectContainer container) {
+                       if(persistent) {
+                               container.activate(BaseManifestPutter.this, 1);
+                       }
+                       BaseManifestPutter.this.addBlock(container);
+                       if(persistent)
+                               container.deactivate(BaseManifestPutter.this, 
1);
+               }
+               
+               @Override
+               public void addBlocks(int num, ObjectContainer container) {
+                       if(persistent)
+                               container.activate(BaseManifestPutter.this, 1);
+                       BaseManifestPutter.this.addBlocks(num, container);
+                       if(persistent)
+                               container.deactivate(BaseManifestPutter.this, 
1);
+               }
+               
+               @Override
+               public void completedBlock(boolean dontNotify, ObjectContainer 
container, ClientContext context) {
+                       if(persistent)
+                               container.activate(BaseManifestPutter.this, 1);
+                       BaseManifestPutter.this.completedBlock(dontNotify, 
container, context);
+                       if(persistent)
+                               container.deactivate(BaseManifestPutter.this, 
1);
+               }
+               
+               @Override
+               public void failedBlock(ObjectContainer container, 
ClientContext context) {
+                       if(persistent)
+                               container.activate(BaseManifestPutter.this, 1);
+                       BaseManifestPutter.this.failedBlock(container, context);
+                       if(persistent)
+                               container.deactivate(BaseManifestPutter.this, 
1);
+               }
+               
+               @Override
+               public void fatallyFailedBlock(ObjectContainer container, 
ClientContext context) {
+                       if(persistent)
+                               container.activate(BaseManifestPutter.this, 1);
+                       BaseManifestPutter.this.fatallyFailedBlock(container, 
context);
+                       if(persistent)
+                               container.deactivate(BaseManifestPutter.this, 
1);
+               }
+               
+               @Override
+               public void addMustSucceedBlocks(int blocks, ObjectContainer 
container) {
+                       if(persistent)
+                               container.activate(BaseManifestPutter.this, 1);
+                       BaseManifestPutter.this.addMustSucceedBlocks(blocks, 
container);
+                       if(persistent)
+                               container.deactivate(BaseManifestPutter.this, 
1);
+               }
+               
+               @Override
+               public void notifyClients(ObjectContainer container, 
ClientContext context) {
+                       if(persistent)
+                               container.activate(BaseManifestPutter.this, 1);
+                       BaseManifestPutter.this.notifyClients(container, 
context);
+                       if(persistent)
+                               container.deactivate(BaseManifestPutter.this, 
1);
+               }
+
+               public void onBlockSetFinished(ClientPutState state, 
ObjectContainer container, ClientContext context) {
+                       if(persistent) {
+                               container.activate(BaseManifestPutter.this, 1);
+                               container.activate(waitingForBlockSets, 2);
+                       }
+                       boolean allBlockSets = false;
+                       synchronized(BaseManifestPutter.this) {
+                               waitingForBlockSets.remove(this);
+                               if(persistent)
+                                       container.store(waitingForBlockSets);
+                               allBlockSets = waitingForBlockSets.isEmpty();
+                       }
+                       if(allBlockSets)
+                               
BaseManifestPutter.this.blockSetFinalized(container, context);
+                       if(persistent) {
+                               container.deactivate(waitingForBlockSets, 1);
+                               container.deactivate(BaseManifestPutter.this, 
1);
+                       }
+               }
+
+               @Override
+               public void onMajorProgress(ObjectContainer container) {
+                       if(persistent)
+                               container.activate(BaseManifestPutter.this, 1);
+                       BaseManifestPutter.this.onMajorProgress(container);
+                       if(persistent)
+                               container.deactivate(BaseManifestPutter.this, 
1);
+               }
+
+               public void onFetchable(ClientPutState state, ObjectContainer 
container) {
+                       if(logMINOR) Logger.minor(this, "onFetchable "+this, 
new Exception("debug"));
+                       
+                       if (isArchive) {
+                               
+                               
+                               
+                               
//perContainerPutHandlersWaitingForFetchable.get(containerHandle).remove(this);
+                               
+                               return;
+
+                       }
+                       
+                       if(persistent)
+                               container.activate(BaseManifestPutter.this, 1);
+                       BaseManifestPutter.this.onFetchable(this, container);
+                       if(persistent)
+                               container.deactivate(BaseManifestPutter.this, 
1);
+               }
+
+               @Override
+               public void onTransition(ClientGetState oldState, 
ClientGetState newState, ObjectContainer container) {
+                       // Ignore
+               }
+
+               public void clearMetadata(ObjectContainer container) {
+                       metadata = null;
+                       if(persistent) container.store(this);
+               }
+               
+               @Override
+               public void removeFrom(ObjectContainer container, ClientContext 
context) {
+                       if(logMINOR) Logger.minor(this, "Removing "+this);
+                       ClientPutState oldSFI;
+                       ClientPutState oldState;
+                       synchronized(this) {
+                               oldSFI = origSFI;
+                               oldState = currentState;
+                               oldSFI = null;
+                               currentState = null;
+                       }
+                       if(oldSFI != null) {
+                               Logger.error(this, "origSFI is set in 
removeFrom() on "+this+" for "+BaseManifestPutter.this, new Exception("debug"));
+                               container.activate(oldSFI, 1);
+                               oldSFI.cancel(container, context);
+                               oldSFI.removeFrom(container, context);
+                               if(oldState == oldSFI) oldState = null;
+                       }
+                       if(oldState != null) {
+                               Logger.error(this, "currentState is set in 
removeFrom() on "+this+" for "+BaseManifestPutter.this, new Exception("debug"));
+                               container.activate(oldState, 1);
+                               oldState.cancel(container, context);
+                               oldState.removeFrom(container, context);
+                       }
+                       if(cm != null) {
+                               container.activate(cm, 5);
+                               cm.removeFrom(container);
+                       }
+                       if(metadata != null) {
+                               // Possible if cancelled
+                               Logger.normal(this, "Metadata is set in 
removeFrom() on "+this+" for "+BaseManifestPutter.this);
+                               container.activate(metadata, 1);
+                               metadata.removeFrom(container);
+                       }
+                       // Data is responsibility of original caller (usually 
ClientPutDir), we don't support freeData atm
+                       super.removeFrom(container, context);
+               }
+               
+               public boolean objectCanNew(ObjectContainer container) {
+                       if(cancelled) {
+                               Logger.error(this, "Storing "+this+" when 
already cancelled!", new Exception("error"));
+                               return false;
+                       }
+                       if(logMINOR) Logger.minor(this, "Storing "+this+" 
activated="+container.ext().isActive(this)+" 
stored="+container.ext().isStored(this), new Exception("debug"));
+                       return true;
+               }
+
+               @Override
+               public String toString() {
+                       if (logDEBUG)
+                               return super.toString() + "{Name="+this.name+", 
isRootContainer="+ (this==rootContainerPutHandler)+", 
isContainer="+this.isContainer+", isArchive="+this.isArchive+'}';
+                       return super.toString();
+               }
+
+       }
+
+       // if true top level metadata is a container
+       private boolean containerMode = false;
+       private ContainerBuilder rootContainer;
+       private PutHandler rootContainerPutHandler;
+       private HashSet<PutHandler> containerPutHandlers;
+       private HashMap<PutHandler, HashSet<PutHandler>> 
perContainerPutHandlersWaitingForMetadata;
+       private HashMap<PutHandler, HashSet<PutHandler>> 
perContainerPutHandlersWaitingForFetchable;
+       private HashMap<PutHandler, HashMap<String, Object>> 
putHandlersTransformMap;
+       private HashMap<PutHandler, Vector<PutHandler>> 
putHandlersArchiveTransformMap;
+       
+       private HashMap<String,Object> putHandlersByName;
+       private HashSet<PutHandler> runningPutHandlers;
+       private HashSet<PutHandler> putHandlersWaitingForMetadata;
+       private HashSet<PutHandler> waitingForBlockSets;
+       private HashSet<PutHandler> putHandlersWaitingForFetchable;
+       private FreenetURI finalURI;
+       private FreenetURI targetURI;
+       private boolean finished;
+       private final InsertContext ctx;
+       final ClientCallback cb;
+       private final boolean getCHKOnly;
+       private boolean insertedAllFiles;
+       private boolean insertedManifest;
+       private final HashMap<Metadata,ClientPutState> 
metadataPuttersByMetadata;
+       private final HashMap<Metadata,ClientPutState> 
metadataPuttersUnfetchable;
+       private final String defaultName;
+       private int numberOfFiles;
+       private long totalSize;
+       private boolean metadataBlockSetFinalized;
+       private Metadata baseMetadata;
+       private boolean hasResolvedBase;
+       private final static String[] defaultDefaultNames =
+               new String[] { "index.html", "index.htm", "default.html", 
"default.htm" };
+       private boolean fetchable;
+       private final boolean earlyEncode;
+       
+       public BaseManifestPutter(ClientCallback cb, 
+                       HashMap<String, Object> manifestElements, short 
prioClass, FreenetURI target, 
+                       String defaultName, InsertContext ctx, boolean 
getCHKOnly2, RequestClient clientContext, boolean earlyEncode) {
+               super(prioClass, clientContext);
+               this.defaultName = defaultName;
+               if(client.persistent())
+                       this.targetURI = target.clone();
+               else
+                       this.targetURI = target;
+               this.cb = cb;
+               this.ctx = ctx;
+               this.getCHKOnly = getCHKOnly2;
+               this.earlyEncode = earlyEncode;
+               putHandlersByName = new HashMap<String,Object>();
+               runningPutHandlers = new HashSet<PutHandler>();
+               putHandlersWaitingForMetadata = new HashSet<PutHandler>();
+               putHandlersWaitingForFetchable = new HashSet<PutHandler>();
+               waitingForBlockSets = new HashSet<PutHandler>();
+               metadataPuttersByMetadata = new 
HashMap<Metadata,ClientPutState>();
+               metadataPuttersUnfetchable = new 
HashMap<Metadata,ClientPutState>();
+               
+               containerPutHandlers = new HashSet<PutHandler>();
+               perContainerPutHandlersWaitingForMetadata = new 
HashMap<PutHandler, HashSet<PutHandler>>();
+               perContainerPutHandlersWaitingForFetchable = new 
HashMap<PutHandler, HashSet<PutHandler>>();
+               putHandlersTransformMap = new HashMap<PutHandler, 
HashMap<String, Object>>();
+               putHandlersArchiveTransformMap = new HashMap<PutHandler, 
Vector<PutHandler>>();
+               makePutHandlers(manifestElements, putHandlersByName);
+       }
+
+       public void start(ObjectContainer container, ClientContext context) 
throws InsertException {
+               if (logMINOR)
+                       Logger.minor(this, "Starting " + this+" 
persistence="+persistent()+ " containermode="+containerMode);
+               PutHandler[] running;
+               PutHandler[] containers;
+
+               if(persistent()) {
+                       container.activate(runningPutHandlers, 2);
+               }
+               synchronized (this) {
+                       running = runningPutHandlers.toArray(new 
PutHandler[runningPutHandlers.size()]);
+                       if (containerMode) {
+                               containers = getContainersToStart();
+                       } else {
+                               containers = null;
+                       }
+               }
+               
+               try {
+                       boolean persistent = persistent(); // this might get 
deactivated ...
+                       for (int i = 0; i < running.length; i++) {
+                               running[i].start(container, context);
+                               if(persistent && 
!container.ext().isActive(this))
+                                       container.activate(this, 1);
+                               if (logMINOR)
+                                       Logger.minor(this, "Started " + i + " 
of " + running.length);
+                               if (isFinished()) {
+                                       if (logMINOR)
+                                               Logger.minor(this, "Already 
finished, killing start() on " + this);
+                                       return;
+                               }
+                       }
+                       if (logMINOR)
+                               Logger.minor(this, "Started " + running.length 
+ " PutHandler's for " + this);
+                       
+                       if (containerMode) {
+                               for (int i = 0; i < containers.length; i++) {
+                                       containers[i].start(container, context);
+                                       if(persistent && 
!container.ext().isActive(this))
+                                               container.activate(this, 1);
+                                       if (logMINOR)
+                                               Logger.minor(this, "Started " + 
i + " of " + containers.length);
+                                       if (isFinished()) {
+                                               if (logMINOR)
+                                                       Logger.minor(this, 
"Already finished, killing start() on " + this);
+                                               return;
+                                       }
+                               }
+                               if (logMINOR)
+                                       Logger.minor(this, "Started " + 
containers.length + " PutHandler's (containers) for " + this);
+                               
+                       }
+                       if (!containerMode && running.length == 0) {
+                               insertedAllFiles = true;
+                               if(persistent())
+                                       container.store(this);
+                               gotAllMetadata(container, context);
+                       }
+               } catch (InsertException e) {
+                       synchronized(this) {
+                               finished = true;
+                       }
+                       cancelAndFinish(container, context);
+                       throw e;
+               }
+       }
+       
+       private PutHandler[] getContainersToStart() {
+               PutHandler[] maybeStartPH = containerPutHandlers.toArray(new 
PutHandler[containerPutHandlers.size()]);
+               ArrayList<PutHandler> phToStart = new ArrayList<PutHandler>();
+               
+               for (PutHandler ph: maybeStartPH) {
+                       if 
(perContainerPutHandlersWaitingForMetadata.get(ph).isEmpty() && 
perContainerPutHandlersWaitingForFetchable.get(ph).isEmpty() ) {
+                               phToStart.add(ph); 
+                       }
+               }
+               if (maybeStartPH.length == 0) {
+                       phToStart.add(rootContainer.selfHandle);
+               }
+               return phToStart.toArray(new PutHandler[phToStart.size()]);
+       }
+
+       /**
+        * Implement the pack logic
+        * 
+        * @param manifestElements
+        * @param putHandlersByName
+        */
+       protected abstract void makePutHandlers(HashMap<String, Object> 
manifestElements, HashMap<String,Object> putHandlersByName);
+
+       @Override
+       public FreenetURI getURI() {
+               return finalURI;
+       }
+
+       @Override
+       public synchronized boolean isFinished() {
+               return finished || cancelled;
+       }
+
+       private final DBJob runGotAllMetadata = new DBJob() {
+
+               public void run(ObjectContainer container, ClientContext 
context) {
+                       context.jobRunner.removeRestartJob(this, 
NativeThread.NORM_PRIORITY, container);
+                       container.activate(BaseManifestPutter.this, 1);
+                       innerGotAllMetadata(container, context);
+                       container.deactivate(BaseManifestPutter.this, 1);
+               }
+               
+       };
+       
+       /**
+        * Called when we have metadata for all the PutHandler's.
+        * This does *not* necessarily mean we can immediately insert the final 
metadata, since
+        * if these metadata's are too big, they will need to be inserted 
separately. See
+        * resolveAndStartBase().
+        * @param container
+        * @param context
+        */
+       private void gotAllMetadata(ObjectContainer container, ClientContext 
context) {
+               // This can be huge! Run it on its own transaction to minimize 
the build up of stuff to commit
+               // and maximise the opportunities for garbage collection.
+               if(persistent()) {
+                       container.activate(runGotAllMetadata, 1); // need to 
activate .this!
+                       context.jobRunner.queueRestartJob(runGotAllMetadata, 
NativeThread.NORM_PRIORITY, container, false);
+                       context.jobRunner.queue(runGotAllMetadata, 
NativeThread.NORM_PRIORITY, false);
+               } else {
+                       innerGotAllMetadata(null, context);
+               }
+       }
+       
+       /**
+        * Generate the global metadata, and then call resolveAndStartBase.
+        * @param container
+        * @param context
+        */
+       private void innerGotAllMetadata(ObjectContainer container, 
ClientContext context) {
+               if(logMINOR) Logger.minor(this, "Got all metadata");
+               //if (!containerMode) {
+               HashMap<String, Object> namesToByteArrays = new HashMap<String, 
Object>();
+               // We'll end up committing it all anyway, and hash maps in hash 
maps can cause
+               // *severe* problems (see COR-1582), so activate to max depth 
first.
+               if(persistent()) container.activate(putHandlersByName, 
Integer.MAX_VALUE);
+               namesToByteArrays(putHandlersByName, namesToByteArrays, 
container);
+               if(defaultName != null) {
+                       Metadata meta = (Metadata) 
namesToByteArrays.get(defaultName);
+                       if(meta == null) {
+                               fail(new 
InsertException(InsertException.INVALID_URI, "Default name "+defaultName+" does 
not exist", null), container, context);
+                               return;
+                       }
+                       namesToByteArrays.put("", meta);
+               } else {
+                       for(int j=0;j<defaultDefaultNames.length;j++) {
+                               String name = defaultDefaultNames[j];
+                               Metadata meta = (Metadata) 
namesToByteArrays.get(name);
+                               if(meta != null) {
+                                       namesToByteArrays.put("", meta);
+                                       break;
+                               }
+                       }
+               }
+               baseMetadata =
+                       
Metadata.mkRedirectionManifestWithMetadata(namesToByteArrays);
+               if(persistent()) {
+                       container.store(baseMetadata);
+                       container.store(this);
+               }
+               resolveAndStartBase(container, context);
+               
+       }
+       
+       /**
+        * Attempt to insert the base metadata and the container. If the base 
metadata cannot be resolved,
+        * try to resolve it: start inserts for each part that cannot be 
resolved, and wait for them to generate
+        * URIs that can be incorporated into the metadata. This method will 
then be called again, and will 
+        * complete, or do more resolutions as necessary.
+        * @param container
+        * @param context
+        */
+       private void resolveAndStartBase(ObjectContainer container, 
ClientContext context) {
+               Bucket bucket = null;
+               synchronized(this) {
+                       if(hasResolvedBase) return;
+               }
+               while(true) {
+                       try {
+                               if(persistent())
+                                       container.activate(baseMetadata, 
Integer.MAX_VALUE);
+                               bucket = 
BucketTools.makeImmutableBucket(context.getBucketFactory(persistent()), 
baseMetadata.writeToByteArray());
+                               if(logMINOR)
+                                       Logger.minor(this, "Metadata bucket is 
"+bucket.size()+" bytes long");
+                               break;
+                       } catch (IOException e) {
+                               fail(new 
InsertException(InsertException.BUCKET_ERROR, e, null), container, context);
+                               return;
+                       } catch (MetadataUnresolvedException e) {
+                               try {
+                                       // Start the insert for the 
sub-Metadata.
+                                       // Eventually it will generate a URI 
and call onEncode(), which will call back here.
+                                       if(logMINOR) Logger.minor(this, "Main 
metadata needs resolving: "+e);
+                                       resolve(e, container, context);
+                                       if(persistent())
+                                               
container.deactivate(baseMetadata, 1);
+                                       return;
+                               } catch (IOException e1) {
+                                       if(persistent())
+                                               
container.deactivate(baseMetadata, 1);
+                                       fail(new 
InsertException(InsertException.BUCKET_ERROR, e, null), container, context);
+                                       return;
+                               } catch (InsertException e2) {
+                                       if(persistent())
+                                               
container.deactivate(baseMetadata, 1);
+                                       fail(e2, container, context);
+                                       return;
+                               }
+                       }
+               }
+               if(bucket == null) return;
+               synchronized(this) {
+                       if(hasResolvedBase) return;
+                       hasResolvedBase = true;
+               }
+               if(persistent()) {
+                       container.store(this);
+                       //container.activate(elementsToPutInArchive, 2);
+               }
+               InsertBlock block;
+               boolean isMetadata = true;
+               ARCHIVE_TYPE archiveType = null;
+               block = new InsertBlock(bucket, null, persistent() ? 
targetURI.clone() : targetURI);
+               SingleFileInserter metadataInserter;
+               try {
+                       metadataInserter = 
+                               new SingleFileInserter(this, this, block, 
isMetadata, ctx, (archiveType == ARCHIVE_TYPE.ZIP) , getCHKOnly, false, 
baseMetadata, archiveType, true, null, earlyEncode);
+                       if(logMINOR) Logger.minor(this, "Inserting main 
metadata: "+metadataInserter+" for "+baseMetadata);
+                       if(persistent()) {
+                               container.activate(metadataPuttersByMetadata, 
2);
+                               container.activate(metadataPuttersUnfetchable, 
2);
+                       }
+                       this.metadataPuttersByMetadata.put(baseMetadata, 
metadataInserter);
+                       metadataPuttersUnfetchable.put(baseMetadata, 
metadataInserter);
+                       if(persistent()) {
+                               
container.ext().store(metadataPuttersByMetadata, 2);
+                               
container.ext().store(metadataPuttersUnfetchable, 2);
+                               container.deactivate(metadataPuttersByMetadata, 
1);
+                               
container.deactivate(metadataPuttersUnfetchable, 1);
+                               container.deactivate(baseMetadata, 1);
+
+                       }
+                       metadataInserter.start(null, container, context);
+               } catch (InsertException e) {
+                       fail(e, container, context);
+                       return;
+               }
+               if(persistent()) {
+                       container.deactivate(metadataInserter, 1);
+               }
+       }
+       
+       /**
+        * Start inserts for unresolved (too big) Metadata's. Eventually these 
will call back with an onEncode(),
+        * meaning they have the CHK, and we can progress to 
resolveAndStartBase(). 
+        * @param e
+        * @param container
+        * @param context
+        * @return
+        * @throws InsertException
+        * @throws IOException
+        */
+       private void resolve(MetadataUnresolvedException e, ObjectContainer 
container, ClientContext context) throws InsertException, IOException {
+               Metadata[] metas = e.mustResolve;
+               if(persistent())
+                       container.activate(metadataPuttersByMetadata, 2);
+               for(int i=0;i<metas.length;i++) {
+                       Metadata m = metas[i];
+                       if(persistent()) container.activate(m, 100);
+                       if(logMINOR) Logger.minor(this, "Resolving "+m);
+                       synchronized(this) {
+                               if(metadataPuttersByMetadata.containsKey(m)) {
+                                       if(logMINOR) Logger.minor(this, 
"Already started insert for "+m+" in resolve() for "+metas.length+" 
Metadata's");
+                                       continue;
+                               }
+                       }
+                       if(m.isResolved()) {
+                               Logger.error(this, "Already resolved: "+m+" in 
resolve() - race condition???");
+                               if(persistent()) container.deactivate(m, 1);
+                               continue;
+                       }
+                       try {
+                               Bucket b = 
m.toBucket(context.getBucketFactory(persistent()));
+                               
+                               InsertBlock ib = new InsertBlock(b, null, 
persistent() ? FreenetURI.EMPTY_CHK_URI.clone() : FreenetURI.EMPTY_CHK_URI);
+                               SingleFileInserter metadataInserter = 
+                                       new SingleFileInserter(this, this, ib, 
true, ctx, false, getCHKOnly, false, m, null, true, null, earlyEncode);
+                               if(logMINOR) Logger.minor(this, "Inserting 
subsidiary metadata: "+metadataInserter+" for "+m);
+                               synchronized(this) {
+                                       this.metadataPuttersByMetadata.put(m, 
metadataInserter);
+                               }
+                               metadataInserter.start(null, container, 
context);
+                               if(persistent()) {
+                                       container.deactivate(metadataInserter, 
1);
+                                       container.deactivate(m, 1);
+                               }
+                       } catch (MetadataUnresolvedException e1) {
+                               resolve(e1, container, context);
+                               container.deactivate(m, 1);
+                       }
+               }
+               if(persistent()) {
+                       container.ext().store(metadataPuttersByMetadata, 2);
+                       container.deactivate(metadataPuttersByMetadata, 1);
+               }
+       }
+
+       private void namesToByteArrays(HashMap<String, Object> 
putHandlersByName, HashMap<String,Object> namesToByteArrays, ObjectContainer 
container) {
+               Iterator<String> i = putHandlersByName.keySet().iterator();
+               while(i.hasNext()) {
+                       String name = i.next();
+                       Object o = putHandlersByName.get(name);
+                       if(o instanceof PutHandler) {
+                               PutHandler ph = (PutHandler) o;
+                               if(persistent())
+                                       container.activate(ph, 1);
+                               Metadata meta = ph.metadata;
+                               if(ph.metadata == null)
+                                       Logger.error(this, "Metadata for 
"+name+" : "+ph+" is null");
+                               else {
+                                       ph.clearMetadata(container);
+                                       if(persistent())
+                                               container.activate(meta, 100);
+                                       Logger.minor(this, "Putting "+name);
+                                       namesToByteArrays.put(name, meta);
+                                       if(logMINOR)
+                                               Logger.minor(this, "Putting 
PutHandler into base metadata: "+ph+" name "+name);
+                               }
+                       } else if(o instanceof HashMap) {
+                               HashMap<String,Object> subMap = new 
HashMap<String,Object>();
+                               // Already activated
+                               namesToByteArrays.put(name, subMap);
+                               if(logMINOR)
+                                       Logger.minor(this, "Putting hashmap 
into base metadata: "+name+" size "+((HashMap)o).size()+" active = "+container 
== null ? "null" : Boolean.toString(container.ext().isActive(o)));
+                               Logger.minor(this, "Putting directory: "+name);
+                               namesToByteArrays((HashMap<String, Object>)o, 
subMap, container);
+                       } else
+                               throw new IllegalStateException();
+               }
+       }
+
+       private void insertedAllFiles(ObjectContainer container, ClientContext 
context) {
+               if(logMINOR) Logger.minor(this, "Inserted all files");
+               synchronized(this) {
+                       insertedAllFiles = true;
+                       if(finished || cancelled) {
+                               if(logMINOR) Logger.minor(this, "Already 
"+(finished?"finished":"cancelled"));
+                               if(persistent())
+                                       container.store(this);
+                               return;
+                       }
+                       if(!insertedManifest) {
+                               if(logMINOR) Logger.minor(this, "Haven't 
inserted manifest");
+                               if(persistent())
+                                       container.store(this);
+                               return;
+                       }
+                       finished = true;
+               }
+               if(persistent())
+                       container.store(this);
+               complete(container, context);
+       }
+       
+       private void complete(ObjectContainer container, ClientContext context) 
{
+               // FIXME we could remove the put handlers after inserting all 
files but not having finished the insert of the manifest
+               // However it would complicate matters for no real gain in most 
cases...
+               // Also doing it this way means we don't need to worry about 
+               if(persistent()) removePutHandlers(container, context);
+               boolean deactivateCB = false;
+               if(persistent()) {
+                       deactivateCB = !container.ext().isActive(cb);
+                       container.activate(cb, 1);
+               }
+               cb.onSuccess(this, container);
+               if(deactivateCB)
+                       container.deactivate(cb, 1);
+       }
+
+       private void fail(InsertException e, ObjectContainer container, 
ClientContext context) {
+               // Cancel all, then call the callback
+               synchronized(this) {
+                       if(finished) return;
+                       finished = true;
+               }
+               cancelAndFinish(container, context);
+               if(persistent()) removePutHandlers(container, context);
+               
+               if(persistent())
+                       container.activate(cb, 1);
+               cb.onFailure(e, this, container);
+       }
+       
+       private void removePutHandlers(ObjectContainer container, ClientContext 
context) {
+               container.activate(putHandlersByName, 2);
+               container.activate(runningPutHandlers, 2);
+               container.activate(putHandlersWaitingForMetadata, 2);
+               container.activate(waitingForBlockSets, 2);
+               container.activate(putHandlersWaitingForFetchable, 2);
+               removePutHandlersByName(container, context, putHandlersByName);
+               putHandlersByName = null;
+               
+               if(!runningPutHandlers.isEmpty()) {
+                       Logger.error(this, "Running put handlers not part of 
putHandlersByName: "+runningPutHandlers.size()+" in removePutHandlers() on 
"+this, new Exception("error"));
+                       PutHandler[] handlers = runningPutHandlers.toArray(new 
PutHandler[runningPutHandlers.size()]);
+                       for(PutHandler handler : handlers) {
+                               container.activate(handler, 1);
+                               Logger.error(this, "Still running, but not in 
putHandlersByName: "+handler);
+                               handler.cancel();
+                               handler.removeFrom(container, context);
+                       }
+                       runningPutHandlers.clear();
+               }
+               if(!putHandlersWaitingForMetadata.isEmpty()) {
+                       Logger.error(this, "Put handlers waiting for metadata, 
not part of putHandlersByName: "+putHandlersWaitingForMetadata.size()+" in 
removePutHandlers() on "+this, new Exception("error"));
+                       PutHandler[] handlers = 
putHandlersWaitingForMetadata.toArray(new 
PutHandler[putHandlersWaitingForMetadata.size()]);
+                       for(PutHandler handler : handlers) {
+                               container.activate(handler, 1);
+                               Logger.error(this, "Still waiting for metadata, 
but not in putHandlersByName: "+handler);
+                               handler.cancel();
+                               handler.removeFrom(container, context);
+                       }
+                       putHandlersWaitingForMetadata.clear();
+               }
+               if(!waitingForBlockSets.isEmpty()) {
+                       Logger.error(this, "Put handlers waiting for block 
sets, not part of putHandlersByName: "+waitingForBlockSets.size()+" in 
removePutHandlers() on "+this, new Exception("error"));
+                       PutHandler[] handlers = waitingForBlockSets.toArray(new 
PutHandler[waitingForBlockSets.size()]);
+                       for(PutHandler handler : handlers) {
+                               container.activate(handler, 1);
+                               Logger.error(this, "Still waiting for block 
set, but not in putHandlersByName: "+handler);
+                               handler.cancel();
+                               handler.removeFrom(container, context);
+                       }
+                       waitingForBlockSets.clear();
+               }
+               if(!putHandlersWaitingForFetchable.isEmpty()) {
+                       Logger.error(this, "Put handlers waiting for fetchable, 
not part of putHandlersByName: "+putHandlersWaitingForFetchable.size()+" in 
removePutHandlers() on "+this, new Exception("error"));
+                       PutHandler[] handlers = 
putHandlersWaitingForFetchable.toArray(new 
PutHandler[putHandlersWaitingForFetchable.size()]);
+                       for(PutHandler handler : handlers) {
+                               container.activate(handler, 1);
+                               Logger.error(this, "Still waiting for 
fetchable, but not in putHandlersByName: "+handler);
+                               handler.cancel();
+                               handler.removeFrom(container, context);
+                       }
+                       putHandlersWaitingForFetchable.clear();
+               }
+
+               container.delete(runningPutHandlers);
+               container.delete(putHandlersWaitingForMetadata);
+               container.delete(waitingForBlockSets);
+               container.delete(putHandlersWaitingForFetchable);
+               runningPutHandlers = null;
+               putHandlersWaitingForMetadata = null;
+               waitingForBlockSets = null;
+               putHandlersWaitingForFetchable = null;
+               container.store(this);
+       }
+
+       /**
+        * Remove all PutHandler's from the given putHandlersByName sub-map.
+        * Remove the PutHandler's themselves also, remove them from and 
complain about
+        * runningPutHandlers, putHandlersWaitingForMetadata, 
waitingForBlockSets, 
+        * putHandlersWaitingForFetchable, which must have been activated by 
the caller.
+        * @param container
+        * @param putHandlersByName
+        */
+       private void removePutHandlersByName(ObjectContainer container, 
ClientContext context, HashMap<String, Object> putHandlersByName) {
+               if(logMINOR) Logger.minor(this, "removePutHandlersByName on 
"+this+" : map size = "+putHandlersByName.size());
+               for(Map.Entry<String, Object> entry : 
putHandlersByName.entrySet()) {
+                       String key = entry.getKey();
+                       Object value = entry.getValue();
+                       if(value instanceof PutHandler) {
+                               PutHandler handler = (PutHandler) value;
+                               container.activate(handler, 1);
+                               if(runningPutHandlers.remove(handler))
+                                       
container.ext().store(runningPutHandlers, 2);
+                               
if(putHandlersWaitingForMetadata.remove(handler))
+                                       
container.ext().store(putHandlersWaitingForMetadata, 2);
+                               if(waitingForBlockSets.remove(handler))
+                                       
container.ext().store(waitingForBlockSets, 2);
+                               
if(putHandlersWaitingForFetchable.remove(handler))
+                                       
container.ext().store(putHandlersWaitingForFetchable, 2);
+                               //if(elementsToPutInArchive.remove(handler))
+                               //      
container.ext().store(elementsToPutInArchive, 2);
+                               handler.removeFrom(container, context);
+                       } else {
+                               @SuppressWarnings("unchecked")
+                               HashMap<String, Object> subMap = 
(HashMap<String, Object>) value;
+                               container.activate(subMap, 2);
+                               removePutHandlersByName(container, context, 
subMap);
+                       }
+                       container.delete(key);
+               }
+               putHandlersByName.clear();
+               container.delete(putHandlersByName);
+       }
+
+       /**
+        * Cancel all running inserters.
+        */
+       private void cancelAndFinish(ObjectContainer container, ClientContext 
context) {
+               PutHandler[] running;
+               boolean persistent = persistent();
+               if(persistent)
+                       container.activate(runningPutHandlers, 2);
+               synchronized(this) {
+                       running = runningPutHandlers.toArray(new 
PutHandler[runningPutHandlers.size()]);
+               }
+               
+               if(logMINOR) Logger.minor(this, "PutHandler's to cancel: 
"+running.length);
+               for(PutHandler putter : running) {
+                       boolean active = true;
+                       if(persistent) {
+                               active = container.ext().isActive(putter);
+                               if(!active) container.activate(putter, 1);
+                       }
+                       putter.cancel(container, context);
+                       if(!active) container.deactivate(putter, 1);
+                       if(persistent) container.activate(this, 1);
+               }
+               
+               ClientPutState[] runningMeta;
+               if(persistent())
+                       container.activate(metadataPuttersByMetadata, 2);
+               synchronized(this) {
+                       runningMeta = 
metadataPuttersByMetadata.values().toArray(new 
ClientPutState[metadataPuttersByMetadata.size()]);
+               }
+               
+               if(logMINOR) Logger.minor(this, "Metadata putters to cancel: 
"+runningMeta.length);
+               for(ClientPutState putter : runningMeta) {
+                       boolean active = true;
+                       if(persistent) {
+                               active = container.ext().isActive(putter);
+                               if(!active) container.activate(putter, 1);
+                       }
+                       putter.cancel(container, context);
+                       if(!active) container.deactivate(putter, 1);
+                       if(persistent) container.activate(this, 1);
+               }
+               
+       }
+       
+       @Override
+       public void cancel(ObjectContainer container, ClientContext context) {
+               synchronized(this) {
+                       if(finished) return;
+                       if(super.cancel()) return;
+               }
+               if(persistent())
+                       container.store(this);
+               fail(new InsertException(InsertException.CANCELLED), container, 
context);
+       }
+       
+       public void onSuccess(ClientPutState state, ObjectContainer container, 
ClientContext context) {
+               if(persistent()) {
+                       container.activate(metadataPuttersByMetadata, 2);
+               }
+               boolean fin = false;
+               ClientPutState oldState = null;
+               Metadata token = (Metadata) state.getToken();
+               synchronized(this) {
+                       if(persistent()) container.activate(token, 1);
+                       boolean present = 
metadataPuttersByMetadata.containsKey(token);
+                       if(present) {
+                               oldState = 
metadataPuttersByMetadata.remove(token);
+                               if(persistent())
+                                       
container.activate(metadataPuttersUnfetchable, 2);
+                               
if(metadataPuttersUnfetchable.containsKey(token)) {
+                                       
metadataPuttersUnfetchable.remove(token);
+                                       if(persistent())
+                                               
container.ext().store(metadataPuttersUnfetchable, 2);
+                               }
+                       }
+                       if(!metadataPuttersByMetadata.isEmpty()) {
+                               if(logMINOR) Logger.minor(this, "Still running 
metadata putters: "+metadataPuttersByMetadata.size());
+                       } else {
+                               Logger.minor(this, "Inserted manifest 
successfully on "+this+" : "+state);
+                               insertedManifest = true;
+                               if(finished) {
+                                       if(logMINOR) Logger.minor(this, 
"Already finished");
+                                       if(persistent())
+                                               container.store(this);
+                               } else if(!insertedAllFiles) {
+                                       if(logMINOR) Logger.minor(this, "Not 
inserted all files");
+                                       if(persistent())
+                                               container.store(this);
+                               } else {
+                                       finished = true;
+                                       if(persistent()) container.store(this);
+                                       fin = true;
+                               }
+                       }
+               }
+               if(token != baseMetadata)
+                       token.removeFrom(container);
+               if(persistent()) {
+                       container.ext().store(metadataPuttersByMetadata, 2);
+                       container.deactivate(metadataPuttersByMetadata, 1);
+                       state.removeFrom(container, context);
+                       if(oldState != state && oldState != null) {
+                               container.activate(oldState, 1);
+                               oldState.removeFrom(container, context);
+                       }
+               }
+               if(fin)
+                       complete(container, context);
+       }
+       
+       public void onFailure(InsertException e, ClientPutState state, 
ObjectContainer container, ClientContext context) {
+               if(persistent()) {
+                       container.activate(metadataPuttersByMetadata, 2);
+               }
+               ClientPutState oldState = null;
+               Metadata token = (Metadata) state.getToken();
+               synchronized(this) {
+                       if(persistent()) container.activate(token, 1);
+                       boolean present = 
metadataPuttersByMetadata.containsKey(token);
+                       if(present) {
+                               oldState = 
metadataPuttersByMetadata.remove(token);
+                               if(persistent())
+                                       
container.activate(metadataPuttersUnfetchable, 2);
+                               
if(metadataPuttersUnfetchable.containsKey(token)) {
+                                       
metadataPuttersUnfetchable.remove(token);
+                                       if(persistent())
+                                               
container.ext().store(metadataPuttersUnfetchable);
+                               }
+                       }
+               }
+               if(token != baseMetadata)
+                       token.removeFrom(container);
+               if(persistent()) {
+                       container.ext().store(metadataPuttersByMetadata, 2);
+                       container.deactivate(metadataPuttersByMetadata, 1);
+                       state.removeFrom(container, context);
+                       if(oldState != state && oldState != null) {
+                               container.activate(oldState, 1);
+                               oldState.removeFrom(container, context);
+                       }
+               }
+               fail(e, container, context);
+       }
+       
+       public void onEncode(BaseClientKey key, ClientPutState state, 
ObjectContainer container, ClientContext context) {
+               if(state.getToken() == baseMetadata) {
+                       this.finalURI = key.getURI();
+                       if(logMINOR) Logger.minor(this, "Got metadata key: 
"+finalURI);
+                       if(persistent())
+                               container.activate(cb, 1);
+                       cb.onGeneratedURI(persistent() ? finalURI.clone() : 
finalURI, this, container);
+                       if(persistent())
+                               container.deactivate(cb, 1);
+                       if(persistent())
+                               container.store(this);
+               } else {
+                       // It's a sub-Metadata
+                       Metadata m = (Metadata) state.getToken();
+                       if(persistent())
+                               container.activate(m, 2);
+                       m.resolve(key.getURI());
+                       if(persistent())
+                               container.store(m);
+                       if(logMINOR) Logger.minor(this, "Resolved "+m+" : 
"+key.getURI());
+                       resolveAndStartBase(container, context);
+               }
+       }
+       
+       public void onTransition(ClientPutState oldState, ClientPutState 
newState, ObjectContainer container) {
+               Metadata m = (Metadata) oldState.getToken();
+               if(persistent()) {
+                       container.activate(m, 100);
+                       container.activate(metadataPuttersUnfetchable, 2);
+                       container.activate(metadataPuttersByMetadata, 2);
+               }
+               synchronized(this) {
+                       if(metadataPuttersByMetadata.containsKey(m)) {
+                               ClientPutState prevState = 
metadataPuttersByMetadata.get(m);
+                               if(prevState != oldState) {
+                                       if(logMINOR) Logger.minor(this, 
"Ignoring transition in "+this+" for metadata putter: "+oldState+" -> 
"+newState+" because current for "+m+" is "+prevState);
+                                       
container.deactivate(metadataPuttersUnfetchable, 1);
+                                       
container.deactivate(metadataPuttersByMetadata, 1);
+                                       return;
+                               }
+                               if(persistent()) container.store(newState);
+                               metadataPuttersByMetadata.put(m, newState);
+                               if(persistent()) 
container.ext().store(metadataPuttersByMetadata, 2);
+                               if(logMINOR) Logger.minor(this, "Metadata 
putter transition: "+oldState+" -> "+newState);
+                               if(metadataPuttersUnfetchable.containsKey(m)) {
+                                       metadataPuttersUnfetchable.put(m, 
newState);
+                                       if(persistent()) 
container.ext().store(metadataPuttersUnfetchable, 2);
+                                       if(logMINOR) Logger.minor(this, 
"Unfetchable metadata putter transition: "+oldState+" -> "+newState);
+                               }
+                               if(logMINOR) Logger.minor(this, "Transition: 
"+oldState+" -> "+newState);
+                       } else {
+                               Logger.error(this, "onTransition() but 
metadataPuttersByMetadata does not contain metadata tag "+m+" for "+oldState+" 
should -> "+newState);
+                       }
+               }
+
+               if(persistent()) {
+                       container.deactivate(m, 100);
+                       container.deactivate(metadataPuttersUnfetchable, 2);
+                       container.deactivate(metadataPuttersByMetadata, 2);
+               }
+       }
+       
+       public void onMetadata(Metadata m, ClientPutState state, 
ObjectContainer container, ClientContext context) {
+               // Ignore
+       }
+
+       @Override
+       public void notifyClients(ObjectContainer container, ClientContext 
context) {
+               if(persistent()) {
+                       container.activate(ctx, 1);
+                       container.activate(ctx.eventProducer, 1);
+               }
+               ctx.eventProducer.produceEvent(new 
SplitfileProgressEvent(this.totalBlocks, this.successfulBlocks, 
this.failedBlocks, this.fatallyFailedBlocks, this.minSuccessBlocks, 
this.blockSetFinalized), container, context);
+       }
+
+       public void onBlockSetFinished(ClientPutState state, ObjectContainer 
container, ClientContext context) {
+               synchronized(this) {
+                       this.metadataBlockSetFinalized = true;
+                       if(persistent())
+                               container.activate(waitingForBlockSets, 2);
+                       if(!waitingForBlockSets.isEmpty()) {
+                               if(persistent()) {
+                                       container.store(this);
+                                       
container.deactivate(waitingForBlockSets, 1);
+                               }
+                               return;
+                       }
+               }
+               this.blockSetFinalized(container, context);
+               if(persistent()) {
+                       container.store(this);
+                       container.deactivate(waitingForBlockSets, 1);
+               }
+       }
+
+       @Override
+       public void blockSetFinalized(ObjectContainer container, ClientContext 
context) {
+               synchronized(this) {
+                       if(!metadataBlockSetFinalized) return;
+                       if(persistent())
+                               container.activate(waitingForBlockSets, 2);
+                       if(waitingForBlockSets.isEmpty()) {
+                               if(persistent())
+                                       
container.deactivate(waitingForBlockSets, 1);
+                               return;
+                       }
+               }
+               if(persistent())
+                       container.deactivate(waitingForBlockSets, 1);
+               super.blockSetFinalized(container, context);
+               if(persistent())
+                       container.store(this);
+       }
+       
+       /**
+        * Convert a HashMap of name -> bucket to a HashSet of ManifestEntry's.
+        * All are to have mimeOverride=null, i.e. we use the auto-detected 
mime type
+        * from the filename.
+        */
+       public static HashMap<String, Object> 
bucketsByNameToManifestEntries(HashMap<String,Object> bucketsByName) {
+               HashMap<String,Object> manifestEntries = new 
HashMap<String,Object>();
+               Iterator<String> i = bucketsByName.keySet().iterator();
+               while(i.hasNext()) {
+                       String name = i.next();
+                       Object o = bucketsByName.get(name);
+                       if(o instanceof Bucket) {
+                               Bucket data = (Bucket) bucketsByName.get(name);
+                               manifestEntries.put(name, new 
ManifestElement(name, data, null,data.size()));
+                       } else if(o instanceof HashMap) {
+                               manifestEntries.put(name, 
bucketsByNameToManifestEntries((HashMap<String, Object>)o));
+                       } else
+                               throw new 
IllegalArgumentException(String.valueOf(o));
+               }
+               return manifestEntries;
+       }
+
+       /**
+        * Convert a hierarchy of HashMap's of ManifestEntries into a series of 
+        * ManifestElement's, each of which has a full path.
+        */
+       public static ManifestElement[] flatten(HashMap<String,Object> 
manifestElements) {
+               Vector<ManifestElement> v = new Vector<ManifestElement>();
+               flatten(manifestElements, v, "");
+               return v.toArray(new ManifestElement[v.size()]);
+       }
+
+       public static void flatten(HashMap<String,Object> manifestElements, 
Vector<ManifestElement> v, String prefix) {
+               Iterator<String> i = manifestElements.keySet().iterator();
+               while(i.hasNext()) {
+                       String name = i.next();
+                       String fullName = prefix.length() == 0 ? name : prefix+ 
'/' +name;
+                       Object o = manifestElements.get(name);
+                       if(o instanceof HashMap) {
+                               flatten((HashMap<String, Object>)o, v, 
fullName);
+                       } else if(o instanceof ManifestElement) {
+                               ManifestElement me = (ManifestElement) o;
+                               v.add(new ManifestElement(me, fullName));
+                       } else
+                               throw new 
IllegalStateException(String.valueOf(o));
+               }
+       }
+
+       /**
+        * Opposite of flatten(...).
+        * Note that this can throw a ClassCastException if the vector passed 
in is
+        * bogus (has files pretending to be directories).
+        */
+       public static <T> HashMap<String, Object> 
unflatten(List<ManifestElement> v) {
+               HashMap<String, Object> manifestElements = new HashMap<String, 
Object>();
+               for(ManifestElement oldElement : v) {
+                       add(oldElement, oldElement.getName(), manifestElements);
+               }
+               return manifestElements;
+       }
+       
+       private static void add(ManifestElement e, String namePart, Map<String, 
Object> target) {
+               int idx = namePart.indexOf('/');
+               if(idx < 0) {
+                       target.put(namePart, new ManifestElement(e, namePart));
+               } else {
+                       String before = namePart.substring(0, idx);
+                       String after = namePart.substring(idx+1);
+                       @SuppressWarnings("unchecked")
+                       HashMap<String, Object> hm = (HashMap<String, Object>) 
target.get(before);
+                       if(hm == null) {
+                               hm = new HashMap<String, Object>();
+                               target.put(before.intern(), hm);
+                       }
+                       add(e, after, hm);
+               }
+       }
+
+       public int countFiles() {
+               return numberOfFiles;
+       }
+
+       public long totalSize() {
+               return totalSize;
+       }
+
+       @Override
+       public void onMajorProgress(ObjectContainer container) {
+               boolean deactivate = false;
+               if(persistent()) {
+                       deactivate = !container.ext().isActive(cb);
+                       if(deactivate) container.activate(cb, 1);
+               }
+               cb.onMajorProgress(container);
+               if(deactivate) container.deactivate(cb, 1);
+       }
+
+       protected void onFetchable(PutHandler handler, ObjectContainer 
container) {
+               if(persistent()) {
+                       container.activate(putHandlersWaitingForFetchable, 2);
+                       container.activate(metadataPuttersUnfetchable, 2);
+               }
+               if(checkFetchable(handler)) {
+                       if(persistent()) {
+                               
container.ext().store(putHandlersWaitingForMetadata, 2);
+                               container.store(this);
+                               
container.deactivate(putHandlersWaitingForFetchable, 1);
+                               
container.deactivate(metadataPuttersUnfetchable, 1);
+                               container.activate(cb, 1);
+                       }
+                       cb.onFetchable(this, container);
+                       if(persistent())
+                               container.deactivate(cb, 1);
+               } else {
+                       if(persistent()) {
+                               
container.deactivate(putHandlersWaitingForFetchable, 1);
+                               
container.deactivate(metadataPuttersUnfetchable, 1);
+                       }
+               }
+       }
+
+       private synchronized boolean checkFetchable(PutHandler handler) {
+               putHandlersWaitingForFetchable.remove(handler);
+               if(fetchable) return false;
+               if(!putHandlersWaitingForFetchable.isEmpty()) return false;
+               if(!hasResolvedBase) return false;
+               if(!metadataPuttersUnfetchable.isEmpty()) return false;
+               fetchable = true;
+               return true;
+       }
+
+       public void onFetchable(ClientPutState state, ObjectContainer 
container) {
+               Metadata m = (Metadata) state.getToken();
+               if(persistent()) {
+                       container.activate(m, 100);
+                       container.activate(metadataPuttersUnfetchable, 2);
+                       container.activate(putHandlersWaitingForFetchable, 2);
+               }
+               if(checkFetchable(m)) {
+                       if(persistent()) {
+                               
container.ext().store(metadataPuttersUnfetchable, 2);
+                               container.store(this);
+                               container.activate(cb, 1);
+                       }
+                       cb.onFetchable(this, container);
+                       if(persistent())
+                               container.deactivate(cb, 1);
+               }
+               if(persistent()) {
+                       container.deactivate(metadataPuttersUnfetchable, 1);
+                       container.deactivate(putHandlersWaitingForFetchable, 1);
+               }
+       }
+
+       private synchronized boolean checkFetchable(Metadata m) {
+               metadataPuttersUnfetchable.remove(m);
+               if(!metadataPuttersUnfetchable.isEmpty()) return false;
+               if(fetchable) return false;
+               if(!putHandlersWaitingForFetchable.isEmpty()) return false;
+               fetchable = true;
+               return true;
+       }
+
+       @Override
+       public void onTransition(ClientGetState oldState, ClientGetState 
newState, ObjectContainer container) {
+               // Ignore
+       }
+
+       @Override
+       public void removeFrom(ObjectContainer container, ClientContext 
context) {
+               if(putHandlersByName != null) {
+                       Logger.error(this, "Put handlers list still present in 
removeFrom() on "+this);
+                       removePutHandlers(container, context);
+               }
+               if(finalURI != null) {
+                       container.activate(finalURI, 5);
+                       finalURI.removeFrom(container);
+               }
+               container.activate(targetURI, 5);
+               targetURI.removeFrom(container);
+               // This is passed in. We should not remove it, because the 
caller (ClientPutDir) should remove it.
+//             container.activate(ctx, 1);
+//             ctx.removeFrom(container);
+               container.activate(metadataPuttersByMetadata, 2);
+               container.activate(metadataPuttersUnfetchable, 2);
+               ArrayList<Metadata> metas = null;
+               if(!metadataPuttersByMetadata.isEmpty()) {
+                       Logger.error(this, "Metadata putters by metadata not 
empty in removeFrom() on "+this);
+                       for(Map.Entry<Metadata, ClientPutState> entry : 
metadataPuttersByMetadata.entrySet()) {
+                               Metadata meta = entry.getKey();
+                               container.activate(meta, 1);
+                               ClientPutState sfi = entry.getValue();
+                               container.activate(sfi, 1);
+                               metadataPuttersUnfetchable.remove(meta);
+                               Logger.error(this, "Metadata putters not empty: 
"+sfi+" for "+this);
+                               sfi.cancel(container, context);
+                               sfi.removeFrom(container, context);
+                               if(metas == null) metas = new 
ArrayList<Metadata>();
+                               metas.add(meta);
+                       }
+               }
+               if(!metadataPuttersUnfetchable.isEmpty()) {
+                       Logger.error(this, "Metadata putters unfetchable by 
metadata not empty in removeFrom() on "+this);
+                       for(Map.Entry<Metadata, ClientPutState> entry : 
metadataPuttersByMetadata.entrySet()) {
+                               Metadata meta = entry.getKey();
+                               container.activate(meta, 1);
+                               ClientPutState sfi = entry.getValue();
+                               container.activate(sfi, 1);
+                               metadataPuttersUnfetchable.remove(meta);
+                               Logger.error(this, "Metadata putters 
unfetchable not empty: "+sfi+" for "+this);
+                               sfi.cancel(container, context);
+                               sfi.removeFrom(container, context);
+                       }
+               }
+               if(metas != null) {
+                       for(Metadata meta : metas) {
+                               if(meta == baseMetadata) continue;
+                               container.activate(meta, 1);
+                               meta.removeFrom(container);
+                       }
+               }
+               metadataPuttersByMetadata.clear();
+               metadataPuttersUnfetchable.clear();
+               container.delete(metadataPuttersByMetadata);
+               container.delete(metadataPuttersUnfetchable);
+               if(baseMetadata != null) {
+                       container.activate(baseMetadata, 1);
+                       baseMetadata.removeFrom(container);
+               }
+               container.activate(runGotAllMetadata, 1);
+               container.delete(runGotAllMetadata);
+               super.removeFrom(container, context);
+       }
+       
+       public void objectOnUpdate(ObjectContainer container) {
+               if(logDEBUG) Logger.debug(this, "Updating "+this+" 
activated="+container.ext().isActive(this)+" 
stored="+container.ext().isStored(this), new Exception("debug"));
+       }
+
+       public boolean objectCanNew(ObjectContainer container) {
+               if(finished) {
+                       Logger.error(this, "Storing "+this+" when already 
finished!", new Exception("error"));
+                       return false;
+               }
+               if(logDEBUG) Logger.debug(this, "Storing "+this+" 
activated="+container.ext().isActive(this)+" 
stored="+container.ext().isStored(this), new Exception("debug"));
+               return true;
+       }
+       
+
+       private void maybeStartParentContainer(PutHandler containerHandle2, 
ObjectContainer container, ClientContext context) throws InsertException {
+               if 
(perContainerPutHandlersWaitingForMetadata.get(containerHandle2).isEmpty() && 
perContainerPutHandlersWaitingForFetchable.get(containerHandle2).isEmpty()) {
+                       containerHandle2.start(container, context);
+               }
+       }
+
+
+       // compose helper stuff
+
+       protected final ClientMetadata guessMime(String name, ManifestElement 
me) {
+               return guessMime(name, me.mimeOverride);
+       }
+               
+       protected final ClientMetadata guessMime(String name, String mimetype) 
{        
+               String mimeType = mimetype;
+               if(mimeType == null)
+                       mimeType = DefaultMIMETypes.guessMIMEType(name, true);
+               ClientMetadata cm;
+               if(mimeType == null || 
mimeType.equals(DefaultMIMETypes.DEFAULT_MIME_TYPE))
+                       cm = null;
+               else
+                       cm = new ClientMetadata(mimeType);
+               return cm;
+       }
+
+       protected final void addRedirectNoMime(String name, ManifestElement me, 
HashMap<String, Object> putHandlersByName2) {
+               addRedirect(name, me, null, putHandlersByName2);
+       }
+
+       protected final void addRedirect(String name, ManifestElement me, 
HashMap<String, Object> putHandlersByName2) {
+               addRedirect(name, me, guessMime(name, me), putHandlersByName2);
+       }
+
+       protected final void addRedirect(String name, ManifestElement me, 
ClientMetadata cm, HashMap<String, Object> putHandlersByName2) {
+               if (containerMode) throw new IllegalStateException("You can not 
add freeform elements in container mode!");
+               PutHandler ph;
+               Bucket data = me.data;
+               if(me.targetURI != null) {
+                       ph = new PutHandler(this, name, me.targetURI, cm);
+                       // Just a placeholder, don't actually run it
+               } else {
+                       ph = new PutHandler(this, name, data, cm, getCHKOnly);
+                       runningPutHandlers.add(ph);
+                       putHandlersWaitingForMetadata.add(ph);
+                       putHandlersWaitingForFetchable.add(ph);
+                       if(logMINOR)
+                               Logger.minor(this, "Inserting separately as 
PutHandler: "+name+" : "+ph+" persistent="+ph.persistent()+":"+ph.persistent+" 
"+persistent());
+                       numberOfFiles++;
+                       totalSize += data.size();
+               }
+               putHandlersByName2.put(name, ph);
+       }
+       
+       public ContainerBuilder makeArchive() {
+               return new ContainerBuilder(false, null, null, true);
+       }
+
+       protected ContainerBuilder getRootContainer() {
+               if (!containerMode) {
+                       containerMode = true;
+                       rootContainer = new ContainerBuilder(true);
+                       putHandlersByName = null;
+               }
+               return rootContainer;
+       }
+       
+       protected final class ContainerBuilder {
+               
+               private final HashMap<String, Object> rootDir;
+               private HashMap<String, Object> currentDir;
+               private final PutHandler selfHandle;
+               
+               private final Stack<HashMap<String, Object>> dirStack;
+               
+               private ContainerBuilder(boolean isRoot) {
+                       this(isRoot, null, null, false);
+               }
+               
+               private ContainerBuilder(PutHandler parent, String name) {
+                       this(false, parent, name, false);
+               }
+               
+               private ContainerBuilder(boolean isRoot, PutHandler parent, 
String name, boolean isArchive) {
+                       if (putHandlersByName != null && 
putHandlersByName.size() > 0) {
+                               throw new IllegalStateException("You can not 
add containers in free form mode!");
+                       }
+                       dirStack = new Stack<HashMap<String, Object>>();
+                       rootDir = new HashMap<String, Object>();
+                       selfHandle = new PutHandler(BaseManifestPutter.this, 
parent, name, rootDir, null, getCHKOnly, isArchive);
+                       currentDir = rootDir;
+                       if (isRoot) {
+                               rootContainerPutHandler = selfHandle;
+                       } else {
+                               containerPutHandlers.add(selfHandle);
+                       }
+                       
perContainerPutHandlersWaitingForMetadata.put(selfHandle, new 
HashSet<PutHandler>());
+                       
perContainerPutHandlersWaitingForFetchable.put(selfHandle, new 
HashSet<PutHandler>());
+                       if (isArchive) 
putHandlersArchiveTransformMap.put(selfHandle, new Vector<PutHandler>());
+               }
+               
+               public ContainerBuilder makeSubContainer(String name) {
+                       ContainerBuilder subCon = new 
ContainerBuilder(selfHandle, name);
+                       currentDir.put(name , subCon.selfHandle);
+                       
perContainerPutHandlersWaitingForMetadata.get(selfHandle).add(subCon.selfHandle);
+                       putHandlersTransformMap.put(subCon.selfHandle, 
currentDir);
+                       
//perContainerPutHandlersWaitingForFetchable.get(selfHandle).add(subCon.selfHandle);
+                       return subCon;
+               }
+               
+               public void makeSubDirCD(String name) {
+                       currentDir = makeSubDir(currentDir, name);
+               }
+               
+               private HashMap<String, Object> makeSubDir(HashMap<String, 
Object> parentDir, String name) {
+                       HashMap<String, Object> newDir = new HashMap<String, 
Object>();
+                       parentDir.put(name , newDir);
+                       return newDir;
+               }
+               
+               public void addItem(String name, String nameInArchive, String 
mimeOverride, Bucket data) {
+                       addItem(name, nameInArchive, mimeOverride, data, false);
+               }
+               
+               public void addItem(String name, String nameInArchive, String 
mimeOverride, Bucket data, boolean isDefaultDoc) {
+                       ManifestElement element = new ManifestElement(name, 
nameInArchive, data, mimeOverride, data.size());
+                       addElement(name, element, isDefaultDoc);
+               }
+               
+               public void addRedirect(String name, String mimeOverride, 
FreenetURI targetURI2) {
+                       addRedirect(name, mimeOverride, targetURI2, false);
+               }
+               
+               public void addRedirect(String name, String mimeOverride, 
FreenetURI targetURI2, boolean isDefaultDoc) {
+                       ManifestElement element = new ManifestElement(name, 
targetURI2, mimeOverride);
+                       addElement(name, element, isDefaultDoc);
+               }
+               
+               public void addExternal(String name, String mimeOverride, 
Bucket data) {
+                       addExternal(name, mimeOverride, data, false);
+               }
+               
+               public void addExternal(String name, String mimeOverride, 
Bucket data, boolean isDefaultDoc) {
+                       PutHandler ph = new PutHandler(BaseManifestPutter.this, 
selfHandle, name, data, guessMime(name, mimeOverride), getCHKOnly);
+                       runningPutHandlers.add(ph);
+                       
perContainerPutHandlersWaitingForMetadata.get(selfHandle).add(ph);
+                       putHandlersTransformMap.put(ph, currentDir);
+               }
+               
+               private void addElement(String name, ManifestElement element, 
boolean isDefaultDoc) {
+                       currentDir.put(name, element);
+                       if (isDefaultDoc) currentDir.put("", element);
+               }
+
+               public void addArchiveItem(ContainerBuilder archive, String 
name, String nameInArchive, String mimeTypeOverride, Bucket data) {
+                       addArchiveItem(archive, name, nameInArchive, 
mimeTypeOverride, data, false);
+               }
+               
+               public void addArchiveItem(ContainerBuilder archive, String 
name, String nameInArchive, String mimeTypeOverride, Bucket data, boolean 
isDefaultDoc) {
+                       archive.addItem(name, nameInArchive, mimeTypeOverride, 
data, isDefaultDoc);
+                       PutHandler ph = new PutHandler(BaseManifestPutter.this, 
selfHandle, name, nameInArchive, FreenetURI.EMPTY_CHK_URI, guessMime(name, 
mimeTypeOverride));
+                       putHandlersTransformMap.put(ph, currentDir);
+                       
perContainerPutHandlersWaitingForFetchable.get(selfHandle).add(archive.selfHandle);
+                       
putHandlersArchiveTransformMap.get(archive.selfHandle).add(ph);
+               }
+
+               public void pushCurrentDir() {
+                       dirStack.push(currentDir);
+               }
+
+               public void popCurrentDir() {
+                       currentDir = dirStack.pop();
+               }
+       }
+}
+


Property changes on: 
trunk/freenet/src/freenet/client/async/BaseManifestPutter.java
___________________________________________________________________
Added: svn:mime-type
   + text/plain


Reply via email to