Package freenet.client.async

Source Code of freenet.client.async.BaseManifestPutter$ContainerPutHandler

/* This code is part of Freenet. It is distributed under the GNU General
* Public License, version 2 (or at your option any later version). See
* http://www.gnu.org/ for further details of the GPL. */
package freenet.client.async;

import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import freenet.client.ClientMetadata;
import freenet.client.DefaultMIMETypes;
import freenet.client.InsertBlock;
import freenet.client.InsertContext;
import freenet.client.InsertContext.CompatibilityMode;
import freenet.client.InsertException;
import freenet.client.InsertException.InsertExceptionMode;
import freenet.client.Metadata;
import freenet.client.MetadataUnresolvedException;
import freenet.client.ArchiveManager.ARCHIVE_TYPE;
import freenet.client.Metadata.DocumentType;
import freenet.client.Metadata.SimpleManifestComposer;
import freenet.client.events.SplitfileProgressEvent;
import freenet.keys.BaseClientKey;
import freenet.keys.FreenetURI;
import freenet.keys.Key;
import freenet.support.Logger;
import freenet.support.api.Bucket;
import freenet.support.api.BucketFactory;
import freenet.support.api.ManifestElement;
import freenet.support.api.RandomAccessBucket;
import freenet.support.io.BucketTools;
import freenet.support.io.ResumeFailedException;

/**
* <P>Base class for site insertion.
* This class contains all the insert logic, but not any 'pack logic'.
* The pack logic have to be implement in a subclass in makePutHandlers.
* <P>
* Internal container redirect URIs:
*  The internal container URIs should be always redirects to CHKs, not just include the metadata into manifest only.
*  The (assumed) default behavior is the reuse of containers between editions,
*  also ArchiveManger want to have a URI given, not Metadata.
*  This rule also makes site update code/logic much more easier.
* <P>
* <DL>
* <DT>container mode: <DD>the metadata are inside the root container (the final URI points to an archive)
* <DT>freeform mode: <DD>the metadata are inserted separately.(the final URI points to a SimpleManifest)
* </DL>
* @see {@link PlainManifestPutter} and {@link DefaultManifestPutter}</P>
*
* WARNING: Changing non-transient members on classes that are Serializable can result in
* restarting downloads or losing uploads.
*/
public abstract class BaseManifestPutter extends ManifestPutter implements ClientRequestSchedulerGroup {

    private static final long serialVersionUID = 1L;
    private static volatile boolean logMINOR;
  private static volatile boolean logDEBUG;

  static {
    Logger.registerClass(BaseManifestPutter.class);
  }
 
  /**
   * ArchivePutHandler - wrapper for ContainerInserter
   *
   * Archives are not part of the site structure, they are used to group files that
   * not fit into a container (for example a directory with brazilion files it)
   * Archives are always inserted as CHK, references to items in it
   * are normal redirects to CHK@blah,blub,AA/nameinarchive
   *
   */
  private final class ArchivePutHandler extends PutHandler {

        private static final long serialVersionUID = 1L;

        private ArchivePutHandler(BaseManifestPutter bmp, PutHandler parent, String name, HashMap<String, Object> data, FreenetURI insertURI) {
      super(bmp, parent, name, null, containerPutHandlers);
      this.origSFI = new ContainerInserter(this, this, data, insertURI, ctx, false, false, null, ARCHIVE_TYPE.TAR, false, forceCryptoKey, cryptoAlgorithm, realTimeFlag);
    }

    @Override
    public void onEncode(BaseClientKey key, ClientPutState state, ClientContext context) {
      if (logMINOR) Logger.minor(this, "onEncode(" + key.getURI().toString(false, false) + ") for " + this);

      synchronized (BaseManifestPutter.this) {
        // transform the placeholders to redirects (redirects to 'uri/name') and
        // remove from waitfor lists
        ArrayList<PutHandler> phv = putHandlersArchiveTransformMap.get(this);
        if(phv == null) return; // Already encoded.
        for (PutHandler ph : phv) {
          HashMap<String, Object> hm = putHandlersTransformMap.get(ph);
          perContainerPutHandlersWaitingForMetadata.get(ph.parentPutHandler).remove(ph);
          if (ph.targetInArchive == null)
            throw new NullPointerException();
          Metadata m = new Metadata(DocumentType.SIMPLE_REDIRECT, null, null, key.getURI().setMetaString(new String[] { ph.targetInArchive }), cm);
          hm.put(ph.itemName, m);
          putHandlersTransformMap.remove(ph);
          try {
            tryStartParentContainer(ph.parentPutHandler, context);
          } catch (InsertException e) {
            fail(new InsertException(InsertExceptionMode.INTERNAL_ERROR, e, null), context);
            return;
          }
        }
        putHandlersArchiveTransformMap.remove(this);
      }
    }

    @Override
    public void onSuccess(ClientPutState state, ClientContext context) {
      if (logMINOR) Logger.minor(this, "Completed '" + this.itemName + "' " + this);
      if (!containerPutHandlers.remove(this)) throw new IllegalStateException("was not in containerPutHandlers");
     
      super.onSuccess(state, context);
    }

  }

  /**
   * ContainerPutHandler - wrapper for ContainerInserter
   *
   * Containers are an integral part of the site structure, they are
   * inserted as CHK, the root container is inserted at targetURI.
   * references to items in it are ARCHIVE_INTERNAL_REDIRECT
   *
   */
  private final class ContainerPutHandler extends PutHandler {

        private static final long serialVersionUID = 1L;

        private ContainerPutHandler(BaseManifestPutter bmp, PutHandler parent, String name, HashMap<String, Object> data, FreenetURI insertURI, Object object, HashSet<PutHandler> runningMap) {
      super(bmp, parent, name, null, runningMap);
      this.origSFI = new ContainerInserter(this, this, data, insertURI, ctx, false, false, null, ARCHIVE_TYPE.TAR, false, forceCryptoKey, cryptoAlgorithm, realTimeFlag);
    }

    @Override
    public void onEncode(BaseClientKey key, ClientPutState state, ClientContext context) {
      if (logMINOR) Logger.minor(this, "onEncode(" + key.getURI().toString(false, false) + ") for " + this);

      if (rootContainerPutHandler == this) {
        finalURI = key.getURI();
        cb.onGeneratedURI(finalURI, this);
      } else {
        synchronized (BaseManifestPutter.this) {
          HashMap<String, Object> hm = putHandlersTransformMap.get(this);
          perContainerPutHandlersWaitingForMetadata.get(parentPutHandler).remove(this);
          Metadata m = new Metadata(DocumentType.SIMPLE_REDIRECT, null, null, key.getURI(), cm);
          hm.put(this.itemName, m);
          putHandlersTransformMap.remove(this);

          try {
            tryStartParentContainer(parentPutHandler, context);
          } catch (InsertException e) {
            fail(e, context);
            return;
          }
        }
      }
    }

