Author: toad
Date: 2006-02-18 22:12:47 +0000 (Sat, 18 Feb 2006)
New Revision: 8064

Added:
   branches/config/src/freenet/config/
   branches/config/src/freenet/config/BooleanCallback.java
   branches/config/src/freenet/config/BooleanOption.java
   branches/config/src/freenet/config/Config.java
   branches/config/src/freenet/config/FilePersistentConfig.java
   branches/config/src/freenet/config/IntCallback.java
   branches/config/src/freenet/config/IntOption.java
   branches/config/src/freenet/config/InvalidConfigValueException.java
   branches/config/src/freenet/config/LongCallback.java
   branches/config/src/freenet/config/LongOption.java
   branches/config/src/freenet/config/Option.java
   branches/config/src/freenet/config/OptionFormatException.java
   branches/config/src/freenet/config/ShortCallback.java
   branches/config/src/freenet/config/ShortOption.java
   branches/config/src/freenet/config/StringCallback.java
   branches/config/src/freenet/config/StringOption.java
   branches/config/src/freenet/config/SubConfig.java
   branches/config/src/freenet/node/LoggingConfigHandler.java
Modified:
   branches/config/src/freenet/clients/http/FproxyToadlet.java
   branches/config/src/freenet/clients/http/SimpleToadletServer.java
   branches/config/src/freenet/io/comm/UdpSocketManager.java
   branches/config/src/freenet/io/xfer/BlockTransmitter.java
   branches/config/src/freenet/node/LocationManager.java
   branches/config/src/freenet/node/Node.java
   branches/config/src/freenet/node/PeerManager.java
   branches/config/src/freenet/node/PeerNode.java
   branches/config/src/freenet/node/StaticSwapRequestInterval.java
   branches/config/src/freenet/node/SwapRequestInterval.java
   branches/config/src/freenet/node/TestnetHandler.java
   branches/config/src/freenet/node/TextModeClientInterface.java
   branches/config/src/freenet/node/fcp/AllDataMessage.java
   branches/config/src/freenet/node/fcp/ClientGetMessage.java
   branches/config/src/freenet/node/fcp/ClientHelloMessage.java
   branches/config/src/freenet/node/fcp/ClientPutMessage.java
   branches/config/src/freenet/node/fcp/DataFoundMessage.java
   branches/config/src/freenet/node/fcp/FCPConnectionInputHandler.java
   branches/config/src/freenet/node/fcp/FCPServer.java
   branches/config/src/freenet/node/fcp/FinishedCompressionMessage.java
   branches/config/src/freenet/node/fcp/GenerateSSKMessage.java
   branches/config/src/freenet/node/fcp/GetFailedMessage.java
   branches/config/src/freenet/node/fcp/IdentifierCollisionMessage.java
   branches/config/src/freenet/node/fcp/NodeHelloMessage.java
   branches/config/src/freenet/node/fcp/ProtocolErrorMessage.java
   branches/config/src/freenet/node/fcp/PutFailedMessage.java
   branches/config/src/freenet/node/fcp/PutSuccessfulMessage.java
   branches/config/src/freenet/node/fcp/SSKKeypairMessage.java
   branches/config/src/freenet/node/fcp/SimpleProgressMessage.java
   branches/config/src/freenet/node/fcp/StartedCompressionMessage.java
   branches/config/src/freenet/node/fcp/URIGeneratedMessage.java
   branches/config/src/freenet/store/BerkeleyDBFreenetStore.java
   branches/config/src/freenet/store/FreenetStore.java
   branches/config/src/freenet/support/Fields.java
   branches/config/src/freenet/support/FileLoggerHook.java
   branches/config/src/freenet/support/Logger.java
   branches/config/src/freenet/support/LoggerHook.java
   branches/config/src/freenet/support/SimpleFieldSet.java
   branches/config/src/snmplib/SNMPStarter.java
Log:
Config. sorry folks, will fix this next week.
feel free to hack at it but please email me when you start working on it.
it really is 99.9% implemented now.

Modified: branches/config/src/freenet/clients/http/FproxyToadlet.java
===================================================================
--- branches/config/src/freenet/clients/http/FproxyToadlet.java 2006-02-18 
22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/clients/http/FproxyToadlet.java 2006-02-18 
22:12:47 UTC (rev 8064)
@@ -9,7 +9,13 @@
 import freenet.client.FetchException;
 import freenet.client.FetchResult;
 import freenet.client.HighLevelSimpleClient;
+import freenet.config.BooleanCallback;
+import freenet.config.Config;
+import freenet.config.InvalidConfigValueException;
+import freenet.config.SubConfig;
 import freenet.keys.FreenetURI;
+import freenet.node.Node;
+import freenet.node.RequestStarter;
 import freenet.support.Bucket;
 import freenet.support.HTMLEncoder;
 import freenet.support.Logger;
@@ -63,4 +69,48 @@
                this.writeReply(ctx, 200, "text/html", "OK", notSupported);
        }

+
+       static class FproxyEnabledCallback implements BooleanCallback {
+               
+               final Node node;
+               
+               public boolean get() {
+                       
+                       // TODO Auto-generated method stub
+                       return false;
+               }
+               public void set(boolean val) throws InvalidConfigValueException 
{
+                       // TODO Auto-generated method stub
+                       
+               }
+       }
+       
+       public static void maybeCreateFproxyEtc(Node node, Config config) {
+               
+               SubConfig fproxyConfig = new SubConfig("fproxy", config);
+               
+               fproxyConfig.register("enabled", true, 1, true, "Enable 
fproxy?", "Whether to enable fproxy and related HTTP services", 
+                               new BooleanCallback() {
+                                       public boolean get() {
+                                               
+                                               // TODO Auto-generated method 
stub
+                                               return false;
+                                       }
+                                       public void set(boolean val) throws 
InvalidConfigValueException {
+                                               // TODO Auto-generated method 
stub
+                                               
+                                       }
+               });
+               
+        SimpleToadletServer server = new SimpleToadletServer(+2000);
+        FproxyToadlet fproxy = new 
FproxyToadlet(makeClient(RequestStarter.INTERACTIVE_PRIORITY_CLASS));
+        server.register(fproxy, "/", false);
+        System.out.println("Starting fproxy on port "+(portNumber+2000));
+
+               // TODO Auto-generated method stub
+               
+       }
+
+
+       
 }

Modified: branches/config/src/freenet/clients/http/SimpleToadletServer.java
===================================================================
--- branches/config/src/freenet/clients/http/SimpleToadletServer.java   
2006-02-18 22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/clients/http/SimpleToadletServer.java   
2006-02-18 22:12:47 UTC (rev 8064)
@@ -9,8 +9,15 @@
 import java.util.Iterator;
 import java.util.LinkedList;

+import freenet.config.BooleanCallback;
+import freenet.config.Config;
+import freenet.config.InvalidConfigValueException;
+import freenet.config.SubConfig;
+import freenet.node.Node;
+import freenet.node.RequestStarter;
 import freenet.support.FileLoggerHook;
 import freenet.support.Logger;
+import freenet.support.FileLoggerHook.IntervalParseException;

 public class SimpleToadletServer implements ToadletContainer, Runnable {

@@ -54,7 +61,7 @@
                return null;
        }

