Package freenet.client.async

Source Code of freenet.client.async.BaseSingleFileFetcher

/* 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 freenet.client.FetchContext;
import freenet.keys.ClientKey;
import freenet.keys.ClientKeyBlock;
import freenet.keys.ClientSSK;
import freenet.keys.Key;
import freenet.keys.KeyBlock;
import freenet.keys.KeyVerifyException;
import freenet.node.KeysFetchingLocally;
import freenet.node.LowLevelGetException;
import freenet.node.NullSendableRequestItem;
import freenet.node.RequestClient;
import freenet.node.RequestScheduler;
import freenet.node.SendableGet;
import freenet.node.SendableRequestItem;
import freenet.support.Logger;
import freenet.support.TimeUtil;

/**
* Base class implements most of what is needed for fetching a single block.
*
* WARNING: Changing non-transient members on classes that are Serializable can result in
* restarting downloads or losing uploads.
* @author toad
*/
public abstract class BaseSingleFileFetcher extends SendableGet implements HasKeyListener {

    private static final long serialVersionUID = 1L;

  protected final ClientKey key;
  protected boolean cancelled;
  protected boolean finished;
  final int maxRetries;
  private int retryCount;
  protected final FetchContext ctx;
  protected boolean deleteFetchContext;
  static final SendableRequestItem[] keys = new SendableRequestItem[] { NullSendableRequestItem.nullItem };
  private int cachedCooldownTries;
  private long cachedCooldownTime;
    public transient long cooldownWakeupTime;

 
  private static volatile boolean logMINOR;
 
  static {
    Logger.registerClass(BaseSingleFileFetcher.class);
  }

  protected BaseSingleFileFetcher(ClientKey key, int maxRetries, FetchContext ctx, ClientRequester parent, boolean deleteFetchContext, boolean realTimeFlag) {
    super(parent, realTimeFlag);
    this.deleteFetchContext = deleteFetchContext;
    if(logMINOR)
      Logger.minor(this, "Creating BaseSingleFileFetcher for "+key);
    retryCount = 0;
    this.maxRetries = maxRetries;
    this.key = key;
    this.ctx = ctx;
    if(ctx == null) throw new NullPointerException();
  }
 
  @Override
  public long countAllKeys(ClientContext context) {
    return 1;
  }
 
  @Override
  public long countSendableKeys(ClientContext context) {
    return 1;
  }
 
  @Override
  public SendableRequestItem chooseKey(KeysFetchingLocally fetching, ClientContext context) {
    Key k = key.getNodeKey(false);
    if(fetching.hasKey(k, this)) return null;
    long l = fetching.checkRecentlyFailed(k, realTimeFlag);
    long now = System.currentTimeMillis();
    if(l > 0 && l > now) {
      if(maxRetries == -1 || (maxRetries >= RequestScheduler.COOLDOWN_RETRIES)) {
        // FIXME synchronization!!!
        if(logMINOR) Logger.minor(this, "RecentlyFailed -> cooldown until "+TimeUtil.formatTime(l-now)+" on "+this);
        cooldownWakeupTime = Math.max(cooldownWakeupTime, l);
        return null;
      } else {
        this.onFailure(new LowLevelGetException(LowLevelGetException.RECENTLY_FAILED), null, context);
        return null;
      }
    }
    return keys[0];
  }
 
  @Override
  public ClientKey getKey(SendableRequestItem token) {
    return key;
  }
 
  @Override
  public FetchContext getContext() {
    return ctx;
  }

  @Override
  public boolean isSSK() {
    return key instanceof ClientSSK;
  }