    @Override
    public void onSuccess(ClientPutState state, ClientContext context) {
      if (logMINOR) Logger.minor(this, "Completed '" + this.itemName + "' " + this);

      if (rootContainerPutHandler == this) {
        if (containerPutHandlers.contains(this)) throw new IllegalStateException("was in containerPutHandlers");
        rootContainerPutHandler = null;
      } else {
        if (!containerPutHandlers.remove(this)) throw new IllegalStateException("was not in containerPutHandlers");
      }
      super.onSuccess(state, context);
    }
  }

  private final class ExternPutHandler extends PutHandler {

        private static final long serialVersionUID = 1L;

        private ExternPutHandler(BaseManifestPutter bmp, PutHandler parent, String name, RandomAccessBucket data, ClientMetadata cm2) {
      super(bmp, parent, name, cm2, runningPutHandlers);
      InsertBlock block = new InsertBlock(data, cm, FreenetURI.EMPTY_CHK_URI);
      this.origSFI = new SingleFileInserter(this, this, block, false, ctx, realTimeFlag, false, true, null, null, false, null, false, persistent(), 0, 0, null, cryptoAlgorithm, forceCryptoKey, -1);
    }

    @Override
    public void onEncode(BaseClientKey key, ClientPutState state, ClientContext context) {
      if (logMINOR) Logger.minor(this, "onEncode(" + key + ") for " + this);

      //debugDecompose("ExternPutHandler.onEncode Begin");
      if(metadata != null) {
        Logger.error(this, "Reassigning metadata: "+metadata, new Exception("debug"));
        //throw new IllegalStateException("Metadata set but we got a uri?!");
      }
      // The file was too small to have its own metadata, we get this instead.
      // So we make the key into metadata.
      Metadata m = new Metadata(DocumentType.SIMPLE_REDIRECT, null, null, key.getURI(), cm);
      onMetadata(m, state, context);
      //debugDecompose("ExternPutHandler.onEncode End");
    }

    @Override
    public void onMetadata(Metadata m, ClientPutState state, ClientContext context) {
      //new Error("DEBUGME").printStackTrace();
      //debugDecompose("ExternPutHandler.onMetadata Begin");
      if(logMINOR) Logger.minor(this, "Assigning metadata: "+m+" for '"+this.itemName+"' "+this+" from "+state+" persistent="+persistent);
      if(metadata != null) {
        Logger.error(this, "Reassigning metadata", new Exception("debug"));
        return;
      }
      metadata = m;

      if (freeformMode) {
        boolean allMetadatas = false;

        synchronized(BaseManifestPutter.this) {
          putHandlersWaitingForMetadata.remove(this);
          allMetadatas = putHandlersWaitingForMetadata.isEmpty();
          if(!allMetadatas) {
            if(logMINOR)
              Logger.minor(this, "Still waiting for metadata: "+putHandlersWaitingForMetadata.size());
          }
        }
        if(allMetadatas) {
          // Will resolve etc.
          gotAllMetadata(context);
        } else {
          // Resolve now to speed up the insert.
          try {
              if(m.writtenLength() > Metadata.MAX_SIZE_IN_MANIFEST)
              throw new MetadataUnresolvedException(new Metadata[] { m }, "Too big");
          } catch (MetadataUnresolvedException e) {
            try {
              resolve(e, context);
            } catch (IOException e1) {
              fail(new InsertException(InsertExceptionMode.BUCKET_ERROR, e1, null), context);
              return;
            } catch (InsertException e1) {
              fail(e1, context);
            }
          }
        }
      } else if (containerMode) {
        HashMap<String, Object> hm = putHandlersTransformMap.get(this);
        perContainerPutHandlersWaitingForMetadata.get(parentPutHandler).remove(this);
        hm.put(this.itemName, m);
        putHandlersTransformMap.remove(this);
        try {
          tryStartParentContainer(parentPutHandler, context);
        } catch (InsertException e) {
          fail(e, context);
          return;
        }
      } else {
        throw new RuntimeException("Neiter container nor freeform mode. Hu?");
      }
      //debugDecompose("ExternPutHandler.onMetadata End");
    }

    @Override
    public void onSuccess(ClientPutState state, ClientContext context) {
      super.onSuccess(state, context);
    }
  }

  // meta data inserter / resolver
  // these MPH are usually created on demand, so they are outside (main)constructor
  private final class MetaPutHandler extends PutHandler {

    // Metadata is not put with a cryptokey. It is derived from other stuff that is already encrypted with random keys.
   
        private static final long serialVersionUID = 1L;

        // final metadata
    private MetaPutHandler(BaseManifestPutter smp, PutHandler parent, InsertBlock insertBlock) {
      super(smp, parent, null, null, null);
      // Treat as splitfile for purposes of determining number of reinserts.
      this.origSFI = new SingleFileInserter(this, this, insertBlock, true, ctx, realTimeFlag, false, false, null, null, true, null, true, persistent(), 0, 0, null, cryptoAlgorithm, null, -1);
      if(logMINOR) Logger.minor(this, "Inserting root metadata: "+origSFI);
    }

    // resolver
    private MetaPutHandler(BaseManifestPutter smp, PutHandler parent, Metadata toResolve, BucketFactory bf) throws MetadataUnresolvedException, IOException {
      super(smp, parent, null, null, runningPutHandlers);
      RandomAccessBucket b = toResolve.toBucket(bf);
      metadata = toResolve;
      // Treat as splitfile for purposes of determining number of reinserts.
      InsertBlock ib = new InsertBlock(b, null, FreenetURI.EMPTY_CHK_URI);
      this.origSFI = new SingleFileInserter(this, this, ib, true, ctx, realTimeFlag, false, false, toResolve, null, true, null, true, persistent(), 0, 0, null, cryptoAlgorithm, null, -1);
      if(logMINOR) Logger.minor(this, "Inserting subsidiary metadata: "+origSFI+" for "+toResolve);
    }

    @Override
    public void onEncode(BaseClientKey key, ClientPutState state, ClientContext context) {
      if (logMINOR) Logger.minor(this, "onEncode(" + key.getURI().toString(false, false) + ") for " + this);

      if (rootMetaPutHandler == this) {
        finalURI = key.getURI();
        cb.onGeneratedURI(finalURI, this);
        return;
      }

      metadata.resolve(key.getURI());
    }

    @Override
    public void onSuccess(ClientPutState state, ClientContext context) {
      boolean wasRoot = false;
      synchronized (BaseManifestPutter.this) {
        if (rootMetaPutHandler == this) {
          //if (containerPutHandlers.contains(this)) throw new IllegalStateException("was in containerPutHandlers");
          rootMetaPutHandler = null;
          wasRoot = true;
        }
      }
      if (!wasRoot)
        resolveAndStartBase(context);
      super.onSuccess(state, context);

    }
  }

  /** Placeholder for Matadata, don't run it! */
  private final class JokerPutHandler extends PutHandler {

        private static final long serialVersionUID = 1L;

