/**
  * Add a store element.
  *
  * @param callbackName If set, the name of the file for which we must call the callback if this
  *     file happens to match.
  * @param gotElement Flag indicating whether we've already found the file for the callback. If so
  *     we must not call it again.
  * @param callback Callback to be called if we do find it. We must getReaderBucket() before adding
  *     the data to the LRU, otherwise it may be deleted before it reaches the client.
  * @throws ArchiveFailureException If a failure occurred resulting in the data not being readable.
  *     Only happens if callback != null.
  */
 private ArchiveStoreItem addStoreElement(
     ArchiveStoreContext ctx,
     FreenetURI key,
     String name,
     Bucket temp,
     MutableBoolean gotElement,
     String callbackName,
     ArchiveExtractCallback callback,
     ClientContext context)
     throws ArchiveFailureException {
   RealArchiveStoreItem element = new RealArchiveStoreItem(ctx, key, name, temp);
   element.addToContext();
   if (logMINOR)
     Logger.minor(
         this,
         "Adding store element: "
             + element
             + " ( "
             + key
             + ' '
             + name
             + " size "
             + element.spaceUsed()
             + " )");
   ArchiveStoreItem oldItem;
   // Let it throw, if it does something is drastically wrong
   Bucket matchBucket = null;
   if ((!gotElement.value) && name.equals(callbackName)) {
     matchBucket = element.getReaderBucket();
   }
   synchronized (this) {
     oldItem = storedData.get(element.key);
     storedData.push(element.key, element);
     cachedData += element.spaceUsed();
     if (oldItem != null) {
       cachedData -= oldItem.spaceUsed();
       if (logMINOR)
         Logger.minor(this, "Dropping old store element from archive cache: " + oldItem);
       oldItem.close();
     }
   }
   if (matchBucket != null) {
     callback.gotBucket(matchBucket, context);
     gotElement.value = true;
   }
   return element;
 }
  private void handleZIPArchive(
      ArchiveStoreContext ctx,
      FreenetURI key,
      InputStream data,
      String element,
      ArchiveExtractCallback callback,
      MutableBoolean gotElement,
      boolean throwAtExit,
      ClientContext context)
      throws ArchiveFailureException, ArchiveRestartException {
    if (logMINOR) Logger.minor(this, "Handling a ZIP Archive");
    ZipInputStream zis = null;
    try {
      zis = new ZipInputStream(data);

      // MINOR: Assumes the first entry in the zip is a directory.
      ZipEntry entry;

      byte[] buf = new byte[32768];
      HashSet<String> names = new HashSet<String>();
      boolean gotMetadata = false;

      outerZIP:
      while (true) {
        entry = zis.getNextEntry();
        if (entry == null) break;
        if (entry.isDirectory()) continue;
        String name = stripLeadingSlashes(entry.getName());
        if (names.contains(name)) {
          Logger.error(this, "Duplicate key " + name + " in archive " + key);
          continue;
        }
        long size = entry.getSize();
        if (name.equals(".metadata")) gotMetadata = true;
        if (size > maxArchivedFileSize && !name.equals(element)) {
          addErrorElement(
              ctx,
              key,
              name,
              "File too big: "
                  + maxArchivedFileSize
                  + " greater than current archived file size limit "
                  + maxArchivedFileSize,
              true);
        } else {
          // Read the element
          long realLen = 0;
          Bucket output = tempBucketFactory.makeBucket(size);
          OutputStream out = output.getOutputStream();
          try {

            int readBytes;
            while ((readBytes = zis.read(buf)) > 0) {
              out.write(buf, 0, readBytes);
              readBytes += realLen;
              if (readBytes > maxArchivedFileSize) {
                addErrorElement(
                    ctx,
                    key,
                    name,
                    "File too big: "
                        + maxArchivedFileSize
                        + " greater than current archived file size limit "
                        + maxArchivedFileSize,
                    true);
                out.close();
                out = null;
                output.free();
                continue outerZIP;
              }
            }

          } finally {
            if (out != null) out.close();
          }
          if (size <= maxArchivedFileSize) {
            addStoreElement(ctx, key, name, output, gotElement, element, callback, context);
            names.add(name);
            trimStoredData();
          } else {
            // We are here because they asked for this file.
            callback.gotBucket(output, context);
            gotElement.value = true;
            addErrorElement(
                ctx,
                key,
                name,
                "File too big: "
                    + size
                    + " greater than current archived file size limit "
                    + maxArchivedFileSize,
                true);
          }
        }
      }

      // If no metadata, generate some
      if (!gotMetadata) {
        generateMetadata(ctx, key, names, gotElement, element, callback, context);
        trimStoredData();
      }
      if (throwAtExit) throw new ArchiveRestartException("Archive changed on re-fetch");

      if ((!gotElement.value) && element != null) callback.notInArchive(context);

    } catch (IOException e) {
      throw new ArchiveFailureException("Error reading archive: " + e.getMessage(), e);
    } finally {
      if (zis != null) {
        try {
          zis.close();
        } catch (IOException e) {
          Logger.error(this, "Failed to close stream: " + e, e);
        }
      }
    }
  }