Package freenet.client.async

Source Code of freenet.client.async.USKInserter

/* 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.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.util.Arrays;

import freenet.client.InsertContext;
import freenet.client.InsertException;
import freenet.client.InsertException.InsertExceptionMode;
import freenet.client.Metadata;
import freenet.keys.BaseClientKey;
import freenet.keys.FreenetURI;
import freenet.keys.InsertableUSK;
import freenet.keys.USK;
import freenet.support.LogThresholdCallback;
import freenet.support.Logger;
import freenet.support.Logger.LogLevel;
import freenet.support.api.Bucket;
import freenet.support.io.BucketTools;
import freenet.support.io.ResumeFailedException;

/**
* Insert a USK. The algorithm is simply to do a thorough search for the latest edition, and insert at the
* following slot. Thereafter, if we get a collision, increment our slot; if we get more than 5 consecutive
* collisions, search for the latest slot again.
*/
public class USKInserter implements ClientPutState, USKFetcherCallback, PutCompletionCallback, Serializable {

    private static final long serialVersionUID = 1L;
    private static volatile boolean logMINOR;
 
  static {
    Logger.registerLogThresholdCallback(new LogThresholdCallback() {
     
      @Override
      public void shouldUpdate() {
        logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
      }
    });
  }
 
  // Stuff to be passed on to the SingleBlockInserter
  final BaseClientPutter parent;
  Bucket data;
  final short compressionCodec;
  final InsertContext ctx;
  final PutCompletionCallback cb;
  final boolean isMetadata;
  final int sourceLength;
  final int token;
  public final Object tokenObject;
  final boolean persistent;
  final boolean realTimeFlag;
 
  final InsertableUSK privUSK;
  final USK pubUSK;
  /** Scanning for latest slot */
  private USKFetcherTag fetcher;
  /** Insert the actual SSK */
  private SingleBlockInserter sbi;
  private long edition;
  /** Number of collisions while trying to insert so far */
  private int consecutiveCollisions;
  private boolean finished;
  /** After attempting inserts on this many slots, go back to the Fetcher */
  private static final long MAX_TRIED_SLOTS = 10;
  private boolean freeData;
  final int hashCode;
  private final int extraInserts;
  final byte cryptoAlgorithm;
  final byte[] forceCryptoKey;
 
  @Override
  public void schedule(ClientContext context) throws InsertException {
    // Caller calls schedule()
    // schedule() calls scheduleFetcher()
    // scheduleFetcher() creates a Fetcher (set up to tell us about author-errors as well as valid inserts)
    // (and starts it)
    // when this completes, onFoundEdition() calls scheduleInsert()
    // scheduleInsert() starts a SingleBlockInserter
    // if that succeeds, we complete
    // if that fails, we increment our index and try again (in the callback)
    // if that continues to fail 5 times, we go back to scheduleFetcher()
    scheduleFetcher(context);
  }

  /**
   * Schedule a Fetcher to find us the latest inserted key of the USK.
   * The Fetcher must be insert-mode, in other words, it must know that we want the latest edition,
   * including author errors and so on.
   */
  private void scheduleFetcher(ClientContext context) {
    synchronized(this) {
      if(logMINOR)
        Logger.minor(this, "scheduling fetcher for "+pubUSK.getURI());
      if(finished) return;
      fetcher = context.uskManager.getFetcherForInsertDontSchedule(persistent ? pubUSK.copy() : pubUSK, parent.priorityClass, this, parent.getClient(), context, persistent, ctx.ignoreUSKDatehints);
      if(logMINOR)
        Logger.minor(this, "scheduled: "+fetcher);
    }
    fetcher.schedule(context);
  }

  @Override
  public void onFoundEdition(long l, USK key, ClientContext context, boolean lastContentWasMetadata, short codec, byte[] hisData, boolean newKnownGood, boolean newSlotToo) {
    boolean alreadyInserted = false;
    synchronized(this) {
      edition = Math.max(l, edition);
      consecutiveCollisions = 0;
      if((lastContentWasMetadata == isMetadata) && hisData != null
          && (codec == compressionCodec)) {
        try {
          byte[] myData = BucketTools.toByteArray(data);
          if(Arrays.equals(myData, hisData)) {
            // Success
            alreadyInserted = true;
            finished = true;
            sbi = null;
          }
        } catch (IOException e) {
          Logger.error(this, "Could not decode: "+e, e);
        }
      }
      if(persistent) {
        fetcher = null;
      }
    }
    if(alreadyInserted) {
      // Success!
      parent.completedBlock(true, context);
      cb.onEncode(pubUSK.copy(edition), this, context);
      insertSucceeded(context, l);
      if(freeData) {
        data.free();
      }
    } else {
      scheduleInsert(context);
    }
  }