        /** a normal ( freeform) redirect */
    public JokerPutHandler(BaseManifestPutter bmp,   String name, FreenetURI targetURI2, ClientMetadata cm2) {
      super(bmp, null, name, null, (Metadata)null, cm2);
      Metadata m = new Metadata(DocumentType.SIMPLE_REDIRECT, null, null, targetURI2, cm2);
      metadata = m;
    }

    /** an archive redirect */
    public JokerPutHandler(BaseManifestPutter bmp, PutHandler parent, String name, ClientMetadata cm2) {
      super(bmp, parent, name, name, (Metadata)null, cm2);
      // we dont know the final uri, so preconstructing the metadata does not help here      Metadata m = new Metadata(Metadata.SIMPLE_REDIRECT, null, null, FreenetURI.EMPTY_CHK_URI, cm2);
    }

    /** a short symlink */
    public JokerPutHandler(BaseManifestPutter bmp, PutHandler parent, String name, String target) {
      super(bmp, parent, name, name, (Metadata)null, null);
      Metadata m = new Metadata(DocumentType.SYMBOLIC_SHORTLINK, null, null, target, null);
      metadata = m;
    }

  }

  // Only implements PutCompletionCallback for the final metadata insert
  abstract class PutHandler extends BaseClientPutter implements PutCompletionCallback {

        private static final long serialVersionUID = 1L;

        // run me
    private PutHandler(final BaseManifestPutter bmp, PutHandler parent, String name, ClientMetadata cm, HashSet<PutHandler> runningMap) {
      super(bmp.priorityClass, bmp.cb);
      this.persistent = bmp.persistent();
      this.cm = cm;
      this.itemName = name;
      metadata = null;
      parentPutHandler = parent;

      if (runningMap != null) {
        synchronized (runningMap) {
          if (runningMap.contains(this)) {
            Logger.error(this, "PutHandler already in 'runningMap': "+runningMap, new Error("error"));
          } else {
            runningMap.add(this);
          }
        }
      }

      synchronized (putHandlerWaitingForBlockSets) {
        if (putHandlerWaitingForBlockSets.contains(this)) {
          Logger.error(this, "PutHandler already in 'waitingForBlockSets'!", new Error("error"));
        } else {
          putHandlerWaitingForBlockSets.add(this);
        }
      }

      synchronized (putHandlersWaitingForFetchable) {
        if (putHandlersWaitingForFetchable.contains(this)) {
          Logger.error(this, "PutHandler already in 'waitingForFetchable'!", new Error("error"));
        } else {
          putHandlersWaitingForFetchable.add(this);
        }
      }
    }

    // place holder, don't run it
    private PutHandler(final BaseManifestPutter bmp, PutHandler parent, String name, String nameInArchive, Metadata md, ClientMetadata cm) {
      super(bmp.priorityClass, bmp.cb);
      this.persistent = bmp.persistent();
      this.cm = cm;
      this.itemName = name;
      this.origSFI = null;
      metadata = md;
      parentPutHandler = parent;
      this.targetInArchive = nameInArchive;
    }

    protected ClientPutState origSFI;
    private ClientPutState currentState;
    protected ClientMetadata cm;
    protected Metadata metadata;
    private String targetInArchive;
    protected final String itemName;
    protected final boolean persistent;
    protected final PutHandler parentPutHandler;

    public void start(ClientContext context) throws InsertException {
      //new Error("trace start "+this).printStackTrace();
      if (logDEBUG)
        Logger.debug(this, "Starting a PutHandler for '"+this.itemName+"' "+ this);

      if (origSFI == null) {
        fail(new IllegalStateException("origSFI is null on start(), impossible"), context);
      }

      if ((!(this instanceof MetaPutHandler)) && (metadata != null)) {
        fail(new IllegalStateException("metdata=" + metadata + " on start(), impossible"), context);
      }

      boolean ok;
      if ((this instanceof ContainerPutHandler) || (this instanceof ArchivePutHandler)) {
        if (this != rootContainerPutHandler) {
          synchronized (containerPutHandlers) {
            ok = containerPutHandlers.contains(this);
          }
          if (!ok) {
            throw new IllegalStateException("Starting a PutHandler thats not in 'containerPutHandlers'! "+this);
          }
        }
      } else {
        if (this != rootMetaPutHandler) {
          synchronized (runningPutHandlers) {
            ok = runningPutHandlers.contains(this);
          }
          if (!ok) {
            throw new IllegalStateException("Starting a PutHandler thats not in 'runningPutHandlers'! "+this);
          }
        }
      }

      synchronized (putHandlerWaitingForBlockSets) {
        ok = putHandlerWaitingForBlockSets.contains(this);
      }
      if (!ok) {
        Logger.error(this, "Starting a PutHandler thats not in 'waitingForBlockSets'! "+this, new Error("error"));
        //throw new IllegalStateException("Starting a PutHandler thats not in 'waitingForBlockSets'! "+this);
      }

      ClientPutState sfi;
      synchronized(this) {
        sfi = origSFI;
        currentState = sfi;
        origSFI = null;
      }
      sfi.schedule(context);
    }

    @Override
    public void cancel(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(oldState != null) oldState.cancel(context);
      onFailure(new InsertException(InsertExceptionMode.CANCELLED), oldState, 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;
    }

    @Override
    public void onSuccess(ClientPutState state, ClientContext context) {
      if (logDEBUG) {
        //temp hack, ignored if called via super
        Throwable t = new Throwable("DEBUG onSuccess");
        StackTraceElement te = t.getStackTrace()[1];
        if (!("BaseManifestPutter.java".equals(te.getFileName()) && "onSuccess".equals(te.getMethodName()))) {
          Logger.error(this, "Not called via super", t);
        }
        //temp hack end
      }

      if (logMINOR) Logger.minor(this, "Completed '" + this.itemName + "' " + this);

      if (putHandlersWaitingForFetchable.contains(this))
        BaseManifestPutter.this.onFetchable(this);

      ClientPutState oldState;
      synchronized(this) {
        oldState = currentState;
        currentState = null;
      }
      synchronized(BaseManifestPutter.this) {
        runningPutHandlers.remove(this);
        if(putHandlersWaitingForMetadata.remove(this)) {
          Logger.error(this, "PutHandler '"+this.itemName+"' was in waitingForMetadata in onSuccess() on "+this+" for "+BaseManifestPutter.this, new Error("debug"));
        }

        if(putHandlerWaitingForBlockSets.remove(this)) {
          Logger.error(this, "PutHandler was in waitingForBlockSets in onSuccess() on "+this+" for "+BaseManifestPutter.this, new Error("debug"));
        }
        if(putHandlersWaitingForFetchable.remove(this)) {
          Logger.error(this, "PutHandler was in waitingForFetchable in onSuccess() on "+this+" for "+BaseManifestPutter.this, new Error("debug"));
        }

        if(!runningPutHandlers.isEmpty()) {
          if(logMINOR) {
            Logger.minor(this, "Running put handlers: "+runningPutHandlers.size());
            for(Object o : runningPutHandlers) {
              Logger.minor(this, "Still running: "+o);
            }
          }
        }
      }
      tryComplete(context);
    }

    @Override
    public void onFailure(InsertException e, ClientPutState state, ClientContext context) {
      ClientPutState oldState;
      synchronized(this) {
        oldState = currentState;
        currentState = null;
      }
      if(logMINOR) Logger.minor(this, "Failed: "+this+" - "+e, e);
      fail(e, context);
    }

    @Override
    public void onEncode(BaseClientKey key, ClientPutState state, ClientContext context) {
      throw new UnsupportedOperationException();
    }

    @Override
    public void onTransition(ClientPutState oldState, ClientPutState newState, ClientContext context) {
      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(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);
      }
    }