  /** Try again - returns true if we can retry */
  protected boolean retry(ClientContext context) {
    if(isEmpty()) {
      if(logMINOR) Logger.minor(this, "Not retrying because empty");
      return false; // Cannot retry e.g. because we got the block and it failed to decode - that's a fatal error.
    }
    // We want 0, 1, ... maxRetries i.e. maxRetries+1 attempts (maxRetries=0 => try once, no retries, maxRetries=1 = original try + 1 retry)
    int r;
    r = ++retryCount;
    if(logMINOR)
      Logger.minor(this, "Attempting to retry... (max "+maxRetries+", current "+r+") on "+this+" finished="+finished+" cancelled="+cancelled);
    if((r <= maxRetries) || (maxRetries == -1)) {
      checkCachedCooldownData();
      if(cachedCooldownTries == 0 || r % cachedCooldownTries == 0) {
        // Add to cooldown queue. Don't reschedule yet.
        long now = System.currentTimeMillis();
        if(cooldownWakeupTime > now) {
          Logger.error(this, "Already on the cooldown queue for "+this+" until "+freenet.support.TimeUtil.formatTime(cooldownWakeupTime - now), new Exception("error"));
        } else {
          if(logMINOR) Logger.minor(this, "Adding to cooldown queue "+this);
          cooldownWakeupTime = now + cachedCooldownTime;
          reduceWakeupTime(cooldownWakeupTime, context);
          if(logMINOR) Logger.minor(this, "Added single file fetcher into cooldown until "+TimeUtil.formatTime(cooldownWakeupTime - now));
        }
        onEnterFiniteCooldown(context);
      } else {
        // Wake the CRS after clearing cache.
        clearWakeupTime(context);
      }
      return true; // We will retry in any case, maybe not just not yet.
    }
    unregister(context, getPriorityClass());
    return false;
  }

  private void checkCachedCooldownData() {
    // 0/0 is illegal, and it's also the default, so use it to indicate we haven't fetched them.
    if(!(cachedCooldownTime == 0 && cachedCooldownTries == 0)) {
      // Okay, we have already got them.
      return;
    }
    innerCheckCachedCooldownData();
  }
 
  private void innerCheckCachedCooldownData() {
    cachedCooldownTries = ctx.getCooldownRetries();
    cachedCooldownTime = ctx.getCooldownTime();
  }

  protected void onEnterFiniteCooldown(ClientContext context) {
    // Do nothing.
  }

  @Override
  public ClientRequester getClientRequest() {
    return parent;
  }

  @Override
  public short getPriorityClass() {
    return parent.getPriorityClass();
  }

  public void cancel(ClientContext context) {
    synchronized(this) {
      cancelled = true;
    }
    unregisterAll(context);
  }
 
  /**
   * Remove the pendingKeys item and then remove from the queue as well.
   * Call unregister(container) if you only want to remove from the queue.
   */
  public void unregisterAll(ClientContext context) {
    getScheduler(context).removePendingKeys(this, false);
    unregister(context, (short)-1);
  }

  @Override
  public void unregister(ClientContext context, short oldPrio) {
    super.unregister(context, oldPrio);
  }

  @Override
  public synchronized boolean isCancelled() {
    return cancelled;
  }
 
  public synchronized boolean isEmpty() {
    return cancelled || finished;
  }
 
  @Override
  public RequestClient getClient() {
    return parent.getClient();
  }

  public void onGotKey(Key key, KeyBlock block, ClientContext context) {
    synchronized(this) {
      if(finished) {
        if(logMINOR)
          Logger.minor(this, "onGotKey() called twice on "+this, new Exception("debug"));
        return;
      }
      finished = true;
      if(isCancelled()) return;
      if(key == null)
        throw new NullPointerException();
      if(this.key == null)
        throw new NullPointerException("Key is null on "+this);
      if(!key.equals(this.key.getNodeKey(false))) {
        Logger.normal(this, "Got sent key "+key+" but want "+this.key+" for "+this);
        return;
      }
    }
    unregister(context, getPriorityClass()); // Key has already been removed from pendingKeys
    onSuccess(block, false, null, context);
  }
 
  public void onSuccess(KeyBlock lowLevelBlock, boolean fromStore, SendableRequestItem token, ClientContext context) {
    ClientKeyBlock block;
    try {
      block = Key.createKeyBlock(this.key, lowLevelBlock);
      onSuccess(block, fromStore, token, context);
    } catch (KeyVerifyException e) {
      onBlockDecodeError(token, context);
    }
  }
 
