r26799 - trunk/freenet/src/freenet/client/async

[email protected] Tue, 14 Apr 2009 19:24:13 +0000
Newsgroups gmane.network.freenet.cvs
Message-ID <[email protected]>
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