    @Override
    public void onMetadata(Metadata m, ClientPutState state, ClientContext context) {
      throw new UnsupportedOperationException();
    }
   
    @Override
    public void onMetadata(Bucket meta, ClientPutState state,
        ClientContext context) {
      throw new UnsupportedOperationException();
    }

    /** The number of blocks that will be needed to fetch the data. We put this in the top block metadata. */
    protected int minSuccessFetchBlocks;
   
    @Override
    public void addBlock() {
      BaseManifestPutter.this.addBlock();
      synchronized(this) {
        minSuccessFetchBlocks++;
      }
    }

    @Override
    public void addBlocks(int num) {
      BaseManifestPutter.this.addBlocks(num);
      synchronized(this) {
        minSuccessFetchBlocks+=num;
      }
    }

    @Override
    public void completedBlock(boolean dontNotify, ClientContext context) {
      BaseManifestPutter.this.completedBlock(dontNotify, context);
    }

    @Override
    public void failedBlock(ClientContext context) {
      BaseManifestPutter.this.failedBlock(context);
    }

    @Override
    public void fatallyFailedBlock(ClientContext context) {
      BaseManifestPutter.this.fatallyFailedBlock(context);
    }

    @Override
    public synchronized void addMustSucceedBlocks(int blocks) {
      BaseManifestPutter.this.addMustSucceedBlocks(blocks);
      synchronized(this) {
        minSuccessFetchBlocks += blocks;
      }
    }
   
    @Override
    public synchronized void addRedundantBlocksInsert(int blocks) {
      BaseManifestPutter.this.addRedundantBlocksInsert(blocks);
    }
   
    @Override
    public synchronized int getMinSuccessFetchBlocks() {
      return minSuccessFetchBlocks;
    }
   
    @Override
    protected void innerNotifyClients(ClientContext context) {
        BaseManifestPutter.this.notifyClients(context);
    }

    @Override
    public void onBlockSetFinished(ClientPutState state, ClientContext context) {
      boolean allBlockSets = false;
      synchronized(BaseManifestPutter.this) {
        putHandlerWaitingForBlockSets.remove(this);
        if (freeformMode) {
          allBlockSets = hasResolvedBase && putHandlerWaitingForBlockSets.isEmpty();
        } else {
          allBlockSets = putHandlerWaitingForBlockSets.isEmpty();
        }
      }
      if(allBlockSets)
        BaseManifestPutter.this.blockSetFinalized(context);
    }

    @Override
    public void onFetchable(ClientPutState state) {
      if(logMINOR) Logger.minor(this, "onFetchable " + this, new Exception("debug"));
      BaseManifestPutter.this.onFetchable(this);
    }

    @Override
    public void onTransition(ClientGetState oldState, ClientGetState newState, ClientContext context) {
      // Ignore
    }

    @Override
    public String toString() {
      if (logDEBUG) return super.toString() + " {"+this.itemName+'}';
      return super.toString();
    }

    @Override
    protected void innerToNetwork(ClientContext context) {
      // Ignore
    }
   
        @Override
        public void innerOnResume(ClientContext context) throws ResumeFailedException {
            super.innerOnResume(context);
            try {
                if(currentState != null)
                    currentState.onResume(context);
                if(origSFI != null)
                    origSFI.onResume(context);
            } catch (InsertException e) {
                Logger.error(this, "Failed to start insert on resume: "+e, e);
                throw new ResumeFailedException("Insert error: "+e);
            }
        }
       
        @Override
        public void onShutdown(ClientContext context) {
            ClientPutState s;
            synchronized(this) {
                s = currentState;
            }
            if(s != null) s.onShutdown(context);
        }
       
        @Override
        protected ClientBaseCallback getCallback() {
            return cb;
        }
       
        /** What is our priority class? */
        @Override
        public short getPriorityClass() {
            return BaseManifestPutter.this.getPriorityClass();
        }
       
        @Override
        public ClientRequestSchedulerGroup getSchedulerGroup() {
            return BaseManifestPutter.this;
        }

  }

  private final static String[] defaultDefaultNames =
    new String[] { "index.html", "index.htm", "default.html", "default.htm" };
  // All the default names are in the root.
  // Code will need to be changed if we have index/index.html or similar.
 
  /** if true top level metadata is a container */
  private boolean containerMode = false;
  /** if true top level metadata is a single chunk */
  private boolean freeformMode = false;

  /* common stuff, fields used in freeform and container mode */
  /** put is finalized if empty */
  private HashSet<PutHandler> putHandlerWaitingForBlockSets;
  /** if empty put is fetchable */
  private HashSet<PutHandler> putHandlersWaitingForFetchable;
  private HashSet<PutHandler> runningPutHandlers;

  // container stuff, all fields can be null'ed in freeform mode
  private ContainerBuilder rootContainerBuilder;
  private ContainerPutHandler rootContainerPutHandler;
  private HashSet<PutHandler> containerPutHandlers;
  private HashMap<PutHandler, HashSet<PutHandler>> perContainerPutHandlersWaitingForMetadata;
  /**
   * PutHandler: the *PutHandler
   * HashMap<String, Object>: the 'metadata dir' that contains the item inserted by PutHandler
   * the *PutHandler fills in its result here (Metadata)
   */
  private HashMap<PutHandler, HashMap<String, Object>> putHandlersTransformMap;
  private HashMap<ArchivePutHandler, ArrayList<PutHandler>> putHandlersArchiveTransformMap;

  // freeform stuff, all fields can be null'ed in container mode
  private FreeFormBuilder rootBuilder;
  private MetaPutHandler rootMetaPutHandler;
  private HashMap<String, Object> rootDir;
  private HashSet<PutHandler> putHandlersWaitingForMetadata;

  private FreenetURI finalURI;
  private final FreenetURI targetURI;
  private boolean finished;
  private final InsertContext ctx;
  final ClientPutCallback cb;

  private int numberOfFiles;
  private long totalSize;
  private Metadata baseMetadata;
  private boolean hasResolvedBase; // if this is true, the final block is ready for insert
  private boolean fetchable;
  final byte[] forceCryptoKey;
  final byte cryptoAlgorithm;