-       public static void main(String[] args) throws IOException {
+       public static void main(String[] args) throws IOException, 
IntervalParseException {
         File logDir = new File("logs-toadlettest");
         logDir.mkdir();
         FileLoggerHook logger = new FileLoggerHook(true, new File(logDir, 
"test-1111").getAbsolutePath(), 

Added: branches/config/src/freenet/config/BooleanCallback.java
===================================================================
--- branches/config/src/freenet/config/BooleanCallback.java     2006-02-18 
22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/config/BooleanCallback.java     2006-02-18 
22:12:47 UTC (rev 8064)
@@ -0,0 +1,22 @@
+package freenet.config;
+
+/**
+ * A callback to be called when a config value of integer type changes.
+ * Also reports the current value.
+ */
+public interface BooleanCallback {
+       
+       /**
+        * Get the current, used value of the config variable.
+        */
+       boolean get();
+       
+       /**
+        * Set the config variable to a new value.
+        * @param val The new value.
+        * @throws InvalidConfigOptionException If the new value is invalid for 
+        * this particular option.
+        */
+       void set(boolean val) throws InvalidConfigValueException;
+
+}

Added: branches/config/src/freenet/config/BooleanOption.java
===================================================================
--- branches/config/src/freenet/config/BooleanOption.java       2006-02-18 
22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/config/BooleanOption.java       2006-02-18 
22:12:47 UTC (rev 8064)
@@ -0,0 +1,43 @@
+package freenet.config;
+
+public class BooleanOption extends Option {
+       
+       final boolean defaultValue;
+       final BooleanCallback cb;
+       private boolean currentValue;
+       
+       public BooleanOption(SubConfig conf, String optionName, boolean 
defaultValue, int sortOrder, 
+                       boolean expert, String shortDesc, String longDesc, 
BooleanCallback cb) {
+               super(conf, optionName, sortOrder, expert, shortDesc, longDesc);
+               this.defaultValue = defaultValue;
+               this.cb = cb;
+               this.currentValue = defaultValue;
+       }
+
+       /** Get the current value. This is the value in use if we have finished
+        * initialization, otherwise it is the value set at startup (possibly 
the default). */
+       public boolean getValue() {
+               if(config.hasFinishedInitialization())
+                       return currentValue = cb.get();
+               else return currentValue;
+       }
+
+       public void setValue(String val) throws InvalidConfigValueException {
+               if(val.equalsIgnoreCase("true") || val.equalsIgnoreCase("yes")) 
{
+                       set(true);
+               } else if(val.equalsIgnoreCase("false") || 
val.equalsIgnoreCase("no")) {
+                       set(false);
+               } else
+                       throw new OptionFormatException("Unrecognized boolean: 
"+val);
+       }
+       
+       public void set(boolean b) throws InvalidConfigValueException {
+               cb.set(b);
+               currentValue = b;
+       }
+       
+       public String getValueString() {
+               return Boolean.toString(getValue());
+       }
+       
+}

Added: branches/config/src/freenet/config/Config.java
===================================================================
--- branches/config/src/freenet/config/Config.java      2006-02-18 22:08:10 UTC 
(rev 8063)
+++ branches/config/src/freenet/config/Config.java      2006-02-18 22:12:47 UTC 
(rev 8064)
@@ -0,0 +1,29 @@
+package freenet.config;
+
+import java.util.HashMap;
+
+/** Global configuration object for a node. SubConfig's register here.
+ * Handles writing to a file etc.
+ */
+public class Config {
+
+       protected final HashMap configsByPrefix;
+       
+       public Config() {
+               configsByPrefix = new HashMap();
+       }
+       
+       public void register(SubConfig sc) {
+               synchronized(this) {
+                       if(configsByPrefix.containsKey(sc.prefix))
+                               throw new IllegalArgumentException("Already 
registered "+sc.prefix+": "+sc);
+                       configsByPrefix.put(sc.prefix, sc);
+               }
+       }
+       
+       /** Write current config to disk */
+       public void store() {
+               // Do nothing
+       }
+
+}

Added: branches/config/src/freenet/config/FilePersistentConfig.java
===================================================================
--- branches/config/src/freenet/config/FilePersistentConfig.java        
2006-02-18 22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/config/FilePersistentConfig.java        
2006-02-18 22:12:47 UTC (rev 8064)
@@ -0,0 +1,99 @@
+package freenet.config;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.Iterator;
+
+import freenet.support.Logger;
+import freenet.support.SimpleFieldSet;
+import freenet.support.io.LineReadingInputStream;
+
+/**
+ * Global Config object which persists to a file.
+ * 
+ * Reads the config file into a SimpleFieldSet when created.
+ * During init, SubConfig's are registered, and fed the relevant parts of the 
SFS.
+ * Once initialization has finished, we check whether there are any options 
remaining.
+ * If so, we complain about them.
+ * And then we write the config file back out.
+ */
+public class FilePersistentConfig extends Config {
+
+       final File filename;
+       final File tempFilename;
+       private SimpleFieldSet origConfigFileContents;
+       
+       public FilePersistentConfig(File f) throws IOException {
+               this.filename = f;
+               this.tempFilename = new File(f.getPath()+".tmp");
+               if(f.exists()) {
+                       if(!f.canWrite()) {
+                               Logger.error(this, "Warning: Cannot write to 
config file!");
+                               System.err.println("Warning: Cannot write to 
config file.");
+                       }
+                       if(f.canRead()) {
+                               try {
+                                       initialLoad();
+                               } catch (FileNotFoundException e) {
+                                       System.err.println("No config file 
found, creating new: "+f);
+                               } // Other IOE's indicate a more serious 
problem.
+                       } else {
+                               throw new IOException("Cannot read config 
file");
+                       }
+               } else {
+                       System.err.println("No config file found, creating new: 
"+f);
+               }
+       }
+
+       /** Load the config file into a SimpleFieldSet. 
+        * @throws IOException */
+       private void initialLoad() throws IOException {
+               FileInputStream fis = new FileInputStream(filename);
+               LineReadingInputStream lis = new LineReadingInputStream(fis);
+               origConfigFileContents = new SimpleFieldSet(lis, 4096, 256, 
true);
+       }
+       
+       public void register(SubConfig sc) {
+               super.register(sc);
+               SimpleFieldSet sfs = origConfigFileContents.subset(sc.prefix);
+               // Set all the options
+               sc.setOptions(sfs);
+       }
+       
+       /**
+        * Finished initialization. So any remaining options must be invalid.
+        */
+       public void finishedInit() {
+               Iterator i = origConfigFileContents.keyIterator();
+               while(i.hasNext()) {
+                       String key = (String) i.next();
+                       Logger.error(this, "Unknown option: "+key+" 
(value="+origConfigFileContents.get(key));
+               }
+       }
+       
+       public void innerStore() throws IOException {
+               SimpleFieldSet fs = exportFieldSet();
+               FileOutputStream fos = new FileOutputStream(tempFilename);
+               fs.writeTo(new BufferedWriter(new OutputStreamWriter(fos)));
+               fos.close();
+               tempFilename.renameTo(filename);
+       }
+
+       private SimpleFieldSet exportFieldSet() {
+               SimpleFieldSet fs = new SimpleFieldSet(true);
+               SubConfig[] configs;
+               synchronized(this) {
+                       configs = (SubConfig[]) 
configsByPrefix.values().toArray(new SubConfig[configsByPrefix.size()]);
+               }
+               for(int i=0;i<configs.length;i++) {
+                       SimpleFieldSet scfs = configs[i].exportFieldSet();
+                       fs.put(configs[i].prefix, scfs);
+               }
+               return fs;
+       }
+}

Added: branches/config/src/freenet/config/IntCallback.java
===================================================================
--- branches/config/src/freenet/config/IntCallback.java 2006-02-18 22:08:10 UTC 
(rev 8063)
+++ branches/config/src/freenet/config/IntCallback.java 2006-02-18 22:12:47 UTC 
(rev 8064)
@@ -0,0 +1,22 @@
+package freenet.config;
+
+/**
+ * A callback to be called when a config value of integer type changes.
+ * Also reports the current value.
+ */
+public interface IntCallback {
+
+       /**
+        * Get the current, used value of the config variable.
+        */
+       int get();
+       
+       /**
+        * Set the config variable to a new value.
+        * @param val The new value.
+        * @throws InvalidConfigOptionException If the new value is invalid for 
+        * this particular option.
+        */
+       void set(int val) throws InvalidConfigValueException;
+       
+}

Added: branches/config/src/freenet/config/IntOption.java
===================================================================
--- branches/config/src/freenet/config/IntOption.java   2006-02-18 22:08:10 UTC 
(rev 8063)
+++ branches/config/src/freenet/config/IntOption.java   2006-02-18 22:12:47 UTC 
(rev 8064)
@@ -0,0 +1,57 @@
+package freenet.config;
+
+import freenet.support.Fields;
+
+/** Integer config variable */
+public class IntOption extends Option {
+
+       final int defaultValue;
+       final IntCallback cb;
+       private int currentValue;
+       // Cache it mostly so that we can keep SI units
+       private String cachedStringValue;
+       
+       public IntOption(SubConfig conf, String optionName, int defaultValue, 
String defaultValueString,
+                       int sortOrder, boolean expert, String shortDesc, String 
longDesc, IntCallback cb) {
+               super(conf, optionName, sortOrder, expert, shortDesc, longDesc);
+               this.defaultValue = defaultValue;
+               this.cb = cb;
+               this.currentValue = defaultValue;
+               this.cachedStringValue = defaultValueString;
+       }
+
+       public IntOption(SubConfig conf, String optionName, String 
defaultValueString,
+                       int sortOrder, boolean expert, String shortDesc, String 
longDesc, IntCallback cb) {
+               super(conf, optionName, sortOrder, expert, shortDesc, longDesc);
+               this.defaultValue = Fields.parseInt(defaultValueString);
+               this.cb = cb;
+               this.currentValue = defaultValue;
+               this.cachedStringValue = defaultValueString;
+       }
+
+       /** Get the current value. This is the value in use if we have finished
+        * initialization, otherwise it is the value set at startup (possibly 
the default). */
+       public int getValue() {
+               if(config.hasFinishedInitialization()) {
+                       int val = cb.get();
+                       if(currentValue != val) {
+                               currentValue = val;
+                               cachedStringValue = null;
+                       }
+               }
+               return currentValue;
+       }
+
+       public void setValue(String val) throws InvalidConfigValueException {
+               int x = Fields.parseInt(val);
+               cb.set(x);
+               cachedStringValue = val;
+               currentValue = x;
+       }
+
+       public String getValueString() {
+               if(cachedStringValue != null) return cachedStringValue;
+               return Integer.toString(getValue());
+       }
+       
+}

Added: branches/config/src/freenet/config/InvalidConfigValueException.java
===================================================================
--- branches/config/src/freenet/config/InvalidConfigValueException.java 
2006-02-18 22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/config/InvalidConfigValueException.java 
2006-02-18 22:12:47 UTC (rev 8064)
@@ -0,0 +1,16 @@
+package freenet.config;
+
+/**
+ * Thrown when the node refuses to set a config variable to a particular
+ * value because it is invalid. Just because this is not thrown does not
+ * necessarily mean that there are no problems with the value defined,
+ * it merely means that there are no immediately detectable problems with 
+ * it.
+ */
+public class InvalidConfigValueException extends Exception {
+
+       public InvalidConfigValueException(String msg) {
+               super(msg);
+       }
+
+}

Added: branches/config/src/freenet/config/LongCallback.java
===================================================================
--- branches/config/src/freenet/config/LongCallback.java        2006-02-18 
22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/config/LongCallback.java        2006-02-18 
22:12:47 UTC (rev 8064)
@@ -0,0 +1,22 @@
+package freenet.config;
+
+/**
+ * A callback to be called when a config value of long type changes.
+ * Also reports the current value.
+ */
+public interface LongCallback {
+
+       /**
+        * Get the current, used value of the config variable.
+        */
+       long get();
+       
+       /**
+        * Set the config variable to a new value.
+        * @param val The new value.
+        * @throws InvalidConfigOptionException If the new value is invalid for 
+        * this particular option.
+        */
+       void set(long val) throws InvalidConfigValueException;
+       
+}

Added: branches/config/src/freenet/config/LongOption.java
===================================================================
--- branches/config/src/freenet/config/LongOption.java  2006-02-18 22:08:10 UTC 
(rev 8063)
+++ branches/config/src/freenet/config/LongOption.java  2006-02-18 22:12:47 UTC 
(rev 8064)
@@ -0,0 +1,57 @@
+package freenet.config;
+
+import freenet.support.Fields;
+
+/** Long config variable */
+public class LongOption extends Option {
+
+       final long defaultValue;
+       final LongCallback cb;
+       private long currentValue;
+       // Cache it mostly so that we can keep SI units
+       private String cachedStringValue;
+
+       public LongOption(SubConfig conf, String optionName, long defaultValue, 
String defaultValueString, 
+                       int sortOrder, boolean expert, String shortDesc, String 
longDesc, LongCallback cb) {
+               super(conf, optionName, sortOrder, expert, shortDesc, longDesc);
+               this.defaultValue = defaultValue;
+               this.cb = cb;
+               this.currentValue = defaultValue;
+               this.cachedStringValue = defaultValueString;
+       }
+       
+       public LongOption(SubConfig conf, String optionName, String 
defaultValueString, 
+                       int sortOrder, boolean expert, String shortDesc, String 
longDesc, LongCallback cb) {
+               super(conf, optionName, sortOrder, expert, shortDesc, longDesc);
+               this.defaultValue = Fields.parseLong(defaultValueString);
+               this.cb = cb;
+               this.currentValue = defaultValue;
+               this.cachedStringValue = defaultValueString;
+       }
+       
+       /** Get the current value. This is the value in use if we have finished
+        * initialization, otherwise it is the value set at startup (possibly 
the default). */
+       public long getValue() {
+               if(config.hasFinishedInitialization()) {
+                       long val = cb.get();
+                       if(currentValue != val) {
+                               currentValue = val;
+                               cachedStringValue = null;
+                       }
+               }
+               return currentValue;
+       }
+       
+       public void setValue(String val) throws InvalidConfigValueException {
+               long x = Fields.parseLong(val);
+               cb.set(x);
+               cachedStringValue = val;
+               currentValue = x;
+       }
+       
+       public String getValueString() {
+               if(cachedStringValue != null) return cachedStringValue;
+               return Long.toString(getValue());
+       }
+
+}

Added: branches/config/src/freenet/config/Option.java
===================================================================
--- branches/config/src/freenet/config/Option.java      2006-02-18 22:08:10 UTC 
(rev 8063)
+++ branches/config/src/freenet/config/Option.java      2006-02-18 22:12:47 UTC 
(rev 8064)
@@ -0,0 +1,34 @@
+package freenet.config;
+
+/**
+ * A config option.
+ */
+public abstract class Option {
+
+       /** The parent SubConfig object */
+       final SubConfig config;
+       /** The option name */
+       final String name;
+       /** The sort order */
+       final int sortOrder;
+       /** Is this config variable expert-only? */
+       final boolean expert;
+       /** Short description of value e.g. "FCP port" */
+       final String shortDesc;
+       /** Long description of value e.g. "The TCP port to listen for FCP 
connections on" */
+       final String longDesc;
+       
+       Option(SubConfig config, String name, int sortOrder, boolean expert, 
String shortDesc, String longDesc) {
+               this.config = config;
+               this.name = name;
+               this.sortOrder = sortOrder;
+               this.expert = expert;
+               this.shortDesc = shortDesc;
+               this.longDesc = longDesc;
+       }
+
+       public abstract void setValue(String val) throws 
InvalidConfigValueException;
+
+       public abstract String getValueString();
+       
+}

Added: branches/config/src/freenet/config/OptionFormatException.java
===================================================================
--- branches/config/src/freenet/config/OptionFormatException.java       
2006-02-18 22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/config/OptionFormatException.java       
2006-02-18 22:12:47 UTC (rev 8064)
@@ -0,0 +1,13 @@
+package freenet.config;
+
+/**
+ * Thrown when a format error occurs, and we cannot parse the string set into 
the appropriate
+ * type.
+ */
+public class OptionFormatException extends InvalidConfigValueException {
+
+       public OptionFormatException(String msg) {
+               super(msg);
+       }
+
+}

Added: branches/config/src/freenet/config/ShortCallback.java
===================================================================
--- branches/config/src/freenet/config/ShortCallback.java       2006-02-18 
22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/config/ShortCallback.java       2006-02-18 
22:12:47 UTC (rev 8064)
@@ -0,0 +1,22 @@
+package freenet.config;
+
+/**
+ * A callback to be called when a config value of short type changes.
+ * Also reports the current value.
+ */
+public interface ShortCallback {
+
+       /**
+        * Get the current, used value of the config variable.
+        */
+       short get();
+       
+       /**
+        * Set the config variable to a new value.
+        * @param val The new value.
+        * @throws InvalidConfigOptionException If the new value is invalid for 
+        * this particular option.
+        */
+       void set(short val) throws InvalidConfigValueException;
+       
+}

Added: branches/config/src/freenet/config/ShortOption.java
===================================================================
--- branches/config/src/freenet/config/ShortOption.java 2006-02-18 22:08:10 UTC 
(rev 8063)
+++ branches/config/src/freenet/config/ShortOption.java 2006-02-18 22:12:47 UTC 
(rev 8064)
@@ -0,0 +1,37 @@
+package freenet.config;
+
+import freenet.support.Fields;
+
+public class ShortOption extends Option {
+       
+       final short defaultValue;
+       final ShortCallback cb;
+       private short currentValue;
+       
+       public ShortOption(SubConfig conf, String optionName, short 
defaultValue, int sortOrder, 
+                       boolean expert, String shortDesc, String longDesc, 
ShortCallback cb) {
+               super(conf, optionName, sortOrder, expert, shortDesc, longDesc);
+               this.defaultValue = defaultValue;
+               this.cb = cb;
+               this.currentValue = defaultValue;
+       }
+       
+       /** Get the current value. This is the value in use if we have finished
+        * initialization, otherwise it is the value set at startup (possibly 
the default). */
+       public short getValue() {
+               if(config.hasFinishedInitialization())
+                       return currentValue = cb.get();
+               else return currentValue;
+       }
+       
+       public void setValue(String val) throws InvalidConfigValueException {
+               short x = Fields.parseShort(val);
+               cb.set(x);
+               currentValue = x;
+       }
+
+       public String getValueString() {
+               return Short.toString(getValue());
+       }
+       
+}

Added: branches/config/src/freenet/config/StringCallback.java
===================================================================
--- branches/config/src/freenet/config/StringCallback.java      2006-02-18 
22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/config/StringCallback.java      2006-02-18 
22:12:47 UTC (rev 8064)
@@ -0,0 +1,19 @@
+package freenet.config;
+
+/** Callback (getter/setter) for a string config variable */
+public interface StringCallback {
+       
+       /**
+        * Get the current, used value of the config variable.
+        */
+       String get();
+
+       /**
+        * Set the config variable to a new value.
+        * @param val The new value.
+        * @throws InvalidConfigOptionException If the new value is invalid for 
+        * this particular option.
+        */
+       void set(String val) throws InvalidConfigValueException;
+       
+}

Added: branches/config/src/freenet/config/StringOption.java
===================================================================
--- branches/config/src/freenet/config/StringOption.java        2006-02-18 
22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/config/StringOption.java        2006-02-18 
22:12:47 UTC (rev 8064)
@@ -0,0 +1,34 @@
+package freenet.config;
+
+public class StringOption extends Option {
+
+       final String defaultValue;
+       final StringCallback cb;
+       private String currentValue;
+       
+       public StringOption(SubConfig conf, String optionName, String 
defaultValue, int sortOrder, 
+                       boolean expert, String shortDesc, String longDesc, 
StringCallback cb) {
+               super(conf, optionName, sortOrder, expert, shortDesc, longDesc);
+               this.defaultValue = defaultValue;
+               this.cb = cb;
+               this.currentValue = defaultValue;
+       }
+       
+       /** Get the current value. This is the value in use if we have finished
+        * initialization, otherwise it is the value set at startup (possibly 
the default). */
+       public String getValue() {
+               if(config.hasFinishedInitialization())
+                       return currentValue = cb.get();
+               else return currentValue;
+       }
+
+       public void setValue(String val) throws InvalidConfigValueException {
+               cb.set(val);
+               this.currentValue = val;
+       }
+       
+       public String getValueString() {
+               return getValue();
+       }
+       
+}

Added: branches/config/src/freenet/config/SubConfig.java
===================================================================
--- branches/config/src/freenet/config/SubConfig.java   2006-02-18 22:08:10 UTC 
(rev 8063)
+++ branches/config/src/freenet/config/SubConfig.java   2006-02-18 22:12:47 UTC 
(rev 8064)
@@ -0,0 +1,164 @@
+package freenet.config;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import freenet.support.Logger;
+import freenet.support.SimpleFieldSet;
+
+/**
+ * A specific configuration block.
+ */
+public class SubConfig {
+       
+       private final HashMap map;
+       public final Config config;
+       final String prefix;
+       private boolean hasInitialized;
+       
+       public SubConfig(String prefix, Config config) {
+               this.config = config;
+               this.prefix = prefix;
+               map = new HashMap();
+               hasInitialized = false;
+       }
+       
+       public synchronized void register(Option o) {
+               if(o.name.indexOf(SimpleFieldSet.MULTI_LEVEL_CHAR) != -1)
+                       throw new IllegalArgumentException("Option names must 
not contain "+SimpleFieldSet.MULTI_LEVEL_CHAR);
+               if(map.containsKey(o.name))
+                       throw new IllegalArgumentException("Already registered: 
"+o.name+" on "+this);
+               map.put(o.name, o);
+       }
+       
+       public void register(String optionName, int defaultValue, int sortOrder,
+                       boolean expert, String shortDesc, String longDesc, 
IntCallback cb) {
+               register(new IntOption(this, optionName, defaultValue, null, 
sortOrder, expert, shortDesc, longDesc, cb));
+       }
+       
+       public void register(String optionName, long defaultValue, int 
sortOrder,
+                       boolean expert, String shortDesc, String longDesc, 
LongCallback cb) {
+               register(new LongOption(this, optionName, defaultValue, null, 
sortOrder, expert, shortDesc, longDesc, cb));
+       }
+       
+       public void register(String optionName, String defaultValueString, int 
sortOrder,
+                       boolean expert, String shortDesc, String longDesc, 
IntCallback cb) {
+               register(new IntOption(this, optionName, defaultValueString, 
sortOrder, expert, shortDesc, longDesc, cb));
+       }
+       
+       public void register(String optionName, String defaultValueString, int 
sortOrder,
+                       boolean expert, String shortDesc, String longDesc, 
LongCallback cb) {
+               register(new LongOption(this, optionName, defaultValueString, 
sortOrder, expert, shortDesc, longDesc, cb));
+       }
+       
+       public void register(String optionName, boolean defaultValue, int 
sortOrder,
+                       boolean expert, String shortDesc, String longDesc, 
BooleanCallback cb) {
+               register(new BooleanOption(this, optionName, defaultValue, 
sortOrder, expert, shortDesc, longDesc, cb));
+       }
+       
+       public void register(String optionName, String defaultValue, int 
sortOrder,
+                       boolean expert, String shortDesc, String longDesc, 
StringCallback cb) {
+               register(new StringOption(this, optionName, defaultValue, 
sortOrder, expert, shortDesc, longDesc, cb));
+       }
+       
+       public void register(String optionName, short defaultValue, int 
sortOrder,
+                       boolean expert, String shortDesc, String longDesc, 
ShortCallback cb) {
+               register(new ShortOption(this, optionName, defaultValue, 
sortOrder, expert, shortDesc, longDesc, cb));
+       }
+       
+       public int getInt(String optionName) {
+               IntOption o;
+               synchronized(this) {
+                       o = (IntOption) map.get(optionName);
+               }
+               return o.getValue();
+       }
+       
+       public long getLong(String optionName) {
+               LongOption o;
+               synchronized(this) {
+                       o = (LongOption) map.get(optionName);
+               }
+               return o.getValue();
+       }
+       
+       public boolean getBoolean(String optionName) {
+               BooleanOption o;
+               synchronized(this) {
+                       o = (BooleanOption) map.get(optionName);
+               }
+               return o.getValue();
+       }
+       
+       public String getString(String optionName) {
+               StringOption o;
+               synchronized(this) {
+                       o = (StringOption) map.get(optionName);
+               }
+               return o.getValue();
+       }
+
+       public short getShort(String optionName) {
+               ShortOption o;
+               synchronized(this) {
+                       o = (ShortOption) map.get(optionName);
+               }
+               return o.getValue();
+       }
+       
+       /**
+        * Has the object we are attached to finished initialization?
+        */
+       public boolean hasFinishedInitialization() {
+               return hasInitialized;
+       }
+
+       /**
+        * Called when the object we are attached to has finished init.
+        * After this point, the callbacks are authoritative for values of
+        * config variables, and will be called when values are changed by
+        * the user.
+        */
+       public void finishedInitialization() {
+               hasInitialized = true;
+       }
+
+       /**
+        * Set options from a SimpleFieldSet. Once we process an option, we 
must remove it.
+        */
+       public void setOptions(SimpleFieldSet sfs) {
+               Set entrySet = map.entrySet();
+               Iterator i = entrySet.iterator();
+               while(i.hasNext()) {
+                       Map.Entry entry = (Map.Entry) i.next();
+                       String key = (String) entry.getKey();
+                       Option o = (Option) entry.getValue();
+                       String val = sfs.get(key);
+                       if(val != null) {
+                               try {
+                                       o.setValue(val);
+                               } catch (InvalidConfigValueException e) {
+                                       String msg = "Invalid config value: 
"+prefix+SimpleFieldSet.MULTI_LEVEL_CHAR+key+" = "+val+" : error: "+e;
+                                       Logger.error(this, msg, e);
+                                       System.err.println(msg); // might be 
about logging?
+                               }
+                       }
+               }
+       }
+
+       public SimpleFieldSet exportFieldSet() {
+               SimpleFieldSet fs = new SimpleFieldSet(true);
+               Set entrySet = map.entrySet();
+               Iterator i = entrySet.iterator();
+               while(i.hasNext()) {
+                       Map.Entry entry = (Map.Entry) i.next();
+                       String key = (String) entry.getKey();
+                       Option o = (Option) entry.getValue();
+                       fs.put(key, o.getValueString());
+               }
+               return fs;
+       }
+
+}

Modified: branches/config/src/freenet/io/comm/UdpSocketManager.java
===================================================================
--- branches/config/src/freenet/io/comm/UdpSocketManager.java   2006-02-18 
22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/io/comm/UdpSocketManager.java   2006-02-18 
22:12:47 UTC (rev 8064)
@@ -77,7 +77,6 @@

        public UdpSocketManager(int listenPort) throws SocketException {
                super("UdpSocketManager sender thread on port " + listenPort);
-               try {
                    // Keep the Updater code in, just commented out, for now
                    // We may want to be able to do on-line updates.
 //                     if (Updater.hasResource()) {
@@ -85,9 +84,6 @@
 //                     } else {
                                _sock = new DatagramSocket(listenPort);
 //                     }
-               } catch (BindException e) {
-                       Logger.fatal(UdpSocketManager.class, -1, "Couldn't 
connect to UDP port " + listenPort + ", is another instance of Dijjer 
running?");
-               }
                // Only used for debugging, no need to seed from Yarrow
                dropRandom = new Random();
        }

Modified: branches/config/src/freenet/io/xfer/BlockTransmitter.java
===================================================================
--- branches/config/src/freenet/io/xfer/BlockTransmitter.java   2006-02-18 
22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/io/xfer/BlockTransmitter.java   2006-02-18 
22:12:47 UTC (rev 8064)
@@ -52,43 +52,106 @@
        final PacketThrottle throttle;
        long timeAllSent = -1;

+       // FIXME make this stuff non-static. Have a context object for limiting.
+       
        // Static stuff for global bandwidth limiter
        /** Synchronization object for bandwidth limiting */
        static final Object lastPacketSendTimeSync = new Object();
+       // Use nanosecond long values for accuracy reasons
        /** Time at which the last known packet is scheduled to be sent.
         * We will not send another packet until at least minPacketDelay ms 
after this time. */
-       static long hardLastPacketSendTime = System.currentTimeMillis();
+       static long hardLastPacketSendTimeNSec = System.currentTimeMillis() * 
1000*1000;
        /** Minimum interval between packet sends, for overall hard bandwidth 
limiter */
-       static long minPacketDelay = 0;
+       static int minPacketDelayNSec = 0;
        /** Minimum average interval between packet sends, for averaged (soft) 
overall
         * bandwidth usage limiter. */
-       static long minSoftDelay = 0;
+       static int minSoftDelayNSec = 0;
        /** "Soft" equivalent to hardLastPacketSendTime. Can lag up to half the 
softLimitPeriod
         * behind the current time. Otherwise is similar. This gives it 
flexibility; we can have
         * spurts above the average limit, but over the softLimitPeriod, it 
will average out to 
         * the target minSoftDelay. */
-       static long softLastPacketSendTime = System.currentTimeMillis();
+       static long softLastPacketSendTimeNSec = System.currentTimeMillis() * 
1000*1000;
        /** Period over which the soft limiter should work */
-       static long softLimitPeriod;
+       static long softLimitPeriodNSec;

-       public static void setMinPacketInterval(int delay) {
+       /**
+        * Set the hard bandwidth limiter.
+        * @param bytesPerSecond The maximum number of bytes (of data blocks) 
to be sent in any
+        * one second.
+        */
+       public static void setHardBandwidthLimit(int bytesPerSecond) {
+               int newMinPacketDelayNS = 
convertBytesPerSecondToNanosPerPacket(bytesPerSecond);
                synchronized(lastPacketSendTimeSync) {
-                       minPacketDelay = delay;
+                       if(minPacketDelayNSec != newMinPacketDelayNS) {
+                               minPacketDelayNSec = newMinPacketDelayNS;
+                               hardLastPacketSendTimeNSec = 
System.currentTimeMillis() * 1000*1000;
+                       }
                }
        }
+
+       public static int convertBytesPerSecondToNanosPerPacket(int 
bytesPerSecond) {
+               if(bytesPerSecond <= 0)
+                       return 0; // no limits
+               
+               int packetSize = getPacketSize();
+               double minNanoSecondsBetweenPackets =
+                       ((1000.0*1000.0*1000.0) * packetSize) / ((double) 
bytesPerSecond);
+               int newMinPacketDelayNS = (int) minNanoSecondsBetweenPackets;
+               double inaccuracy = minNanoSecondsBetweenPackets - 
newMinPacketDelayNS;
+               double inaccuracyPercent = (inaccuracy / 
minNanoSecondsBetweenPackets) * 100.0;
+               Logger.minor(BlockTransmitter.class, "Quantization inaccuracy: 
"+inaccuracyPercent+"%");
+               return newMinPacketDelayNS;
+       }

-       public static void setSoftMinPacketInterval(int delay) {
+       public static int convertBytesPerPeriodToNanosPerPacket(int 
bytesPerSecond, long periodLengthNanos) {
+               if(bytesPerSecond <= 0)
+                       return 0; // no limits
+               
+               int packetSize = getPacketSize();
+               double minNanoSecondsBetweenPackets =
+                       (periodLengthNanos * packetSize) / ((double) 
bytesPerSecond);
+               int newMinPacketDelayNS = (int) minNanoSecondsBetweenPackets;
+               double inaccuracy = minNanoSecondsBetweenPackets - 
newMinPacketDelayNS;
+               double inaccuracyPercent = (inaccuracy / 
minNanoSecondsBetweenPackets) * 100.0;
+               Logger.minor(BlockTransmitter.class, "Quantization inaccuracy: 
"+inaccuracyPercent+"%");
+               return newMinPacketDelayNS;
+       }
+       
+       public static int convertNanosPerPacketToBytesPerSecond(int delay) {
+               if(delay == 0) return 0;
+               return (int) (((1000.0*1000.0*1000.0) * getPacketSize()) / 
((double)delay));
+       }
+
+       /** @return The average packet size for a block sent by a 
BlockTransmitter, including all 
+        * headers and protocol overhead */
+       private static int getPacketSize() {
+               // FIXME make this more accurate!
+               return 1024 + 200;
+       }
+
+       public static int getHardBandwidthLimit() {
+               int delay;
                synchronized(lastPacketSendTimeSync) {
-                       minSoftDelay = delay;
+                       delay = minPacketDelayNSec;
                }
+               return convertNanosPerPacketToBytesPerSecond(delay);
        }
-       
-       public static void setSoftLimitPeriod(long period) {
+
+       /**
+        * Set the long-term bandwidth limiter.
+        * @param bytes The number of bytes to allow at most over the period. 
(in data packets)
+        * @param period The length of time over which the limit should apply. 
(ms)
+        */
+       public static void setSoftBandwidthLimit(int bytes, long period) {
+               if(period > Long.MAX_VALUE / (1000*1000)) throw new 
IllegalArgumentException("Period too long");
+               int newSoftLimit = convertBytesPerPeriodToNanosPerPacket(bytes, 
period);
+               period = period * 1000 * 1000;
                synchronized(lastPacketSendTimeSync) {
-                       softLimitPeriod = period;
-                       long now = System.currentTimeMillis();
-                       if(now - softLastPacketSendTime > period / 2) {
-                               softLastPacketSendTime = now - (period / 2);
+                       minSoftDelayNSec = newSoftLimit;
+                       softLimitPeriodNSec = period;
+                       long nowNS = System.currentTimeMillis() * 1000 * 1000;
+                       if(nowNS - softLastPacketSendTimeNSec > period / 2) {
+                               softLastPacketSendTimeNSec = nowNS - (period / 
2);
                        }
                }
        }
@@ -149,10 +212,10 @@
                                                        
((PeerNode)_destination).reportThrottledPacketSendTime(delayTime);
                                                        
((PeerNode)_destination).sendAsync(DMT.createPacketTransmit(_uid, packetNo, 
_sentPackets, _prb.getPacket(packetNo)), null);
                                                        // May have been delays 
in sending, so update to avoid sending more frequently than allowed
-                                                       long now = 
System.currentTimeMillis();
+                                                       long nowNS = 
System.currentTimeMillis() * 1000 * 1000;
                                                        
synchronized(lastPacketSendTimeSync) {
-                                                               
if(hardLastPacketSendTime < now)
-                                                                       
hardLastPacketSendTime = now;
+                                                               
if(hardLastPacketSendTimeNSec < nowNS)
+                                                                       
hardLastPacketSendTimeNSec = nowNS;
                                                        }
                                                // We accelerate the ping rate 
during the transfer to keep a closer eye on round-trip-time
                                                sentSinceLastPing++;
@@ -185,7 +248,7 @@

                                while(true) {

-                                       long now = System.currentTimeMillis();
+                                       long nowNS = System.currentTimeMillis() 
* 1000 * 1000;

                                        long endTime = -1;

@@ -195,15 +258,18 @@
                                        synchronized(lastPacketSendTimeSync) {

                                                // Get the current time
-                                               now = 
System.currentTimeMillis();
+                                               nowNS = 
System.currentTimeMillis() * 1000 * 1000;

                                                // Update time if necessary to 
avoid spurts
-                                               if(hardLastPacketSendTime < 
(now - minPacketDelay))
-                                                       hardLastPacketSendTime 
= now - minPacketDelay;
+                                               if(hardLastPacketSendTimeNSec < 
(nowNS - minPacketDelayNSec))
+                                                       
hardLastPacketSendTimeNSec = nowNS - minPacketDelayNSec;

                                                // Wait until the next send 
window
+                                               long 
newHardLastPacketSendTimeNS =
+                                                       
hardLastPacketSendTimeNSec + minPacketDelayNSec;
+                                               
                                                long newHardLastPacketSendTime =
-                                                       hardLastPacketSendTime 
+ minPacketDelay;
+                                                       
newHardLastPacketSendTimeNS / (1000 * 1000);

                                                long earliestSendTime = 
startCycleTime + delay;

@@ -212,24 +278,32 @@
                                                        thenSend = false;
                                                        endTime = 
earliestSendTime;
                                                } else {
-                                                       hardLastPacketSendTime 
= newHardLastPacketSendTime;
-                                                       endTime = 
hardLastPacketSendTime;
+                                                       
hardLastPacketSendTimeNSec = newHardLastPacketSendTimeNS;
+                                                       endTime = 
hardLastPacketSendTimeNSec / (1000 * 1000);

                                                        // What about the soft 
limit?

-                                                       if(now - 
softLastPacketSendTime > minSoftDelay / 2) {
-                                                               
softLastPacketSendTime = now - (minSoftDelay / 2);
+                                                       // We can only 
accumulate burst traffic rights for a full period at most.
+                                                       // If we have a period 
of 1 hour, and we send no traffic in the first 30 minutes,
+                                                       // then we can use up 
our whole hour's quota in the next 30 minutes if we need to.
+                                                       // We could even use 
our entire quota in the last 5 minutes. After that, we can
+                                                       // only send at the 
limit (which may be very low), since we have no quota left.
+                                                       // However, after 1 
hour we forget our burst rights.
+                                                       if(nowNS - 
softLastPacketSendTimeNSec > softLimitPeriodNSec) {
+                                                               
softLastPacketSendTimeNSec = nowNS - (softLimitPeriodNSec);
                                                        }

-                                                       softLastPacketSendTime 
+= minSoftDelay;
+                                                       
softLastPacketSendTimeNSec += minSoftDelayNSec;

-                                                       
if(softLastPacketSendTime > hardLastPacketSendTime) {
-                                                               endTime = 
hardLastPacketSendTime = softLastPacketSendTime;
+                                                       
if(softLastPacketSendTimeNSec > hardLastPacketSendTimeNSec) {
+                                                               endTime = 
((hardLastPacketSendTimeNSec = softLastPacketSendTimeNSec) / (1000 * 1000));
                                                        }
                                                }
                                        }

-                                       while(now < endTime) {
+                                       long now = nowNS / (1000 * 1000);
+                                       
+                                       while(nowNS < endTime) {
                                                synchronized(_senderThread) {
                                                        if(_sendComplete)
                                                                return true;
@@ -244,6 +318,8 @@
                                                now = 
System.currentTimeMillis();
                                        }

+                                       nowNS = now * 1000 * 1000;
+                                       
                                        if(thenSend) return false;
                                }
                        }

Modified: branches/config/src/freenet/node/LocationManager.java
===================================================================
--- branches/config/src/freenet/node/LocationManager.java       2006-02-18 
22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/node/LocationManager.java       2006-02-18 
22:12:47 UTC (rev 8064)
@@ -99,7 +99,7 @@
                     long startTime = System.currentTimeMillis();
                     double nextRandom = r.nextDouble();
                     while(true) {
-                        double sleepTime = interval.getValue();
+                        int sleepTime = interval.getValue();
                         sleepTime *= nextRandom;
                         sleepTime = Math.min(sleepTime, Integer.MAX_VALUE);
                         long endTime = startTime + (int)sleepTime;

Added: branches/config/src/freenet/node/LoggingConfigHandler.java
===================================================================
--- branches/config/src/freenet/node/LoggingConfigHandler.java  2006-02-18 
22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/node/LoggingConfigHandler.java  2006-02-18 
22:12:47 UTC (rev 8064)
@@ -0,0 +1,278 @@
+package freenet.node;
+
+import java.io.File;
+import java.io.IOException;
+
+import freenet.config.BooleanCallback;
+import freenet.config.IntCallback;
+import freenet.config.InvalidConfigValueException;
+import freenet.config.LongCallback;
+import freenet.config.OptionFormatException;
+import freenet.config.StringCallback;
+import freenet.config.SubConfig;
+import freenet.support.FileLoggerHook;
+import freenet.support.Logger;
+import freenet.support.LoggerHook;
+import freenet.support.LoggerHookChain;
+import freenet.support.FileLoggerHook.IntervalParseException;
+
+public class LoggingConfigHandler {
+
+       protected static final String LOG_PREFIX = "freenet-";
+       private final SubConfig config;
+       private FileLoggerHook fileLoggerHook;
+       private File logDir;
+       private long maxZippedLogsSize;
+       private String logRotateInterval;
+       private long maxCachedLogBytes;
+       private int maxCachedLogLines;
+       
+       public LoggingConfigHandler(SubConfig loggingConfig) throws 
InvalidConfigValueException {
+               this.config = loggingConfig;
+       
+       loggingConfig.register("enabled", true, 1, true, "Enable logging?", 
"Set to false to completely disable logging",
+                       new BooleanCallback() {
+                                       public boolean get() {
+                                               return fileLoggerHook != null;
+                                       }
+                                       public void set(boolean val) throws 
InvalidConfigValueException {
+                                               if(val == (fileLoggerHook != 
null)) return;
+                                               if(!val) {
+                                                       disableLogger();
+                                               } else 
+                                                       enableLogger();
+                                       }
+       });
+       
+       boolean loggingEnabled = loggingConfig.getBoolean("enabled");
+       
+       loggingConfig.register("dirname", "logs", 2, true, "Logging directory", 
"Directory into which to put log files", 
+                       new StringCallback() {
+
+                                       public String get() {
+                                               return logDir.getPath();
+                                       }
+
+                                       public void set(String val) throws 
InvalidConfigValueException {
+                                               File f = new File(val);
+                                               if(f.equals(logDir)) return;
+                                               preSetLogDir(f);
+                                               // Still here
+                                               if(fileLoggerHook == null) {
+                                                       logDir = f;
+                                               } else {
+                                                       // Discard old data
+                                                       
fileLoggerHook.switchBaseFilename(f.getPath()+File.separator+LOG_PREFIX);
+                                                       logDir = f;
+                                                       new Deleter(logDir);
+                                               }
+                                       }
+       });
+       
+       logDir = new File(config.getString("dirname"));
+       if(loggingEnabled)
+                       preSetLogDir(logDir);
+       // => enableLogger must run preSetLogDir
+       
+       // max space used by zipped logs
+       
+       config.register("maxZippedLogsSize", "1G", 3, false, "Maximum disk 
space used by old logs", "Maximum disk space used by old logs",
+                       new LongCallback() {
+                                       public long get() {
+                                               return maxZippedLogsSize;
+                                       }
+                                       public void set(long val) throws 
InvalidConfigValueException {
+                                               if(val < 0) val = 0;
+                                               maxZippedLogsSize = val;
+                                               if(fileLoggerHook != null) {
+                                                       
fileLoggerHook.setMaxOldLogsSize(val);
+                                               }
+                                       }
+       });
+       
+       // priority
+       
+       // Node must override this to minor on testnet.
+       config.register("priority", "normal", 4, false, "Minimum priority to 
log messages at", "Minimum priority at which messages are logged. options are 
debug, minor, normal, error, in order of diminishing verbosity",
+                       new StringCallback() {
+                                       public String get() {
+                                               LoggerHookChain chain = 
Logger.getChain();
+                                               return 
LoggerHook.priorityOf(chain.getThreshold());
+                                       }
+                                       public void set(String val) throws 
InvalidConfigValueException {
+                                               LoggerHookChain chain = 
Logger.getChain();
+                                               try {
+                                                       chain.setThreshold(val);
+                                               } catch 
(LoggerHook.InvalidThresholdException e) {
+                                                       throw new 
OptionFormatException(e.getMessage());
+                                               }
+                                       }
+       });
+       
+       // interval
+       
+       config.register("interval", "5MINUTE", 5, true, "Log rotation 
interval", "Log rotation interval - period after which logs are rotated. We 
keep the last two log files (current and prev), plus lots of compressed 
logfiles up to maxZippedLogsSize",
+                       new StringCallback() {
+                                       public String get() {
+                                               return logRotateInterval;
+                                       }
+
+                                       public void set(String val) throws 
InvalidConfigValueException {
+                                               
if(val.equals(logRotateInterval)) return;
+                                               if(fileLoggerHook != null) {
+                                                       try {
+                                                               
fileLoggerHook.setInterval(val);
+                                                       } catch 
(FileLoggerHook.IntervalParseException e) {
+                                                               throw new 
OptionFormatException(e.getMessage());
+                                                       }
+                                               }
+                                               logRotateInterval = val;
+                                       }
+       });
+       
+       logRotateInterval = config.getString("interval");
+       
+       // max cached bytes in RAM
+       config.register("maxCachedBytes", "10M", 6, true, "Max cached log bytes 
in RAM", "Maximum number of bytes of logging cached in RAM", 
+                       new LongCallback() {
+                                       public long get() {
+                                               return maxCachedLogBytes;
+                                       }
+                                       public void set(long val) throws 
InvalidConfigValueException {
+                                               if(val < 0) val = 0;
+                                               if(val == maxCachedLogBytes) 
return;
+                                               maxCachedLogBytes = val;
+                                               if(fileLoggerHook != null)
+                                                       
fileLoggerHook.setMaxListBytes(val);
+                                       }
+       });
+       
+       maxCachedLogBytes = config.getLong("maxCachedBytes");
+       
+       // max cached lines in RAM
+       config.register("maxCachedLines", "100k", 7, true, "Max cached log 
lines in RAM", "Maximum number of lines of logging cached in RAM",
+                       new IntCallback() {
+                                       public int get() {
+                                               return maxCachedLogLines;
+                                       }
+                                       public void set(int val) throws 
InvalidConfigValueException {
+                                               if(val < 0) val = 0;
+                                               if(val == maxCachedLogLines) 
return;
+                                               maxCachedLogLines = val;
+                                               if(fileLoggerHook != null)
+                                                       
fileLoggerHook.setMaxListLength(val);
+                                       }
+       });
+       
+       maxCachedLogLines = config.getInt("maxCachedLines");
+       
+       enableLogger();
+       
+       config.finishedInitialization();
+       }
+
+       private final Object enableLoggerLock = new Object();
+       
+       /**
+        * Turn on the logger.
+        */
+       private void enableLogger() {
+               synchronized(enableLoggerLock) {
+                       if(fileLoggerHook != null) return;
+                       Logger.setupChain();
+                       FileLoggerHook hook;
+                       try {
+                               hook = 
+                                       new FileLoggerHook(true, new 
File(logDir, LOG_PREFIX).getAbsolutePath(), 
+                                               "d (c, t, p): m", "MMM dd, yyyy 
HH:mm:ss:SSS", Logger.DEBUG /* filtered by chain */, false, true, 
+                                               maxZippedLogsSize /* 1GB of old 
compressed logfiles */);
+                       } catch (IOException e) {
+                               System.err.println("CANNOT START LOGGER: 
"+e.getMessage());
+                               return;
+                       }
+                       try {
+                               hook.setInterval(logRotateInterval);
+                       } catch (IntervalParseException e) {
+                               System.err.println("INVALID LOGGING INTERVAL: 
"+e.getMessage());
+                               try {
+                                       hook.setInterval("5MINUTE");
+                               } catch (IntervalParseException e1) {
+                                       System.err.println("Impossible: 
"+e1.getMessage());
+                               }
+                       }
+                       hook.setMaxListBytes(maxCachedLogBytes);
+                       hook.setMaxListLength(maxCachedLogLines);
+                       Logger.globalAddHook(hook);
+                       hook.start();
+               }
+       }
+
+       protected void disableLogger() {
+               synchronized(enableLoggerLock) {
+                       if(fileLoggerHook == null) return;
+                       FileLoggerHook hook = fileLoggerHook;
+                       Logger.globalRemoveHook(hook);
+                       hook.close();
+                       fileLoggerHook = null;
+                       Logger.destroyChainIfEmpty();
+               }
+       }
+       
+       protected void preSetLogDir(File f) throws InvalidConfigValueException {
+               boolean exists = f.exists();
+               if(exists && !f.isDirectory())
+                       throw new InvalidConfigValueException("Cannot overwrite 
a file with a log directory");
+               if(!exists) {
+                       f.mkdir();
+                       exists = f.exists();
+                       if(!exists || !f.isDirectory())
+                               throw new InvalidConfigValueException("Cannot 
create log directory");
+               }
+       }
+       
+       class Deleter implements Runnable {
+               
+               File logDir;
+               
+               public Deleter(File logDir) {
+                       this.logDir = logDir;
+                       Thread t = new Thread(this, "Old log directory 
"+logDir+" deleter");
+                       t.setDaemon(true);
+                       t.start();
+               }
+
+               public void run() {
+                       fileLoggerHook.waitForSwitch();
+                       delete(logDir);
+               }
+
+               /** @return true if we can't delete due to presence of 
non-freenet files */
+               private boolean delete(File dir) {
+                       boolean failed = false;
+                       File[] files = dir.listFiles();
+                       for(int i=0;i<files.length;i++) {
+                               File f = files[i];
+                               String s = f.getName();
+                               if(s.startsWith("freenet-") && 
s.indexOf(".log") != -1) {
+                                       if(f.isFile()) {
+                                               if(!f.delete()) failed = true;
+                                       } else if(f.isDirectory()) {
+                                               if(delete(f)) failed = true;
+                                       }
+                               } else {
+                                       failed = true;
+                               }
+                       }
+                       if(!failed) {
+                               failed = !(dir.delete());
+                       }
+                       return failed;
+               }
+               
+       }
+
+       public FileLoggerHook getFileLoggerHook() {
+               return fileLoggerHook;
+       }
+       
+}

Modified: branches/config/src/freenet/node/Node.java
===================================================================
--- branches/config/src/freenet/node/Node.java  2006-02-18 22:08:10 UTC (rev 
8063)
+++ branches/config/src/freenet/node/Node.java  2006-02-18 22:12:47 UTC (rev 
8064)
@@ -15,6 +15,7 @@
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.OutputStreamWriter;
+import java.net.BindException;
 import java.net.InetAddress;
 import java.net.SocketException;
 import java.net.UnknownHostException;
@@ -37,6 +38,14 @@
 import freenet.client.async.ClientRequestScheduler;
 import freenet.clients.http.FproxyToadlet;
 import freenet.clients.http.SimpleToadletServer;
+import freenet.config.BooleanCallback;
+import freenet.config.Config;
+import freenet.config.FilePersistentConfig;
+import freenet.config.IntCallback;
+import freenet.config.InvalidConfigValueException;
+import freenet.config.LongCallback;
+import freenet.config.StringCallback;
+import freenet.config.SubConfig;
 import freenet.crypt.DSAPublicKey;
 import freenet.crypt.DiffieHellman;
 import freenet.crypt.RandomSource;
@@ -76,6 +85,7 @@
 import freenet.support.LRUHashtable;
 import freenet.support.LRUQueue;
 import freenet.support.Logger;
+import freenet.support.LoggerHookChain;
 import freenet.support.PaddedEphemerallyEncryptedBucketFactory;
 import freenet.support.SimpleFieldSet;
 import freenet.support.io.FilenameGenerator;
@@ -86,9 +96,19 @@
  * @author amphibian
  */
 public class Node {
-    
-       static final long serialVersionUID = -1;
+
+       /** Config object for the whole node. */
+       public final Config config;

+       // Static stuff related to logger
+       
+       /** Directory to log to */
+       static File logDir;
+       /** Maximum size of gzipped logfiles */
+       static long maxLogSize;
+       /** Log config handler */
+       static LoggingConfigHandler logConfigHandler;
+       
        /** If true, local requests and inserts aren't cached.
         * This opens up a glaring vulnerability; connected nodes
         * can then probe the store, and if the node doesn't have the
@@ -136,7 +156,18 @@

     // FIXME: abstract out address stuff? Possibly to something like 
NodeReference?
     final int portNumber;
+
+    /** Datastore directory */
+    private final File storeDir;
+
+    /** The number of bytes per key total in all the different datastores. All 
the datastores
+     * are always the same size in number of keys. */
+    static final int sizePerKey = CHKBlock.DATA_LENGTH + 
CHKBlock.TOTAL_HEADERS_LENGTH +
+               DSAPublicKey.PADDED_SIZE + SSKBlock.DATA_LENGTH + 
SSKBlock.TOTAL_HEADERS_LENGTH;

+    /** The maximum number of keys stored in each of the datastores. */
+    private long maxStoreKeys;
+    
     /** These 3 are private because must be protected by synchronized(this) */
     /** The CHK datastore */
     private final FreenetStore chkDatastore;
@@ -163,19 +194,21 @@
     String myName;
     final LocationManager lm;
     final PeerManager peers; // my peers
+    /** Directory to put node, peers, etc into */
+    final File nodeDir;
+    final File tempDir;
     public final RandomSource random; // strong RNG
     final UdpSocketManager usm;
     final FNPPacketMangler packetMangler;
     final PacketSender ps;
     final NodeDispatcher dispatcher;
     final NodePinger nodePinger;
-    final String filenamesPrefix;
     final FilenameGenerator tempFilenameGenerator;
-    final FileLoggerHook fileLoggerHook;
     static final int MAX_CACHED_KEYS = 1000;
     final LRUHashtable cachedPubKeys;
     final boolean testnetEnabled;
-    final int testnetPort;
+    final TestnetHandler testnetHandler;
+    final StaticSwapRequestInterval swapInterval;
     static short MAX_HTL = 10;
     static final int EXIT_STORE_FILE_NOT_FOUND = 1;
     static final int EXIT_STORE_IOEXCEPTION = 2;
@@ -185,7 +218,17 @@
     static final int EXIT_TEMP_INIT_ERROR = 6;
     static final int EXIT_TESTNET_FAILED = 7;
     public static final int EXIT_MAIN_LOOP_LOST = 8;
+    public static final int EXIT_COULD_NOT_BIND_USM = 9;
+    static final int EXIT_IMPOSSIBLE_USM_PORT = 10;
+    static final int EXIT_NO_AVAILABLE_UDP_PORTS = 11;
+       public static final int EXIT_TESTNET_DISABLED_NOT_SUPPORTED = 12;
+       static final int EXIT_INVALID_STORE_SIZE = 13;
+       static final int EXIT_BAD_DOWNLOADS_DIR = 14;
+       static final int EXIT_BAD_NODE_DIR = 15;
+       static final int EXIT_BAD_TEMP_DIR = 16;
+       static final int EXIT_COULD_NOT_START_FCP = 17;

+    
     public final long bootID;
     public final long startupTime;

@@ -196,11 +239,11 @@
     final RequestStarter requestStarter;
     final RequestThrottle insertThrottle;
     final RequestStarter insertStarter;
-    final File downloadDir;
-    final TestnetHandler testnetHandler;
-    final TestnetStatusUploader statusUploader;
+    File downloadDir;
     public final ClientRequestScheduler fetchScheduler;
     public final ClientRequestScheduler putScheduler;
+    TextModeClientInterface tmci;
+    FCPServer fcpServer;

     // Things that's needed to keep track of
     public final PluginManager pluginManager;
@@ -211,7 +254,7 @@
     static final long MAX_ARCHIVE_SIZE = 1024*1024; // ??? FIXME
     static final long MAX_ARCHIVED_FILE_SIZE = 1024*1024; // arbitrary... FIXME
     static final int MAX_CACHED_ELEMENTS = 1024; // equally arbitrary! FIXME 
hopefully we can cache many of these though
-    
+
     // Helpers
        public final InetAddress localhostAddress;

@@ -224,7 +267,7 @@
         FileInputStream fis = new FileInputStream(filename);
         InputStreamReader isr = new InputStreamReader(fis);
         BufferedReader br = new BufferedReader(isr);
-        SimpleFieldSet fs = new SimpleFieldSet(br);
+        SimpleFieldSet fs = new SimpleFieldSet(br, false);
         br.close();
         // Read contents
         String physical = fs.get("physical.udp");
@@ -274,18 +317,16 @@

     public void writeNodeFile() {
         try {
-            writeNodeFile(filenamesPrefix+"node-"+portNumber, 
filenamesPrefix+"node-"+portNumber+".bak");
+            writeNodeFile(new File(nodeDir, "node-"+portNumber), new 
File(nodeDir, "node-"+portNumber+".bak"));
         } catch (IOException e) {
             Logger.error(this, "Cannot write node file!: "+e+" : 
"+"node-"+portNumber);
         }
     }

-    private void writeNodeFile(String filename, String backupFilename) throws 
IOException {
+    private void writeNodeFile(File orig, File backup) throws IOException {
         SimpleFieldSet fs = exportFieldSet();
-        File orig = new File(filename);
-        File backup = new File(backupFilename);
         orig.renameTo(backup);
-        FileOutputStream fos = new FileOutputStream(filename);
+        FileOutputStream fos = new FileOutputStream(orig);
         OutputStreamWriter osr = new OutputStreamWriter(fos);
         fs.writeTo(osr);
         osr.close();
@@ -311,169 +352,429 @@
     /**
      * Read the port number from the arguments.
      * Then create a node.
+     * Anything that needs static init should ideally be in here.
      */
     public static void main(String[] args) throws IOException {
-       int length = args.length;
-       if (length < 1 || length > 3) {
-               System.out.println("Usage: $ java freenet.node.Node 
<portNumber> [ipOverride] [max data packets / second]");
+       if(args.length>1) {
+               System.out.println("Usage: $ java freenet.node.Node 
<configFile>");
+               System.out.println("We recommend you move your old 
<filename>-<portnumber> files to <filename>, otherwise the node won't use 
them!");
                return;
        }

-        int port = Integer.parseInt(args[0]);
-        System.out.println("Port number: "+port);
-        File logDir = new File("logs-"+port);
-        logDir.mkdir();
-        FileLoggerHook logger = new FileLoggerHook(true, new File(logDir, 
"freenet-"+port).getAbsolutePath(), 
-                       "d (c, t, p): m", "MMM dd, yyyy HH:mm:ss:SSS", 
Logger.MINOR, false, true, 
-                       1024*1024*1024 /* 1GB of old compressed logfiles */);
-        logger.setInterval("5MINUTES");
-        Logger.setupChain();
-        Logger.globalSetThreshold(Logger.MINOR);
-        Logger.globalAddHook(logger);
-        logger.start();
-        Logger.normal(Node.class, "Creating node...");
-        Yarrow yarrow = new Yarrow();
-        InetAddress overrideIP = null;
-        int packetsPerSecond = 15;
-        if(args.length > 1) {
-            overrideIP = InetAddress.getByName(args[1]);
-            System.err.println("Overriding IP detection: 
"+overrideIP.getHostAddress());
-            if(args.length > 2) {
-               packetsPerSecond = Integer.parseInt(args[2]);
-            }
-        }
-        DiffieHellman.init(yarrow);
-        Node n = new Node(port, yarrow, overrideIP, "", 1000 / 
packetsPerSecond, true, logger, 32768 /* 1GB */);
-        n.start(new StaticSwapRequestInterval(2000));
-        new TextModeClientInterface(n);
+       File configFilename;
+       if(args.length == 0) {
+               System.out.println("Using default config filename freenet.ini");
+               configFilename = new File("freenet.ini");
+       } else
+               configFilename = new File(args[0]);
+       
+       FilePersistentConfig cfg = new FilePersistentConfig(configFilename);
+       
+       // First, set up logging. It is global, and may be shared between 
several nodes.
+       
+       SubConfig loggingConfig = new SubConfig("logger", cfg);
+       
+       try {
+                       logConfigHandler = new 
LoggingConfigHandler(loggingConfig);
+               } catch (InvalidConfigValueException e) {
+                       System.err.println("Error: could not set up logging: 
"+e.getMessage());
+                       e.printStackTrace();
+                       return;
+               }
+       
+       // Setup RNG
+
+       RandomSource random = new Yarrow();
+       
+        DiffieHellman.init(random);
+        
         Thread t = new Thread(new MemoryChecker(), "Memory checker");
         t.setPriority(Thread.MAX_PRIORITY);
         t.start();
-        SimpleToadletServer server = new SimpleToadletServer(port+2000);
-        FproxyToadlet fproxy = new 
FproxyToadlet(n.makeClient(RequestStarter.INTERACTIVE_PRIORITY_CLASS));
-        server.register(fproxy, "/", false);
-        System.out.println("Starting fproxy on port "+(port+2000));
-        new FCPServer(port+3000, n);
-        System.out.println("Starting FCP server on port "+(port+3000));
-        SNMPAgent.setSNMPPort(port+4000);
-        System.out.println("Starting SNMP server on port "+(port+4000));
-        SNMPStarter.initialize();
-        //server.register(fproxy, "/SSK@", false);
-        //server.register(fproxy, "/KSK@", false);
+        
+       Node node;
+               try {
+                       node = new Node(cfg, random);
+               node.start(false);
+               } catch (NodeInitException e) {
+                       System.err.println("Failed to load node: 
"+e.getMessage());
+                       e.printStackTrace();
+                       System.exit(e.exitCode);
+               }
     }

-    // FIXME - the whole overrideIP thing is a hack to avoid config
-    // Implement the config!
-    Node(int port, RandomSource rand, InetAddress overrideIP, String prefix, 
int throttleInterval, boolean enableTestnet, FileLoggerHook logger, int 
maxStoreKeys) {
-       this.fileLoggerHook = logger;
+    static class NodeInitException extends Exception {
+       // One of the exit codes from above
+       public final int exitCode;
+       
+       NodeInitException(int exitCode, String msg) {
+               super(msg+" ("+exitCode+")");
+               this.exitCode = exitCode;
+       }
+    }
+
+    /**
+     * Create a Node from a Config object.
+     * @param config The Config object for this node.
+     * @param random The random number generator for this node. Passed in 
because we may want
+     * to use a non-secure RNG for e.g. one-JVM live-code simulations. Should 
be a Yarrow in
+     * a production node.
+     * @throws NodeInitException If the node initialization fails.
+     */
+    private Node(Config config, RandomSource random) throws NodeInitException {
+       
+       // Easy stuff
+        startupTime = System.currentTimeMillis();
+        recentlyCompletedIDs = new LRUQueue();
+       this.config = config;
+       this.random = random;
        cachedPubKeys = new LRUHashtable();
-       if(enableTestnet) {
-               Logger.error(this, "WARNING: ENABLING TESTNET CODE! This may 
seriously jeopardize your anonymity!");
-               testnetEnabled = true;
-               testnetPort = 1024 + (port-1024+1000) % (65536 - 1024);
-               testnetHandler = new TestnetHandler(this, testnetPort);
-               statusUploader = new TestnetStatusUploader(this, 180000);
-       } else {
-               testnetEnabled = false;
-               testnetPort = -1;
-               testnetHandler = null;
-               statusUploader = null;
-       }
+               lm = new LocationManager(random);
        try {
                        localhostAddress = InetAddress.getByName("127.0.0.1");
                } catch (UnknownHostException e3) {
                        // Does not do a reverse lookup, so this is impossible
                        throw new Error(e3);
                }
-        portNumber = port;
-        startupTime = System.currentTimeMillis();
-        recentlyCompletedIDs = new LRUQueue();
         ipDetector = new IPAddressDetector(10*1000, this);
-        if(prefix == null) prefix = "";
-        filenamesPrefix = prefix;
-        this.overrideIPAddress = overrideIP;
-        downloadDir = new File("downloads");
-        downloadDir.mkdir();
-        try {
-            chkDatastore = new 
BerkeleyDBFreenetStore(prefix+"store-"+portNumber, maxStoreKeys, 32768, 
CHKBlock.TOTAL_HEADERS_LENGTH);
-            sskDatastore = new 
BerkeleyDBFreenetStore(prefix+"sskstore-"+portNumber, maxStoreKeys, 1024, 
SSKBlock.TOTAL_HEADERS_LENGTH);
-            pubKeyDatastore = new 
BerkeleyDBFreenetStore(prefix+"pubkeystore-"+portNumber, maxStoreKeys, 
DSAPublicKey.PADDED_SIZE, 0);
-        } catch (FileNotFoundException e1) {
-            Logger.error(this, "Could not open datastore: "+e1, e1);
-            System.err.println("Could not open datastore: "+e1);
-            System.exit(EXIT_STORE_FILE_NOT_FOUND);
-            throw new Error();
-        } catch (IOException e1) {
-            Logger.error(this, "Could not open datastore: "+e1, e1);
-            System.err.println("Could not open datastore: "+e1);
-            System.exit(EXIT_STORE_IOEXCEPTION);
-            throw new Error();
-        } catch (Exception e1) {
-            Logger.error(this, "Could not open datastore: "+e1, e1);
-            System.err.println("Could not open datastore: "+e1);
-            System.exit(EXIT_STORE_OTHER);
-            throw new Error();
-        }
-        random = rand;
         requestSenders = new HashMap();
         transferringRequestSenders = new HashMap();
         insertSenders = new HashMap();
         runningUIDs = new HashSet();
+        ps = new PacketSender(this);
+        // FIXME maybe these should persist? They need to be private though, 
so after the node/peers split. (bug 51).
+        decrementAtMax = random.nextDouble() <= DECREMENT_AT_MAX_PROB;
+        decrementAtMin = random.nextDouble() <= DECREMENT_AT_MIN_PROB;
+        bootID = random.nextLong();

-        BlockTransmitter.setMinPacketInterval(throttleInterval);
+       // Setup node-specific configuration
+       
+       SubConfig nodeConfig = new SubConfig("node", config);

-        /*
-         * FIXME: test the soft limit.
-         * 
-         * The soft limit is implemented, except for:
-         * - We need to write the current status to disk every 1 minute or so.
-         * - When we start up, we need to read this in, assume that the node 
sent
-         *   as many packets as it was allowed to in the following minute, and
-         *   then shut down before writing again (worst case scenario).
-         * - We need to test the soft limit!
-         */
-        BlockTransmitter.setSoftLimitPeriod(14*24*60*60*1000);
-        BlockTransmitter.setSoftMinPacketInterval(0);
+       // IP address override
+       
+       nodeConfig.register("ipAddressOverride", "", 0, true, "IP address 
override", "IP address override (not usually needed)", new StringCallback() {
+
+                       public String get() {
+                               return Peer.getHostName(overrideIPAddress);
+                       }
+                       
+                       public void set(String val) throws 
InvalidConfigValueException {
+                               // FIXME do we need to tell anyone?
+                               if(val.length() == 0) {
+                                       // Set to null
+                                       overrideIPAddress = null;
+                                       return;
+                               }
+                               InetAddress addr;
+                               try {
+                                       addr = InetAddress.getByName(val);
+                               } catch (UnknownHostException e) {
+                                       throw new 
InvalidConfigValueException("Unknown host: "+e.getMessage());
+                               }
+                               overrideIPAddress = addr;
+                       }
+               
+       });
+       
+       String ipOverrideString = nodeConfig.getString("ipAddressOVerride");
+       if(ipOverrideString.length() == 0)
+               overrideIPAddress = null;
+       else {
+                       try {
+                               overrideIPAddress = 
InetAddress.getByName(ipOverrideString);
+                       } catch (UnknownHostException e) {
+                               String msg = "Unknown host: 
"+ipOverrideString+" in config: "+e.getMessage();
+                               Logger.error(this, msg);
+                               System.err.println(msg+" but starting up anyway 
with no IP override");
+                               overrideIPAddress = null;
+                       }
+       }
+       
+       // Determine the port number
+       
+       nodeConfig.register("listenPort", -1 /* means random */, 1, true, "FNP 
port number (UDP)", "UDP port for node-to-node communications (Freenet Node 
Protocol)",
+                       new IntCallback() {
+                                       public int get() {
+                                               return portNumber;
+                                       }
+                                       public void set(int val) throws 
InvalidConfigValueException {
+                                               // FIXME implement on the fly 
listenPort changing
+                                               // Note that this sort of thing 
should be the exception rather than the rule!!!!
+                                               String msg = "Switching 
listenPort on the fly not yet supported!";
+                                               Logger.error(this, msg);
+                                               throw new 
InvalidConfigValueException(msg);
+                                       }
+       });
+
+       int port = nodeConfig.getInt("listenPort");
+       
+       UdpSocketManager u = null;
+       
+       if(port > 65535) {
+               throw new NodeInitException(EXIT_IMPOSSIBLE_USM_PORT, 
"Impossible port number: "+port);
+       } else if(port == -1) {
+               // Pick a random port
+               for(int i=0;i<200000;i++) {
+                       int portNo = 1024 + random.nextInt(65535-1024);
+                       try {
+                               u = new UdpSocketManager(port);
+                               port = u.getPortNumber();
+                               break;
+                       } catch (SocketException e) {
+                               continue;
+                       }
+               }
+               throw new NodeInitException(EXIT_NO_AVAILABLE_UDP_PORTS, "Could 
not find an available UDP port number for FNP (none specified)");
+       } else {
+               try {
+                       u = new UdpSocketManager(port);
+               } catch (SocketException e) {
+                       throw new NodeInitException(EXIT_IMPOSSIBLE_USM_PORT, 
"Could not bind to port: "+port+" (node already running?)");
+               }
+       }
+       usm = u;
+        usm.setDispatcher(dispatcher=new NodeDispatcher(this));
+        usm.setLowLevelFilter(packetMangler = new FNPPacketMangler(this));
+       
+        System.out.println("Port number: "+port);
+        portNumber = port;

-               lm = new LocationManager(random);
+        Logger.normal(Node.class, "Creating node...");

+        // Now pull the override IP address, if any, from the config
+        
+        nodeConfig.register("ipAddress", "", 2, true, "IP address", "IP 
address of the node (should not usually be necessary)", 
+                       new StringCallback() {
+                                       public String get() {
+                                               return 
Peer.getHostName(overrideIPAddress);
+                                       }
+                                       public void set(String val) throws 
InvalidConfigValueException {
+                                               overrideIPAddress = 
resolve(val);
+                                       }
+        });
+
+        String ip = nodeConfig.getString("ipAddress");
+        
+        overrideIPAddress = resolve(ip);
+        
+        // Bandwidth limit
+
+        // FIXME These should not be static !!!! Need a context object for BT 
for bwlimiting.
+        // See bug 77
+        nodeConfig.register("outputBandwidthLimit", "15K", 3, false, 
+                       "Output bandwidth limit", "Hard output bandwidth limit 
(bytes/sec); the node should almost never exceed this", 
+                       new IntCallback() {
+                                       public int get() {
+                                               return 
BlockTransmitter.getHardBandwidthLimit();
+                                       }
+                                       public void set(int val) throws 
InvalidConfigValueException {
+                                               
BlockTransmitter.setHardBandwidthLimit(val);
+                                       }
+        });
+        
+        int obwLimit = nodeConfig.getInt("outputBandwidthLimit");
+        BlockTransmitter.setHardBandwidthLimit(obwLimit);
+        // FIXME add an averaging/long-term/soft bandwidth limit. (bug 76)
+        // There is already untested support for this in BlockTransmitter.
+        // No long-term limit for now.
+        BlockTransmitter.setSoftBandwidthLimit(0, 0);
+        
+        // SwapRequestInterval
+        
+        nodeConfig.register("swapRequestSendInterval", 2000, 4, true,
+                       "Swap request send interval (ms)", "Interval between 
swap attempting to send swap requests in milliseconds. Leave this alone!",
+                       new IntCallback() {
+                                       public int get() {
+                                               return 
swapInterval.fixedInterval;
+                                       }
+                                       public void set(int val) throws 
InvalidConfigValueException {
+                                               swapInterval.set(val);
+                                       }
+        });
+        
+        swapInterval = new 
StaticSwapRequestInterval(nodeConfig.getInt("swapRequestSendInterval"));
+        
+        // Testnet.
+        // Cannot be enabled/disabled on the fly.
+        // If enabled, forces certain other config options.
+        
+        if((testnetHandler = TestnetHandler.maybeCreate(this, config)) != 
null) {
+               String msg = "WARNING: ENABLING TESTNET CODE! This WILL 
seriously jeopardize your anonymity!";
+               Logger.error(this, msg);
+               System.err.println(msg);
+               testnetEnabled = true;
+        } else {
+               Logger.normal(this, "Testnet mode DISABLED. You may have some 
level of anonymity. :)");
+               testnetEnabled = false;
+        }
+
+        // Directory for node-related files other than store
+        
+        nodeConfig.register("nodeDir", ".", 6, true, "Node directory", "Name 
of directory to put node-related files e.g. peers list in", 
+                       new StringCallback() {
+                                       public String get() {
+                                               return nodeDir.getPath();
+                                       }
+                                       public void set(String val) throws 
InvalidConfigValueException {
+                                               if(nodeDir.equals(new 
File(val))) return;
+                                               // FIXME
+                                               throw new 
InvalidConfigValueException("Moving node directory on the fly not supported at 
present");
+                                       }
+        });
+        
+        nodeDir = new File(nodeConfig.getString("nodeDir"));
+        if(!((nodeDir.exists() && nodeDir.isDirectory()) || 
(nodeDir.mkdir()))) {
+               String msg = "Could not find or create datastore directory";
+               throw new NodeInitException(EXIT_BAD_NODE_DIR, msg);
+        }
+
+        peers = new PeerManager(this, new File(nodeDir, 
"peers-"+portNumber).getPath());
+        peers.writePeers();
+        nodePinger = new NodePinger(this);
+        
+        // After we have set up testnet and IP address, load the node file
         try {
-               readNodeFile(prefix+"node-"+portNumber);
+               // FIXME should take file directly?
+               readNodeFile(new File(nodeDir, "node-"+portNumber).getPath());
         } catch (IOException e) {
             try {
-                readNodeFile(prefix+"node-"+portNumber+".bak");
+                readNodeFile(new File("node-"+portNumber+".bak").getPath());
             } catch (IOException e1) {
                 initNodeFileSettings(random);
             }
         }
         writeNodeFile();
+
+        // Temp files

-        ps = new PacketSender(this);
-        peers = new PeerManager(this, prefix+"peers-"+portNumber);
+        nodeConfig.register("tempDir", new File(nodeDir, 
"temp-"+portNumber).toString(), 6, true, "Temp files directory", "Name of 
directory to put temporary files in", 
+                       new StringCallback() {
+                                       public String get() {
+                                               return tempDir.getPath();
+                                       }
+                                       public void set(String val) throws 
InvalidConfigValueException {
+                                               if(tempDir.equals(new 
File(val))) return;
+                                               // FIXME
+                                               throw new 
InvalidConfigValueException("Moving node directory on the fly not supported at 
present");
+                                       }
+        });

-        try {
-            usm = new UdpSocketManager(portNumber);
-            usm.setDispatcher(dispatcher=new NodeDispatcher(this));
-            usm.setLowLevelFilter(packetMangler = new FNPPacketMangler(this));
-        } catch (SocketException e2) {
-            Logger.error(this, "Could not listen for traffic: "+e2, e2);
-            System.exit(EXIT_USM_DIED);
-            throw new Error();
+        tempDir = new File(nodeConfig.getString("tempDir"));
+        if(!((tempDir.exists() && tempDir.isDirectory()) || 
(tempDir.mkdir()))) {
+               String msg = "Could not find or create temporary directory";
+               throw new NodeInitException(EXIT_BAD_TEMP_DIR, msg);
         }
-        decrementAtMax = random.nextDouble() <= DECREMENT_AT_MAX_PROB;
-        decrementAtMin = random.nextDouble() <= DECREMENT_AT_MIN_PROB;
-        bootID = random.nextLong();
-        peers.writePeers();
+        
         try {
-               String dirName = "temp-"+portNumber;
-                       tempFilenameGenerator = new FilenameGenerator(random, 
true, new File(dirName), "temp-");
+                       tempFilenameGenerator = new FilenameGenerator(random, 
true, tempDir, "temp-");
                } catch (IOException e) {
                        Logger.error(this, "Could not create temp bucket 
factory: "+e, e);
                        System.exit(EXIT_TEMP_INIT_ERROR);
                        throw new Error();
                }
-        nodePinger = new NodePinger(this);
                tempBucketFactory = new 
PaddedEphemerallyEncryptedBucketFactory(new 
TempBucketFactory(tempFilenameGenerator), random, 1024);
+
+        
+        // Datastore
+        
+        nodeConfig.register("storeSize", "1G", 5, false, "Store size in 
bytes", "Store size in bytes", 
+                       new LongCallback() {
+
+                                       public long get() {
+                                               return maxStoreKeys * 
sizePerKey;
+                                       }
+
+                                       public void set(long storeSize) throws 
InvalidConfigValueException {
+                                               if(storeSize < 0 || storeSize < 
(32 * 1024 * 1024))
+                                                       throw new 
InvalidConfigValueException("Invalid store size");
+                                               long newMaxStoreKeys = 
storeSize / sizePerKey;
+                                               if(newMaxStoreKeys == 
maxStoreKeys) return;
+                                               // Update each datastore
+                                               maxStoreKeys = newMaxStoreKeys;
+                                               
chkDatastore.setMaxKeys(maxStoreKeys);
+                                               
sskDatastore.setMaxKeys(maxStoreKeys);
+                                               
pubKeyDatastore.setMaxKeys(maxStoreKeys);
+                                       }
+        });
+        
+        long storeSize = nodeConfig.getLong("storeSize");
+        
+        if(/*storeSize < 0 || */storeSize < (32 * 1024 * 1024)) { // totally 
arbitrary minimum!
+               throw new NodeInitException(EXIT_INVALID_STORE_SIZE, "Invalid 
store size");
+        }
+
+        maxStoreKeys = storeSize / sizePerKey;
+        
+        nodeConfig.register("storeDir", new 
File(nodeDir,"store-"+portNumber).toString(), 6, true, "Store directory", "Name 
of directory to put store files in", 
+                       new StringCallback() {
+                                       public String get() {
+                                               return storeDir.getPath();
+                                       }
+                                       public void set(String val) throws 
InvalidConfigValueException {
+                                               if(storeDir.equals(new 
File(val))) return;
+                                               // FIXME
+                                               throw new 
InvalidConfigValueException("Moving datastore on the fly not supported at 
present");
+                                       }
+        });
+        
+        storeDir = new File(nodeConfig.getString("storeDir"));
+        if(!((storeDir.exists() && storeDir.isDirectory()) || 
(storeDir.mkdir()))) {
+               String msg = "Could not find or create datastore directory";
+               throw new NodeInitException(EXIT_STORE_OTHER, msg);
+        }
+
+        try {
+            chkDatastore = new 
BerkeleyDBFreenetStore(storeDir.getPath()+File.separator+"store-"+portNumber, 
maxStoreKeys, 32768, CHKBlock.TOTAL_HEADERS_LENGTH);
+            sskDatastore = new 
BerkeleyDBFreenetStore(storeDir.getPath()+File.separator+"sskstore-"+portNumber,
 maxStoreKeys, 1024, SSKBlock.TOTAL_HEADERS_LENGTH);
+            pubKeyDatastore = new 
BerkeleyDBFreenetStore(storeDir.getPath()+File.separator+"pubkeystore-"+portNumber,
 maxStoreKeys, DSAPublicKey.PADDED_SIZE, 0);
+        } catch (FileNotFoundException e1) {
+               String msg = "Could not open datastore: "+e1;
+            Logger.error(this, msg, e1);
+            System.err.println(msg);
+            throw new NodeInitException(EXIT_STORE_FILE_NOT_FOUND, msg);
+        } catch (IOException e1) {
+               String msg = "Could not open datastore: "+e1;
+            Logger.error(this, msg, e1);
+            System.err.println(msg);
+            throw new NodeInitException(EXIT_STORE_IOEXCEPTION, msg);
+        } catch (Exception e1) {
+               String msg = "Could not open datastore: "+e1;
+            Logger.error(this, msg, e1);
+            System.err.println(msg);
+            throw new NodeInitException(EXIT_STORE_OTHER, msg);
+        }
+        
+        // Downloads directory
+        
+        nodeConfig.register("downloadsDir", "downloads", 8, false, "Default 
download directory", "The directory to save downloaded files into by default", 
new StringCallback() {
+
+                       public String get() {
+                               return downloadDir.getPath();
+                       }
+
+                       public void set(String val) throws 
InvalidConfigValueException {
+                               if(downloadDir.equals(new File(val)))
+                                       return;
+                               File f = new File(val);
+                       if(!((f.exists() && f.isDirectory()) || (f.mkdir()))) {
+                               throw new InvalidConfigValueException("Could 
not find or create directory");
+                       }
+                               downloadDir = new File(val);
+                       }
+               
+        });
+        
+        String val = nodeConfig.getString("downloadsDir");
+               downloadDir = new File(val);
+        if(!((downloadDir.exists() && downloadDir.isDirectory()) || 
(downloadDir.mkdir()))) {
+               throw new NodeInitException(EXIT_BAD_DOWNLOADS_DIR, "Could not 
find or create default downloads directory");
+        }
+
+        nodeConfig.finishedInitialization();
+        
+        // FIXME make all the below arbitrary constants configurable!
+        
                archiveManager = new ArchiveManager(MAX_ARCHIVE_HANDLERS, 
MAX_CACHED_ARCHIVE_DATA, MAX_ARCHIVE_SIZE, MAX_ARCHIVED_FILE_SIZE, 
MAX_CACHED_ELEMENTS, random, tempFilenameGenerator);
                requestThrottle = new RequestThrottle(5000, 2.0F);
                requestStarter = new RequestStarter(this, requestThrottle, 
"Request starter ("+portNumber+")");
@@ -487,11 +788,6 @@
                putScheduler = new ClientRequestScheduler(true, random, 
insertStarter, this);
                insertStarter.setScheduler(putScheduler);
                insertStarter.start();
-               if(testnetHandler != null)
-                       testnetHandler.start();
-               if(statusUploader != null)
-                       statusUploader.start();
-               
                // And finally, Initialize the plugin manager
                PluginManager pm = null;
                try {
@@ -504,14 +800,57 @@
                        System.err.println("THIS SHOULDN'T OCCUR!!!! (plugin 
system now disabled)");
                }
                pluginManager = pm;
-               System.err.println("Created Node on port "+port);
     }
-
-    void start(SwapRequestInterval interval) {
-        if(interval != null)
-            lm.startSender(this, interval);
+    
+       private InetAddress resolve(String val) {
+               try {
+                       if(val == null || val.length() == 0)
+                               return null;
+                       else
+                               return InetAddress.getByName(val);
+               } catch (UnknownHostException e) {
+                       Logger.error(this, "Ignoring unresolvable overridden IP 
address: "+overrideIPAddress);
+                       return null;
+               }
+       }
+       
+    void start(boolean noSwaps) throws NodeInitException {
+        if(!noSwaps)
+            lm.startSender(this, this.swapInterval);
         ps.start();
         usm.start();
+        
+        // Start services
+        
+        // TMCI
+        TextModeClientInterface.maybeCreate(this, config);
+        
+        // Fproxy
+        // FIXME this is a hack, the real way to do this is plugins
+        SimpleToadletServer.maybeCreateFproxyEtc(this, config);
+        
+        // FCP
+        try {
+                       fcpServer = FCPServer.maybeCreate(this, config);
+               } catch (IOException e) {
+                       throw new NodeInitException(EXIT_COULD_NOT_START_FCP, 
"Could not start FCP: "+e);
+               }
+        
+        // SNMP
+        SNMPStarter.maybeCreate(this, config);
+
+        
+        // FIXME old code to use above
+        
+        new FCPServer(portNumber+3000, this);
+        System.out.println("Starting FCP server on port "+(portNumber+3000));
+        SNMPAgent.setSNMPPort(portNumber+4000);
+        System.out.println("Starting SNMP server on port "+(portNumber+4000));
+        SNMPStarter.initialize();
+        
+        // Start testnet handler
+               if(testnetHandler != null)
+                       testnetHandler.start();
     }

     public ClientKeyBlock realGetKey(ClientKey key, boolean localOnly, boolean 
cache, boolean ignoreStore) throws LowLevelGetException {
@@ -942,7 +1281,7 @@
      * @return
      */
     public SimpleFieldSet exportFieldSet() {
-        SimpleFieldSet fs = new SimpleFieldSet();
+        SimpleFieldSet fs = new SimpleFieldSet(false);
         fs.put("physical.udp", 
Peer.getHostName(getPrimaryIPAddress())+":"+portNumber);
         fs.put("identity", HexUtil.bytesToHex(myIdentity));
         fs.put("location", Double.toString(lm.getLocation().getValue()));
@@ -1551,4 +1890,8 @@
                if(block == null) return null;
                return new ClientCHKBlock(block, clientCHK);
        }
+       
+       public FCPServer getFCPServer() {
+               return fcpServer;
+       }
 }

Modified: branches/config/src/freenet/node/PeerManager.java
===================================================================
--- branches/config/src/freenet/node/PeerManager.java   2006-02-18 22:08:10 UTC 
(rev 8063)
+++ branches/config/src/freenet/node/PeerManager.java   2006-02-18 22:12:47 UTC 
(rev 8064)
@@ -62,7 +62,7 @@
                 while(true) {
                     // Read a single NodePeer
                     SimpleFieldSet fs;
-                    fs = new SimpleFieldSet(br);
+                    fs = new SimpleFieldSet(br, false);
                     PeerNode pn;
                     try {
                         pn = new PeerNode(fs, node);

Modified: branches/config/src/freenet/node/PeerNode.java
===================================================================
--- branches/config/src/freenet/node/PeerNode.java      2006-02-18 22:08:10 UTC 
(rev 8063)
+++ branches/config/src/freenet/node/PeerNode.java      2006-02-18 22:12:47 UTC 
(rev 8064)
@@ -825,7 +825,7 @@
         BufferedReader br = new BufferedReader(isr);
         SimpleFieldSet fs;
         try {
-            fs = new SimpleFieldSet(br);
+            fs = new SimpleFieldSet(br, false);
         } catch (IOException e) {
             Logger.error(this, "Impossible: e", e);
             return;
@@ -967,7 +967,7 @@
      * Export our noderef as a SimpleFieldSet
      */
     private SimpleFieldSet exportFieldSet() {
-        SimpleFieldSet fs = new SimpleFieldSet();
+        SimpleFieldSet fs = new SimpleFieldSet(false);
         if(lastGoodVersion != null)
                fs.put("lastGoodVersion", lastGoodVersion);
         for(int i=0;i<nominalPeer.size();i++)

Modified: branches/config/src/freenet/node/StaticSwapRequestInterval.java
===================================================================
--- branches/config/src/freenet/node/StaticSwapRequestInterval.java     
2006-02-18 22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/node/StaticSwapRequestInterval.java     
2006-02-18 22:12:47 UTC (rev 8064)
@@ -1,20 +1,18 @@
 package freenet.node;

-/**
- * @author root
- *
- * TODO To change the template for this generated type comment go to
- * Window - Preferences - Java - Code Generation - Code and Comments
- */
 public class StaticSwapRequestInterval implements SwapRequestInterval {

-    double fixedInterval;
+    int fixedInterval;

-    public StaticSwapRequestInterval(double d) {
+    public StaticSwapRequestInterval(int d) {
         fixedInterval = d;
     }

-    public double getValue() {
+    public int getValue() {
         return fixedInterval;
     }
+
+       public void set(int val) {
+               this.fixedInterval = val;
+       }
 }

Modified: branches/config/src/freenet/node/SwapRequestInterval.java
===================================================================
--- branches/config/src/freenet/node/SwapRequestInterval.java   2006-02-18 
22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/node/SwapRequestInterval.java   2006-02-18 
22:12:47 UTC (rev 8064)
@@ -4,5 +4,5 @@
  * Interface to get the current swap request interval
  */
 public interface SwapRequestInterval {
-    double getValue();
+    int getValue();
 }

Modified: branches/config/src/freenet/node/TestnetHandler.java
===================================================================
--- branches/config/src/freenet/node/TestnetHandler.java        2006-02-18 
22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/node/TestnetHandler.java        2006-02-18 
22:12:47 UTC (rev 8064)
@@ -14,6 +14,13 @@
 import java.util.Locale;
 import java.util.TimeZone;

+import freenet.config.BooleanCallback;
+import freenet.config.Config;
+import freenet.config.IntCallback;
+import freenet.config.InvalidConfigValueException;
+import freenet.config.SubConfig;
+import freenet.node.Node.NodeInitException;
+import freenet.support.FileLoggerHook;
 import freenet.support.Logger;

 /**
@@ -33,6 +40,8 @@
  */
 public class TestnetHandler implements Runnable {

+       private final TestnetStatusUploader uploader;
+       
        public TestnetHandler(Node node2, int testnetPort) {
                this.node = node2;
                this.testnetPort = testnetPort;
@@ -44,12 +53,14 @@
                System.err.println("We repeat: YOU HAVE NO ANONYMITY 
WHATSOEVER. DO NOT POST ANYTHING YOU DO NOT WANT TO BE ASSOCIATED WITH.");
                System.err.println("If you want a real freenet node, with 
anonymity, turn off testnet mode.");
                System.err.println("Note, this node will not connect to 
non-testnet nodes, for security reasons. You can of course run a testnet node 
and a non-testnet node separately.");
+               uploader = new TestnetStatusUploader(node, 180000);
        }

        public void start() {
                serverThread = new Thread(this, "Testnet handler thread");
                serverThread.setDaemon(true);
                serverThread.start();
+               uploader.start();
                System.err.println("Started testnet handler on port 
"+testnetPort);
        }

@@ -102,10 +113,18 @@
                                String command = br.readLine();
                                if(command == null) return;
                                Logger.minor(this, "Command: "+command);
+                               FileLoggerHook loggerHook;
+                               loggerHook = 
Node.logConfigHandler.getFileLoggerHook();
+                               if(loggerHook == null) {
+                                       Logger.error(this, "Could not serve 
testnet command because no FileLoggerHook");
+                                       OutputStreamWriter osw = new 
OutputStreamWriter(os);
+                                       osw.write("ERROR: Could not serve 
testnet command because no FileLoggerHook");
+                                       return;
+                               }
                                if(command.equalsIgnoreCase("LIST")) {
                                        Logger.minor(this, "Listing available 
logs");
                                        OutputStreamWriter osw = new 
OutputStreamWriter(os, "ISO-8859-1");
-                                       
node.fileLoggerHook.listAvailableLogs(osw);
+                                       loggerHook.listAvailableLogs(osw);
                                        osw.close();
                                } else if(command.startsWith("GET:")) {
                                        Logger.minor(this, "Sending log: 
"+command);
@@ -119,7 +138,7 @@
                                                Logger.minor(this, "Cannot 
parse: "+e+" for "+date);
                                                return;
                                        }
-                                       
node.fileLoggerHook.sendLogByContainedDate(d.getTime(), os);
+                                       
loggerHook.sendLogByContainedDate(d.getTime(), os);
                                } else if(command.equalsIgnoreCase("STATUS")) {
                                        Logger.minor(this, "Sending status");
                                        OutputStreamWriter osw = new 
OutputStreamWriter(os, "ISO-8859-1");
@@ -148,4 +167,82 @@

        }

+       private static class TestnetEnabledCallback implements BooleanCallback {
+
+               final Node node;
+               
+               TestnetEnabledCallback(Node node) {
+                       this.node = node;
+               }
+               
+               public boolean get() {
+                       return node.testnetEnabled;
+               }
+
+               public void set(boolean val) throws InvalidConfigValueException 
{
+                       if(node.testnetEnabled == val) return;
+                       String msg = "On-line enable/disable of testnet mode 
impossible; restart the node and get new connections";
+                       throw new InvalidConfigValueException(msg);
+               }
+               
+       }
+
+       private static class TestnetPortNumberCallback implements IntCallback {
+               
+               final Node node;
+               
+               TestnetPortNumberCallback(Node node) {
+                       this.node = node;
+               }
+               
+               public int get() {
+                       TestnetHandler th = node.testnetHandler;
+                       if(th == null)
+                               return node.portNumber+1000;
+                       else
+                               return th.testnetPort;
+               }
+               
+               public void set(int val) throws InvalidConfigValueException {
+                       TestnetHandler th = node.testnetHandler;
+                       if(th == null)
+                               return;
+                       if(val == th.testnetPort) return;
+                       throw new InvalidConfigValueException("Changing testnet 
port number on the fly not yet supported");
+               }
+       }
+       
+       public static TestnetHandler maybeCreate(Node node, Config config) 
throws NodeInitException {
+        SubConfig testnetConfig = new SubConfig("node.testnet", config);
+        
+        testnetConfig.register("enabled", true /* for now */, 1, false, 
"Enable testnet mode? (DANGEROUS)",
+                       "Whether to enable testnet mode (DANGEROUS!). Testnet 
mode eliminates your anonymity in exchange for greatly assisting the developers 
in debugging the node.",
+                       new TestnetEnabledCallback(node));
+        
+        boolean enabled = testnetConfig.getBoolean("enabled");
+        
+        if(!enabled) {
+               // FIXME
+               String msg = "Sorry, testnet must be enabled while Freenet 0.7 
is in pre-alpha testing phase.";
+               Logger.error(TestnetHandler.class, msg);
+               System.err.println(msg);
+               throw new 
Node.NodeInitException(Node.EXIT_TESTNET_DISABLED_NOT_SUPPORTED, msg);
+        }
+        
+        // Testnet is enabled.
+        
+        // Get the testnet port
+        
+        // Default to node port plus 1000
+        
+        int defaultPort = 1024 + (node.portNumber-1024+1000) % (65536 - 1024);
+        
+        testnetConfig.register("port", defaultPort, 2, true, "Testnet port", 
"Testnet port number",
+                       new TestnetPortNumberCallback(node));
+        
+        testnetConfig.finishedInitialization();
+        
+        return new TestnetHandler(node, testnetConfig.getInt("port"));
+       }
+
 }

Modified: branches/config/src/freenet/node/TextModeClientInterface.java
===================================================================
--- branches/config/src/freenet/node/TextModeClientInterface.java       
2006-02-18 22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/node/TextModeClientInterface.java       
2006-02-18 22:12:47 UTC (rev 8064)
@@ -24,6 +24,10 @@
 import freenet.client.InserterException;
 import freenet.client.Metadata;
 import freenet.client.events.EventDumper;
+import freenet.config.BooleanCallback;
+import freenet.config.Config;
+import freenet.config.InvalidConfigValueException;
+import freenet.config.SubConfig;
 import freenet.crypt.RandomSource;
 import freenet.io.comm.PeerParseException;
 import freenet.keys.FreenetURI;
@@ -630,7 +634,7 @@
         SimpleFieldSet fs;
         System.out.println("Connecting to:\n"+content);
         try {
-            fs = new SimpleFieldSet(content);
+            fs = new SimpleFieldSet(content, false);
         } catch (IOException e) {
             System.err.println("Did not parse: "+e);
             e.printStackTrace();
@@ -681,4 +685,9 @@
         }
         return sb.toString();
     }
+
+       public static void maybeCreate(Node node, Config config) {
+               // FIXME make this configurable.
+               // Depends on fixing QUIT issues. (bug #81)
+       }
 }

Modified: branches/config/src/freenet/node/fcp/AllDataMessage.java
===================================================================
--- branches/config/src/freenet/node/fcp/AllDataMessage.java    2006-02-18 
22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/node/fcp/AllDataMessage.java    2006-02-18 
22:12:47 UTC (rev 8064)
@@ -25,7 +25,7 @@
        }

        public SimpleFieldSet getFieldSet() {
-               SimpleFieldSet fs = new SimpleFieldSet();
+               SimpleFieldSet fs = new SimpleFieldSet(false);
                fs.put("DataLength", Long.toString(dataLength));
                fs.put("Identifier", identifier);
                return fs;

Modified: branches/config/src/freenet/node/fcp/ClientGetMessage.java
===================================================================
--- branches/config/src/freenet/node/fcp/ClientGetMessage.java  2006-02-18 
22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/node/fcp/ClientGetMessage.java  2006-02-18 
22:12:47 UTC (rev 8064)
@@ -156,7 +156,7 @@
        }

        public SimpleFieldSet getFieldSet() {
-               SimpleFieldSet fs = new SimpleFieldSet();
+               SimpleFieldSet fs = new SimpleFieldSet(false);
                fs.put("IgnoreDS", Boolean.toString(ignoreDS));
                fs.put("URI", uri.toString(false));
                fs.put("Identifier", identifier);

Modified: branches/config/src/freenet/node/fcp/ClientHelloMessage.java
===================================================================
--- branches/config/src/freenet/node/fcp/ClientHelloMessage.java        
2006-02-18 22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/node/fcp/ClientHelloMessage.java        
2006-02-18 22:12:47 UTC (rev 8064)
@@ -26,7 +26,7 @@
        }

        public SimpleFieldSet getFieldSet() {
-               SimpleFieldSet sfs = new SimpleFieldSet();
+               SimpleFieldSet sfs = new SimpleFieldSet(false);
                sfs.put("Name", clientName);
                sfs.put("ExpectedVersion", clientExpectedVersion);
                return sfs;

Modified: branches/config/src/freenet/node/fcp/ClientPutMessage.java
===================================================================
--- branches/config/src/freenet/node/fcp/ClientPutMessage.java  2006-02-18 
22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/node/fcp/ClientPutMessage.java  2006-02-18 
22:12:47 UTC (rev 8064)
@@ -120,7 +120,7 @@
        }

        public SimpleFieldSet getFieldSet() {
-               SimpleFieldSet sfs = new SimpleFieldSet();
+               SimpleFieldSet sfs = new SimpleFieldSet(false);
                sfs.put("URI", uri.toString());
                sfs.put("Identifier", identifier);
                sfs.put("DataLength", Long.toString(dataLength));

Modified: branches/config/src/freenet/node/fcp/DataFoundMessage.java
===================================================================
--- branches/config/src/freenet/node/fcp/DataFoundMessage.java  2006-02-18 
22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/node/fcp/DataFoundMessage.java  2006-02-18 
22:12:47 UTC (rev 8064)
@@ -17,7 +17,7 @@
        }

        public SimpleFieldSet getFieldSet() {
-               SimpleFieldSet fs = new SimpleFieldSet();
+               SimpleFieldSet fs = new SimpleFieldSet(false);
                fs.put("Identifier", identifier);
                fs.put("Metadata.ContentType", mimeType);
                fs.put("DataLength", Long.toString(dataLength));

Modified: branches/config/src/freenet/node/fcp/FCPConnectionInputHandler.java
===================================================================
--- branches/config/src/freenet/node/fcp/FCPConnectionInputHandler.java 
2006-02-18 22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/node/fcp/FCPConnectionInputHandler.java 
2006-02-18 22:12:47 UTC (rev 8064)
@@ -44,7 +44,7 @@
                        // Read a message
                        String messageType = lis.readLine(64, 64);
                        if(messageType.equals("")) continue;
-                       fs = new SimpleFieldSet(lis, 4096, 128);
+                       fs = new SimpleFieldSet(lis, 4096, 128, false);
                        FCPMessage msg;
                        try {
                                msg = FCPMessage.create(messageType, fs);

Modified: branches/config/src/freenet/node/fcp/FCPServer.java
===================================================================
--- branches/config/src/freenet/node/fcp/FCPServer.java 2006-02-18 22:08:10 UTC 
(rev 8063)
+++ branches/config/src/freenet/node/fcp/FCPServer.java 2006-02-18 22:12:47 UTC 
(rev 8064)
@@ -5,6 +5,10 @@
 import java.net.ServerSocket;
 import java.net.Socket;

+import freenet.config.Config;
+import freenet.config.IntCallback;
+import freenet.config.InvalidConfigValueException;
+import freenet.config.SubConfig;
 import freenet.node.Node;
 import freenet.support.Logger;

@@ -15,8 +19,10 @@

        final ServerSocket sock;
        final Node node;
+       final int port;

        public FCPServer(int port, Node node) throws IOException {
+               this.port = port;
                this.sock = new ServerSocket(port, 0, 
InetAddress.getByName("127.0.0.1"));
                this.node = node;
                Thread t = new Thread(this, "FCP server");
@@ -42,4 +48,33 @@
                FCPConnectionHandler handler = new FCPConnectionHandler(s, 
node);
        }

+       static class FCPPortNumberCallback implements IntCallback {
+
+               final Node node;
+               
+               FCPPortNumberCallback(Node node) {
+                       this.node = node;
+               }
+               
+               public int get() {
+                       return node.getFCPServer().port;
+               }
+
+               public void set(int val) throws InvalidConfigValueException {
+                       if(val != get()) {
+                               throw new InvalidConfigValueException("Cannot 
change FCP port number on the fly");
+                       }
+               }
+               
+       }
+       
+       public static FCPServer maybeCreate(Node node, Config config) throws 
IOException {
+               SubConfig fcpConfig = new SubConfig("fcp", config);
+               // FIXME check enabled etc
+               fcpConfig.register("port", 9481 /* anagram of 1984, and 1000 up 
from old number */,
+                               2, true, "FCP port number", "FCP port number", 
new FCPPortNumberCallback(node));
+               fcpConfig.finishedInitialization();
+               return new FCPServer(fcpConfig.getInt("port"), node);
+       }
+
 }

Modified: branches/config/src/freenet/node/fcp/FinishedCompressionMessage.java
===================================================================
--- branches/config/src/freenet/node/fcp/FinishedCompressionMessage.java        
2006-02-18 22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/node/fcp/FinishedCompressionMessage.java        
2006-02-18 22:12:47 UTC (rev 8064)
@@ -19,7 +19,7 @@
        }

        public SimpleFieldSet getFieldSet() {
-               SimpleFieldSet fs = new SimpleFieldSet();
+               SimpleFieldSet fs = new SimpleFieldSet(false);
                fs.put("Identifier", identifier);
                fs.put("Codec", Integer.toString(codec));
                fs.put("OriginalSize", Long.toString(origSize));

Modified: branches/config/src/freenet/node/fcp/GenerateSSKMessage.java
===================================================================
--- branches/config/src/freenet/node/fcp/GenerateSSKMessage.java        
2006-02-18 22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/node/fcp/GenerateSSKMessage.java        
2006-02-18 22:12:47 UTC (rev 8064)
@@ -15,7 +15,7 @@
        }

        public SimpleFieldSet getFieldSet() {
-               return new SimpleFieldSet();
+               return new SimpleFieldSet(false);
        }

        public String getName() {

Modified: branches/config/src/freenet/node/fcp/GetFailedMessage.java
===================================================================
--- branches/config/src/freenet/node/fcp/GetFailedMessage.java  2006-02-18 
22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/node/fcp/GetFailedMessage.java  2006-02-18 
22:12:47 UTC (rev 8064)
@@ -26,7 +26,7 @@
        }

        public SimpleFieldSet getFieldSet() {
-               SimpleFieldSet sfs = new SimpleFieldSet();
+               SimpleFieldSet sfs = new SimpleFieldSet(false);
                sfs.put("Code", Integer.toString(code));
                sfs.put("CodeDescription", codeDescription);
                if(extraDescription != null)

Modified: branches/config/src/freenet/node/fcp/IdentifierCollisionMessage.java
===================================================================
--- branches/config/src/freenet/node/fcp/IdentifierCollisionMessage.java        
2006-02-18 22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/node/fcp/IdentifierCollisionMessage.java        
2006-02-18 22:12:47 UTC (rev 8064)
@@ -12,7 +12,7 @@
        }

        public SimpleFieldSet getFieldSet() {
-               SimpleFieldSet sfs = new SimpleFieldSet();
+               SimpleFieldSet sfs = new SimpleFieldSet(false);
                sfs.put("Identifier", identifier);
                return sfs;
        }

Modified: branches/config/src/freenet/node/fcp/NodeHelloMessage.java
===================================================================
--- branches/config/src/freenet/node/fcp/NodeHelloMessage.java  2006-02-18 
22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/node/fcp/NodeHelloMessage.java  2006-02-18 
22:12:47 UTC (rev 8064)
@@ -23,7 +23,7 @@
        }

        public SimpleFieldSet getFieldSet() {
-               SimpleFieldSet sfs = new SimpleFieldSet();
+               SimpleFieldSet sfs = new SimpleFieldSet(false);
                // FIXME
                sfs.put("FCPVersion", "2.0");
                sfs.put("Node", "Fred");

Modified: branches/config/src/freenet/node/fcp/ProtocolErrorMessage.java
===================================================================
--- branches/config/src/freenet/node/fcp/ProtocolErrorMessage.java      
2006-02-18 22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/node/fcp/ProtocolErrorMessage.java      
2006-02-18 22:12:47 UTC (rev 8064)
@@ -82,7 +82,7 @@
        }

        public SimpleFieldSet getFieldSet() {
-               SimpleFieldSet sfs = new SimpleFieldSet();
+               SimpleFieldSet sfs = new SimpleFieldSet(false);
                if(ident != null)
                        sfs.put("Identifier", ident);
                sfs.put("Code", Integer.toString(code));

Modified: branches/config/src/freenet/node/fcp/PutFailedMessage.java
===================================================================
--- branches/config/src/freenet/node/fcp/PutFailedMessage.java  2006-02-18 
22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/node/fcp/PutFailedMessage.java  2006-02-18 
22:12:47 UTC (rev 8064)
@@ -29,7 +29,7 @@
        }

        public SimpleFieldSet getFieldSet() {
-               SimpleFieldSet fs = new SimpleFieldSet();
+               SimpleFieldSet fs = new SimpleFieldSet(false);
                fs.put("Identifier", identifier);
                fs.put("Code", Integer.toString(code));
                fs.put("CodeDescription", codeDescription);

Modified: branches/config/src/freenet/node/fcp/PutSuccessfulMessage.java
===================================================================
--- branches/config/src/freenet/node/fcp/PutSuccessfulMessage.java      
2006-02-18 22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/node/fcp/PutSuccessfulMessage.java      
2006-02-18 22:12:47 UTC (rev 8064)
@@ -15,7 +15,7 @@
        }

        public SimpleFieldSet getFieldSet() {
-               SimpleFieldSet fs = new SimpleFieldSet();
+               SimpleFieldSet fs = new SimpleFieldSet(false);
                fs.put("Identifier", identifier);
                fs.put("URI", uri.toString());
                return fs;

Modified: branches/config/src/freenet/node/fcp/SSKKeypairMessage.java
===================================================================
--- branches/config/src/freenet/node/fcp/SSKKeypairMessage.java 2006-02-18 
22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/node/fcp/SSKKeypairMessage.java 2006-02-18 
22:12:47 UTC (rev 8064)
@@ -17,7 +17,7 @@
        }

        public SimpleFieldSet getFieldSet() {
-               SimpleFieldSet sfs = new SimpleFieldSet();
+               SimpleFieldSet sfs = new SimpleFieldSet(false);
                sfs.put("InsertURI", insertURI.toString());
                sfs.put("RequestURI", requestURI.toString());
                if(identifier != null) // is optional on these two only

Modified: branches/config/src/freenet/node/fcp/SimpleProgressMessage.java
===================================================================
--- branches/config/src/freenet/node/fcp/SimpleProgressMessage.java     
2006-02-18 22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/node/fcp/SimpleProgressMessage.java     
2006-02-18 22:12:47 UTC (rev 8064)
@@ -15,7 +15,7 @@
        }

        public SimpleFieldSet getFieldSet() {
-               SimpleFieldSet fs = new SimpleFieldSet();
+               SimpleFieldSet fs = new SimpleFieldSet(false);
                fs.put("Total", Integer.toString(event.totalBlocks));
                fs.put("Required", Integer.toString(event.minSuccessfulBlocks));
                fs.put("Failed", Integer.toString(event.failedBlocks));

Modified: branches/config/src/freenet/node/fcp/StartedCompressionMessage.java
===================================================================
--- branches/config/src/freenet/node/fcp/StartedCompressionMessage.java 
2006-02-18 22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/node/fcp/StartedCompressionMessage.java 
2006-02-18 22:12:47 UTC (rev 8064)
@@ -15,7 +15,7 @@
        }

        public SimpleFieldSet getFieldSet() {
-               SimpleFieldSet fs = new SimpleFieldSet();
+               SimpleFieldSet fs = new SimpleFieldSet(false);
                fs.put("Identifier", identifier);
                fs.put("Codec", Integer.toString(codec));
                return fs;

Modified: branches/config/src/freenet/node/fcp/URIGeneratedMessage.java
===================================================================
--- branches/config/src/freenet/node/fcp/URIGeneratedMessage.java       
2006-02-18 22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/node/fcp/URIGeneratedMessage.java       
2006-02-18 22:12:47 UTC (rev 8064)
@@ -15,7 +15,7 @@
        }

        public SimpleFieldSet getFieldSet() {
-               SimpleFieldSet fs = new SimpleFieldSet();
+               SimpleFieldSet fs = new SimpleFieldSet(false);
                fs.put("URI", uri.toString());
                fs.put("Identifier", identifier);
                return fs;

Modified: branches/config/src/freenet/store/BerkeleyDBFreenetStore.java
===================================================================
--- branches/config/src/freenet/store/BerkeleyDBFreenetStore.java       
2006-02-18 22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/store/BerkeleyDBFreenetStore.java       
2006-02-18 22:12:47 UTC (rev 8064)
@@ -53,8 +53,8 @@
        private final TupleBinding storeBlockTupleBinding;
        private final TupleBinding longTupleBinding;

-       private int chkBlocksInStore;
-       private final int maxChkBlocks;
+       private long chkBlocksInStore;
+       private final long maxChkBlocks;
        private final Database chkDB;
        private final Database chkDB_accessTime;
        private final RandomAccessFile chkStore;
@@ -68,7 +68,7 @@
      * @param the directory where the store is located
      * @throws FileNotFoundException if the dir does not exist and could not 
be created
      */
-       public BerkeleyDBFreenetStore(String storeDir, int maxChkBlocks, int 
blockSize, int headerSize) throws Exception {
+       public BerkeleyDBFreenetStore(String storeDir, long maxChkBlocks, int 
blockSize, int headerSize) throws Exception {
                this.dataBlockSize = blockSize;
                this.headerBlockSize = headerSize;
                // Percentage of the database that must contain usefull data
@@ -425,7 +425,7 @@
                synchronized(chkStore) {
                        if(chkBlocksInStore<maxChkBlocks) {
                                // Expand the store file
-                               int byteOffset = 
chkBlocksInStore*(dataBlockSize+headerBlockSize);
+                               long byteOffset = 
chkBlocksInStore*(dataBlockSize+headerBlockSize);
                                StoreBlock storeBlock = new 
StoreBlock(chkBlocksInStore);
                                DatabaseEntry blockDBE = new DatabaseEntry();
                        storeBlockTupleBinding.objectToEntry(storeBlock, 
blockDBE);
@@ -494,7 +494,7 @@
                synchronized(chkStore) {
                        if(chkBlocksInStore<maxChkBlocks) {
                                // Expand the store file
-                               int byteOffset = 
chkBlocksInStore*(dataBlockSize+headerBlockSize);
+                               long byteOffset = 
chkBlocksInStore*(dataBlockSize+headerBlockSize);
                                StoreBlock storeBlock = new 
StoreBlock(chkBlocksInStore);
                                DatabaseEntry blockDBE = new DatabaseEntry();
                        storeBlockTupleBinding.objectToEntry(storeBlock, 
blockDBE);
@@ -538,14 +538,14 @@
     private class StoreBlock
     {
        private long recentlyUsed;
-       private int offset;
+       private long offset;

-       public StoreBlock(int offset)
+       public StoreBlock(long offset)
        {
                this(offset,getNewRecentlyUsed());
        }

-       public StoreBlock(int offset,long recentlyUsed)
+       public StoreBlock(long offset,long recentlyUsed)
        {
                this.offset = offset;
                this.recentlyUsed = recentlyUsed;
@@ -566,7 +566,7 @@
                recentlyUsed = getNewRecentlyUsed();
        }

-       public int getOffset() {
+       public long getOffset() {
                return offset;
        }
     }
@@ -580,7 +580,7 @@
        public void objectToEntry(Object object, TupleOutput to)  {
                StoreBlock myData = (StoreBlock)object;

-               to.writeInt(myData.getOffset());
+               to.writeLong(myData.getOffset());
                to.writeLong(myData.getRecentlyUsed());
        }


Modified: branches/config/src/freenet/store/FreenetStore.java
===================================================================
--- branches/config/src/freenet/store/FreenetStore.java 2006-02-18 22:08:10 UTC 
(rev 8063)
+++ branches/config/src/freenet/store/FreenetStore.java 2006-02-18 22:12:47 UTC 
(rev 8064)
@@ -43,4 +43,10 @@
      * Store a public key.
      */
     public void put(byte[] hash, DSAPublicKey key) throws IOException;
+
+    /**
+     * Change the store size.
+     * @param maxStoreKeys The maximum number of keys to be cached.
+     */
+       public void setMaxKeys(long maxStoreKeys);
 }

Modified: branches/config/src/freenet/support/Fields.java
===================================================================
--- branches/config/src/freenet/support/Fields.java     2006-02-18 22:08:10 UTC 
(rev 8063)
+++ branches/config/src/freenet/support/Fields.java     2006-02-18 22:12:47 UTC 
(rev 8064)
@@ -522,4 +522,99 @@
         return buf;
        }

+    /**
+     * Parse a human-readable string possibly including SI units into a short.
+        * @throws NumberFormatException
+        *             if the string is not parseable
+        */
+       public static short parseShort(String s) throws NumberFormatException {
+               short res = 1;
+               int x = s.length() - 1;
+               int idx;
+               try {
+                       long[] l =
+                               {
+                                       1000,
+                                       1 << 10 };
+                       while (x >= 0
+                               && ((idx = "kK".indexOf(s.charAt(x))) != -1)) {
+                               x--;
+                               res *= l[idx];
+                       }
+                       res *= Double.parseDouble(s.substring(0, x + 1));
+               } catch (ArithmeticException e) {
+                       res = Short.MAX_VALUE;
+                       throw new NumberFormatException(e.getMessage());
+               }
+               return res;
+       }
+
+       /**
+        * Parse a human-readable string possibly including SI units into an 
integer.
+        * @throws NumberFormatException
+        *             if the string is not parseable
+        */
+       public static int parseInt(String s) throws NumberFormatException {
+               int res = 1;
+               int x = s.length() - 1;
+               int idx;
+               try {
+                       long[] l =
+                               {
+                                       1000,
+                                       1 << 10,
+                                       1000 * 1000,
+                                       1 << 20,
+                                       1000 * 1000 * 1000,
+                                       1 << 30 };
+                       while (x >= 0
+                               && ((idx = "kKmMgG".indexOf(s.charAt(x))) != 
-1)) {
+                               x--;
+                               res *= l[idx];
+                       }
+                       res *= Double.parseDouble(s.substring(0, x + 1));
+               } catch (ArithmeticException e) {
+                       res = Integer.MAX_VALUE;
+                       throw new NumberFormatException(e.getMessage());
+               }
+               return res;
+       }
+       
+       /**
+        * Parse a human-readable string possibly including SI units into a 
long.
+        * @throws NumberFormatException
+        *             if the string is not parseable
+        */
+       public static long parseLong(String s) throws NumberFormatException {
+               long res = 1;
+               int x = s.length() - 1;
+               int idx;
+               try {
+                       long[] l =
+                               {
+                                       1000,
+                                       1 << 10,
+                                       1000 * 1000,
+                                       1 << 20,
+                                       1000 * 1000 * 1000,
+                                       1 << 30,
+                                       1000 * 1000 * 1000 * 1000,
+                                       1 << 40,
+                                       1000 * 1000 * 1000 * 1000 * 1000,
+                                       1 << 50,
+                                       1000 * 1000 * 1000 * 1000 * 1000 * 1000,
+                                       1 << 60 };
+                       while (x >= 0
+                               && ((idx = "kKmMgGtTpPeE".indexOf(s.charAt(x))) 
!= -1)) {
+                               x--;
+                               res *= l[idx];
+                       }
+                       res *= Double.parseDouble(s.substring(0, x + 1));
+               } catch (ArithmeticException e) {
+                       res = Long.MAX_VALUE;
+                       throw new NumberFormatException(e.getMessage());
+               }
+               return res;
+       }
+
 }

Modified: branches/config/src/freenet/support/FileLoggerHook.java
===================================================================
--- branches/config/src/freenet/support/FileLoggerHook.java     2006-02-18 
22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/support/FileLoggerHook.java     2006-02-18 
22:12:47 UTC (rev 8064)
@@ -13,6 +13,7 @@
 import java.io.PrintWriter;
 import java.net.InetAddress;
 import java.text.DateFormat;
+import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.Calendar;
 import java.util.Date;
@@ -96,7 +97,7 @@
        protected long MAX_LIST_BYTES = 10 * (1 << 20);
        // FIXME: should reimplement LinkedList with minimal locking

-       final long maxOldLogfilesDiskUsage;
+       long maxOldLogfilesDiskUsage;
        protected final LinkedList logFiles = new LinkedList();
        private long oldLogFilesDiskSpaceUsage = 0;

@@ -121,7 +122,7 @@
                MAX_LIST_BYTES = len;
        }

-       public void setInterval(String intervalName) {
+       public void setInterval(String intervalName) throws 
IntervalParseException {
                StringBuffer sb = new StringBuffer(intervalName.length());
                for(int i=0;i<intervalName.length();i++) {
                        char c = intervalName.charAt(i);
@@ -151,10 +152,17 @@
                else if (intervalName.equalsIgnoreCase("YEAR"))
                        INTERVAL = Calendar.YEAR;
                else
-                       throw new IllegalArgumentException(
-                               "invalid interval " + intervalName);
+                       throw new IntervalParseException("invalid interval " + 
intervalName);
        }

+       public class IntervalParseException extends Exception {
+
+               public IntervalParseException(String string) {
+                       super(string);
+               }
+
+       }
+
        protected String getHourLogName(Calendar c, boolean compressed) {
                StringBuffer buf = new StringBuffer(50);
                buf.append(baseFilename).append('-');
@@ -243,7 +251,7 @@
                                try {
                                        thisTime = System.currentTimeMillis();
                                        if (baseFilename != null) {
-                                               if (thisTime > nextHour) {
+                                               if (thisTime > nextHour || 
switchedBaseFilename) {
                                                        // Switch logs
                                                        try {
                                                                
logStream.flush();
@@ -292,6 +300,11 @@
                                                        gc.add(INTERVAL, 
INTERVAL_MULTIPLIER);
                                                        nextHour = 
gc.getTimeInMillis();
                                                        
//System.err.println("Rotated");
+                                                       
if(switchedBaseFilename) {
+                                                               
synchronized(FileLoggerHook.class) {
+                                                                       
switchedBaseFilename = false;
+                                                               }
+                                                       }
                                                }
                                        }
                                        if(list.size() == 0) {
@@ -437,16 +450,20 @@
                        maxOldLogfilesDiskUsage);
        }

+       private Object trimOldLogFilesLock;
+       
        public void trimOldLogFiles() {
-               while(oldLogFilesDiskSpaceUsage > maxOldLogfilesDiskUsage) {
-                       OldLogFile olf;
-                       synchronized(logFiles) {
-                               olf = (OldLogFile) logFiles.removeFirst();
+               synchronized(trimOldLogFilesLock) {
+                       while(oldLogFilesDiskSpaceUsage > 
maxOldLogfilesDiskUsage) {
+                               OldLogFile olf;
+                               synchronized(logFiles) {
+                                       olf = (OldLogFile) 
logFiles.removeFirst();
+                               }
+                               olf.filename.delete();
+                               oldLogFilesDiskSpaceUsage -= olf.size;
+                               Logger.minor(this, "Deleting "+olf.filename+" - 
saving "+olf.size+
+                                               " bytes, disk usage now: 
"+oldLogFilesDiskSpaceUsage+" of "+maxOldLogfilesDiskUsage);
                        }
-                       olf.filename.delete();
-                       oldLogFilesDiskSpaceUsage -= olf.size;
-                       Logger.minor(this, "Deleting "+olf.filename+" - saving 
"+olf.size+
-                                       " bytes, disk usage now: 
"+oldLogFilesDiskSpaceUsage+" of "+maxOldLogfilesDiskUsage);
                }
        }

@@ -903,4 +920,45 @@
                        written += toRead;
                }
        }
+
+       /** Set the maximum size of old (gzipped) log files to keep.
+        * Will start to prune old files immediately, but this will likely not 
be completed
+        * by the time the function returns as it is run off-thread.
+        */
+       public void setMaxOldLogsSize(long val) {
+               maxOldLogfilesDiskUsage = val;
+               Runnable r = new Runnable() {
+                       public void run() {
+                               trimOldLogFiles();
+                       }
+               };
+               Thread t = new Thread(r, "Shrink logs");
+               t.setDaemon(true);
+               t.start();
+       }
+
+       private boolean switchedBaseFilename;
+       
+       public void switchBaseFilename(String filename) {
+               synchronized(this) {
+                       this.baseFilename = filename;
+                       switchedBaseFilename = true;
+               }
+       }
+
+       public void waitForSwitch() {
+               synchronized(this) {
+                       if(!switchedBaseFilename) return;
+                       long startTime = System.currentTimeMillis();
+                       long endTime = startTime + 10000;
+                       long now;
+                       while((now = System.currentTimeMillis()) < endTime && 
!switchedBaseFilename) {
+                               try {
+                                       wait(Math.max(1, endTime-now));
+                               } catch (InterruptedException e) {
+                                       // Ignore
+                               }
+                       }
+               }
+       }
 }

Modified: branches/config/src/freenet/support/Logger.java
===================================================================
--- branches/config/src/freenet/support/Logger.java     2006-02-18 22:08:10 UTC 
(rev 8063)
+++ branches/config/src/freenet/support/Logger.java     2006-02-18 22:12:47 UTC 
(rev 8064)
@@ -201,11 +201,36 @@
         System.exit(retcode);
     }

-    public static void globalAddHook(LoggerHook logger2) {
-        ((LoggerHookChain)logger).addHook(logger2);
+    public synchronized static void globalAddHook(LoggerHook logger2) {
+       if(logger instanceof VoidLogger)
+               setupChain();
+               ((LoggerHookChain)logger).addHook(logger2);
     }

     public static void globalSetThreshold(int i) {
         logger.setThreshold(i);
     }
+
+       public synchronized static void globalRemoveHook(FileLoggerHook hook) {
+               if(logger instanceof LoggerHookChain)
+                       ((LoggerHookChain)logger).removeHook(hook);
+               else
+                       System.err.println("Cannot remove hook: "+hook+" global 
logger is "+logger);
+       }
+
+       public synchronized static void destroyChainIfEmpty() {
+               if(logger instanceof VoidLogger) return;
+               if(logger instanceof LoggerHookChain && 
((LoggerHookChain)logger).getHooks().length == 0) {
+                       logger = new VoidLogger();
+               }
+       }
+
+       public static LoggerHookChain getChain() {
+               if(logger instanceof LoggerHookChain)
+                       return (LoggerHookChain) logger;
+               else {
+                       setupChain();
+                       return (LoggerHookChain) logger;
+               }
+       }
 }

Modified: branches/config/src/freenet/support/LoggerHook.java
===================================================================
--- branches/config/src/freenet/support/LoggerHook.java 2006-02-18 22:08:10 UTC 
(rev 8063)
+++ branches/config/src/freenet/support/LoggerHook.java 2006-02-18 22:12:47 UTC 
(rev 8064)
@@ -107,7 +107,7 @@
                return threshold;
        }

-       public void setThreshold(String symbolicThreshold) {
+       public void setThreshold(String symbolicThreshold) throws 
InvalidThresholdException {
                setThreshold(priorityOf(symbolicThreshold));
        }

@@ -141,7 +141,7 @@
      * @param s  A string matching one of the logging priorities, case
      *           insensitive.
      **/
-    public static int priorityOf(String s) {
+    public static int priorityOf(String s) throws InvalidThresholdException {
         if (s.equalsIgnoreCase("error"))
             return Logger.ERROR;
         else if (s.equalsIgnoreCase("normal"))
@@ -152,10 +152,17 @@
             return Logger.DEBUG;
         else if (s.equalsIgnoreCase("debug"))
             return Logger.DEBUG;
-        else 
-            return Logger.NORMAL;
+        else
+               throw new InvalidThresholdException("Unrecognized priority: 
"+s);
+        // return Logger.NORMAL;
     }

+    public static class InvalidThresholdException extends Exception {
+       InvalidThresholdException(String msg) {
+               super(msg);
+       }
+    }
+    
     /**
      * Returns the name of the priority matching a number, null if none do
      * @param priority  The priority

Modified: branches/config/src/freenet/support/SimpleFieldSet.java
===================================================================
--- branches/config/src/freenet/support/SimpleFieldSet.java     2006-02-18 
22:08:10 UTC (rev 8063)
+++ branches/config/src/freenet/support/SimpleFieldSet.java     2006-02-18 
22:12:47 UTC (rev 8064)
@@ -11,8 +11,6 @@
 import java.util.Map;
 import java.util.Set;

-import java.util.Vector;
-
 import freenet.support.io.LineReader;

 /**
@@ -23,32 +21,38 @@
  */
 public class SimpleFieldSet {

-    final Map map;
+       final Map map;
     String endMarker;
+    final boolean multiLevel;
+    static public final char MULTI_LEVEL_CHAR = '.';

-    public SimpleFieldSet(BufferedReader br) throws IOException {
+    public SimpleFieldSet(BufferedReader br, boolean multiLevel) throws 
IOException {
         map = new HashMap();
+        this.multiLevel = multiLevel;
         read(br);
     }

-    public SimpleFieldSet(LineReader lis, int maxLineLength, int 
lineBufferSize) throws IOException {
+    public SimpleFieldSet(LineReader lis, int maxLineLength, int 
lineBufferSize, boolean multiLevel) throws IOException {
        map = new HashMap();
+       this.multiLevel = multiLevel;
        read(lis, maxLineLength, lineBufferSize);
     }

     /**
      * Empty constructor
      */
-    public SimpleFieldSet() {
+    public SimpleFieldSet(boolean multiLevel) {
         map = new HashMap();
+        this.multiLevel = multiLevel;
     }

     /**
      * Construct from a string.
      * @throws IOException if the string is too short or invalid.
      */
-    public SimpleFieldSet(String content) throws IOException {
+    public SimpleFieldSet(String content, boolean multiLevel) throws 
IOException {
         map = new HashMap();
+        this.multiLevel = multiLevel;
         StringReader sr = new StringReader(content);
         BufferedReader br = new BufferedReader(sr);
            read(br);
@@ -58,8 +62,9 @@
      * Construct from a string[].
      * @throws IOException if the string is too short or invalid.
      */
-    public SimpleFieldSet(String[] content) throws IOException {
+    public SimpleFieldSet(String[] content, boolean multiLevel) throws 
IOException {
         map = new HashMap();
+        this.multiLevel = multiLevel;
         String content2=new String();
         for(int i=0;i<content.length;i++)
                content2.concat(content[i]+";");
@@ -89,7 +94,7 @@
                 // Mapping
                 String before = line.substring(0, index);
                 String after = line.substring(index+1);
-                map.put(before, after);
+                put(before, after);
             } else {
                endMarker = line;
                return;
@@ -119,7 +124,7 @@
                 // Mapping
                 String before = line.substring(0, index);
                 String after = line.substring(index+1);
-                map.put(before, after);
+                put(before, after);
             } else {
                endMarker = line;
                return;
@@ -127,56 +132,97 @@

         }
     }
-

     public String get(String key) {
-        return (String) map.get(key);
+       if(multiLevel) {
+               int idx = key.indexOf(MULTI_LEVEL_CHAR);
+               if(idx == -1)
+                       return (String) map.get(key);
+               else {
+                       String before = key.substring(0, idx);
+                       String after = key.substring(idx+1);
+                       SimpleFieldSet fs = (SimpleFieldSet) (map.get(before));
+                       if(fs == null) return null;
+                       return fs.get(after);
+               }
+       } else {
+               return (String) map.get(key);
+       }
     }

     public String[] getAll(String key) {
-       int index = key.indexOf(';');
-       if(index == -1) return null;
-       Vector v=new Vector();
-       v.removeAllElements();
-        while(index>0){
-            // Mapping
-            String before = key.substring(0, index);         
-            String after = key.substring(index+1);
-            v.addElement(before);
-            key=after;
-            index = key.indexOf(';');
-        }
-       
-       return (String[]) v.toArray();
+       return split(get(key));
     }

-    public void put(String key, String value) {
-       String x = (String) map.get(key);
-       
-       if(x == null) {
-               map.put(key, value);
-       } else {
-               map.put(key, ((String)map.get(key))+";"+value);
-       }
+    private static final String[] split(String string) {
+       return string.split(";"); // slower???
+//     int index = string.indexOf(';');
+//     if(index == -1) return null;
+//     Vector v=new Vector();
+//     v.removeAllElements();
+//        while(index>0){
+//            // Mapping
+//            String before = string.substring(0, index);         
+//            String after = string.substring(index+1);
+//            v.addElement(before);
+//            string=after;
+//            index = string.indexOf(';');
+//        }
+//     
+//     return (String[]) v.toArray();
+       }
+
+       public void put(String key, String value) {
+               int idx;
+               if((!multiLevel) || (idx = key.indexOf(MULTI_LEVEL_CHAR)) == 
-1) {
+                       String x = (String) map.get(key);
+                       
+                       if(x == null) {
+                               map.put(key, value);
+                       } else {
+                               map.put(key, ((String)map.get(key))+";"+value);
+                       }
+               } else {
+                       String before = key.substring(0, idx);
+                       String after = key.substring(idx+1);
+                       SimpleFieldSet fs = (SimpleFieldSet) (map.get(before));
+                       if(fs == null) {
+                               fs = new SimpleFieldSet(true);
+                               map.put(before, fs);
+                       }
+                       fs.put(after, value);
+               }
     }

     /**
      * Write the contents of the SimpleFieldSet to a Writer.
      * @param osr
      */
-    public void writeTo(Writer w) throws IOException {
+       public void writeTo(Writer w) throws IOException {
+               writeTo(w, "", false);
+       }
+       
+    void writeTo(Writer w, String prefix, boolean noEndMarker) throws 
IOException {
         Set s = map.entrySet();
         Iterator i = s.iterator();
         for(;i.hasNext();) {
             Map.Entry entry = (Map.Entry) i.next();
             String key = (String) entry.getKey();
-            String value = (String) entry.getValue();
-            w.write(key+"="+value+"\n");
+            Object v = entry.getValue();
+            if(v instanceof String) {
+                String value = (String) v;
+                w.write(prefix+key+"="+value+"\n");
+            } else {
+               SimpleFieldSet sfs = (SimpleFieldSet) v;
+               sfs.writeTo(w, prefix+key+MULTI_LEVEL_CHAR, true);
+            }
         }
-        if(endMarker != null)
-               w.write(endMarker+"\n");
-        else
-               w.write("End\n");
+        if(!noEndMarker) {
+               if(endMarker != null)
+                       w.write(endMarker+"\n");
+               else
+                       w.write("End\n");
+        }
     }

     public String toString() {
@@ -196,4 +242,73 @@
     public void setEndMarker(String s) {
        endMarker = s;
     }
+
+       public SimpleFieldSet subset(String key) {
+               if(!multiLevel)
+                       throw new IllegalArgumentException("Not multi-level!");
+               int idx = key.indexOf(MULTI_LEVEL_CHAR);
+               if(idx == -1)
+                       return (SimpleFieldSet) map.get(key);
+               String before = key.substring(0, idx);
+               String after = key.substring(idx+1);
+               SimpleFieldSet fs = (SimpleFieldSet) map.get(before);
+               if(fs == null) return null;
+               return fs.subset(after);
+       }
+
+       public Iterator keyIterator() {
+               return new KeyIterator();
+       }
+       
+    public class KeyIterator implements Iterator {
+       
+       final Iterator mapIterator;
+       KeyIterator subIterator;
+       
+       public KeyIterator() {
+               mapIterator = map.keySet().iterator();
+       }
+
+               public boolean hasNext() {
+                       if(subIterator != null && subIterator.hasNext()) return 
true;
+                       if(subIterator != null) subIterator = null;
+                       return mapIterator.hasNext();
+               }
+
+               public Object next() {
+                       while(true) { // tail-recurse so we get infinite loop 
instead of OOM in case of a loop...
+                               if(subIterator != null && subIterator.hasNext())
+                                       return subIterator.next();
+                               if(subIterator != null) subIterator = null;
+                               if(mapIterator.hasNext()) {
+                                       String key = (String) 
mapIterator.next();
+                                       Object value = map.get(key);
+                                       if(value instanceof String)
+                                               return value;
+                                       else {
+                                               SimpleFieldSet fs = 
(SimpleFieldSet) value;
+                                               subIterator = (KeyIterator) 
fs.keyIterator();
+                                               continue;
+                                       }
+                               }
+                               return null;
+                       }
+               }
+
+               public void remove() {
+                       throw new UnsupportedOperationException();
+               }
+
+       }
+
+       public void put(String key, SimpleFieldSet fs) {
+               if(!multiLevel)
+                       throw new IllegalArgumentException("Not multi-level");
+               if(!fs.multiLevel)
+                       throw new IllegalArgumentException("Argument not 
multi-level");
+               if(map.containsKey(key))
+                       throw new IllegalArgumentException("Already contains 
"+key+" but trying to add a SimpleFieldSet!");
+               map.put(key, fs);
+       }
+
 }

Modified: branches/config/src/snmplib/SNMPStarter.java
===================================================================
--- branches/config/src/snmplib/SNMPStarter.java        2006-02-18 22:08:10 UTC 
(rev 8063)
+++ branches/config/src/snmplib/SNMPStarter.java        2006-02-18 22:12:47 UTC 
(rev 8064)
@@ -1,6 +1,8 @@
 package snmplib;

+import freenet.config.Config;
 import freenet.io.comm.IOStatisticCollector;
+import freenet.node.Node;

 /**
  * @author cyberdo
@@ -23,4 +25,8 @@

                has_been_runned = true;
        }
+
+       public static void maybeCreate(Node node, Config config) {
+               // FIXME any config needed for SNMPStarter?
+       }
 }


Reply via email to