  private void insertSucceeded(ClientContext context, long edition) {
    if(ctx.ignoreUSKDatehints) {
      if(logMINOR) Logger.minor(this, "Inserted to edition "+edition);
      cb.onSuccess(this, context);
      return;
    }
    if(logMINOR) Logger.minor(this, "Inserted to edition "+edition+" - inserting USK date hints...");
    USKDateHint hint = USKDateHint.now();
    MultiPutCompletionCallback m = new MultiPutCompletionCallback(cb, parent, tokenObject, persistent, true);
    byte[] hintData;
    try {
      hintData = hint.getData(edition).getBytes("UTF-8");
    } catch (UnsupportedEncodingException e) {
      throw new Error(e); // Impossible
    }
    FreenetURI[] hintURIs = hint.getInsertURIs(privUSK);
    boolean added = false;
    for(FreenetURI uri : hintURIs) {
      try {
        Bucket bucket = BucketTools.makeImmutableBucket(context.getBucketFactory(persistent), hintData);
        SingleBlockInserter sb =
          new SingleBlockInserter(parent, bucket, (short) -1, uri,
              ctx, realTimeFlag, m, false, sourceLength, token, true, true /* we don't use it */, null, context, persistent, true, extraInserts, cryptoAlgorithm, forceCryptoKey);
        Logger.normal(this, "Inserting "+uri+" with "+sb+" for insert of "+pubUSK);
        m.add(sb);
        sb.schedule(context);
        added = true;
      } catch (IOException e) {
        Logger.error(this, "Unable to insert USK date hints due to disk I/O error: "+e, e);
        if(!added) {
          cb.onFailure(new InsertException(InsertExceptionMode.BUCKET_ERROR, e, pubUSK.getSSK(edition).getURI()), this, context);
          return;
        } // Else try to insert the other hints.
      } catch (InsertException e) {
        Logger.error(this, "Unable to insert USK date hints due to error: "+e, e);
        if(!added) {
          cb.onFailure(e, this, context);
          return;
        } // Else try to insert the other hints.
      }
    }
    cb.onTransition(this, m, context);
    m.arm(context);
  }

  private void scheduleInsert(ClientContext context) {
    long edNo = Math.max(edition, context.uskManager.lookupLatestSlot(pubUSK)+1);
    synchronized(this) {
      if(finished) return;
      edition = edNo;
      if(logMINOR)
        Logger.minor(this, "scheduling insert for "+pubUSK.getURI()+ ' ' +edition);
      sbi = new SingleBlockInserter(parent, data, compressionCodec, privUSK.getInsertableSSK(edition).getInsertURI(),
          ctx, realTimeFlag, this, isMetadata, sourceLength, token, false, true /* we don't use it */, tokenObject, context, persistent, false, extraInserts, cryptoAlgorithm, forceCryptoKey);
    }
    try {
      sbi.schedule(context);
    } catch (InsertException e) {
      synchronized(this) {
        finished = true;
      }
      if(freeData) {
        data.free();
        synchronized(this) {
          data = null;
        }
      }
      cb.onFailure(e, this, context);
    }
  }

  @Override
  public synchronized void onSuccess(ClientPutState state, ClientContext context) {
    USK newEdition = pubUSK.copy(edition);
    finished = true;
    sbi = null;
    FreenetURI targetURI = pubUSK.getSSK(edition).getURI();
    FreenetURI realURI = ((SingleBlockInserter)state).getURI(context);
    if(!targetURI.equals(realURI))
      Logger.error(this, "URI should be "+targetURI+" actually is "+realURI);
    else {
      if(logMINOR)
        Logger.minor(this, "URI should be "+targetURI+" actually is "+realURI);
      context.uskManager.updateKnownGood(pubUSK, edition, context);
    }
    if(freeData) {
      data.free();
      data = null;
    }
    cb.onEncode(newEdition, this, context);
    insertSucceeded(context, edition);
    // FINISHED!!!! Yay!!!
  }

  @Override
  public void onFailure(InsertException e, ClientPutState state, ClientContext context) {
    synchronized(this) {
      sbi = null;
      if(e.getMode() == InsertExceptionMode.COLLISION) {
        // Try the next slot
        edition++;
        consecutiveCollisions++;
        if(consecutiveCollisions > MAX_TRIED_SLOTS)
          scheduleFetcher(context);
        else
          scheduleInsert(context);
      } else {
        Bucket d = null;
        synchronized(this) {
          finished = true;
          if(freeData) {
            d = data;
            data = null;
          }
        }
        if(freeData) {
          d.free();
        }
        cb.onFailure(e, state, context);
      }
    }
  }

  @Override
  public int hashCode() {
    return hashCode;
  }
 