  public BaseManifestPutter(ClientPutCallback cb,
      HashMap<String, Object> manifestElements, short prioClass, FreenetURI target, String defaultName,
      InsertContext ctx, boolean randomiseCryptoKeys, byte [] forceCryptoKey, ClientContext context) throws TooManyFilesInsertException {
    super(prioClass, cb);
    this.targetURI = target;
    this.cb = cb;
    this.ctx = ctx;
    if(randomiseCryptoKeys && forceCryptoKey == null) {
      forceCryptoKey = new byte[32];
      context.random.nextBytes(forceCryptoKey);
    }
    this.forceCryptoKey = forceCryptoKey;
   
    CompatibilityMode mode = ctx.getCompatibilityMode();
    if(!(mode == CompatibilityMode.COMPAT_CURRENT || mode.ordinal() >= CompatibilityMode.COMPAT_1416.ordinal()))
      this.cryptoAlgorithm = Key.ALGO_AES_PCFB_256_SHA256;
    else
      this.cryptoAlgorithm = Key.ALGO_AES_CTR_256_SHA256;
    runningPutHandlers = new HashSet<PutHandler>();
    putHandlersWaitingForMetadata = new HashSet<PutHandler>();
    putHandlersWaitingForFetchable = new HashSet<PutHandler>();
    putHandlerWaitingForBlockSets = new HashSet<PutHandler>();
    containerPutHandlers = new HashSet<PutHandler>();
    perContainerPutHandlersWaitingForMetadata = new HashMap<PutHandler, HashSet<PutHandler>>();
    putHandlersTransformMap = new HashMap<PutHandler, HashMap<String, Object>>();
    putHandlersArchiveTransformMap = new HashMap<ArchivePutHandler, ArrayList<PutHandler>>();
    if(defaultName == null)
      defaultName = findDefaultName(manifestElements);
    makePutHandlers(manifestElements, defaultName);
    // builders are not longer needed after constructor
    rootBuilder = null;
    rootContainerBuilder = null;
  }
 
  private String findDefaultName(HashMap<String, Object> manifestElements) {
    // Find the default name if it has not been set explicitly.
    for(String name : defaultDefaultNames) {
      Object o = manifestElements.get(name);
      if(o == null) continue;
      if(o instanceof HashMap) continue;
      return name;
    }
    for(String name : defaultDefaultNames) {
      boolean found = false;
      for(Map.Entry<String, Object> entry : manifestElements.entrySet()) {
        Object o = entry.getValue();
        if(o == null) continue;
        if(o instanceof HashMap) continue;
        if(entry.getKey().equalsIgnoreCase(name)) {
          found = true;
          name = entry.getKey();
          break;
        }
      }
      if(!found) continue;
      return name;
    }
    return "";
  }

  public void start(ClientContext context) throws InsertException {
    if (logMINOR)
      Logger.minor(this, "Starting " + this+" persistence="+persistent()+ " containermode="+containerMode);
    PutHandler[] running;
    PutHandler[] containers;

    synchronized (this) {
      running = runningPutHandlers.toArray(new PutHandler[runningPutHandlers.size()]);
      if (containerMode) {
        containers = getContainersToStart(running.length > 0);
      } else {
        containers = null;
      }
    }

    try {
      for (int i = 0; i < running.length; i++) {
        running[i].start(context);
        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(context);
          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) {
        gotAllMetadata(context);
      }
    } catch (InsertException e) {
      synchronized(this) {
        finished = true;
      }
      cancelAndFinish(context);
      throw e;
    }
    //debugDecompose("Start - End");
  }

  private PutHandler[] getContainersToStart(boolean excludeRoot) {
    PutHandler[] maybeStartPH = containerPutHandlers.toArray(new PutHandler[containerPutHandlers.size()]);
    ArrayList<PutHandler> phToStart = new ArrayList<PutHandler>();

    for (PutHandler ph: maybeStartPH) {
      if (perContainerPutHandlersWaitingForMetadata.get(ph).isEmpty()) {
        phToStart.add(ph);
      }
    }
    if ((!excludeRoot) && (maybeStartPH.length == 0)) {
      phToStart.add(rootContainerPutHandler);
    }
    return phToStart.toArray(new PutHandler[phToStart.size()]);
  }

  /**
   * Implement the pack logic.
   *
   * @param manifestElements A map from String to either ManifestElement or another String. This is the
   * site structure, which will be split into containers and/or external inserts by the method.
   * @throws TooManyFilesInsertException
   */
  protected abstract void makePutHandlers(HashMap<String, Object> manifestElements, String defaultName) throws TooManyFilesInsertException;

  @Override
  public FreenetURI getURI() {
    return finalURI;
  }

  @Override
  public synchronized boolean isFinished() {
    return finished || cancelled;
  }
 
  @Override
  public byte[] getSplitfileCryptoKey() {
    return forceCryptoKey;
  }

