/**
  * Remove a file from the cache. Called after it has been removed from its ArchiveHandler.
  *
  * @param item The ArchiveStoreItem to remove.
  */
 synchronized void removeCachedItem(ArchiveStoreItem item) {
   long size = item.spaceUsed();
   storedData.removeKey(item.key);
   // Hard disk space limit = remove it here.
   // Soft disk space limit would be to remove it outside the lock.
   // Soft disk space limit = we go over the limit significantly when we
   // are overloaded.
   cachedData -= size;
   if (logMINOR) Logger.minor(this, "removeCachedItem: " + item);
   item.close();
 }
 /**
  * Get a cached, previously extracted, file from an archive.
  *
  * @param key The key used to fetch the archive.
  * @param filename The name of the file within the archive.
  * @return A Bucket containing the data requested, or null.
  * @throws ArchiveFailureException
  */
 public Bucket getCached(FreenetURI key, String filename) throws ArchiveFailureException {
   if (logMINOR) Logger.minor(this, "Fetch cached: " + key + ' ' + filename);
   ArchiveKey k = new ArchiveKey(key, filename);
   ArchiveStoreItem asi = null;
   synchronized (this) {
     asi = storedData.get(k);
     if (asi == null) return null;
     // Promote to top of LRU
     storedData.push(k, asi);
   }
   if (logMINOR) Logger.minor(this, "Found data");
   return asi.getReaderBucket();
 }
 /**
  * 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;
 }
 /**
  * Add an error element to the cache. This happens when a single file in the archive is invalid
  * (usually because it is too large).
  *
  * @param ctx The ArchiveStoreContext which must be notified about this element's creation.
  * @param key The key from which the archive was fetched.
  * @param name The name of the file within the archive.
  * @param error The error message to be included on the eventual exception thrown, if anyone tries
  *     to extract the data for this element.
  */
 private void addErrorElement(
     ArchiveStoreContext ctx, FreenetURI key, String name, String error, boolean tooBig) {
   ErrorArchiveStoreItem element = new ErrorArchiveStoreItem(ctx, key, name, error, tooBig);
   element.addToContext();
   if (logMINOR)
     Logger.minor(this, "Adding error element: " + element + " for " + key + ' ' + name);
   ArchiveStoreItem oldItem;
   synchronized (this) {
     oldItem = storedData.get(element.key);
     storedData.push(element.key, element);
     if (oldItem != null) {
       oldItem.close();
       cachedData -= oldItem.spaceUsed();
       if (logMINOR)
         Logger.minor(this, "Dropping old store element from archive cache: " + oldItem);
     }
   }
 }
 /** Drop any stored data beyond the limit. Call synchronized on storedData. */
 private void trimStoredData() {
   synchronized (this) {
     while (true) {
       ArchiveStoreItem item;
       if (cachedData <= maxCachedData && storedData.size() <= maxCachedElements) return;
       if (storedData.isEmpty()) {
         // Race condition? cachedData out of sync?
         Logger.error(
             this,
             "storedData is empty but still over limit: cachedData="
                 + cachedData
                 + " / "
                 + maxCachedData);
         return;
       }
       item = storedData.popValue();
       long space = item.spaceUsed();
       cachedData -= space;
       // Hard limits = delete file within lock, soft limits = delete outside of lock
       // Here we use a hard limit
       if (logMINOR)
         Logger.minor(
             this,
             "Dropping "
                 + item
                 + " : cachedData="
                 + cachedData
                 + " of "
                 + maxCachedData
                 + " stored items : "
                 + storedData.size()
                 + " of "
                 + maxCachedElements);
       item.close();
     }
   }
 }