Author: toad
Date: 2006-03-24 02:29:37 +0000 (Fri, 24 Mar 2006)
New Revision: 8294
Added:
trunk/freenet/src/freenet/client/async/BaseSingleFileFetcher.java
trunk/freenet/src/freenet/client/async/USKChecker.java
trunk/freenet/src/freenet/client/async/USKCheckerCallback.java
trunk/freenet/src/freenet/client/async/USKFetcher.java
trunk/freenet/src/freenet/client/async/USKFetcherCallback.java
trunk/freenet/src/freenet/client/async/USKManager.java
trunk/freenet/src/freenet/client/async/USKProxyCompletionCallback.java
trunk/freenet/src/freenet/keys/BaseClientKey.java
trunk/freenet/src/freenet/keys/USK.java
Modified:
trunk/freenet/src/freenet/client/FetchException.java
trunk/freenet/src/freenet/client/FetcherContext.java
trunk/freenet/src/freenet/client/HighLevelSimpleClientImpl.java
trunk/freenet/src/freenet/client/Metadata.java
trunk/freenet/src/freenet/client/async/ClientGetState.java
trunk/freenet/src/freenet/client/async/ClientGetter.java
trunk/freenet/src/freenet/client/async/SingleFileFetcher.java
trunk/freenet/src/freenet/client/async/SplitFileFetcher.java
trunk/freenet/src/freenet/client/async/SplitFileFetcherSegment.java
trunk/freenet/src/freenet/keys/ClientKey.java
trunk/freenet/src/freenet/keys/FreenetURI.java
trunk/freenet/src/freenet/node/LowLevelGetException.java
trunk/freenet/src/freenet/node/Node.java
trunk/freenet/src/freenet/node/TextModeClientInterface.java
trunk/freenet/src/freenet/node/Version.java
Log:
557: Basic USK retrieval support.
Not yet supported in Fproxy, or bookmark servlet.
Insertion is trivial: SSK at .../sitename-<number> - it will pick this up.
Will later implement saving to disk of current status of all USKs as an option.
Also related refactoring.
Modified: trunk/freenet/src/freenet/client/FetchException.java
===================================================================
--- trunk/freenet/src/freenet/client/FetchException.java 2006-03-24
01:34:27 UTC (rev 8293)
+++ trunk/freenet/src/freenet/client/FetchException.java 2006-03-24
02:29:37 UTC (rev 8294)
@@ -1,5 +1,6 @@
package freenet.client;
+import freenet.keys.FreenetURI;
import freenet.support.Logger;
/**
@@ -11,6 +12,8 @@
private static final long serialVersionUID = -1106716067841151962L;
public final int mode;
+
+ public final FreenetURI newURI;
/** For collection errors */
public final FailureCodeTracker errorCodes;
@@ -27,6 +30,7 @@
extraMessage = null;
mode = m;
errorCodes = null;
+ newURI = null;
Logger.minor(this, "FetchException("+getMessage(mode)+")",
this);
}
@@ -36,6 +40,7 @@
mode = INVALID_METADATA;
errorCodes = null;
initCause(e);
+ newURI = null;
Logger.minor(this, "FetchException("+getMessage(mode)+"):
"+e,e);
}
@@ -44,6 +49,7 @@
extraMessage = e.getMessage();
mode = ARCHIVE_FAILURE;
errorCodes = null;
+ newURI = null;
initCause(e);
Logger.minor(this, "FetchException("+getMessage(mode)+"):
"+e,e);
}
@@ -54,6 +60,7 @@
mode = ARCHIVE_FAILURE;
errorCodes = null;
initCause(e);
+ newURI = null;
Logger.minor(this, "FetchException("+getMessage(mode)+"):
"+e,e); }
public FetchException(int mode, Throwable t) {
@@ -62,6 +69,7 @@
this.mode = mode;
errorCodes = null;
initCause(t);
+ newURI = null;
Logger.minor(this, "FetchException("+getMessage(mode)+"):
"+t.getMessage(),t);
}
@@ -70,8 +78,8 @@
extraMessage = null;
this.mode = mode;
this.errorCodes = errorCodes;
+ newURI = null;
Logger.minor(this, "FetchException("+getMessage(mode)+")");
-
}
public FetchException(int mode, String msg) {
@@ -79,9 +87,19 @@
extraMessage = msg;
errorCodes = null;
this.mode = mode;
+ newURI = null;
Logger.minor(this, "FetchException("+getMessage(mode)+"):
"+msg,this);
}
+ public FetchException(int mode, FreenetURI newURI) {
+ super(getMessage(mode));
+ extraMessage = null;
+ this.mode = mode;
+ errorCodes = null;
+ this.newURI = newURI;
+ Logger.minor(this, "FetchException("+getMessage(mode)+") ->
"+newURI, this);
+ }
+
public static String getShortMessage(int mode) {
switch(mode) {
case TOO_DEEP_ARCHIVE_RECURSION:
@@ -136,6 +154,8 @@
return "Cancelled";
case ARCHIVE_RESTART:
return "Archive restarted";
+ case PERMANENT_REDIRECT:
+ return "New URI";
default:
return "Unknown code "+mode;
}
@@ -196,6 +216,8 @@
return "Cancelled by caller";
case ARCHIVE_RESTART:
return "Archive restarted";
+ case PERMANENT_REDIRECT:
+ return "Permanent redirect: use the new URI";
default:
return "Unknown fetch error code: "+mode;
}
@@ -255,6 +277,8 @@
public static final int CANCELLED = 25;
/** Archive restart */
public static final int ARCHIVE_RESTART = 26;
+ /** There is a more recent version of the USK, ~= HTTP 301; fproxy will
turn this into a 301 */
+ public static final int PERMANENT_REDIRECT = 27;
/** Is an error fatal i.e. is there no point retrying? */
public boolean isFatal() {
Modified: trunk/freenet/src/freenet/client/FetcherContext.java
===================================================================
--- trunk/freenet/src/freenet/client/FetcherContext.java 2006-03-24
01:34:27 UTC (rev 8293)
+++ trunk/freenet/src/freenet/client/FetcherContext.java 2006-03-24
02:29:37 UTC (rev 8294)
@@ -1,5 +1,6 @@
package freenet.client;
+import freenet.client.async.USKManager;
import freenet.client.events.ClientEventProducer;
import freenet.client.events.SimpleEventProducer;
import freenet.crypt.RandomSource;
@@ -18,6 +19,7 @@
public long maxTempLength;
public final ArchiveManager archiveManager;
public final BucketFactory bucketFactory;
+ public final USKManager uskManager;
public int maxRecursionLevel;
public int maxArchiveRestarts;
public boolean dontEnterImplicitArchives;
@@ -48,8 +50,9 @@
boolean allowSplitfiles, boolean followRedirects,
boolean localRequestOnly,
int maxDataBlocksPerSegment, int
maxCheckBlocksPerSegment,
RandomSource random, ArchiveManager archiveManager,
BucketFactory bucketFactory,
- ClientEventProducer producer, boolean
cacheLocalRequests) {
+ ClientEventProducer producer, boolean
cacheLocalRequests, USKManager uskManager) {
this.maxOutputLength = curMaxLength;
+ this.uskManager = uskManager;
this.maxTempLength = curMaxTempLength;
this.maxMetadataSize = maxMetadataSize;
this.archiveManager = archiveManager;
@@ -76,6 +79,7 @@
this.eventProducer = ctx.eventProducer;
else
this.eventProducer = new SimpleEventProducer();
+ this.uskManager = ctx.uskManager;
if(maskID == IDENTICAL_MASK) {
this.maxOutputLength = ctx.maxOutputLength;
this.maxMetadataSize = ctx.maxMetadataSize;
Modified: trunk/freenet/src/freenet/client/HighLevelSimpleClientImpl.java
===================================================================
--- trunk/freenet/src/freenet/client/HighLevelSimpleClientImpl.java
2006-03-24 01:34:27 UTC (rev 8293)
+++ trunk/freenet/src/freenet/client/HighLevelSimpleClientImpl.java
2006-03-24 02:29:37 UTC (rev 8294)
@@ -145,7 +145,7 @@
SPLITFILE_THREADS, SPLITFILE_BLOCK_RETRIES,
NON_SPLITFILE_RETRIES,
FETCH_SPLITFILES, FOLLOW_REDIRECTS,
LOCAL_REQUESTS_ONLY,
MAX_SPLITFILE_BLOCKS_PER_SEGMENT,
MAX_SPLITFILE_CHECK_BLOCKS_PER_SEGMENT,
- random, archiveManager, bucketFactory,
globalEventProducer, cacheLocalRequests);
+ random, archiveManager, bucketFactory,
globalEventProducer, cacheLocalRequests, node.uskManager);
}
public InserterContext getInserterContext() {
Modified: trunk/freenet/src/freenet/client/Metadata.java
===================================================================
--- trunk/freenet/src/freenet/client/Metadata.java 2006-03-24 01:34:27 UTC
(rev 8293)
+++ trunk/freenet/src/freenet/client/Metadata.java 2006-03-24 02:29:37 UTC
(rev 8294)
@@ -10,6 +10,7 @@
import java.util.HashMap;
import java.util.Iterator;
+import freenet.keys.BaseClientKey;
import freenet.keys.ClientCHK;
import freenet.keys.ClientKey;
import freenet.keys.FreenetURI;
@@ -606,7 +607,7 @@
String[] meta = freenetURI.getAllMetaStrings();
if(meta != null && meta.length > 0)
throw new MalformedURLException("Not a plain
CHK");
- ClientKey key = ClientKey.getBaseKey(freenetURI);
+ BaseClientKey key =
BaseClientKey.getBaseKey(freenetURI);
if(key instanceof ClientCHK) {
((ClientCHK)key).writeRawBinaryKey(dos);
} else throw new IllegalArgumentException("Full keys
must be enabled to write non-CHKs");
Added: trunk/freenet/src/freenet/client/async/BaseSingleFileFetcher.java
===================================================================
--- trunk/freenet/src/freenet/client/async/BaseSingleFileFetcher.java
2006-03-24 01:34:27 UTC (rev 8293)
+++ trunk/freenet/src/freenet/client/async/BaseSingleFileFetcher.java
2006-03-24 02:29:37 UTC (rev 8294)
@@ -0,0 +1,103 @@
+package freenet.client.async;
+
+import freenet.client.FetcherContext;
+import freenet.keys.ClientCHK;
+import freenet.keys.ClientKey;
+import freenet.keys.ClientKeyBlock;
+import freenet.keys.ClientSSK;
+import freenet.node.LowLevelGetException;
+import freenet.node.Node;
+import freenet.support.Logger;
+
+public abstract class BaseSingleFileFetcher implements SendableGet {
+
+ final ClientKey key;
+ protected boolean cancelled;
+ final int maxRetries;
+ private int retryCount;
+ final FetcherContext ctx;
+ final ClientRequester parent;
+
+ BaseSingleFileFetcher(ClientKey key, int maxRetries, FetcherContext
ctx, ClientRequester parent) {
+ retryCount = 0;
+ this.maxRetries = maxRetries;
+ this.key = key;
+ this.ctx = ctx;
+ this.parent = parent;
+ }
+
+ public ClientKey getKey() {
+ return key;
+ }
+
+ /** Do the request, blocking. Called by RequestStarter. */
+ public void send(Node node) {
+ if(cancelled) {
+ onFailure(new
LowLevelGetException(LowLevelGetException.CANCELLED));
+ return;
+ }
+ // Do we need to support the last 3?
+ ClientKeyBlock block;
+ try {
+ block = node.realGetKey(key, ctx.localRequestOnly,
ctx.cacheLocalRequests, ctx.ignoreStore);
+ } catch (LowLevelGetException e) {
+ onFailure(e);
+ return;
+ } catch (Throwable t) {
+ Logger.error(this, "Caught "+t, t);
+ onFailure(new
LowLevelGetException(LowLevelGetException.INTERNAL_ERROR));
+ return;
+ }
+ onSuccess(block, false);
+ }
+
+ /** Try again - returns true if we can retry */
+ protected boolean retry() {
+ if(retryCount <= maxRetries || maxRetries == -1) {
+ retryCount++;
+ schedule();
+ return true;
+ }
+ return false;
+ }
+
+ public void schedule() {
+ Logger.minor(this, "Scheduling "+this);
+ if(key instanceof ClientCHK)
+ parent.chkScheduler.register(this);
+ else if(key instanceof ClientSSK)
+ parent.sskScheduler.register(this);
+ else
+ throw new IllegalStateException(String.valueOf(key));
+ }
+
+ public int getRetryCount() {
+ return retryCount;
+ }
+
+ public ClientRequester getClientRequest() {
+ return parent;
+ }
+
+ public short getPriorityClass() {
+ return parent.getPriorityClass();
+ }
+
+ public boolean ignoreStore() {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ public synchronized void cancel() {
+ cancelled = true;
+ }
+
+ public boolean isFinished() {
+ return cancelled;
+ }
+
+ public Object getClient() {
+ return parent.getClient();
+ }
+
+}
Modified: trunk/freenet/src/freenet/client/async/ClientGetState.java
===================================================================
--- trunk/freenet/src/freenet/client/async/ClientGetState.java 2006-03-24
01:34:27 UTC (rev 8293)
+++ trunk/freenet/src/freenet/client/async/ClientGetState.java 2006-03-24
02:29:37 UTC (rev 8294)
@@ -4,12 +4,12 @@
* A ClientGetState.
* Represents a stage in the fetch process.
*/
-public abstract class ClientGetState {
+public abstract interface ClientGetState {
- public abstract ClientGetter getParent();
+ public ClientGetter getParent();
- public abstract void schedule();
+ public void schedule();
- public abstract void cancel();
+ public void cancel();
}
Modified: trunk/freenet/src/freenet/client/async/ClientGetter.java
===================================================================
--- trunk/freenet/src/freenet/client/async/ClientGetter.java 2006-03-24
01:34:27 UTC (rev 8293)
+++ trunk/freenet/src/freenet/client/async/ClientGetter.java 2006-03-24
02:29:37 UTC (rev 8294)
@@ -54,8 +54,11 @@
public void start() throws FetchException {
try {
- currentState = new SingleFileFetcher(this, this, new
ClientMetadata(), uri, ctx, actx, ctx.maxNonSplitfileRetries, 0, false, null,
true, returnBucket);
- currentState.schedule();
+ currentState = SingleFileFetcher.create(this, this, new
ClientMetadata(),
+ uri, ctx, actx,
ctx.maxNonSplitfileRetries, 0, false, null, true,
+ returnBucket);
+ if(currentState != null)
+ currentState.schedule();
} catch (MalformedURLException e) {
throw new FetchException(FetchException.INVALID_URI, e);
}
Modified: trunk/freenet/src/freenet/client/async/SingleFileFetcher.java
===================================================================
--- trunk/freenet/src/freenet/client/async/SingleFileFetcher.java
2006-03-24 01:34:27 UTC (rev 8293)
+++ trunk/freenet/src/freenet/client/async/SingleFileFetcher.java
2006-03-24 02:29:37 UTC (rev 8294)
@@ -14,12 +14,14 @@
import freenet.client.FetcherContext;
import freenet.client.Metadata;
import freenet.client.MetadataParseException;
+import freenet.keys.BaseClientKey;
import freenet.keys.ClientCHK;
import freenet.keys.ClientKey;
import freenet.keys.ClientKeyBlock;
import freenet.keys.ClientSSK;
import freenet.keys.FreenetURI;
import freenet.keys.KeyDecodeException;
+import freenet.keys.USK;
import freenet.node.LowLevelGetException;
import freenet.node.Node;
import freenet.support.Bucket;
@@ -28,27 +30,21 @@
import freenet.support.compress.CompressionOutputSizeException;
import freenet.support.compress.Compressor;
-public class SingleFileFetcher extends ClientGetState implements SendableGet {
+public class SingleFileFetcher extends BaseSingleFileFetcher implements
ClientGetState {
- final ClientGetter parent;
//final FreenetURI uri;
- final ClientKey key;
final LinkedList metaStrings;
- final FetcherContext ctx;
final GetCompletionCallback rcb;
final ClientMetadata clientMetadata;
private Metadata metadata;
- final int maxRetries;
final ArchiveContext actx;
/** Archive handler. We can only have one archive handler at a time. */
private ArchiveStoreContext ah;
private int recursionLevel;
/** The URI of the currently-being-processed data, for archives etc. */
private FreenetURI thisKey;
- private int retryCount;
private final LinkedList decompressors;
private final boolean dontTellClientGet;
- private boolean cancelled;
private Object token;
private final Bucket returnBucket;
@@ -57,23 +53,23 @@
* @param token
* @param dontTellClientGet
*/
- public SingleFileFetcher(ClientGetter get, GetCompletionCallback cb,
ClientMetadata metadata, ClientKey key, LinkedList metaStrings, FetcherContext
ctx, ArchiveContext actx, int maxRetries, int recursionLevel, boolean
dontTellClientGet, Object token, boolean isEssential, Bucket returnBucket)
throws FetchException {
+ public SingleFileFetcher(ClientGetter get, GetCompletionCallback cb,
ClientMetadata metadata,
+ ClientKey key, LinkedList metaStrings, FetcherContext
ctx,
+ ArchiveContext actx, int maxRetries, int
recursionLevel,
+ boolean dontTellClientGet, Object token, boolean
isEssential,
+ Bucket returnBucket) throws FetchException {
+ super(key, maxRetries, ctx, get);
Logger.minor(this, "Creating SingleFileFetcher for "+key);
this.cancelled = false;
this.returnBucket = returnBucket;
this.dontTellClientGet = dontTellClientGet;
this.token = token;
- this.parent = get;
//this.uri = uri;
//this.key = ClientKey.getBaseKey(uri);
//metaStrings = uri.listMetaStrings();
- this.key = key;
this.metaStrings = metaStrings;
- this.ctx = ctx;
- retryCount = 0;
this.rcb = cb;
this.clientMetadata = metadata;
- this.maxRetries = maxRetries;
thisKey = key.getURI();
this.actx = actx;
this.recursionLevel = recursionLevel + 1;
@@ -85,14 +81,10 @@
parent.addMustSucceedBlocks(1);
}
- /** Called by ClientGet. */
- public SingleFileFetcher(ClientGetter get, GetCompletionCallback cb,
ClientMetadata metadata, FreenetURI uri, FetcherContext ctx, ArchiveContext
actx, int maxRetries, int recursionLevel, boolean dontTellClientGet, Object
token, boolean isEssential, Bucket returnBucket) throws MalformedURLException,
FetchException {
- this(get, cb, metadata, ClientKey.getBaseKey(uri),
uri.listMetaStrings(), ctx, actx, maxRetries, recursionLevel,
dontTellClientGet, token, isEssential, returnBucket);
- }
-
/** Copy constructor, modifies a few given fields, don't call
schedule().
* Used for things like slave fetchers for MultiLevelMetadata,
therefore does not remember returnBucket. */
public SingleFileFetcher(SingleFileFetcher fetcher, Metadata newMeta,
GetCompletionCallback callback, FetcherContext ctx2) throws FetchException {
+ super(fetcher.key, fetcher.maxRetries, ctx2, fetcher.parent);
Logger.minor(this, "Creating SingleFileFetcher for
"+fetcher.key);
this.token = fetcher.token;
this.returnBucket = null;
@@ -100,14 +92,9 @@
this.actx = fetcher.actx;
this.ah = fetcher.ah;
this.clientMetadata = fetcher.clientMetadata;
- this.ctx = ctx2;
- this.key = fetcher.key;
- this.maxRetries = fetcher.maxRetries;
this.metadata = newMeta;
this.metaStrings = fetcher.metaStrings;
- this.parent = fetcher.parent;
this.rcb = callback;
- this.retryCount = 0;
this.recursionLevel = fetcher.recursionLevel + 1;
if(recursionLevel > ctx.maxRecursionLevel)
throw new
FetchException(FetchException.TOO_MUCH_RECURSION);
@@ -115,33 +102,6 @@
this.decompressors = fetcher.decompressors;
}
- public void schedule() {
- if(!dontTellClientGet)
- this.parent.currentState = this;
- if(key instanceof ClientCHK)
- parent.chkScheduler.register(this);
- else if(key instanceof ClientSSK)
- parent.sskScheduler.register(this);
- else
- throw new IllegalStateException(String.valueOf(key));
- }
-
- public ClientGetter getParent() {
- return parent;
- }
-
- public ClientKey getKey() {
- return key;
- }
-
- public short getPriorityClass() {
- return parent.getPriorityClass();
- }
-
- public int getRetryCount() {
- return retryCount;
- }
-
// Process the completed data. May result in us going to a
// splitfile, or another SingleFileFetcher, etc.
public void onSuccess(ClientKeyBlock block, boolean fromStore) {
@@ -319,7 +279,14 @@
Logger.minor(this, "Redirecting to "+uri);
ClientKey key;
try {
- key = ClientKey.getBaseKey(uri);
+ BaseClientKey k =
ClientKey.getBaseKey(uri);
+ if(k instanceof ClientKey)
+ key = (ClientKey) k;
+ else
+ // FIXME do we want to allow
redirects to USKs?
+ // Without redirects to USKs,
all SSK and CHKs are static.
+ // This may be a desirable
property.
+ throw new
FetchException(FetchException.UNKNOWN_METADATA, "Redirect to a USK");
} catch (MalformedURLException e) {
throw new
FetchException(FetchException.INVALID_URI, e);
}
@@ -333,7 +300,7 @@
metaStrings.addFirst(o);
}
- SingleFileFetcher f = new
SingleFileFetcher(parent, rcb, clientMetadata, key, metaStrings, ctx, actx,
maxRetries, recursionLevel, false, null, true, returnBucket);
+ SingleFileFetcher f = new
SingleFileFetcher((ClientGetter)parent, rcb, clientMetadata, key, metaStrings,
ctx, actx, maxRetries, recursionLevel, false, null, true, returnBucket);
if(metadata.isCompressed()) {
Compressor codec =
Compressor.getCompressionAlgorithmByMetadataID(metadata.getCompressionCodec());
f.addDecompressor(codec);
@@ -354,7 +321,7 @@
addDecompressor(codec);
}
- SplitFileFetcher sf = new
SplitFileFetcher(metadata, rcb, parent, ctx,
+ SplitFileFetcher sf = new
SplitFileFetcher(metadata, rcb, (ClientGetter)parent, ctx,
decompressors, clientMetadata,
actx, recursionLevel, returnBucket, false);
sf.schedule();
rcb.onBlockSetFinished(this);
@@ -399,7 +366,7 @@
}
public void onSuccess(FetchResult result, ClientGetState state)
{
- parent.currentState = SingleFileFetcher.this;
+ ((ClientGetter)parent).currentState =
SingleFileFetcher.this;
try {
ctx.archiveManager.extractToCache(thisKey,
ah.getArchiveType(), result.asBucket(), actx, ah);
} catch (ArchiveFailureException e) {
@@ -436,10 +403,10 @@
class MultiLevelMetadataCallback implements GetCompletionCallback {
public void onSuccess(FetchResult result, ClientGetState state)
{
- parent.currentState = SingleFileFetcher.this;
+ ((ClientGetter)parent).currentState =
SingleFileFetcher.this;
try {
metadata =
Metadata.construct(result.asBucket());
- SingleFileFetcher f = new
SingleFileFetcher(parent, rcb, clientMetadata, key, metaStrings, ctx, actx,
maxRetries, recursionLevel, dontTellClientGet, null, true, returnBucket);
+ SingleFileFetcher f = new
SingleFileFetcher((ClientGetter)parent, rcb, clientMetadata, key, metaStrings,
ctx, actx, maxRetries, recursionLevel, dontTellClientGet, null, true,
returnBucket);
f.metadata = metadata;
f.handleMetadata();
} catch (MetadataParseException e) {
@@ -469,25 +436,18 @@
}
- private final void onFailure(FetchException e) {
+ final void onFailure(FetchException e) {
onFailure(e, false);
}
// Real onFailure
- private void onFailure(FetchException e, boolean forceFatal) {
+ protected void onFailure(FetchException e, boolean forceFatal) {
if(parent.isCancelled() || cancelled) {
e = new FetchException(FetchException.CANCELLED);
+ forceFatal = true;
}
if(!(e.isFatal() || forceFatal) ) {
- if((retryCount <= maxRetries) && maxRetries != -1) {
- if(parent.isCancelled()) {
- onFailure(new
FetchException(FetchException.CANCELLED));
- return;
- }
- retryCount++;
- schedule();
- return;
- }
+ if(retry()) return;
}
// :(
if(e.isFatal() || forceFatal)
@@ -524,6 +484,9 @@
case LowLevelGetException.VERIFY_FAILED:
onFailure(new
FetchException(FetchException.BLOCK_DECODE_ERROR));
return;
+ case LowLevelGetException.CANCELLED:
+ onFailure(new FetchException(FetchException.CANCELLED));
+ return;
default:
Logger.error(this, "Unknown LowLevelGetException code:
"+e.code);
onFailure(new
FetchException(FetchException.INTERNAL_ERROR));
@@ -531,48 +494,120 @@
}
}
+ public void schedule() {
+ if(!dontTellClientGet)
+ ((ClientGetter)parent).currentState = this;
+ super.schedule();
+ }
+
public Object getToken() {
return token;
}
- public synchronized void cancel() {
- cancelled = true;
+ public boolean ignoreStore() {
+ return ctx.ignoreStore;
}
- public boolean isFinished() {
- return cancelled;
+ public ClientGetter getParent() {
+ return (ClientGetter) parent;
}
- /** Do the request, blocking. Called by RequestStarter. */
- public void send(Node node) {
- if(cancelled) {
- onFailure(new FetchException(FetchException.CANCELLED),
false);
- return;
+ public static ClientGetState create(ClientGetter parent,
GetCompletionCallback cb, ClientMetadata clientMetadata, FreenetURI uri,
FetcherContext ctx, ArchiveContext actx, int maxRetries, int recursionLevel,
boolean dontTellClientGet, Object token, boolean isEssential, Bucket
returnBucket) throws MalformedURLException, FetchException {
+ BaseClientKey key = ClientKey.getBaseKey(uri);
+ if(key instanceof ClientKey)
+ return new SingleFileFetcher(parent, cb,
clientMetadata, (ClientKey)key, uri.listMetaStrings(), ctx, actx, maxRetries,
recursionLevel, dontTellClientGet, token, isEssential, returnBucket);
+ else {
+ return uskCreate(parent, cb, clientMetadata, (USK)key,
uri.listMetaStrings(), ctx, actx, maxRetries, recursionLevel,
dontTellClientGet, token, isEssential, returnBucket);
}
- // Do we need to support the last 3?
- ClientKeyBlock block;
- try {
- block = node.realGetKey(key, ctx.localRequestOnly,
ctx.cacheLocalRequests, ctx.ignoreStore);
- } catch (LowLevelGetException e) {
- onFailure(e);
- return;
- } catch (Throwable t) {
- onFailure(new
LowLevelGetException(LowLevelGetException.INTERNAL_ERROR));
- return;
- }
- onSuccess(block, false);
}
- public Object getClient() {
- return parent.getClient();
+ private static ClientGetState uskCreate(ClientGetter parent,
GetCompletionCallback cb, ClientMetadata clientMetadata, USK usk, LinkedList
metaStrings, FetcherContext ctx, ArchiveContext actx, int maxRetries, int
recursionLevel, boolean dontTellClientGet, Object token, boolean isEssential,
Bucket returnBucket) throws FetchException {
+ if(usk.suggestedEdition > 0) {
+ // Return the latest known version but at least
suggestedEdition.
+ long edition = ctx.uskManager.lookup(usk);
+ if(edition <= usk.suggestedEdition) {
+ // Transition to SingleFileFetcher
+ GetCompletionCallback myCB =
+ new USKProxyCompletionCallback(usk,
ctx.uskManager, cb);
+ // Want to update the latest known good iff the
fetch succeeds.
+ SingleFileFetcher sf =
+ new SingleFileFetcher(parent, myCB,
clientMetadata, usk.getSSK(usk.suggestedEdition),
+ metaStrings, ctx, actx,
maxRetries, recursionLevel, dontTellClientGet,
+ token, false,
returnBucket);
+ sf.schedule();
+ // Background fetch
+ USKFetcher fetcher =
+ ctx.uskManager.getFetcher(usk, ctx,
parent);
+ return sf;
+ } else {
+ cb.onFailure(new
FetchException(FetchException.PERMANENT_REDIRECT, usk.copy(edition).getURI()),
null);
+ return null;
+ }
+ } else {
+ // Do a thorough, blocking search
+ USKFetcher fetcher =
+
ctx.uskManager.getFetcher(usk.copy(-usk.suggestedEdition), ctx, parent);
+ if(isEssential)
+ parent.addMustSucceedBlocks(1);
+ fetcher.addCallback(new MyUSKFetcherCallback(parent,
cb, clientMetadata, usk, metaStrings, ctx, actx, maxRetries, recursionLevel,
dontTellClientGet, token, returnBucket));
+ return fetcher;
+ }
}
- public boolean ignoreStore() {
- return ctx.ignoreStore;
- }
+ public static class MyUSKFetcherCallback implements USKFetcherCallback {
- public ClientRequester getClientRequest() {
- return parent;
+ final ClientGetter parent;
+ final GetCompletionCallback cb;
+ final ClientMetadata clientMetadata;
+ final USK usk;
+ final LinkedList metaStrings;
+ final FetcherContext ctx;
+ final ArchiveContext actx;
+ final int maxRetries;
+ final int recursionLevel;
+ final boolean dontTellClientGet;
+ final Object token;
+ final Bucket returnBucket;
+
+ public MyUSKFetcherCallback(ClientGetter parent,
GetCompletionCallback cb, ClientMetadata clientMetadata, USK usk, LinkedList
metaStrings, FetcherContext ctx, ArchiveContext actx, int maxRetries, int
recursionLevel, boolean dontTellClientGet, Object token, Bucket returnBucket) {
+ this.parent = parent;
+ this.cb = cb;
+ this.clientMetadata = clientMetadata;
+ this.usk = usk;
+ this.metaStrings = metaStrings;
+ this.ctx = ctx;
+ this.actx = actx;
+ this.maxRetries = maxRetries;
+ this.recursionLevel = recursionLevel;
+ this.dontTellClientGet = dontTellClientGet;
+ this.token = token;
+ this.returnBucket = returnBucket;
+ }
+
+ public void onFoundEdition(long l) {
+ ClientSSK key = usk.getSSK(l);
+ try {
+ if(l == Math.abs(usk.suggestedEdition)) {
+ SingleFileFetcher sf = new
SingleFileFetcher(parent, cb, clientMetadata, key, metaStrings,
+ ctx, actx, maxRetries,
recursionLevel+1, dontTellClientGet,
+ token, false,
returnBucket);
+ sf.schedule();
+ } else {
+ cb.onFailure(new
FetchException(FetchException.PERMANENT_REDIRECT, usk.copy(l).getURI()), null);
+ }
+ } catch (FetchException e) {
+ cb.onFailure(e, null);
+ }
+ }
+
+ public void onFailure() {
+ cb.onFailure(new
FetchException(FetchException.DATA_NOT_FOUND, "No USK found"), null);
+ }
+
+ public void onCancelled() {
+ cb.onFailure(new
FetchException(FetchException.CANCELLED, (String)null), null);
+ }
+
}
-}
+}
\ No newline at end of file
Modified: trunk/freenet/src/freenet/client/async/SplitFileFetcher.java
===================================================================
--- trunk/freenet/src/freenet/client/async/SplitFileFetcher.java
2006-03-24 01:34:27 UTC (rev 8293)
+++ trunk/freenet/src/freenet/client/async/SplitFileFetcher.java
2006-03-24 02:29:37 UTC (rev 8294)
@@ -11,7 +11,6 @@
import freenet.client.FetcherContext;
import freenet.client.Metadata;
import freenet.client.MetadataParseException;
-import freenet.client.events.SplitfileProgressEvent;
import freenet.keys.FreenetURI;
import freenet.keys.NodeCHK;
import freenet.support.Bucket;
@@ -24,7 +23,7 @@
* Fetch a splitfile, decompress it if need be, and return it to the
GetCompletionCallback.
* Most of the work is done by the segments, and we do not need a thread.
*/
-public class SplitFileFetcher extends ClientGetState {
+public class SplitFileFetcher implements ClientGetState {
final FetcherContext fetchContext;
final ArchiveContext archiveContext;
Modified: trunk/freenet/src/freenet/client/async/SplitFileFetcherSegment.java
===================================================================
--- trunk/freenet/src/freenet/client/async/SplitFileFetcherSegment.java
2006-03-24 01:34:27 UTC (rev 8293)
+++ trunk/freenet/src/freenet/client/async/SplitFileFetcherSegment.java
2006-03-24 02:29:37 UTC (rev 8294)
@@ -321,13 +321,19 @@
public void schedule() {
try {
for(int i=0;i<dataBlocks.length;i++) {
+ // FIXME maybe within a non-FECced splitfile at
least?
+ if(dataBlocks[i].getKeyType().equals("USK"))
+ fail(new
FetchException(FetchException.INVALID_METADATA, "Cannot have USKs within a
splitfile!"));
dataBlockStatus[i] =
- new
SingleFileFetcher(parentFetcher.parent, this, null, dataBlocks[i],
blockFetchContext, archiveContext, blockFetchContext.maxSplitfileBlockRetries,
recursionLevel, true, new Integer(i), true, null);
+ (SingleFileFetcher)
SingleFileFetcher.create(parentFetcher.parent, this, null, dataBlocks[i],
blockFetchContext, archiveContext, blockFetchContext.maxSplitfileBlockRetries,
recursionLevel, true, new Integer(i), true, null);
dataBlockStatus[i].schedule();
}
for(int i=0;i<checkBlocks.length;i++) {
+ // FIXME maybe within a non-FECced splitfile at
least?
+ if(checkBlocks[i].getKeyType().equals("USK"))
+ fail(new
FetchException(FetchException.INVALID_METADATA, "Cannot have USKs within a
splitfile!"));
checkBlockStatus[i] =
- new
SingleFileFetcher(parentFetcher.parent, this, null, checkBlocks[i],
blockFetchContext, archiveContext, blockFetchContext.maxSplitfileBlockRetries,
recursionLevel, true, new Integer(dataBlocks.length+i), false, null);
+ (SingleFileFetcher)
SingleFileFetcher.create(parentFetcher.parent, this, null, checkBlocks[i],
blockFetchContext, archiveContext, blockFetchContext.maxSplitfileBlockRetries,
recursionLevel, true, new Integer(dataBlocks.length+i), false, null);
checkBlockStatus[i].schedule();
}
} catch (MalformedURLException e) {
Added: trunk/freenet/src/freenet/client/async/USKChecker.java
===================================================================
--- trunk/freenet/src/freenet/client/async/USKChecker.java 2006-03-24
01:34:27 UTC (rev 8293)
+++ trunk/freenet/src/freenet/client/async/USKChecker.java 2006-03-24
02:29:37 UTC (rev 8294)
@@ -0,0 +1,76 @@
+package freenet.client.async;
+
+import freenet.client.FetcherContext;
+import freenet.keys.ClientKey;
+import freenet.keys.ClientKeyBlock;
+import freenet.node.LowLevelGetException;
+import freenet.support.Logger;
+
+/**
+ * Checks a single USK slot.
+ */
+class USKChecker extends BaseSingleFileFetcher {
+
+ final USKCheckerCallback cb;
+ private int dnfs;
+
+ USKChecker(USKCheckerCallback cb, ClientKey key, int maxRetries,
FetcherContext ctx, ClientRequester parent) {
+ super(key, maxRetries, ctx, parent);
+ Logger.minor(this, "Created USKChecker for "+key);
+ this.cb = cb;
+ }
+
+ public void onSuccess(ClientKeyBlock block, boolean fromStore) {
+ cb.onSuccess();
+ }
+
+ public void onFailure(LowLevelGetException e) {
+ Logger.minor(this, "onFailure: "+e+" for "+this);
+ // Firstly, can we retry?
+ boolean canRetry;
+ switch(e.code) {
+ case LowLevelGetException.CANCELLED:
+ case LowLevelGetException.DECODE_FAILED:
+ // Cannot retry
+ canRetry = false;
+ break;
+ case LowLevelGetException.DATA_NOT_FOUND:
+ case LowLevelGetException.DATA_NOT_FOUND_IN_STORE:
+ dnfs++;
+ case LowLevelGetException.INTERNAL_ERROR:
+ case LowLevelGetException.REJECTED_OVERLOAD:
+ case LowLevelGetException.ROUTE_NOT_FOUND:
+ case LowLevelGetException.TRANSFER_FAILED:
+ case LowLevelGetException.VERIFY_FAILED:
+ // Can retry
+ canRetry = true;
+ break;
+ default:
+ Logger.error(this, "Unknown low-level fetch error code:
"+e.code, new Exception("error"));
+ canRetry = true;
+ }
+
+ if(canRetry && retry()) return;
+
+ // Ran out of retries.
+
+ switch(e.code) {
+ case LowLevelGetException.CANCELLED:
+ cb.onCancelled();
+ return;
+ case LowLevelGetException.DECODE_FAILED:
+ cb.onFatalAuthorError();
+ return;
+ }
+ // Rest are non-fatal. If have DNFs, DNF, else network error.
+ if(dnfs > 0)
+ cb.onDNF();
+ else
+ cb.onNetworkError();
+ }
+
+ public String toString() {
+ return "USKChecker for "+key.getURI();
+ }
+
+}
Added: trunk/freenet/src/freenet/client/async/USKCheckerCallback.java
===================================================================
--- trunk/freenet/src/freenet/client/async/USKCheckerCallback.java
2006-03-24 01:34:27 UTC (rev 8293)
+++ trunk/freenet/src/freenet/client/async/USKCheckerCallback.java
2006-03-24 02:29:37 UTC (rev 8294)
@@ -0,0 +1,23 @@
+package freenet.client.async;
+
+/**
+ * Callback for a USKChecker
+ */
+interface USKCheckerCallback {
+
+ /** Data Not Found */
+ public void onDNF();
+
+ /** Successfully found the latest version of the key */
+ public void onSuccess();
+
+ /** Error committed by author */
+ public void onFatalAuthorError();
+
+ /** Network on our node or on nodes we have been talking to */
+ public void onNetworkError();
+
+ /** Request cancelled */
+ public void onCancelled();
+
+}
Added: trunk/freenet/src/freenet/client/async/USKFetcher.java
===================================================================
--- trunk/freenet/src/freenet/client/async/USKFetcher.java 2006-03-24
01:34:27 UTC (rev 8293)
+++ trunk/freenet/src/freenet/client/async/USKFetcher.java 2006-03-24
02:29:37 UTC (rev 8294)
@@ -0,0 +1,310 @@
+package freenet.client.async;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Vector;
+
+import freenet.client.ArchiveContext;
+import freenet.client.ClientMetadata;
+import freenet.client.FetchException;
+import freenet.client.FetcherContext;
+import freenet.keys.FreenetURI;
+import freenet.keys.USK;
+import freenet.support.Bucket;
+import freenet.support.Logger;
+
+/**
+ *
+ * On 0.7, this shouldn't however take more than 10 seconds or so; these are
SSKs
+ * we are talking about. If this is fast enough then people will use the "-"
form.
+ *
+ * Fproxy should cause USKs with negative edition numbers to be redirected to
USKs
+ * with negative edition numbers.
+ *
+ * If the number specified is up to date, we just do the fetch. If a more
recent
+ * USK can be found, then we fail with an exception with the new version. The
+ * client is expected to redirect to this. The point here is that fproxy will
then
+ * have the correct number in the location bar, so that if the user copies the
URL,
+ * it will keep the edition number hint.
+ *
+ * A positive number fetch triggers a background fetch.
+ *
+ * This class does both background fetches and negative number fetches.
+ *
+ * It does them in the same way.
+ *
+ * There is one USKFetcher for a given USK, at most. They are registered on the
+ * USKManager. They have a list of ClientGetState-implementing callbacks; these
+ * are all triggered on completion.
+ *
+ * When a new suggestedEdition is added, if that is later than the
+ * currently searched-for version, the USKFetcher will try to fetch that as
well
+ * as its current pointer.
+ *
+ * Current algorithm:
+ * - Fetch the next 5 editions all at once.
+ * - If we have four consecutive editions with DNF, and no later pending
fetches,
+ * then we finish with the last known good version. (There are details
relating
+ * to other error codes handled below in the relevant method).
+ * - We immediately update the USKManager if we successfully fetch an edition.
+ * - If a new, higher suggestion comes in, that is also fetched.
+ *
+ * Future extensions:
+ * - Binary search.
+ * - Hierarchical DBRs.
+ * - TUKs (when we have TUKs).
+ * - Passive requests (when we have passive requests).
+ */
+public class USKFetcher implements ClientGetState {
+
+ /** USK manager */
+ private final USKManager uskManager;
+
+ /** The USK to fetch */
+ private final USK origUSK;
+
+ /** Callbacks */
+ private final LinkedList callbacks;
+
+ /** Fetcher context */
+ final FetcherContext ctx;
+
+ /** Finished? */
+ private boolean completed;
+
+ /** Cancelled? */
+ private boolean cancelled;
+
+ final ClientGetter parent;
+
+ public synchronized boolean addCallback(USKFetcherCallback cb) {
+ if(completed) return false;
+ callbacks.add(cb);
+ return true;
+ }
+
+ class USKAttempt implements USKCheckerCallback {
+ /** Edition number */
+ long number;
+ /** Attempt to fetch that edition number (or null if the fetch
has finished) */
+ USKChecker checker;
+ /** Successful fetch? */
+ boolean succeeded;
+ /** DNF? */
+ boolean dnf;
+ public USKAttempt(long i) {
+ this.number = i;
+ this.succeeded = false;
+ this.dnf = false;
+ this.checker = new USKChecker(this, origUSK.getSSK(i),
ctx.maxNonSplitfileRetries, ctx, parent);
+ }
+ public void onDNF() {
+ checker = null;
+ dnf = true;
+ USKFetcher.this.onDNF(this);
+ }
+ public void onSuccess() {
+ checker = null;
+ succeeded = true;
+ USKFetcher.this.onSuccess(this, false);
+ }
+
+ public void onFatalAuthorError() {
+ checker = null;
+ // Counts as success except it doesn't update
+ USKFetcher.this.onSuccess(this, true);
+ }
+
+ public void onNetworkError() {
+ checker = null;
+ // Not a DNF
+ USKFetcher.this.onFail(this);
+ }
+
+ public void onCancelled() {
+ checker = null;
+ USKFetcher.this.onCancelled(this);
+ }
+
+ public void cancel() {
+ if(checker != null) checker.cancel();
+ }
+
+ public void schedule() {
+ if(checker == null)
+ Logger.error(this, "Checker == null in
schedule()", new Exception("error"));
+ else
+ checker.schedule();
+ }
+
+ public String toString() {
+ return "USKAttempt for "+number+" for
"+origUSK.getURI();
+ }
+ }
+
+ private final Vector runningAttempts;
+
+ private long lastFetchedEdition;
+ private long lastAddedEdition;
+
+ static final long MIN_FAILURES = 3;
+
+ USKFetcher(USK origUSK, USKManager manager, FetcherContext ctx,
ClientGetter parent) {
+ this.parent = parent;
+ this.origUSK = origUSK;
+ this.uskManager = manager;
+ runningAttempts = new Vector();
+ callbacks = new LinkedList();
+ lastFetchedEdition = -1;
+ lastAddedEdition = -1;
+ this.ctx = ctx;
+ }
+
+ void onDNF(USKAttempt att) {
+ Logger.minor(this, "DNF: "+att);
+ boolean finished = false;
+ synchronized(this) {
+ lastFetchedEdition = Math.max(lastFetchedEdition,
att.number);
+ runningAttempts.remove(att);
+ if(runningAttempts.isEmpty()) {
+ long curLatest = uskManager.lookup(origUSK);
+ Logger.minor(this, "latest: "+curLatest+", last
fetched: "+lastFetchedEdition+", curLatest+MIN_FAILURES:
"+(curLatest+MIN_FAILURES));
+ if(curLatest + MIN_FAILURES >=
lastFetchedEdition) {
+ finished = true;
+ }
+ } else
+ Logger.minor(this, "Remaining:
"+runningAttempts.size());
+ }
+ if(finished) {
+ finishSuccess();
+ }
+ }
+
+ private void finishSuccess() {
+ long ed = uskManager.lookup(origUSK);
+ USKFetcherCallback[] cb;
+ synchronized(this) {
+ completed = true;
+ cb = (USKFetcherCallback[]) callbacks.toArray(new
USKFetcherCallback[callbacks.size()]);
+ }
+ for(int i=0;i<cb.length;i++)
+ cb[i].onFoundEdition(ed);
+ }
+
+ void onSuccess(USKAttempt att, boolean dontUpdate) {
+ LinkedList l = null;
+ synchronized(this) {
+ runningAttempts.remove(att);
+ long curLatest = att.number;
+ if(!dontUpdate)
+ uskManager.update(origUSK, curLatest);
+ curLatest = Math.max(uskManager.lookup(origUSK),
curLatest);
+ Logger.minor(this, "Latest: "+curLatest);
+ long addTo = curLatest + MIN_FAILURES;
+ long addFrom = Math.max(lastAddedEdition + 1, curLatest
+ 1);
+ if(addTo >= addFrom) {
+ l = new LinkedList();
+ for(long i=addFrom;i<=addTo;i++)
+ l.add(add(i));
+ }
+ cancelBefore(curLatest);
+ }
+ if(l == null) return;
+ else {
+ for(Iterator i=l.iterator();i.hasNext();) {
+ USKAttempt a = (USKAttempt) i.next();
+ a.schedule();
+ }
+ }
+ }
+
+ public void onCancelled(USKAttempt att) {
+ synchronized(this) {
+ runningAttempts.remove(att);
+ if(!runningAttempts.isEmpty()) return;
+ }
+ if(cancelled)
+ finishCancelled();
+ }
+
+ private void finishCancelled() {
+ USKFetcherCallback[] cb;
+ synchronized(this) {
+ completed = true;
+ cb = (USKFetcherCallback[]) callbacks.toArray(new
USKCheckerCallback[callbacks.size()]);
+ }
+ for(int i=0;i<cb.length;i++)
+ cb[i].onCancelled();
+ }
+
+ public void onFail(USKAttempt attempt) {
+ // FIXME what else can we do?
+ // Certainly we don't want to continue fetching indefinitely...
+ // ... e.g. RNFs don't indicate we should try a later slot,
none of them
+ // really do.
+ onDNF(attempt);
+ }
+
+ private synchronized void cancelBefore(long curLatest) {
+ for(Iterator i=runningAttempts.iterator();i.hasNext();) {
+ USKAttempt att = (USKAttempt) (i.next());
+ if(att.number < curLatest)
+ att.cancel();
+ }
+ }
+
+ /**
+ * Add a USKAttempt for another edition number.
+ * Caller is responsible for calling .schedule().
+ */
+ private synchronized USKAttempt add(long i) {
+ Logger.minor(this, "Adding USKAttempt for "+i+" for
"+origUSK.getURI());
+ if(!runningAttempts.isEmpty()) {
+ USKAttempt last = (USKAttempt)
runningAttempts.lastElement();
+ if(last.number > i)
+ throw new IllegalStateException("Adding "+i+"
but last was "+last.number);
+ }
+ USKAttempt a = new USKAttempt(i);
+ runningAttempts.add(a);
+ lastAddedEdition = i;
+ return a;
+ }
+
+ public FreenetURI getURI() {
+ return origUSK.getURI();
+ }
+
+ public boolean isFinished() {
+ return completed || cancelled;
+ }
+
+ public USK getOriginalUSK() {
+ return origUSK;
+ }
+
+ public ClientGetter getParent() {
+ return parent;
+ }
+
+ public void schedule() {
+ USKAttempt[] attempts;
+ synchronized(this) {
+ for(long
i=origUSK.suggestedEdition;i<origUSK.suggestedEdition+MIN_FAILURES;i++)
+ add(i);
+ attempts = (USKAttempt[]) runningAttempts.toArray(new
USKAttempt[runningAttempts.size()]);
+ }
+ for(int i=0;i<attempts.length;i++)
+ attempts[i].schedule();
+ }
+
+ public void cancel() {
+ USKAttempt[] attempts;
+ synchronized(this) {
+ cancelled = true;
+ attempts = (USKAttempt[]) runningAttempts.toArray(new
USKAttempt[runningAttempts.size()]);
+ }
+ for(int i=0;i<attempts.length;i++)
+ attempts[i].cancel();
+ }
+
+}
Added: trunk/freenet/src/freenet/client/async/USKFetcherCallback.java
===================================================================
--- trunk/freenet/src/freenet/client/async/USKFetcherCallback.java
2006-03-24 01:34:27 UTC (rev 8293)
+++ trunk/freenet/src/freenet/client/async/USKFetcherCallback.java
2006-03-24 02:29:37 UTC (rev 8294)
@@ -0,0 +1,18 @@
+package freenet.client.async;
+
+/**
+ * Callback interface for USK fetches. If you submit a USK fetch via
+ * USKManager.getFetcher, then register yourself on it as a listener, then you
+ * must implement these callback methods.
+ */
+public interface USKFetcherCallback {
+
+ /** Found the latest edition */
+ void onFoundEdition(long l);
+
+ /** Failed to find any edition at all (later than or equal to the
specified hint) */
+ void onFailure();
+
+ void onCancelled();
+
+}
Added: trunk/freenet/src/freenet/client/async/USKManager.java
===================================================================
--- trunk/freenet/src/freenet/client/async/USKManager.java 2006-03-24
01:34:27 UTC (rev 8293)
+++ trunk/freenet/src/freenet/client/async/USKManager.java 2006-03-24
02:29:37 UTC (rev 8294)
@@ -0,0 +1,75 @@
+package freenet.client.async;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+
+import freenet.client.ArchiveContext;
+import freenet.client.ClientMetadata;
+import freenet.client.FetcherContext;
+import freenet.keys.ClientSSK;
+import freenet.keys.USK;
+import freenet.support.Bucket;
+import freenet.support.Logger;
+
+/**
+ * Tracks the latest version of every known USK.
+ * Also does auto-updates.
+ */
+public class USKManager {
+
+ /** Latest version by blanked-edition-number USK */
+ final HashMap latestVersionByClearUSK;
+
+ /** USKFetcher's by USK. USK includes suggested edition number, so
there is one
+ * USKFetcher for each {USK, edition number}. */
+ final HashMap fetchersByUSK;
+
+ /** USKChecker's by USK. Deleted immediately on completion. */
+ final HashMap checkersByUSK;
+
+ public USKManager() {
+ latestVersionByClearUSK = new HashMap();
+ fetchersByUSK = new HashMap();
+ checkersByUSK = new HashMap();
+ }
+
+ /**
+ * Look up the latest known version of the given USK.
+ * @return The latest known edition number, or -1.
+ */
+ public synchronized long lookup(USK usk) {
+ Long l = (Long) latestVersionByClearUSK.get(usk.clearCopy());
+ if(l != null)
+ return l.longValue();
+ else return -1;
+ }
+
+ public synchronized USKFetcher getFetcher(USK usk, FetcherContext ctx,
+ ClientGetter parent) {
+ USKFetcher f = (USKFetcher) fetchersByUSK.get(usk);
+ if(f != null) {
+ if(f.parent.priorityClass == parent.priorityClass &&
f.ctx.equals(ctx))
+ return f;
+ }
+ f = new USKFetcher(usk, this, ctx, parent);
+ fetchersByUSK.put(usk, f);
+ return f;
+ }
+
+ synchronized void finished(USKFetcher f) {
+ USK u = f.getOriginalUSK();
+ fetchersByUSK.remove(u);
+ }
+
+ synchronized void update(USK origUSK, long number) {
+ Logger.minor(this, "Updating "+origUSK.getURI()+" : "+number);
+ USK clear = origUSK.clearCopy();
+ Long l = (Long) latestVersionByClearUSK.get(clear);
+ Logger.minor(this, "Old value: "+l);
+ if(!(l != null && l.longValue() > number)) {
+ l = new Long(number);
+ latestVersionByClearUSK.put(clear, l);
+ Logger.minor(this, "Put "+number);
+ }
+ }
+}
Added: trunk/freenet/src/freenet/client/async/USKProxyCompletionCallback.java
===================================================================
--- trunk/freenet/src/freenet/client/async/USKProxyCompletionCallback.java
2006-03-24 01:34:27 UTC (rev 8293)
+++ trunk/freenet/src/freenet/client/async/USKProxyCompletionCallback.java
2006-03-24 02:29:37 UTC (rev 8294)
@@ -0,0 +1,32 @@
+package freenet.client.async;
+
+import freenet.client.FetchException;
+import freenet.client.FetchResult;
+import freenet.keys.USK;
+
+public class USKProxyCompletionCallback implements GetCompletionCallback {
+
+ final USK usk;
+ final USKManager uskManager;
+ final GetCompletionCallback cb;
+
+ public USKProxyCompletionCallback(USK usk, USKManager um,
GetCompletionCallback cb) {
+ this.usk = usk;
+ this.uskManager = um;
+ this.cb = cb;
+ }
+
+ public void onSuccess(FetchResult result, ClientGetState state) {
+ uskManager.update(usk, usk.suggestedEdition);
+ cb.onSuccess(result, state);
+ }
+
+ public void onFailure(FetchException e, ClientGetState state) {
+ cb.onFailure(e, state);
+ }
+
+ public void onBlockSetFinished(ClientGetState state) {
+ cb.onBlockSetFinished(state);
+ }
+
+}
Added: trunk/freenet/src/freenet/keys/BaseClientKey.java
===================================================================
--- trunk/freenet/src/freenet/keys/BaseClientKey.java 2006-03-24 01:34:27 UTC
(rev 8293)
+++ trunk/freenet/src/freenet/keys/BaseClientKey.java 2006-03-24 02:29:37 UTC
(rev 8294)
@@ -0,0 +1,26 @@
+package freenet.keys;
+
+import java.net.MalformedURLException;
+
+/**
+ * Anything that a Node can fetch.
+ * Base class of ClientKey; non-ClientKey subclasses are things like USKs,
which
+ * don't directly translate to a routing key.
+ */
+public abstract class BaseClientKey {
+
+ public static BaseClientKey getBaseKey(FreenetURI origURI) throws
MalformedURLException {
+ if(origURI.getKeyType().equals("CHK"))
+ return new ClientCHK(origURI);
+ if(origURI.getKeyType().equals("SSK"))
+ return new ClientSSK(origURI);
+ if(origURI.getKeyType().equals("KSK"))
+ return ClientKSK.create(origURI.getDocName());
+ if(origURI.getKeyType().equals("USK"))
+ return new USK(origURI);
+ throw new UnsupportedOperationException("Unknown keytype from
"+origURI);
+ }
+
+ public abstract FreenetURI getURI();
+
+}
Modified: trunk/freenet/src/freenet/keys/ClientKey.java
===================================================================
--- trunk/freenet/src/freenet/keys/ClientKey.java 2006-03-24 01:34:27 UTC
(rev 8293)
+++ trunk/freenet/src/freenet/keys/ClientKey.java 2006-03-24 02:29:37 UTC
(rev 8294)
@@ -6,20 +6,8 @@
* Base class for client keys.
* Client keys are decodable. Node keys are not.
*/
-public abstract class ClientKey {
+public abstract class ClientKey extends BaseClientKey {
- public static ClientKey getBaseKey(FreenetURI origURI) throws
MalformedURLException {
- if(origURI.getKeyType().equals("CHK"))
- return new ClientCHK(origURI);
- if(origURI.getKeyType().equals("SSK"))
- return new ClientSSK(origURI);
- if(origURI.getKeyType().equals("KSK"))
- return ClientKSK.create(origURI.getDocName());
- throw new UnsupportedOperationException("Unknown keytype from
"+origURI);
- }
-
- public abstract FreenetURI getURI();
-
/**
* @return a NodeCHK corresponding to this key. Basically keep the
* routingKey and lose everything else.
Modified: trunk/freenet/src/freenet/keys/FreenetURI.java
===================================================================
--- trunk/freenet/src/freenet/keys/FreenetURI.java 2006-03-24 01:34:27 UTC
(rev 8293)
+++ trunk/freenet/src/freenet/keys/FreenetURI.java 2006-03-24 02:29:37 UTC
(rev 8294)
@@ -61,6 +61,7 @@
private String keyType, docName;
private String[] metaStr;
private byte[] routingKey, cryptoKey, extra;
+ private long suggestedEdition; // for USKs
public Object clone() {
return new FreenetURI(this);
@@ -159,12 +160,23 @@
sv.addElement(s);
URI = URI.substring(0, slash2);
}
- if("SSK".equals(keyType)) {
+ boolean b = false;
+ if("SSK".equals(keyType) || (b="USK".equals(keyType))) {
// docName not necessary, nor is it supported, for CHKs.
if(sv.isEmpty())
throw new MalformedURLException("No docname");
docName = (String) sv.remove(sv.size()-1);
+ if(b) {
+ if(sv.isEmpty()) throw new
MalformedURLException("No suggested edition number for USK");
+ try {
+ suggestedEdition =
Long.parseLong((String)sv.remove(sv.size()-1));
+ } catch (NumberFormatException e) {
+ MalformedURLException e1 = new
MalformedURLException("Invalid suggested edition: "+e);
+ e1.initCause(e);
+ throw e1;
+ }
+ }
}
if (!sv.isEmpty()) {
@@ -201,6 +213,15 @@
}
}
+ /** USK constructor from components. */
+ public FreenetURI(byte[] pubKeyHash, byte[] cryptoKey2, String
siteName, long suggestedEdition2) {
+ this.keyType = "USK";
+ this.routingKey = pubKeyHash;
+ this.cryptoKey = cryptoKey2;
+ this.docName = siteName;
+ this.suggestedEdition = suggestedEdition2;
+ }
+
public void decompose() {
String r = routingKey == null ? "none" :
HexUtil.bytesToHex(routingKey);
String k = cryptoKey == null ? "none" :
HexUtil.bytesToHex(cryptoKey);
@@ -356,6 +377,10 @@
if (docName != null)
b.append(docName);
+ if(keyType.equals("USK")) {
+ b.append('/');
+ b.append(suggestedEdition);
+ }
if (metaStr != null) {
for (int i = 0; i < metaStr.length; i++) {
b.append("/").append(metaStr[i]);
@@ -384,6 +409,7 @@
static final byte CHK = 1;
static final byte SSK = 2;
static final byte KSK = 3;
+ static final byte USK = 4;
public static FreenetURI readFullBinaryKeyWithLength(DataInputStream
dis) throws IOException {
int len = dis.readShort();
@@ -472,6 +498,8 @@
dos.writeByte(SSK);
} else if(keyType.equals("KSK")) {
dos.writeByte(KSK);
+ } else if(keyType.equals("USK")) {
+ throw new MalformedURLException("Cannot write USKs as
binary keys");
} else
throw new MalformedURLException("Cannot write key of
type "+keyType+" - do not know how");
if(!keyType.equals("KSK")) {
@@ -496,4 +524,11 @@
} else
dos.writeInt(0);
}
+
+ /** Get suggested edition. Only valid for USKs. */
+ public long getSuggestedEdition() {
+ if(keyType.equals("USK"))
+ return suggestedEdition;
+ else throw new IllegalArgumentException("Not a USK requesting
suggested edition");
+ }
}
Added: trunk/freenet/src/freenet/keys/USK.java
===================================================================
--- trunk/freenet/src/freenet/keys/USK.java 2006-03-24 01:34:27 UTC (rev
8293)
+++ trunk/freenet/src/freenet/keys/USK.java 2006-03-24 02:29:37 UTC (rev
8294)
@@ -0,0 +1,102 @@
+package freenet.keys;
+
+import java.net.MalformedURLException;
+import java.util.Arrays;
+
+import freenet.support.Fields;
+import freenet.support.Logger;
+
+/**
+ * Updatable Subspace Key.
+ * Not really a ClientKey as it cannot be directly requested.
+ *
+ * Contains:
+ * - Enough information to produce a real SSK.
+ * - Site name.
+ * - Site edition number.
+ */
+public class USK extends BaseClientKey {
+
+ /* The character to separate the site name from the edition number in
its SSK form.
+ * I chose "-", because it makes it ludicrously easy to go from the USK
form to the
+ * SSK form, and we don't need to go vice versa.
+ */
+ private static final String SEPARATOR = "-";
+ /** Public key hash */
+ public final byte[] pubKeyHash;
+ /** Encryption key */
+ public final byte[] cryptoKey;
+ // Extra must be verified on creation, and is fixed for now. FIXME if
it becomes changeable, need to keep values here.
+
+ public final String siteName;
+ public final long suggestedEdition;
+
+ private final int hashCode;
+
+ public USK(byte[] pubKeyHash, byte[] cryptoKey, byte[] extra, String
siteName, long suggestedEdition) throws MalformedURLException {
+ this.pubKeyHash = pubKeyHash;
+ this.cryptoKey = cryptoKey;
+ this.siteName = siteName;
+ this.suggestedEdition = suggestedEdition;
+ if(!Arrays.equals(extra, ClientSSK.getExtraBytes()))
+ throw new MalformedURLException("Invalid extra bytes");
+ if(pubKeyHash.length != NodeSSK.PUBKEY_HASH_SIZE)
+ throw new MalformedURLException("Pubkey hash wrong
length: "+pubKeyHash.length+" should be "+NodeSSK.PUBKEY_HASH_SIZE);
+ if(cryptoKey.length != ClientSSK.CRYPTO_KEY_LENGTH)
+ throw new MalformedURLException("Decryption key wrong
length: "+cryptoKey.length+" should be "+ClientSSK.CRYPTO_KEY_LENGTH);
+ hashCode = Fields.hashCode(pubKeyHash) ^
Fields.hashCode(cryptoKey) ^
+ siteName.hashCode() ^ (int)suggestedEdition ^
(int)(suggestedEdition >> 32);
+ }
+
+ public USK(FreenetURI uri) throws MalformedURLException {
+ this(uri.getRoutingKey(), uri.getCryptoKey(), uri.getExtra(),
uri.getDocName(), uri.getSuggestedEdition());
+ }
+
+ private USK(byte[] pubKeyHash2, byte[] cryptoKey2, String siteName2,
long suggestedEdition2) {
+ this.pubKeyHash = pubKeyHash2;
+ this.cryptoKey = cryptoKey2;
+ this.siteName = siteName2;
+ this.suggestedEdition = suggestedEdition2;
+ hashCode = Fields.hashCode(pubKeyHash) ^
Fields.hashCode(cryptoKey) ^
+ siteName.hashCode() ^ (int)suggestedEdition ^
(int)(suggestedEdition >> 32);
+ }
+
+ public FreenetURI getURI() {
+ return new FreenetURI(pubKeyHash, cryptoKey, siteName,
suggestedEdition);
+ }
+
+ public ClientSSK getSSK(long ver) {
+ try {
+ return new ClientSSK(siteName + SEPARATOR + ver,
pubKeyHash, ClientSSK.getExtraBytes(), null, cryptoKey);
+ } catch (MalformedURLException e) {
+ Logger.error(this, "Caught "+e+" should not be possible
in USK.getSSK", e);
+ throw new Error(e);
+ }
+ }
+
+ public USK copy(long edition) {
+ if(suggestedEdition == edition) return this;
+ return new USK(pubKeyHash, cryptoKey, siteName, edition);
+ }
+
+ public USK clearCopy() {
+ return copy(0);
+ }
+
+ public boolean equals(Object o) {
+ if(o instanceof USK) {
+ USK u = (USK)o;
+ if(!Arrays.equals(pubKeyHash, u.pubKeyHash)) return
false;
+ if(!Arrays.equals(cryptoKey, u.cryptoKey)) return false;
+ if(!siteName.equals(u.siteName)) return false;
+ if(suggestedEdition != u.suggestedEdition) return false;
+ return true;
+ }
+ return false;
+ }
+
+ public int hashCode() {
+ return hashCode;
+ }
+
+}
Modified: trunk/freenet/src/freenet/node/LowLevelGetException.java
===================================================================
--- trunk/freenet/src/freenet/node/LowLevelGetException.java 2006-03-24
01:34:27 UTC (rev 8293)
+++ trunk/freenet/src/freenet/node/LowLevelGetException.java 2006-03-24
02:29:37 UTC (rev 8294)
@@ -24,6 +24,8 @@
/** Data successfully transferred, but was not valid (at the node key
level
* i.e. before decode) */
public static final int VERIFY_FAILED = 8;
+ /** Request cancelled by user */
+ public static final int CANCELLED = 9;
static final String getMessage(int reason) {
switch(reason) {
@@ -43,6 +45,8 @@
return "Started to transfer data, then failed (should
be rare)";
case VERIFY_FAILED:
return "Node sent us invalid data";
+ case CANCELLED:
+ return "Request cancelled";
default:
return "Unknown error code: "+reason;
}
Modified: trunk/freenet/src/freenet/node/Node.java
===================================================================
--- trunk/freenet/src/freenet/node/Node.java 2006-03-24 01:34:27 UTC (rev
8293)
+++ trunk/freenet/src/freenet/node/Node.java 2006-03-24 02:29:37 UTC (rev
8294)
@@ -29,6 +29,7 @@
import freenet.client.HighLevelSimpleClient;
import freenet.client.HighLevelSimpleClientImpl;
import freenet.client.async.ClientRequestScheduler;
+import freenet.client.async.USKManager;
import freenet.clients.http.FproxyToadlet;
import freenet.clients.http.SimpleToadletServer;
import freenet.config.Config;
@@ -232,6 +233,7 @@
public final long startupTime;
// Client stuff
+ public final USKManager uskManager;
final ArchiveManager archiveManager;
public final BucketFactory tempBucketFactory;
final RequestThrottle chkRequestThrottle;
@@ -452,6 +454,7 @@
private Node(Config config, RandomSource random) throws NodeInitException {
// Easy stuff
+ uskManager = new USKManager();
startupTime = System.currentTimeMillis();
recentlyCompletedIDs = new LRUQueue();
this.config = config;
Modified: trunk/freenet/src/freenet/node/TextModeClientInterface.java
===================================================================
--- trunk/freenet/src/freenet/node/TextModeClientInterface.java 2006-03-24
01:34:27 UTC (rev 8293)
+++ trunk/freenet/src/freenet/node/TextModeClientInterface.java 2006-03-24
02:29:37 UTC (rev 8294)
@@ -216,10 +216,12 @@
outsb.append("Data:\n");
outsb.append(new String(dataBytes));
} catch (FetchException e) {
- outsb.append("Error: "+e.getMessage());
+ outsb.append("Error: "+e.getMessage()+"\n");
if(e.getMode() == e.SPLITFILE_ERROR && e.errorCodes != null) {
outsb.append(e.errorCodes.toVerboseString());
}
+ if(e.newURI != null)
+ outsb.append("Permanent redirect: "+e.newURI+"\n");
}
} else if(uline.startsWith("GETFILE:")) {
// Should have a key next
@@ -281,6 +283,8 @@
if(e.getMode() == e.SPLITFILE_ERROR && e.errorCodes != null) {
outsb.append(e.errorCodes.toVerboseString());
}
+ if(e.newURI != null)
+ outsb.append("Permanent redirect: "+e.newURI+"\n");
}
} else if(uline.startsWith("QUIT")) {
n.exit();
Modified: trunk/freenet/src/freenet/node/Version.java
===================================================================
--- trunk/freenet/src/freenet/node/Version.java 2006-03-24 01:34:27 UTC (rev
8293)
+++ trunk/freenet/src/freenet/node/Version.java 2006-03-24 02:29:37 UTC (rev
8294)
@@ -20,7 +20,7 @@
public static final String protocolVersion = "1.0";
/** The build number of the current revision */
- private static final int buildNumber = 556;
+ private static final int buildNumber = 557;
/** Oldest build of Fred we will talk to */
private static final int lastGoodBuild = 507;