  public USKInserter(BaseClientPutter parent, Bucket data, short compressionCodec, FreenetURI uri,
      InsertContext ctx, PutCompletionCallback cb, boolean isMetadata, int sourceLength, int token,
      boolean addToParent, Object tokenObject, ClientContext context, boolean freeData, boolean persistent, boolean realTimeFlag, int extraInserts, byte cryptoAlgorithm, byte[] forceCryptoKey) throws MalformedURLException {
    this.hashCode = super.hashCode();
    this.tokenObject = tokenObject;
    this.persistent = persistent;
    this.parent = parent;
    this.data = data;
    this.compressionCodec = compressionCodec;
    this.ctx = ctx;
    this.cb = cb;
    this.isMetadata = isMetadata;
    this.sourceLength = sourceLength;
    this.token = token;
    if(addToParent) {
      parent.addMustSucceedBlocks(1);
      parent.notifyClients(context);
    }
    privUSK = InsertableUSK.createInsertable(uri, persistent);
    pubUSK = privUSK.getUSK();
    edition = pubUSK.suggestedEdition;
    this.freeData = freeData;
    this.extraInserts = extraInserts;
    this.cryptoAlgorithm = cryptoAlgorithm;
    this.forceCryptoKey = forceCryptoKey;
    this.realTimeFlag = realTimeFlag;
  }
 
  protected USKInserter() {
      // For serialization.
      this.hashCode = 0;
      this.tokenObject = null;
      this.persistent = false;
      this.parent = null;
      this.data = null;
      this.compressionCodec = 0;
      this.ctx = null;
      this.cb = null;
      this.isMetadata = false;
      this.sourceLength = 0;
      this.token = 0;
      this.privUSK = null;
      this.pubUSK = null;
      this.edition = 0;
      this.freeData = false;
      this.extraInserts = 0;
      this.cryptoAlgorithm = 0;
      this.forceCryptoKey = null;
      this.realTimeFlag = false;
  }

  @Override
  public BaseClientPutter getParent() {
    return parent;
  }

  @Override
  public void cancel(ClientContext context) {
    USKFetcherTag tag;
    synchronized(this) {
      if(finished) return;
      finished = true;
      tag = fetcher;
      fetcher = null;
    }
    if(tag != null) {
      tag.cancel(context);
    }
    if(sbi != null) {
      sbi.cancel(context); // will call onFailure, which will removeFrom()
    }
    if(freeData) {
      if(data == null) {
        Logger.error(this, "data == null in cancel() on "+this, new Exception("error"));
      } else {
        data.free();
        synchronized(this) {
          data = null;
        }
      }
    }
    cb.onFailure(new InsertException(InsertExceptionMode.CANCELLED), this, context);
  }

  @Override
  public void onFailure(ClientContext context) {
    if(logMINOR) Logger.minor(this, "Fetcher failed to find the given edition or any later edition on "+this);
    scheduleInsert(context);
  }

  @Override
  public void onCancelled(ClientContext context) {
    synchronized(this) {
        fetcher = null;
      if(finished) return;
    }
    Logger.error(this, "Unexpected onCancelled()", new Exception("error"));
    cancel(context);
  }

  @Override
  public void onEncode(BaseClientKey key, ClientPutState state, ClientContext context) {
    // Ignore
  }

  @Override
  public void onTransition(ClientPutState oldState, ClientPutState newState, ClientContext context) {
    // Shouldn't happen
    Logger.error(this, "Got onTransition("+oldState+ ',' +newState+ ')');
  }

  @Override
  public void onMetadata(Metadata m, ClientPutState state, ClientContext context) {
    // Shouldn't happen
    Logger.error(this, "Got onMetadata("+m+ ',' +state+ ')');
  }

  @Override
  public void onBlockSetFinished(ClientPutState state, ClientContext context) {
    // Ignore
  }

  @Override
  public Object getToken() {
    return tokenObject;
  }

  @Override
  public void onFetchable(ClientPutState state) {
    // Ignore
  }

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

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

  @Override
  public void onMetadata(Bucket meta, ClientPutState state, ClientContext context) {
    Logger.error(this, "onMetadata on "+this+" from "+state, new Exception("error"));
    meta.free();
  }
 
  private transient boolean resumed = false;

    @Override
    public void onResume(ClientContext context) throws InsertException, ResumeFailedException {
        if(resumed) return;
        resumed = true;
        if(data != null) data.onResume(context);
        if(cb != null && cb != parent) cb.onResume(context);
        if(fetcher != null) fetcher.onResume(context);
        if(sbi != null) sbi.onResume(context);
    }

    @Override
    public void onShutdown(ClientContext context) {
        SingleBlockInserter sbi;
        synchronized(this) {
            sbi = this.sbi;
        }
        if(sbi != null) sbi.onShutdown(context);
    }

}
TOP

Related Classes of freenet.client.async.USKInserter

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.