  /**
   * 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(ClientContext context) {
    if (containerMode) throw new IllegalStateException();
    if(logMINOR) Logger.minor(this, "Got all metadata");
    baseMetadata = makeMetadata(rootDir);
    context.jobRunner.setCheckpointASAP();
    resolveAndStartBase(context);
  }

  @SuppressWarnings("unchecked")
  private Metadata makeMetadata(HashMap<String, Object> dir) {
    SimpleManifestComposer smc = new SimpleManifestComposer();
    for(Map.Entry<String, Object> entry:dir.entrySet()) {
      String name = entry.getKey();
      Object item = entry.getValue();
      if (item == null) throw new NullPointerException();
      Metadata m;
      if (item instanceof HashMap) {
        m = makeMetadata((HashMap<String, Object>) item);
        if (m == null) throw new NullPointerException("HERE!!");
      } else {
        m = ((PutHandler)item).metadata;
        if (m == null) throw new NullPointerException("HERE!!" +item);
      }
      smc.addItem(name, m);
    }
    return smc.getMetadata();
  }

  /**
   * 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(ClientContext context) {
    //new Error("DEBUG_ME_resolveAndStartBase").printStackTrace();
      RandomAccessBucket bucket = null;
    synchronized(this) {
      if(hasResolvedBase) return;
    }
    while(true) {
      try {
          bucket = baseMetadata.toBucket(context.getBucketFactory(persistent()));
        if(logMINOR)
          Logger.minor(this, "Metadata bucket is "+bucket.size()+" bytes long");
        break;
      } catch (IOException e) {
        fail(new InsertException(InsertExceptionMode.BUCKET_ERROR, e, null), 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, context);
          return;
        } catch (IOException e1) {
          fail(new InsertException(InsertExceptionMode.BUCKET_ERROR, e, null), context);
          return;
        } catch (InsertException e2) {
          fail(e2, context);
          return;
        }
      }
    }
    if(bucket == null) return;
    synchronized(this) {
      if(hasResolvedBase) return;
      hasResolvedBase = true;
    }
    InsertBlock block;
    block = new InsertBlock(bucket, null, targetURI);
    try {
      rootMetaPutHandler = new MetaPutHandler(this, null, block);

      if(logMINOR) Logger.minor(this, "Inserting main metadata: "+rootMetaPutHandler+" for "+baseMetadata);
      rootMetaPutHandler.start(context);
    } catch (InsertException e) {
      fail(e, context);
      return;
    }
  }

  /**
   * 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, ClientContext context) throws InsertException, IOException {
    new Error("RefactorME-resolve").printStackTrace();
    Metadata[] metas = e.mustResolve;
    for(Metadata m: metas) {
      if(logMINOR) Logger.minor(this, "Resolving "+m);
      if(m.isResolved()) {
        Logger.error(this, "Already resolved: "+m+" in resolve() - race condition???");
        continue;
      }
      try {
        MetaPutHandler ph = new MetaPutHandler(this, null, m, context.getBucketFactory(persistent()));
        ph.start(context);
      } catch (MetadataUnresolvedException e1) {
        resolve(e1, context);
      }
    }
  }

  private void tryComplete(ClientContext context) {
    //debugDecompose("try complete");
    if(logDEBUG) Logger.debug(this, "try complete", new Error("trace tryComplete()"));
    synchronized(this) {
      if(finished || cancelled) {
        if(logMINOR) Logger.minor(this, "Already "+(finished?"finished":"cancelled"));
        return;
      }
      if (!runningPutHandlers.isEmpty()) {
        if (logDEBUG) Logger.debug(this, "Not finished, runningPutHandlers not empty.");
        return;
      }
      if (!containerPutHandlers.isEmpty()) {
        if (logDEBUG) Logger.debug(this, "Not finished, containerPutHandlers not empty.");
        return;
      }
      if (containerMode) {
        if (rootContainerPutHandler != null) {
          if (logDEBUG) Logger.debug(this, "Not finished, rootContainerPutHandler not empty.");
          return;
        }
      } else {
        if (rootMetaPutHandler != null) {
          if (logDEBUG) Logger.debug(this, "Not finished, rootMetaPutHandler not empty.");
          return;
        }
      }
      finished = true;
    }
    complete(context);
  }

  private void complete(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
    cb.onSuccess(this);
  }

  private void fail(Exception e, ClientContext context) {
    InsertException ie = new InsertException(InsertExceptionMode.INTERNAL_ERROR, e, null);
    fail(ie, context);
  }

  private void fail(InsertException e, ClientContext context) {
    // Cancel all, then call the callback
    synchronized(this) {
      if(finished) return;
      finished = true;
    }
    cancelAndFinish(context);

    cb.onFailure(e, this);
  }

  /**
   * Cancel all running inserters.
   */
  private void cancelAndFinish(ClientContext context) {
    PutHandler[] running;
    synchronized(this) {
      running = runningPutHandlers.toArray(new PutHandler[runningPutHandlers.size()]);
    }

    if(logMINOR) Logger.minor(this, "PutHandler's to cancel: "+running.length);
    for(PutHandler putter : running) {
      putter.cancel(context);
    }
    // TODO
//    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(ClientContext context) {
    synchronized(this) {
      if(finished) return;
      if(super.cancel()) return;
    }
    fail(new InsertException(InsertExceptionMode.CANCELLED), context);
  }

  /** The number of blocks that will be needed to fetch the data. We put this in the top block metadata. */
  protected int minSuccessFetchBlocks;
 
  @Override
  public void addBlock() {
    synchronized(this) {
      minSuccessFetchBlocks++;
    }
    super.addBlock();
  }
 
  @Override
  public void addBlocks(int num) {
    synchronized(this) {
      minSuccessFetchBlocks+=num;
    }
    super.addBlocks(num);
  }
 
  /** Add one or more blocks to the number of requires blocks, and don't notify the clients. */
  @Override
  public void addMustSucceedBlocks(int blocks) {
    synchronized(this) {
      minSuccessFetchBlocks += blocks;
    }
    super.addMustSucceedBlocks(blocks);
  }

  /** Add one or more blocks to the number of requires blocks, and don't notify the clients.
   * These blocks are added to the minSuccessFetchBlocks for the insert, but not to the counter for what
   * the requestor must fetch. */
  @Override
  public void addRedundantBlocksInsert(int blocks) {
    super.addMustSucceedBlocks(blocks);
  }

  @Override
  public void innerNotifyClients(ClientContext context) {
      SplitfileProgressEvent e;
      synchronized(this) {
          e = new SplitfileProgressEvent(this.totalBlocks, this.successfulBlocks, this.failedBlocks, this.fatallyFailedBlocks, this.minSuccessBlocks, minSuccessFetchBlocks, this.blockSetFinalized);
      }
    ctx.eventProducer.produceEvent(e, context);
  }

  @Override
  public int getMinSuccessFetchBlocks() {
    return minSuccessFetchBlocks;
  }
 
  @Override
  public void blockSetFinalized(ClientContext context) {
    super.blockSetFinalized(context);
  }

  public int countFiles() {
    return numberOfFiles;
  }

  public long totalSize() {
    return totalSize;
  }

  protected void onFetchable(PutHandler handler) {
    //new Error("Trace_ME onFetchable").printStackTrace();
    if(checkFetchable(handler)) {
      cb.onFetchable(this);
    }
  }

  private synchronized boolean checkFetchable(PutHandler handler) {
    //new Error("RefactorME").printStackTrace();
    if (!putHandlersWaitingForFetchable.remove(handler)) {
      throw new IllegalStateException("was not in putHandlersWaitingForFetchable! : "+handler);
    }
    if(fetchable) return false;
    if(!putHandlersWaitingForFetchable.isEmpty()) return false;
    fetchable = true;
    return true;
  }

  @Override
  public void onTransition(ClientGetState oldState, ClientGetState newState, ClientContext context) {
    // Ignore
  }

  @Override
  public void onTransition(ClientPutState from, ClientPutState to, ClientContext context) {
      // Everything should be on the PutHandler's, right?
      Logger.error(this, "Ignoring transition from "+from+" to "+to+" on "+this);
    // Ignore
  }

  @Override
  protected void innerToNetwork(ClientContext context) {
    // Ignore
  }

  private void tryStartParentContainer(PutHandler containerHandle2, ClientContext context) throws InsertException {
    //new Error("RefactorME").printStackTrace();
    if (containerHandle2 == null) throw new NullPointerException();
    //if (perContainerPutHandlersWaitingForMetadata.get(containerHandle2).isEmpty() && perContainerPutHandlersWaitingForFetchable.get(containerHandle2).isEmpty()) {
    if (perContainerPutHandlersWaitingForMetadata.get(containerHandle2).isEmpty()) {
      perContainerPutHandlersWaitingForMetadata.remove(containerHandle2);
      containerHandle2.start(context);
    } else {
      //System.out.println(" waiting m:"+perContainerPutHandlersWaitingForMetadata.get(containerHandle2).size()+" F:"+perContainerPutHandlersWaitingForFetchable.get(containerHandle2).size() + " for "+containerHandle2);
      if(logMINOR)
        Logger.minor(this, "(spc) waiting m:"+perContainerPutHandlersWaitingForMetadata.get(containerHandle2).size() + " for "+containerHandle2);
    }
  }