  protected abstract void onBlockDecodeError(SendableRequestItem token, ClientContext context);

  /** Called when/if the low-level request succeeds. */
  public abstract void onSuccess(ClientKeyBlock block, boolean fromStore, Object token, ClientContext context);
 
  @Override
  public long getCooldownWakeup(SendableRequestItem token, ClientContext context) {
    return cooldownWakeupTime;
  }

  public void schedule(ClientContext context) {
    if(key == null) throw new NullPointerException();
    try {
      getScheduler(context).register(this, new SendableGet[] { this }, persistent, ctx.blocks, false);
    } catch (KeyListenerConstructionException e) {
      Logger.error(this, "Impossible: "+e+" on "+this, e);
    }
  }
 
  public void reschedule(ClientContext context) {
    try {
      getScheduler(context).register(null, new SendableGet[] { this }, persistent, ctx.blocks, true);
    } catch (KeyListenerConstructionException e) {
      Logger.error(this, "Impossible: "+e+" on "+this, e);
    }
  }
 
  public SendableGet getRequest(Key key) {
    return this;
  }

  @Override
  public Key[] listKeys() {
    synchronized(this) {
      if(cancelled || finished)
        return new Key[0];
    }
    return new Key[] { key.getNodeKey(true) };
  }

  @Override
  public KeyListener makeKeyListener(ClientContext context, boolean onStartup) {
    synchronized(this) {
      if(finished) return null;
      if(cancelled) return null;
    }
    if(key == null) {
      Logger.error(this, "Key is null - left over BSSF? on "+this+" in makeKeyListener()", new Exception("error"));
      return null;
    }
    Key newKey = key.getNodeKey(true);
    if(parent == null) {
      Logger.error(this, "Parent is null on "+this+" persistent="+persistent+" key="+key+" ctx="+ctx);
      return null;
    }
    short prio = parent.getPriorityClass();
    KeyListener ret = new SingleKeyListener(newKey, this, prio, persistent);
    return ret;
  }

  protected abstract void notFoundInStore(ClientContext context);
 
  @Override
  public boolean preRegister(ClientContext context, boolean toNetwork) {
    if(!toNetwork) return false;
    boolean localOnly = ctx.localRequestOnly;
    if(localOnly) {
      notFoundInStore(context);
      return true;
    }
    parent.toNetwork(context);
    return false;
  }
 
  @Override
  public synchronized long getWakeupTime(ClientContext context, long now) {
    if(cancelled || finished) return -1;
    long wakeTime = cooldownWakeupTime;
    if(wakeTime <= now)
      cooldownWakeupTime = wakeTime = 0;
    KeysFetchingLocally fetching = getScheduler(context).fetchingKeys();
    if(wakeTime <= 0 && fetching.hasKey(getNodeKey(null), this)) {
      wakeTime = Long.MAX_VALUE;
      // tracker.cooldownWakeupTime is only set for a real cooldown period, NOT when we go into hierarchical cooldown because the request is already running.
    }
    if(wakeTime == 0)
      return 0;
    return wakeTime;
  }
 
  /** Reread the cached cooldown values (and anything else) from the FetchContext
   * after it changes. FIXME: Ideally this should be a generic mechanism, but
   * that looks too complex without significant changes to data structures.
   * For now it's just a hack to make changing the polling interval in USKs work.
   * See bug https://bugs.freenetproject.org/view.php?id=4984
   * @param container The database if this is a persistent request.
   * @param context The context object.
   */
  public void onChangedFetchContext(ClientContext context) {
    synchronized(this) {
      if(cancelled || finished) return;
    }
    innerCheckCachedCooldownData();
  }
 
    public void onResume(ClientContext context) {
        schedule(context);
    }

}
TOP

Related Classes of freenet.client.async.BaseSingleFileFetcher

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.