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;


Reply via email to