  // 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) && (name != 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;
  }

  public ContainerBuilder makeArchive() {
    return new ContainerBuilder(false, null, null, true);
  }

  protected ContainerBuilder getRootContainer() {
    if (freeformMode) throw new IllegalStateException("Already in freeform mode!");
    if (!containerMode) {
      containerMode = true;
      rootContainerBuilder = new ContainerBuilder(true);
    }
    return rootContainerBuilder;
  }

  protected FreeFormBuilder getRootBuilder() {
    if (containerMode) throw new IllegalStateException("Already in container mode!");
    if (!freeformMode) {
      freeformMode = true;
      rootBuilder = new FreeFormBuilder();
    }
    return rootBuilder;
  }

  protected abstract class ManifestBuilder implements Serializable {

        private static final long serialVersionUID = 1L;
        private final Stack<HashMap<String, Object>> dirStack;
    /** Map from name to either a Metadata (to be included as-is), a ManifestElement (either a redirect
     * or a file), or another HashMap. Eventually processed by e.g. ContainerInserter.makeManifest()
     * (for a ContainerBuilder). */
    protected HashMap<String, Object> currentDir;

    private ClientMetadata makeClientMetadata(String mime) {
      if (mime == null)
        return null;
      ClientMetadata cm = new ClientMetadata(mime.trim());
      if (cm.isTrivial())
        return null;
      return cm;
    }

    ManifestBuilder() {
      dirStack = new Stack<HashMap<String, Object>>();
    }

    public void pushCurrentDir() {
      dirStack.push(currentDir);
    }

    public void popCurrentDir() {
      currentDir = dirStack.pop();
    }

    /**
     * make 'name' the current subdir (cd into it).<br>
     * if it not exists, it is created.
     *
     * @param name name of the subdir
     */
    public void makeSubDirCD(String name) {
      Object dir = currentDir.get(name);
      if (dir != null) {
        currentDir = Metadata.forceMap(dir);
      } else {
        currentDir = makeSubDir(currentDir, name);
      }
    }

    private HashMap<String, Object> makeSubDir(HashMap<String, Object> parentDir, String name) {
      if (parentDir.containsKey(name)) {
        throw new IllegalStateException("Item '"+name+"' already exist!");
      }
      HashMap<String, Object> newDir = new HashMap<String, Object>();
      parentDir.put(name , newDir);
      return newDir;
    }

    /**
     * add a ManifestElement, either a redirect (target uri given) or an external
     * @param name
     * @param element
     * @param isDefaultDoc
     */
    public final void addElement(String name, ManifestElement element, boolean isDefaultDoc) {
      ClientMetadata cm = makeClientMetadata(element.mimeOverride);

      if (element.getData() != null) {
        addExternal(name, element.getData(), cm, isDefaultDoc);
        return;
      }
      if (element.targetURI != null) {
        addRedirect(name, element.targetURI, cm, isDefaultDoc);
        return;
      }
      throw new IllegalStateException("ME is neither a redirect nor dircet data. "+element);
    }

    /** Add a file as an external. It will be inserted separately and we will add a redirect to the
     * metadata.
     * @param name The name of the file (short name within the original folder, it's not in a container).
     * @param data The data to be inserted.
     * @param mimeOverride Optional MIME type override.
     * @param isDefaultDoc If true, make this the default document.
     */
    public final void addExternal(String name, RandomAccessBucket data, String mimeOverride, boolean isDefaultDoc) {
      assert(data != null);
      ClientMetadata cm = makeClientMetadata(mimeOverride);
      addExternal(name, data, cm, isDefaultDoc);
    }

    public final void addRedirect(String name, FreenetURI targetUri, String mimeOverride, boolean isDefaultDoc) {
      ClientMetadata cm = makeClientMetadata(mimeOverride);
      addRedirect(name, targetUri, cm, isDefaultDoc);
    }

    public abstract void addExternal(String name, RandomAccessBucket data, ClientMetadata cm, boolean isDefaultDoc);
    public abstract void addRedirect(String name, FreenetURI targetUri, ClientMetadata cm, boolean isDefaultDoc);
  }

  protected final class FreeFormBuilder extends ManifestBuilder {

        private static final long serialVersionUID = 1L;

        protected FreeFormBuilder() {
      rootDir = new HashMap<String, Object>();
      currentDir = rootDir;
    }

    @Override
    public void addExternal(String name, RandomAccessBucket data, ClientMetadata cm, boolean isDefaultDoc) {
      PutHandler ph;
      ph = new ExternPutHandler(BaseManifestPutter.this, null, name, data, cm);
//      putHandlersWaitingForMetadata.add(ph);
//      putHandlersWaitingForFetchable.add(ph);
      if(logMINOR) Logger.minor(this, "Inserting separately as PutHandler: "+name+" : "+ph+" persistent="+ph.persistent());
      numberOfFiles++;
      totalSize += data.size();
      currentDir.put(name, ph);
      if (isDefaultDoc) {
        ph = new JokerPutHandler(BaseManifestPutter.this, null, name, name);
        currentDir.put("", ph);
      }
    }

    @Override
    public void addRedirect(String name, FreenetURI targetURI2, ClientMetadata cm, boolean isDefaultDoc) {
      PutHandler ph;
      ph = new JokerPutHandler(BaseManifestPutter.this, name, targetURI2, cm);
      currentDir.put(name, ph);
      if (isDefaultDoc)
        currentDir.put("", ph);
    }
  }
 
  protected final class ContainerBuilder extends ManifestBuilder {

        private static final long serialVersionUID = 1L;
        /** Tree containing the status of the insert. Can have ManifestElement's (original files to
         * insert or bundle inside a container), HashMap's (more subdirs), Metadata (to be put into
         * a container as metadata for e.g. an external file), a ContainerPutHandler or an
         * ArchivePutHandler (for containers that are part of the structure, and external containers
         * for overflow, respectively). */
        private final HashMap<String, Object> _rootDir;
    private final PutHandler selfHandle;

    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 (!containerMode) {
        throw new IllegalStateException("You can not add containers in free form mode!");
      }
      _rootDir = new HashMap<String, Object>();
      if (isArchive)
        selfHandle = new ArchivePutHandler(BaseManifestPutter.this,
            parent, name, _rootDir,
            (isRoot ? BaseManifestPutter.this.targetURI
                : FreenetURI.EMPTY_CHK_URI));
      else
        selfHandle = new ContainerPutHandler(BaseManifestPutter.this,
            parent, name, _rootDir,
            (isRoot ? BaseManifestPutter.this.targetURI
                : FreenetURI.EMPTY_CHK_URI), null, (isRoot ? null : containerPutHandlers));
      currentDir = _rootDir;
      if (isRoot) {
        rootContainerPutHandler = (ContainerPutHandler)selfHandle;
      } else {
        containerPutHandlers.add(selfHandle);
      }
      perContainerPutHandlersWaitingForMetadata.put(selfHandle, new HashSet<PutHandler>());
      //perContainerPutHandlersWaitingForFetchable.put(selfHandle, new HashSet<PutHandler>());
      if (isArchive) putHandlersArchiveTransformMap.put((ArchivePutHandler)selfHandle, new ArrayList<PutHandler>());
    }

    public ContainerBuilder makeSubContainer(String name) {
      ContainerBuilder subCon = new ContainerBuilder(selfHandle, name);
      currentDir.put(name , subCon.selfHandle);
      putHandlersTransformMap.put(subCon.selfHandle, currentDir);
      perContainerPutHandlersWaitingForMetadata.get(selfHandle).add(subCon.selfHandle);
      return subCon;
    }

    /**
     * Add a ManifestElement, which can be a file in an archive, or a redirect.
     * @param name The original name of the file (e.g. index.html).
     * @param nameInArchive The fully qualified name of the file in the archive (e.g. testing/index.html).
     * @param element The ManifestElement specifying the data, redirect, etc. Note that redirects are
     * still included in containers, both for structural reasons and because the metadata can be large
     * enough that we need to split it.
     * @param isDefaultDoc If true, add a link from "" to this element, making it the default document
     * in this container.
     */
    public void addItem(String name, String nameInArchive, ManifestElement element, boolean isDefaultDoc) {
      ManifestElement me = new ManifestElement(element, name, nameInArchive);
      addItem(name, me, isDefaultDoc);
    }

    private void addItem(String name, ManifestElement element, boolean isDefaultDoc) {
      currentDir.put(name, element);
      if (isDefaultDoc) {
        Metadata m = new Metadata(DocumentType.SYMBOLIC_SHORTLINK, null, null, name, null);
        currentDir.put("", m);
      }
      numberOfFiles++;
      if(element.getData() != null)
          totalSize += element.getSize();
    }

    @Override
    public void addRedirect(String name, FreenetURI targetUri, ClientMetadata cm, boolean isDefaultDoc) {
      Metadata m = new Metadata(DocumentType.SIMPLE_REDIRECT, null, null, targetUri, cm);
      currentDir.put(name, m);
      if (isDefaultDoc) {
        currentDir.put("", m);
      }
    }

    @Override
    public void addExternal(String name, RandomAccessBucket data, ClientMetadata cm, boolean isDefaultDoc) {
      PutHandler ph = new ExternPutHandler(BaseManifestPutter.this, selfHandle, name, data, cm);
      perContainerPutHandlersWaitingForMetadata.get(selfHandle).add(ph);
      putHandlersTransformMap.put(ph, currentDir);
      if (isDefaultDoc) {
        Metadata m = new Metadata(DocumentType.SYMBOLIC_SHORTLINK, null, null, name, null);
        currentDir.put("", m);
      }
      numberOfFiles++;
      totalSize += data.size();
    }

    /** FIXME what is going on here? Why do we need to add a JokerPutHandler, when a lot of code just
     * calls addItem()? */
    public void addArchiveItem(ContainerBuilder archive, String name, ManifestElement element, boolean isDefaultDoc) {
      assert(element.getData() != null);
      archive.addItem(name, new ManifestElement(element, name, name), false);
      PutHandler ph = new JokerPutHandler(BaseManifestPutter.this, selfHandle, name, guessMime(name, element.mimeOverride));
      putHandlersTransformMap.put(ph, currentDir);
      perContainerPutHandlersWaitingForMetadata.get(selfHandle).add(ph);
      putHandlersArchiveTransformMap.get(archive.selfHandle).add(ph);
      if (isDefaultDoc) {
        Metadata m = new Metadata(DocumentType.SYMBOLIC_SHORTLINK, null, null, name, null);
        currentDir.put("", m);
      }
      numberOfFiles++;
      if(element.getData() != null)
          totalSize += element.getSize();
    }
  }
 
    @Override
    protected ClientBaseCallback getCallback() {
        return cb;
    }
   
    public static HashMap<String, Object> bucketsByNameToManifestEntries(HashMap<String,Object> bucketsByName) {
        HashMap<String,Object> manifestEntries = new HashMap<String,Object>();
        for(Map.Entry<String,Object> entry: bucketsByName.entrySet()) {
            String name = entry.getKey();
            Object o = entry.getValue();
            if(o instanceof ManifestElement) {
                manifestEntries.put(name, o);
            } else if(o instanceof Bucket) {
                RandomAccessBucket data = (RandomAccessBucket) o;
                manifestEntries.put(name, new ManifestElement(name, data, null,data.size()));
            } else if(o instanceof HashMap) {
                manifestEntries.put(name, bucketsByNameToManifestEntries(Metadata.forceMap(o)));
            } else
                throw new IllegalArgumentException(String.valueOf(o));
        }
        return manifestEntries;
    }
 
    public static ManifestElement[] flatten(HashMap<String,Object> manifestElements) {
        List<ManifestElement> v = new ArrayList<ManifestElement>();
        flatten(manifestElements, v, "");
        return v.toArray(new ManifestElement[v.size()]);
    }
   
    public static void flatten(HashMap<String,Object> manifestElements, List<ManifestElement> v, String prefix) {
        for(Map.Entry<String,Object> entry: manifestElements.entrySet()) {
            String name = entry.getKey();
            String fullName = prefix.length() == 0 ? name : prefix+ '/' +name;
            Object o = entry.getValue();
            if(o instanceof HashMap) {
                flatten(Metadata.forceMap(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));
        }
    }
   
    @Override
    public void onShutdown(ClientContext context) {
        for(PutHandler h : runningPutHandlers)
            h.onShutdown(context);
        if(rootContainerPutHandler != null)
            rootContainerPutHandler.onShutdown(context);
        if(containerPutHandlers != null) {
            for(PutHandler h : containerPutHandlers)
                h.onShutdown(context);
        }
        if(rootMetaPutHandler != null)
            rootMetaPutHandler.onShutdown(context);
    }
   
    protected void innerOnResume(ClientContext context) throws ResumeFailedException {
        super.innerOnResume(context);
        for(PutHandler h : runningPutHandlers)
            h.onResume(context);
        if(rootContainerPutHandler != null)
            rootContainerPutHandler.onResume(context);
        if(containerPutHandlers != null) {
            for(PutHandler h : containerPutHandlers)
                h.onResume(context);
        }
        if(rootMetaPutHandler != null)
            rootMetaPutHandler.onResume(context);
    }
   
    @Override
    public ClientRequestSchedulerGroup getSchedulerGroup() {
        return this;
    }
}
TOP

Related Classes of freenet.client.async.BaseManifestPutter$ContainerPutHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.