Author: toad
Date: 2008-04-14 16:00:44 +0000 (Mon, 14 Apr 2008)
New Revision: 19326

Added:
   trunk/freenet/src/freenet/node/fcp/RequestCompletionCallback.java
   trunk/freenet/src/freenet/pluginmanager/FredPluginVersioned.java
   trunk/freenet/src/freenet/support/OOMHook.java
   trunk/freenet/test/freenet/support/BufferTest.java
   trunk/freenet/test/net/i2p/util/NativeBigIntegerTest.java
Removed:
   
trunk/freenet/src/freenet/clients/http/staticfiles/themes/grayandblue/bluebuttonbg.gif
   
trunk/freenet/src/freenet/clients/http/staticfiles/themes/grayandblue/bluebuttonbg_larger1.gif
Modified:
   trunk/freenet/build.xml
   trunk/freenet/src/freenet/client/async/ClientRequestScheduler.java
   trunk/freenet/src/freenet/clients/http/BookmarkEditorToadlet.java
   trunk/freenet/src/freenet/clients/http/PproxyToadlet.java
   trunk/freenet/src/freenet/clients/http/QueueToadlet.java
   trunk/freenet/src/freenet/clients/http/StaticToadlet.java
   trunk/freenet/src/freenet/clients/http/StatisticsToadlet.java
   trunk/freenet/src/freenet/clients/http/ToadletContext.java
   trunk/freenet/src/freenet/clients/http/ToadletContextImpl.java
   trunk/freenet/src/freenet/clients/http/bookmark/BookmarkManager.java
   trunk/freenet/src/freenet/clients/http/filter/HTMLFilter.java
   trunk/freenet/src/freenet/clients/http/staticfiles/defaultbookmarks.dat
   trunk/freenet/src/freenet/clients/http/staticfiles/themes/boxed/theme.css
   
trunk/freenet/src/freenet/clients/http/staticfiles/themes/grayandblue/theme.css
   trunk/freenet/src/freenet/clients/http/staticfiles/themes/sky/theme.css
   trunk/freenet/src/freenet/config/Option.java
   trunk/freenet/src/freenet/crypt/RandomSource.java
   trunk/freenet/src/freenet/io/AddressTracker.java
   trunk/freenet/src/freenet/io/comm/FreenetInetAddress.java
   trunk/freenet/src/freenet/keys/BaseClientKey.java
   trunk/freenet/src/freenet/keys/ClientCHKBlock.java
   trunk/freenet/src/freenet/keys/FreenetURI.java
   trunk/freenet/src/freenet/keys/NodeCHK.java
   trunk/freenet/src/freenet/keys/NodeSSK.java
   trunk/freenet/src/freenet/l10n/freenet.l10n.de.properties
   trunk/freenet/src/freenet/l10n/freenet.l10n.en.properties
   trunk/freenet/src/freenet/l10n/freenet.l10n.fr.properties
   trunk/freenet/src/freenet/node/DarknetPeerNode.java
   trunk/freenet/src/freenet/node/FNPPacketMangler.java
   trunk/freenet/src/freenet/node/FailureTable.java
   trunk/freenet/src/freenet/node/IPDetectorPluginManager.java
   trunk/freenet/src/freenet/node/LocationManager.java
   trunk/freenet/src/freenet/node/MemoryChecker.java
   trunk/freenet/src/freenet/node/Node.java
   trunk/freenet/src/freenet/node/NodeCrypto.java
   trunk/freenet/src/freenet/node/NodeIPPortDetector.java
   trunk/freenet/src/freenet/node/OpennetManager.java
   trunk/freenet/src/freenet/node/PacketSender.java
   trunk/freenet/src/freenet/node/PeerManager.java
   trunk/freenet/src/freenet/node/PeerNode.java
   trunk/freenet/src/freenet/node/UptimeEstimator.java
   trunk/freenet/src/freenet/node/fcp/ClientGet.java
   trunk/freenet/src/freenet/node/fcp/ClientPutBase.java
   trunk/freenet/src/freenet/node/fcp/ClientPutComplexDirMessage.java
   trunk/freenet/src/freenet/node/fcp/FCPClient.java
   trunk/freenet/src/freenet/node/fcp/FCPServer.java
   trunk/freenet/src/freenet/node/fcp/PersistentPutDir.java
   trunk/freenet/src/freenet/node/updater/NodeUpdater.java
   trunk/freenet/src/freenet/node/useralerts/OpennetUserAlert.java
   trunk/freenet/src/freenet/node/useralerts/UserAlertManager.java
   trunk/freenet/src/freenet/pluginmanager/PluginInfoWrapper.java
   trunk/freenet/src/freenet/pluginmanager/PluginManager.java
   trunk/freenet/src/freenet/store/BerkeleyDBFreenetStore.java
   trunk/freenet/src/freenet/store/CHKStore.java
   trunk/freenet/src/freenet/store/FreenetStore.java
   trunk/freenet/src/freenet/store/PubkeyStore.java
   trunk/freenet/src/freenet/store/RAMFreenetStore.java
   trunk/freenet/src/freenet/store/SSKStore.java
   trunk/freenet/src/freenet/store/StoreCallback.java
   trunk/freenet/src/freenet/support/Base64.java
   trunk/freenet/src/freenet/support/Buffer.java
   trunk/freenet/src/freenet/support/Logger.java
   trunk/freenet/src/freenet/support/LoggerHookChain.java
   trunk/freenet/src/freenet/support/NumberedItem.java
   trunk/freenet/src/freenet/support/NumberedItemComparator.java
   trunk/freenet/src/freenet/support/OOMHandler.java
   trunk/freenet/src/freenet/support/SectoredRandomGrabArray.java
   trunk/freenet/src/freenet/support/SizeUtil.java
   trunk/freenet/src/freenet/support/SortedLongSet.java
   trunk/freenet/src/freenet/support/WeakHashSet.java
   trunk/freenet/src/freenet/support/io/BaseFileBucket.java
   trunk/freenet/src/freenet/support/io/FileBucket.java
   trunk/freenet/src/freenet/support/io/MultiReaderBucket.java
   trunk/freenet/src/freenet/support/io/PaddedEphemerallyEncryptedBucket.java
   trunk/freenet/src/freenet/support/math/SimpleBinaryRunningAverage.java
   trunk/freenet/src/freenet/support/math/TrivialRunningAverage.java
   trunk/freenet/src/freenet/tools/AddRef.java
   trunk/freenet/src/net/i2p/util/NativeBigInteger.java
   trunk/freenet/test/freenet/support/SortedLongSetTest.java
   trunk/freenet/test/freenet/support/compress/GzipCompressorTest.java
Log:
Revert the revert: go back to r19322, except for Version.java which we keep.



Modified: trunk/freenet/build.xml
===================================================================
--- trunk/freenet/build.xml     2008-04-14 15:55:20 UTC (rev 19325)
+++ trunk/freenet/build.xml     2008-04-14 16:00:44 UTC (rev 19326)
@@ -155,6 +155,8 @@
                                        <include name="**/*.class"/>
                                </fileset>
                        </batchtest>
+                       <sysproperty key="benchmark" value="${benchmark}" />
+                       <sysproperty key="extensiveTesting" 
value="${extensiveTesting}" />
                </junit>
        </target>


Modified: trunk/freenet/src/freenet/client/async/ClientRequestScheduler.java
===================================================================
--- trunk/freenet/src/freenet/client/async/ClientRequestScheduler.java  
2008-04-14 15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/client/async/ClientRequestScheduler.java  
2008-04-14 16:00:44 UTC (rev 19326)
@@ -531,10 +531,11 @@
                                }
                        } else {
                                SendableGet[] gets = (SendableGet[]) o;
-                               SendableGet[] newGets = new 
SendableGet[gets.length-1];
+                               final int getsLength = gets.length;
+                               SendableGet[] newGets = new 
SendableGet[getsLength > 1 ? getsLength-1 : 0];
                                boolean found = false;
                                int x = 0;
-                               for(int j=0;j<gets.length;j++) {
+                               for(int j=0;j<getsLength;j++) {
                                        if(gets[j] == getter) {
                                                found = true;
                                                continue;
@@ -542,7 +543,7 @@
                                        if(j == newGets.length) {
                                                if(!found) {
                                                        if(complain)
-                                                               
Logger.normal(this, "Not found: "+getter+" for "+key+" removing 
("+gets.length+" getters)");
+                                                               
Logger.normal(this, "Not found: "+getter+" for "+key+" removing ("+getsLength+" 
getters)");
                                                        return; // not here
                                                }
                                        }
@@ -555,7 +556,7 @@
                                } else if(x == 1) {
                                        pendingKeys.put(key, newGets[0]);
                                } else {
-                                       if(x != gets.length-1) {
+                                       if(x != getsLength-1) {
                                                SendableGet[] newNewGets = new 
SendableGet[x];
                                                System.arraycopy(newGets, 0, 
newNewGets, 0, x);
                                                newGets = newNewGets;

Modified: trunk/freenet/src/freenet/clients/http/BookmarkEditorToadlet.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/BookmarkEditorToadlet.java   
2008-04-14 15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/clients/http/BookmarkEditorToadlet.java   
2008-04-14 16:00:44 UTC (rev 19326)
@@ -16,6 +16,7 @@
 import freenet.node.NodeClientCore;
 import freenet.client.HighLevelSimpleClient;
 import freenet.support.HTMLNode;
+import freenet.support.Logger;
 import freenet.support.URLDecoder;
 import freenet.support.URLEncodedFormatException;
 import freenet.support.URLEncoder;
@@ -238,8 +239,9 @@
                        HTMLNode infoBox = 
content.addChild(pageMaker.getInfobox("infobox-normal", 
L10n.getString("BookmarkEditorToadlet.pasteTitle")));
                        HTMLNode infoBoxContent = 
pageMaker.getContentNode(infoBox);
                        infoBoxContent.addChild("#", 
L10n.getString("BookmarkEditorToadlet.pasteOrCancel"));
-                       HTMLNode cancelForm = ctx.addFormChild(infoBoxContent, 
"", "cancelCutForm");
+                       HTMLNode cancelForm = ctx.addFormChild(infoBoxContent, 
"/bookmarkEditor/", "cancelCutForm");
                        cancelForm.addChild("input", new String[]{"type", 
"name", "value"}, new String[]{"submit", "cancelCut", 
L10n.getString("BookmarkEditorToadlet.cancelCut")});
+                       cancelForm.addChild("input", new String[]{"type", 
"name", "value"}, new String[]{"hidden", "action", "cancelCut"});
                }

                HTMLNode bookmarksBox = 
content.addChild(pageMaker.getInfobox("infobox-normal", 
L10n.getString("BookmarkEditorToadlet.myBookmarksTitle")));
@@ -248,6 +250,9 @@
                HTMLNode addDefaultBookmarksForm = ctx.addFormChild(content, 
"", "AddDefaultBookmarks");
                addDefaultBookmarksForm.addChild("input", new String[]{"type", 
"name", "value"}, new String[]{"submit", "AddDefaultBookmarks", 
L10n.getString("BookmarkEditorToadlet.addDefaultBookmarks")});

+               if(Logger.shouldLog(Logger.DEBUG, this))
+                       Logger.debug(this, "Returning:\n"+pageNode.generate());
+               
                this.writeHTMLReply(ctx, 200, "OK", pageNode.generate());
        }

@@ -278,7 +283,7 @@
                                bookmark = 
bookmarkManager.getCategoryByPath(bookmarkPath);
                        else
                                bookmark = 
bookmarkManager.getItemByPath(bookmarkPath);
-                       if(bookmark == null) {
+                       if(bookmark == null && !req.isPartSet("cancelCut")) {
                                HTMLNode errorBox = 
content.addChild(pageMaker.getInfobox("infobox-error", 
L10n.getString("BookmarkEditorToadlet.error")));
                                
pageMaker.getContentNode(errorBox).addChild("#", 
L10n.getString("BookmarkEditorToadlet.bookmarkDoesNotExist", new 
String[]{"bookmark"}, new String[]{bookmarkPath}));
                                this.writeHTMLReply(ctx, 200, "OK", 
pageNode.generate());

Modified: trunk/freenet/src/freenet/clients/http/PproxyToadlet.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/PproxyToadlet.java   2008-04-14 
15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/clients/http/PproxyToadlet.java   2008-04-14 
16:00:44 UTC (rev 19326)
@@ -422,6 +422,7 @@
                        HTMLNode pluginTable = infoboxContent.addChild("table", 
"class", "plugins");
                        HTMLNode headerRow = pluginTable.addChild("tr");
                        headerRow.addChild("th", l10n("classNameTitle"));
+                       headerRow.addChild("th", l10n("versionTitle"));
                        headerRow.addChild("th", l10n("internalIDTitle"));
                        headerRow.addChild("th", l10n("startedAtTitle"));
                        headerRow.addChild("th");
@@ -432,6 +433,7 @@
                                PluginInfoWrapper pi = (PluginInfoWrapper) 
it.next();
                                HTMLNode pluginRow = pluginTable.addChild("tr");
                                pluginRow.addChild("td", 
pi.getPluginClassName());
+                               pluginRow.addChild("td", pi.getPluginVersion());
                                pluginRow.addChild("td", pi.getThreadName());
                                pluginRow.addChild("td", new 
Date(pi.getStarted()).toString());
                                if (pi.isStopping()) {

Modified: trunk/freenet/src/freenet/clients/http/QueueToadlet.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/QueueToadlet.java    2008-04-14 
15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/clients/http/QueueToadlet.java    2008-04-14 
16:00:44 UTC (rev 19326)
@@ -3,14 +3,24 @@
  * http://www.gnu.org/ for further details of the GPL. */
 package freenet.clients.http;

+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.EOFException;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
 import java.net.MalformedURLException;
 import java.net.URI;
 import java.text.NumberFormat;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
@@ -30,6 +40,9 @@
 import freenet.node.fcp.IdentifierCollisionException;
 import freenet.node.fcp.MessageInvalidException;
 import freenet.node.fcp.NotAllowedException;
+import freenet.node.fcp.RequestCompletionCallback;
+import freenet.node.useralerts.SimpleHTMLUserAlert;
+import freenet.node.useralerts.UserAlert;
 import freenet.support.HTMLNode;
 import freenet.support.Logger;
 import freenet.support.MultiValueTable;
@@ -38,9 +51,10 @@
 import freenet.support.api.HTTPRequest;
 import freenet.support.api.HTTPUploadedFile;
 import freenet.support.io.BucketTools;
+import freenet.support.io.Closer;
 import freenet.support.io.FileBucket;

-public class QueueToadlet extends Toadlet {
+public class QueueToadlet extends Toadlet implements RequestCompletionCallback 
{

        private static final int LIST_IDENTIFIER = 1;
        private static final int LIST_SIZE = 2;
@@ -70,6 +84,8 @@
                this.core = core;
                this.fcp = fcp;
                if(fcp == null) throw new NullPointerException();
+               fcp.setCompletionCallback(this);
+               loadCompletedIdentifiers();
        }

        public void handlePost(URI uri, HTTPRequest request, ToadletContext 
ctx) throws ToadletContextClosedException, IOException, RedirectException {
@@ -1051,4 +1067,191 @@
                return "GET, POST";
        }

+       /**
+        * List of completed request identifiers which the user hasn't 
acknowledged yet.
+        */
+       private final HashSet completedRequestIdentifiers = new HashSet();
+       
+       public void notifyFailure(ClientRequest req) {
+               // FIXME do something???
+       }
+
+       public void notifySuccess(ClientRequest req) {
+               synchronized(completedRequestIdentifiers) {
+                       completedRequestIdentifiers.add(req.getIdentifier());
+               }
+               registerAlert(req);
+               saveCompletedIdentifiersOffThread();
+       }
+       
+       private void saveCompletedIdentifiersOffThread() {
+               core.getExecutor().execute(new Runnable() {
+                       public void run() {
+                               saveCompletedIdentifiers();
+                       }
+               }, "Save completed identifiers");
+       }
+
+       private void loadCompletedIdentifiers() {
+               File completedIdentifiersList = new 
File(core.node.getNodeDir(), "completed.list");
+               File completedIdentifiersListNew = new 
File(core.node.getNodeDir(), "completed.list.bak");
+               if(!readCompletedIdentifiers(completedIdentifiersList)) {
+                       readCompletedIdentifiers(completedIdentifiersListNew);
+               }
+               String[] identifiers;
+               synchronized(completedRequestIdentifiers) {
+                       identifiers = (String[]) 
completedRequestIdentifiers.toArray(new 
String[completedRequestIdentifiers.size()]);
+               }
+               for(int i=0;i<identifiers.length;i++) {
+                       ClientRequest req = 
fcp.getGlobalClient().getRequest(identifiers[i]);
+                       if(req == null) continue;
+                       registerAlert(req);
+               }
+       }
+       
+       private boolean readCompletedIdentifiers(File file) {
+               FileInputStream fis = null;
+               try {
+                       fis = new FileInputStream(file);
+                       BufferedInputStream bis = new BufferedInputStream(fis);
+                       InputStreamReader isr = new InputStreamReader(bis, 
"UTF-8");
+                       BufferedReader br = new BufferedReader(isr);
+                       synchronized(completedRequestIdentifiers) {
+                               completedRequestIdentifiers.clear();
+                               while(true) {
+                                       String identifier = br.readLine();
+                                       if(identifier == null) return true;
+                                       
completedRequestIdentifiers.add(identifier);
+                               }
+                       }
+               } catch (EOFException e) {
+                       // Normal
+                       return true;
+               } catch (FileNotFoundException e) {
+                       // Normal
+                       return false;
+               } catch (UnsupportedEncodingException e) {
+                       throw new RuntimeException(e);
+               } catch (IOException e) {
+                       Logger.error(this, "Could not read completed 
identifiers list from "+file);
+                       return false;
+               } finally {
+                       Closer.close(fis);
+               }
+       }
+
+       private void saveCompletedIdentifiers() {
+               FileOutputStream fos = null;
+               BufferedWriter bw = null;
+               File completedIdentifiersList = new 
File(core.node.getNodeDir(), "completed.list");
+               File completedIdentifiersListNew = new 
File(core.node.getNodeDir(), "completed.list.bak");
+               File temp;
+               try {
+                       temp = File.createTempFile("completed.list", ".tmp", 
core.node.getNodeDir());
+                       temp.deleteOnExit();
+                       fos = new FileOutputStream(temp);
+                       OutputStreamWriter osw = new OutputStreamWriter(fos, 
"UTF-8");
+                       bw = new BufferedWriter(osw);
+                       String[] identifiers;
+                       synchronized(completedRequestIdentifiers) {
+                               identifiers = (String[]) 
completedRequestIdentifiers.toArray(new 
String[completedRequestIdentifiers.size()]);
+                       }
+                       for(int i=0;i<identifiers.length;i++)
+                               bw.write(identifiers[i]+'\n');
+               } catch (FileNotFoundException e) {
+                       Logger.error(this, "Unable to save completed requests 
list (can't find node directory?!!?): "+e, e);
+                       return;
+               } catch (IOException e) {
+                       Logger.error(this, "Unable to save completed requests 
list: "+e, e);
+                       return;
+               } finally {
+                       if(bw != null) {
+                               try {
+                                       bw.close();
+                               } catch (IOException e) {
+                                       try {
+                                               fos.close();
+                                       } catch (IOException e1) {
+                                               // Ignore
+                                       }
+                               }
+                       } else {
+                               try {
+                                       fos.close();
+                               } catch (IOException e1) {
+                                       // Ignore
+                               }
+                       }
+               }
+               completedIdentifiersListNew.delete();
+               temp.renameTo(completedIdentifiersListNew);
+               
if(!completedIdentifiersListNew.renameTo(completedIdentifiersList)) {
+                       completedIdentifiersList.delete();
+                       
if(!completedIdentifiersListNew.renameTo(completedIdentifiersList)) {
+                               Logger.error(this, "Unable to store completed 
identifiers list because unable to rename "+completedIdentifiersListNew+" to 
"+completedIdentifiersList);
+                       }
+               }
+       }
+
+       private void registerAlert(ClientRequest req) {
+               final String identifier = req.getIdentifier();
+               if(req instanceof ClientGet) {
+                       FreenetURI uri = ((ClientGet)req).getURI();
+                       long size = ((ClientGet)req).getDataSize();
+                       String name = uri.getPreferredFilename();
+                       String title = l10n("downloadSucceededTitle", 
"filename", name);
+                       HTMLNode text = new HTMLNode("div");
+                       L10n.addL10nSubstitution(text, 
"QueueToadlet.downloadSucceeded",
+                                       new String[] { "link", "/link", 
"origlink", "/origlink", "filename", "size" },
+                                       new String[] { "<a 
href=\"/queue/"+uri.toACIIString()+"\">", "</a>", "<a 
href=\"/"+uri.toACIIString()+"\">", "</a>", name, SizeUtil.formatSize(size) } );
+                       core.alerts.register(new SimpleHTMLUserAlert(true, 
title, text, UserAlert.MINOR) {
+                               public void onDismiss() {
+                                       
synchronized(completedRequestIdentifiers) {
+                                               
completedRequestIdentifiers.remove(identifier);
+                                       }
+                                       saveCompletedIdentifiersOffThread();
+                               }
+                       });
+               } else if(req instanceof ClientPut) {
+                       FreenetURI uri = ((ClientPut)req).getFinalURI();
+                       long size = ((ClientPut)req).getDataSize();
+                       String name = uri.getPreferredFilename();
+                       String title = l10n("uploadSucceededTitle", "filename", 
name);
+                       HTMLNode text = new HTMLNode("div");
+                       L10n.addL10nSubstitution(text, 
"QueueToadlet.uploadSucceeded",
+                                       new String[] { "link", "/link", 
"filename", "size" },
+                                       new String[] { "<a 
href=\"/"+uri.toACIIString()+"\">", "</a>", name, SizeUtil.formatSize(size) } );
+                       core.alerts.register(new SimpleHTMLUserAlert(true, 
title, text, UserAlert.MINOR) {
+                               public void onDismiss() {
+                                       
synchronized(completedRequestIdentifiers) {
+                                               
completedRequestIdentifiers.remove(identifier);
+                                       }
+                                       saveCompletedIdentifiersOffThread();
+                               }
+                       });
+               } else if(req instanceof ClientPutDir) {
+                       FreenetURI uri = ((ClientPutDir)req).getFinalURI();
+                       long size = ((ClientPutDir)req).getTotalDataSize();
+                       int files = ((ClientPutDir)req).getNumberOfFiles();
+                       String name = uri.getPreferredFilename();
+                       String title = 
l10n("QueueToadlet.siteUploadSucceededTitle", "filename", name);
+                       HTMLNode text = new HTMLNode("div");
+                       L10n.addL10nSubstitution(text, 
"QueueToadlet.siteUploadSucceeded",
+                                       new String[] { "link", "/link", 
"filename", "size", "files" },
+                                       new String[] { "<a 
href=\"/"+uri.toACIIString()+"\">", "</a>", name, SizeUtil.formatSize(size), 
Integer.toString(files) } );
+                       core.alerts.register(new SimpleHTMLUserAlert(true, 
title, text, UserAlert.MINOR) {
+                               public void onDismiss() {
+                                       
synchronized(completedRequestIdentifiers) {
+                                               
completedRequestIdentifiers.remove(identifier);
+                                       }
+                                       saveCompletedIdentifiersOffThread();
+                               }
+                       });
+               }
+       }
+
+       String l10n(String key, String pattern, String value) {
+               return L10n.getString("QueueToadlet."+key, pattern, value);
+       }
+
 }

Modified: trunk/freenet/src/freenet/clients/http/StaticToadlet.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/StaticToadlet.java   2008-04-14 
15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/clients/http/StaticToadlet.java   2008-04-14 
16:00:44 UTC (rev 19326)
@@ -1,9 +1,12 @@
 package freenet.clients.http;

+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.URI;
+import java.net.URL;
+import java.util.Date;

 import freenet.client.DefaultMIMETypes;
 import freenet.l10n.L10n;
@@ -58,12 +61,32 @@
                strm.close();
                os.close();

-               ctx.sendReplyHeaders(200, "OK", null, 
DefaultMIMETypes.guessMIMEType(path, false), data.size());
+               URL url = getClass().getResource(ROOT_PATH+path);
+               Date mTime = getUrlMTime(url);
+               
+               ctx.sendReplyHeaders(200, "OK", null, 
DefaultMIMETypes.guessMIMEType(path, false), data.size(), mTime);

                ctx.writeData(data);
                data.free();
        }

+       /**
+        * Try to find the modification time for a URL, or return null if not 
possible
+        * We usually load our resources from the JAR, or possibly from a file 
in some setups, so we check the modification time of
+        * the JAR for resources in a jar and the mtime for files.
+        */
+       private Date getUrlMTime(URL url) {
+               if (url.getProtocol().equals("jar")) {
+                       File f = new File(url.getPath().substring(0, 
url.getPath().indexOf('!')));
+                       return new Date(f.lastModified());
+               } else if (url.getProtocol().equals("file")) {
+                       File f = new File(url.getPath());
+                       return new Date(f.lastModified());
+               } else {
+                       return null;
+               }
+       }
+       
        private String l10n(String key) {
                return L10n.getString("StaticToadlet."+key);
        }

Modified: trunk/freenet/src/freenet/clients/http/StatisticsToadlet.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/StatisticsToadlet.java       
2008-04-14 15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/clients/http/StatisticsToadlet.java       
2008-04-14 16:00:44 UTC (rev 19326)
@@ -481,8 +481,14 @@

                row=storeSizeTable.addChild("tr");
                row.addChild("td", "Success Rate");
-               row.addChild("td", 
fix1p4.format(100.0*storeHits/storeAccesses)+"%");
-               row.addChild("td", 
fix1p4.format(100.0*cacheHits/cacheAccesses)+"%");
+               if (storeAccesses > 0)
+                       row.addChild("td", fix1p4.format(100.0 * storeHits / 
storeAccesses) + "%");
+               else
+                       row.addChild("td", "N/A");
+               if (cacheAccesses > 0)
+                       row.addChild("td", fix1p4.format(100.0 * cacheHits / 
cacheAccesses) + "%");
+               else
+                       row.addChild("td", "N/A");

                row=storeSizeTable.addChild("tr");
                row.addChild("td", "Writes");

Modified: trunk/freenet/src/freenet/clients/http/ToadletContext.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/ToadletContext.java  2008-04-14 
15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/clients/http/ToadletContext.java  2008-04-14 
16:00:44 UTC (rev 19326)
@@ -1,6 +1,7 @@
 package freenet.clients.http;

 import java.io.IOException;
+import java.util.Date;

 import freenet.support.HTMLNode;
 import freenet.support.MultiValueTable;
@@ -20,7 +21,10 @@
         * @param mvt Any extra headers.
         * @param mimeType The MIME type of the reply.
         * @param length The length of the reply.
+        * @param mTime The modification time of the data being sent or null 
for 'now' and disabling caching
         */
+       void sendReplyHeaders(int code, String desc, MultiValueTable mvt, 
String mimeType, long length, Date mTime) throws ToadletContextClosedException, 
IOException;
+       
        void sendReplyHeaders(int code, String desc, MultiValueTable mvt, 
String mimeType, long length) throws ToadletContextClosedException, IOException;

        /**

Modified: trunk/freenet/src/freenet/clients/http/ToadletContextImpl.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/ToadletContextImpl.java      
2008-04-14 15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/clients/http/ToadletContextImpl.java      
2008-04-14 16:00:44 UTC (rev 19326)
@@ -104,7 +104,7 @@
        private static void sendHTMLError(OutputStream os, int code, String 
httpReason, String htmlMessage, boolean disconnect, MultiValueTable mvt) throws 
IOException {
                if(mvt == null) mvt = new MultiValueTable();
                byte[] messageBytes = htmlMessage.getBytes("UTF-8");
-               sendReplyHeaders(os, code, httpReason, mvt, "text/html; 
charset=UTF-8", messageBytes.length, disconnect);
+               sendReplyHeaders(os, code, httpReason, mvt, "text/html; 
charset=UTF-8", messageBytes.length, null, disconnect);
                os.write(messageBytes);
        }

@@ -123,8 +123,12 @@
        }

        public void sendReplyHeaders(int replyCode, String replyDescription, 
MultiValueTable mvt, String mimeType, long contentLength) throws 
ToadletContextClosedException, IOException {
+               sendReplyHeaders(replyCode, replyDescription, mvt, mimeType, 
contentLength, null);
+       }
+       
+       public void sendReplyHeaders(int replyCode, String replyDescription, 
MultiValueTable mvt, String mimeType, long contentLength, Date mTime) throws 
ToadletContextClosedException, IOException {
                if(closed) throw new ToadletContextClosedException();
-               sendReplyHeaders(sockOutputStream, replyCode, replyDescription, 
mvt, mimeType, contentLength, shouldDisconnect);
+               sendReplyHeaders(sockOutputStream, replyCode, replyDescription, 
mvt, mimeType, contentLength, mTime, shouldDisconnect);
        }

        public PageMaker getPageMaker() {
@@ -135,7 +139,7 @@
                return headers;
        }

-       static void sendReplyHeaders(OutputStream sockOutputStream, int 
replyCode, String replyDescription, MultiValueTable mvt, String mimeType, long 
contentLength, boolean disconnect) throws IOException {
+       static void sendReplyHeaders(OutputStream sockOutputStream, int 
replyCode, String replyDescription, MultiValueTable mvt, String mimeType, long 
contentLength, Date mTime, boolean disconnect) throws IOException {
                // Construct headers
                if(mvt == null)
                        mvt = new MultiValueTable();
@@ -147,17 +151,30 @@
                        }
                if(contentLength >= 0)
                        mvt.put("content-length", Long.toString(contentLength));
-               // FIXME allow caching on a config option.
-               // For now cater to the paranoid.
-               // Also this may fix a wierd bug...
-               // All keys are lower-case
-               mvt.put("expires", "Thu, 01 Jan 1970 00:00:00 GMT");
-               // Sent now, expires now.
-               String time = makeHTTPDate(System.currentTimeMillis());
-               mvt.put("last-modified", time);
-               mvt.put("date", time);
-               mvt.put("pragma", "no-cache");
-               mvt.put("cache-control", "max-age=0, must-revalidate, no-cache, 
no-store, post-check=0, pre-check=0");
+               
+               String expiresTime;
+               if (mTime == null) {
+                       expiresTime = "Thu, 01 Jan 1970 00:00:00 GMT";
+               } else {
+                       // use an expiry time of 1 day, somewhat arbitrarily
+                       expiresTime = makeHTTPDate(mTime.getTime() + (24 * 60 * 
60 * 1000));
+               }
+               mvt.put("expires", expiresTime);
+               
+               String nowString = makeHTTPDate(System.currentTimeMillis());
+               String lastModString;
+               if (mTime == null) {
+                       lastModString = nowString;
+               } else {
+                       lastModString = makeHTTPDate(mTime.getTime());
+               }
+               
+               mvt.put("last-modified", lastModString);
+               mvt.put("date", nowString);
+               if (mTime == null) {
+                       mvt.put("pragma", "no-cache");
+                       mvt.put("cache-control", "max-age=0, must-revalidate, 
no-cache, no-store, post-check=0, pre-check=0");
+               }
                if(disconnect)
                        mvt.put("connection", "close");
                else

Modified: trunk/freenet/src/freenet/clients/http/bookmark/BookmarkManager.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/bookmark/BookmarkManager.java        
2008-04-14 15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/clients/http/bookmark/BookmarkManager.java        
2008-04-14 16:00:44 UTC (rev 19326)
@@ -23,7 +23,7 @@

 public class BookmarkManager {

-       public static SimpleFieldSet DEFAULT_BOOKMARKS = null;
+       public static final SimpleFieldSet DEFAULT_BOOKMARKS;
        private final NodeClientCore node;
        private final USKUpdatedCallback uskCB = new USKUpdatedCallback();
        public static final BookmarkCategory MAIN_CATEGORY = new 
BookmarkCategory("/");
@@ -33,6 +33,7 @@
        private boolean isSavingBookmarks = false;
        static {
                String name = 
"freenet/clients/http/staticfiles/defaultbookmarks.dat";
+               SimpleFieldSet defaultBookmarks = null;
                InputStream in = null;
                try {
                        ClassLoader loader = ClassLoader.getSystemClassLoader();
@@ -40,11 +41,12 @@
                        // Returns null on lookup failures:
                        in = loader.getResourceAsStream(name);
                        if(in != null)
-                               DEFAULT_BOOKMARKS = SimpleFieldSet.readFrom(in, 
false, false);
+                               defaultBookmarks = SimpleFieldSet.readFrom(in, 
false, false);
                } catch(Exception e) {
                        Logger.error("BookmarkManager", "Error while loading 
the default bookmark file from " + name + " :" + e.getMessage(), e);
                } finally {
                        Closer.close(in);
+                       DEFAULT_BOOKMARKS = defaultBookmarks;
                }
        }


Modified: trunk/freenet/src/freenet/clients/http/filter/HTMLFilter.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/filter/HTMLFilter.java       
2008-04-14 15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/clients/http/filter/HTMLFilter.java       
2008-04-14 16:00:44 UTC (rev 19326)
@@ -16,6 +16,7 @@
 import java.io.StringWriter;
 import java.io.UnsupportedEncodingException;
 import java.io.Writer;
+import java.nio.charset.MalformedInputException;
 import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -95,6 +96,9 @@
                HTMLParseContext pc = new HTMLParseContext(r, w, null, new 
NullFilterCallback(), true);
                try {
                        pc.run(null);
+               } catch (MalformedInputException e) {
+                       // Not this charset
+                       return null;
                } catch (IOException e) {
                        throw e;
                } catch (Throwable t) {

Modified: 
trunk/freenet/src/freenet/clients/http/staticfiles/defaultbookmarks.dat
===================================================================
--- trunk/freenet/src/freenet/clients/http/staticfiles/defaultbookmarks.dat     
2008-04-14 15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/clients/http/staticfiles/defaultbookmarks.dat     
2008-04-14 16:00:44 UTC (rev 19326)
@@ -15,15 +15,15 @@
 BookmarkCategory0.Content.Bookmark2.Name=Another Index
 BookmarkCategory0.Content.Bookmark2.Description=Comprehensive categorised 
index of freesites and Thaw indexes; links to everything
 BookmarkCategory0.Content.Bookmark2.hasAnActivelink=false
-BookmarkCategory0.Content.Bookmark2.URI=USK at 
zQyF2O1o8B4y40w7Twz8y2I9haW3d2DTlxjTHPu7zc8,h2mhQNNE9aQvF~2yKAmKV1uorr7141-QOroBf5hrlbw,AQACAAE/AnotherIndex/57/
+BookmarkCategory0.Content.Bookmark2.URI=USK at 
zQyF2O1o8B4y40w7Twz8y2I9haW3d2DTlxjTHPu7zc8,h2mhQNNE9aQvF~2yKAmKV1uorr7141-QOroBf5hrlbw,AQACAAE/AnotherIndex/58/
 BookmarkCategory0.Content.Bookmark1.Name=Freenet Activelink Index (warning: it 
will take a long time for all the images to load!)
 BookmarkCategory0.Content.Bookmark1.hasAnActivelink=true
 BookmarkCategory0.Content.Bookmark1.Description=A graphical freenet index 
(this will take a long time to load as it preloads the sites; contains no porn)
-BookmarkCategory0.Content.Bookmark1.URI=USK at 
qd-hk0vHYg7YvK2BQsJMcUD5QSF0tDkgnnF6lnWUH0g,xTFOV9ddCQQk6vQ6G~jfL6IzRUgmfMcZJ6nuySu~NUc,AQACAAE/activelink-index/37/
+BookmarkCategory0.Content.Bookmark1.URI=USK at 
qd-hk0vHYg7YvK2BQsJMcUD5QSF0tDkgnnF6lnWUH0g,xTFOV9ddCQQk6vQ6G~jfL6IzRUgmfMcZJ6nuySu~NUc,AQACAAE/activelink-index/38/
 BookmarkCategory0.Content.Bookmark4.Name=Freenet Activelink Index Text Version 
(non-graphical version of FAI)
 BookmarkCategory0.Content.Bookmark4.hasAnActivelink=true
 BookmarkCategory0.Content.Bookmark4.Description=Text version of the Activelink 
Index
-BookmarkCategory0.Content.Bookmark4.URI=USK at 
qd-hk0vHYg7YvK2BQsJMcUD5QSF0tDkgnnF6lnWUH0g,xTFOV9ddCQQk6vQ6G~jfL6IzRUgmfMcZJ6nuySu~NUc,AQACAAE/activelink-index-text/2/
+BookmarkCategory0.Content.Bookmark4.URI=USK at 
qd-hk0vHYg7YvK2BQsJMcUD5QSF0tDkgnnF6lnWUH0g,xTFOV9ddCQQk6vQ6G~jfL6IzRUgmfMcZJ6nuySu~NUc,AQACAAE/activelink-index-text/3/
 BookmarkCategory1.Name=Freenet devel's flogs
 BookmarkCategory1.Content.BookmarkCategory=0
 BookmarkCategory1.Content.Bookmark=4
@@ -34,7 +34,7 @@
 BookmarkCategory1.Content.Bookmark2.Name=Bombe
 BookmarkCategory1.Content.Bookmark2.Description=Bombe's blog
 BookmarkCategory1.Content.Bookmark2.hasAnActivelink=true
-BookmarkCategory1.Content.Bookmark2.URI=USK at 
e3myoFyp5avg6WYN16ImHri6J7Nj8980Fm~aQe4EX1U,QvbWT0ImE0TwLODTl7EoJx2NBnwDxTbLTE6zkB-eGPs,AQACAAE/bombe/32/
+BookmarkCategory1.Content.Bookmark2.URI=USK at 
e3myoFyp5avg6WYN16ImHri6J7Nj8980Fm~aQe4EX1U,QvbWT0ImE0TwLODTl7EoJx2NBnwDxTbLTE6zkB-eGPs,AQACAAE/bombe/33/
 BookmarkCategory1.Content.Bookmark1.Name=Nextgen$
 BookmarkCategory1.Content.Bookmark1.Description=NextGen$' blog
 BookmarkCategory1.Content.Bookmark1.hasAnActivelink=true
@@ -53,7 +53,7 @@
 BookmarkCategory2.Content.Bookmark1.Name=Freenet Message System
 BookmarkCategory2.Content.Bookmark1.Description=The official freesite of FMS, 
a spam resistant message board system for Freenet
 BookmarkCategory2.Content.Bookmark1.hasAnActivelink=true
-BookmarkCategory2.Content.Bookmark1.URI=USK at 
0npnMrqZNKRCRoGojZV93UNHCMN-6UU3rRSAmP6jNLE,~BG-edFtdCC1cSH4O3BWdeIYa8Sw5DfyrSV-TKdO5ec,AQACAAE/fms/42/
+BookmarkCategory2.Content.Bookmark1.URI=USK at 
0npnMrqZNKRCRoGojZV93UNHCMN-6UU3rRSAmP6jNLE,~BG-edFtdCC1cSH4O3BWdeIYa8Sw5DfyrSV-TKdO5ec,AQACAAE/fms/43/
 BookmarkCategory2.Content.Bookmark2.Name=Publish!
 BookmarkCategory2.Content.Bookmark2.Description=Simple guide to publishing web 
sites to Freenet
 BookmarkCategory2.Content.Bookmark2.hasAnActivelink=true

Modified: 
trunk/freenet/src/freenet/clients/http/staticfiles/themes/boxed/theme.css
===================================================================
--- trunk/freenet/src/freenet/clients/http/staticfiles/themes/boxed/theme.css   
2008-04-14 15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/clients/http/staticfiles/themes/boxed/theme.css   
2008-04-14 16:00:44 UTC (rev 19326)
@@ -329,7 +329,7 @@

 .request-progress {
        width: 100px;
-       vertical-align: center;
+       vertical-align: middle;
 }

 .progressbar {

Deleted: 
trunk/freenet/src/freenet/clients/http/staticfiles/themes/grayandblue/bluebuttonbg.gif
===================================================================
(Binary files differ)

Deleted: 
trunk/freenet/src/freenet/clients/http/staticfiles/themes/grayandblue/bluebuttonbg_larger1.gif
===================================================================
(Binary files differ)

Modified: 
trunk/freenet/src/freenet/clients/http/staticfiles/themes/grayandblue/theme.css
===================================================================
--- 
trunk/freenet/src/freenet/clients/http/staticfiles/themes/grayandblue/theme.css 
    2008-04-14 15:55:20 UTC (rev 19325)
+++ 
trunk/freenet/src/freenet/clients/http/staticfiles/themes/grayandblue/theme.css 
    2008-04-14 16:00:44 UTC (rev 19326)
@@ -27,22 +27,6 @@
        border:1px dotted #676767;
 }

-.bluebutton {
-       background-image: url(bluebuttonbg_larger1.gif);
-       background-repeat: no-repeat;
-       font-family: verdana;
-       font-size: 8pt;
-       align: center;
-       border: solid 0 #FFFFFF;
-       color: #000000;
-       margin: 0px;
-       width: 140px;
-       height: 21px;
-       padding-top: 1px;
-       padding-bottom: 3px;
-       vertical-align: middle;
-}
-
 pre, textarea {
        font-family: Courier;
        font-weight: normal;

Modified: 
trunk/freenet/src/freenet/clients/http/staticfiles/themes/sky/theme.css
===================================================================
--- trunk/freenet/src/freenet/clients/http/staticfiles/themes/sky/theme.css     
2008-04-14 15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/clients/http/staticfiles/themes/sky/theme.css     
2008-04-14 16:00:44 UTC (rev 19326)
@@ -207,6 +207,11 @@

 /* configuration page */

+table.config_navigation {
+       list-style-type: none;
+       margin-top: 10px;
+}
+
 ul.config {
        margin: 0 0 5px 0;
        padding: 0;
@@ -214,18 +219,13 @@
 }

 ul.config li {
-       margin: 0;
+       margin: 5px;
        padding: 5px;
        list-style: none;
        text-align: left;
-       display: table;
-       position: relative;
+       background-color: #e5efff;
 }

-ul.config li:hover span.configlongdesc {
-       display: block;
-}
-
 ul.config span.configprefix {
        display: block;
        width: 100%;
@@ -236,36 +236,25 @@
 }

 ul.config span.configshortdesc {
+       display: block;
        text-align: left;
        font-weight: bold;
-        float: left;
        margin-right: 10px;
-       width: 300px;
-       display: table-cell;
+       width: 30%;
+       float: left;
 }

 ul.config span.configlongdesc {
-       display: none;
-       position: absolute;
-       left: 40px;
-       background-color: #e1ecff;
-       border: 1px solid #97beff;
-       padding: 5px;
-       /*
-        * This should be 'top', but firefox is broken and doesn't
-        + set the li to be a containing block with 'display: table' on
-        */
-       margin-top: 20px;
-       z-index: 10;
+       display: block;
+       clear: left;
 }

 span.config {
-       display: table-cell;
-       width: 100%;
+       width: 70%;
 }

 ul.config input[type=text] {
-       width: 100%;
+       width: 67%;
 }

 /* darknet page */
@@ -513,7 +502,7 @@

 div.progressbar {
        float: left;
-       margin: none;
+       margin: 0px;
        margin-right: 3px;
        background-color: #f6f9ff;
        border: solid 1px #97beff;

Modified: trunk/freenet/src/freenet/config/Option.java
===================================================================
--- trunk/freenet/src/freenet/config/Option.java        2008-04-14 15:55:20 UTC 
(rev 19325)
+++ trunk/freenet/src/freenet/config/Option.java        2008-04-14 16:00:44 UTC 
(rev 19326)
@@ -9,21 +9,21 @@
 public abstract class Option {

        /** The parent SubConfig object */
-       final SubConfig config;
+       protected final SubConfig config;
        /** The option name */
-       final String name;
+       protected final String name;
        /** The sort order */
-       final int sortOrder;
+       protected final int sortOrder;
        /** Is this config variable expert-only? */
-       final boolean expert;
+       protected final boolean expert;
        /** Is this config variable to be written out even if it uses the 
default value? */
-       final boolean forceWrite;
+       protected final boolean forceWrite;
        /** Short description of value e.g. "FCP port" */
-       final String shortDesc;
+       protected final String shortDesc;
        /** Long description of value e.g. "The TCP port to listen for FCP 
connections on" */
-       final String longDesc;
+       protected final String longDesc;
        /** The configCallback associated to the Option */
-       final ConfigCallback cb;
+       protected final ConfigCallback cb;

        public final static int DATA_TYPE_STRING = 0;
        public final static int DATA_TYPE_NUMBER = 1;

Modified: trunk/freenet/src/freenet/crypt/RandomSource.java
===================================================================
--- trunk/freenet/src/freenet/crypt/RandomSource.java   2008-04-14 15:55:20 UTC 
(rev 19325)
+++ trunk/freenet/src/freenet/crypt/RandomSource.java   2008-04-14 16:00:44 UTC 
(rev 19326)
@@ -13,17 +13,29 @@
 public abstract class RandomSource extends Random {

     /**
-     * Returns a 32 bit random floating point number.  With this method,
-     * all possible float values are approximately as likely to occur
-     */
-    public float nextFullFloat() {
+        * Returns a 32 bit random floating point number. With this method, all 
possible float values
+        * are approximately as likely to occur.
+        * 
+        * This method may return <tt>NaN</tt>, <tt>+Inf</tt>, <tt>-Inf</tt> or 
other weird
+        * stuff. If you don't know what they are, this method is not for you.
+        * 
+        * @see RandomSource#nextFloat()
+        */
+    // FIXME this method is unused, do you *really* want this method?
+       public float nextFullFloat() {
        return Float.intBitsToFloat(nextInt());
     }

     /**
-     * Returns a 64 bit random double.  With this method, all possible
-     * double values are approximately as likely to occur
-     */
+        * Returns a 64 bit random double. With this method, all possible 
double values are
+        * approximately as likely to occur.
+        * 
+        * This method may return <tt>NaN</tt>, <tt>+Inf</tt>, <tt>-Inf</tt> or 
other weird
+        * stuff. If you don't know what they are, this method is not for you.
+        * 
+        * @see RandomSource#nextDouble()
+        */
+    // FIXME this method is unused, do you *really* want this method?
     public double nextFullDouble() {
        return Double.longBitsToDouble(nextLong());
     }

Modified: trunk/freenet/src/freenet/io/AddressTracker.java
===================================================================
--- trunk/freenet/src/freenet/io/AddressTracker.java    2008-04-14 15:55:20 UTC 
(rev 19325)
+++ trunk/freenet/src/freenet/io/AddressTracker.java    2008-04-14 16:00:44 UTC 
(rev 19326)
@@ -133,7 +133,7 @@
                packetTo(peer, false);
        }

-       void packetTo(Peer peer, boolean sent) {
+       private void packetTo(Peer peer, boolean sent) {
                peer = peer.dropHostName();
                InetAddress ip = peer.getAddress();
                long now = System.currentTimeMillis();

Modified: trunk/freenet/src/freenet/io/comm/FreenetInetAddress.java
===================================================================
--- trunk/freenet/src/freenet/io/comm/FreenetInetAddress.java   2008-04-14 
15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/io/comm/FreenetInetAddress.java   2008-04-14 
16:00:44 UTC (rev 19326)
@@ -147,7 +147,7 @@
                if(!allowUnknown) throw e;
                 addr = null;
             }
-            if(logDEBUG) Logger.debug(this, "host is '"+host+"' and 
addr.getHostAddress() is '"+addr.getHostAddress()+ '\'');
+            if(logDEBUG) Logger.debug(this, "host is '"+host+"' and 
addr.getHostAddress() is '"+(addr != null ? addr.getHostAddress()+ '\'' : ""));
             if(addr != null && addr.getHostAddress().equals(host)) {
                if(logDEBUG) Logger.debug(this, '\'' +host+"' looks like an IP 
address");
                 host = null;

Modified: trunk/freenet/src/freenet/keys/BaseClientKey.java
===================================================================
--- trunk/freenet/src/freenet/keys/BaseClientKey.java   2008-04-14 15:55:20 UTC 
(rev 19325)
+++ trunk/freenet/src/freenet/keys/BaseClientKey.java   2008-04-14 16:00:44 UTC 
(rev 19326)
@@ -13,13 +13,14 @@
 public abstract class BaseClientKey {

        public static BaseClientKey getBaseKey(FreenetURI origURI) throws 
MalformedURLException {
-               if("CHK".equals(origURI.getKeyType()))
+               String keyType = origURI.getKeyType();
+               if("CHK".equals(keyType))
                        return new ClientCHK(origURI);
-               if("SSK".equals(origURI.getKeyType()))
+               if("SSK".equals(keyType))
                        return new ClientSSK(origURI);
-               if("KSK".equals(origURI.getKeyType()))
+               if("KSK".equals(keyType))
                        return ClientKSK.create(origURI.getDocName());
-               if("USK".equals(origURI.getKeyType()))
+               if("USK".equals(keyType))
                        return USK.create(origURI);
                throw new MalformedURLException("Unknown keytype from 
"+origURI);
        }

Modified: trunk/freenet/src/freenet/keys/ClientCHKBlock.java
===================================================================
--- trunk/freenet/src/freenet/keys/ClientCHKBlock.java  2008-04-14 15:55:20 UTC 
(rev 19325)
+++ trunk/freenet/src/freenet/keys/ClientCHKBlock.java  2008-04-14 16:00:44 UTC 
(rev 19326)
@@ -89,8 +89,9 @@
             throw new CHKDecodeException("Crypto key too short");
         cipher.initialize(key.cryptoKey);
         PCFBMode pcfb = PCFBMode.create(cipher);
-        byte[] hbuf = new byte[headers.length-2];
-        System.arraycopy(headers, 2, hbuf, 0, headers.length-2);
+       int headersLength = headers.length;
+        byte[] hbuf = new byte[headersLength > 2 ? headersLength-2 : 0];
+        System.arraycopy(headers, 2, hbuf, 0, hbuf.length);
         byte[] dbuf = new byte[data.length];
         System.arraycopy(data, 0, dbuf, 0, data.length);
         // Decipher header first - functions as IV

Modified: trunk/freenet/src/freenet/keys/FreenetURI.java
===================================================================
--- trunk/freenet/src/freenet/keys/FreenetURI.java      2008-04-14 15:55:20 UTC 
(rev 19325)
+++ trunk/freenet/src/freenet/keys/FreenetURI.java      2008-04-14 16:00:44 UTC 
(rev 19326)
@@ -434,8 +434,9 @@
         */
        public FreenetURI popMetaString() {
                String[] newMetaStr = null;
-               if ((metaStr != null) && (metaStr.length > 1)) {
-                       newMetaStr = new String[metaStr.length - 1];
+               final int metaStrLength = metaStr.length;
+               if ((metaStr != null) && (metaStrLength > 1)) {
+                       newMetaStr = new String[metaStrLength - 1];
                        System.arraycopy(metaStr, 1, newMetaStr, 0, 
newMetaStr.length);
                }
                return setMetaString(newMetaStr);
@@ -643,12 +644,12 @@
                        dis.readFully(cryptoKey);
                        // Number of bytes of extra depends on key type
                        int extraLen;
-                       if(type == CHK)
+                       if((type == CHK) || (type == SSK)) {
                                extraLen = ClientCHK.EXTRA_LENGTH;
-                       else //if(type == SSK)
-                               extraLen = ClientSSK.EXTRA_LENGTH;
-                       extra = new byte[extraLen];
-                       dis.readFully(extra);
+                               extra = new byte[extraLen];
+                               dis.readFully(extra);
+                       } else
+                               extra = new byte[0];
                }
                String docName = null;
                if(type != CHK)

Modified: trunk/freenet/src/freenet/keys/NodeCHK.java
===================================================================
--- trunk/freenet/src/freenet/keys/NodeCHK.java 2008-04-14 15:55:20 UTC (rev 
19325)
+++ trunk/freenet/src/freenet/keys/NodeCHK.java 2008-04-14 16:00:44 UTC (rev 
19326)
@@ -8,6 +8,7 @@
 import java.io.DataOutputStream;
 import java.io.IOException;
 import freenet.support.Base64;
+import freenet.support.Logger;

 /**
  * @author amphibian
@@ -83,4 +84,14 @@
                System.arraycopy(routingKey, 0, buf, 2, routingKey.length);
                return buf;
        }
+
+       public static byte[] routingKeyFromFullKey(byte[] keyBuf) {
+               if(keyBuf.length == KEY_LENGTH) return keyBuf;
+               if(keyBuf.length != FULL_KEY_LENGTH) {
+                       Logger.error(NodeCHK.class, "routingKeyFromFullKey() on 
"+keyBuf.length+" bytes");
+               }
+               byte[] out = new byte[KEY_LENGTH];
+               System.arraycopy(keyBuf, 2, out, 0, KEY_LENGTH);
+               return out;
+       }
 }

Modified: trunk/freenet/src/freenet/keys/NodeSSK.java
===================================================================
--- trunk/freenet/src/freenet/keys/NodeSSK.java 2008-04-14 15:55:20 UTC (rev 
19325)
+++ trunk/freenet/src/freenet/keys/NodeSSK.java 2008-04-14 16:00:44 UTC (rev 
19326)
@@ -193,5 +193,16 @@
                pubKey = pubkeyCache.getKey(pubKeyHash);
                return pubKey != null;
        }
+
+       public static byte[] routingKeyFromFullKey(byte[] keyBuf) {
+               if(keyBuf.length != FULL_KEY_LENGTH) {
+                       Logger.error(NodeSSK.class, "routingKeyFromFullKey() on 
buffer length "+keyBuf.length);
+               }
+               byte[] encryptedHashedDocname = new byte[E_H_DOCNAME_SIZE];
+               byte[] pubKeyHash = new byte[PUBKEY_HASH_SIZE];
+               System.arraycopy(keyBuf, 2, encryptedHashedDocname, 0, 
E_H_DOCNAME_SIZE);
+               System.arraycopy(keyBuf, 2+E_H_DOCNAME_SIZE, pubKeyHash, 0, 
PUBKEY_HASH_SIZE);
+               return makeRoutingKey(pubKeyHash, encryptedHashedDocname);
+       }

 }

Modified: trunk/freenet/src/freenet/l10n/freenet.l10n.de.properties
===================================================================
--- trunk/freenet/src/freenet/l10n/freenet.l10n.de.properties   2008-04-14 
15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/l10n/freenet.l10n.de.properties   2008-04-14 
16:00:44 UTC (rev 19326)
@@ -2,6 +2,8 @@
 Announcer.announceAlertNoSeednodes=Es wurde keine seednodes.fref-Datei 
gefunden, deshalb wird der Knoten nicht in der Lage sein sich aus eigener Kraft 
automatisch mit dem Opennet zu verbinden. Bitte f?gen Sie manuell ein paar 
Knoten hinzu oder laden Sie die Seednodes-Datei von 
http://downloads.freenetproject.org/alpha/opennet/ herunter.
 Announcer.announceAlertTitle=Knoten macht sich bekannt
 Announcer.announceDetails=Wir haben k?rzlich ${recentSentAnnouncements} 
Bekanntgaben gesendet, von denen ${runningAnnouncements} noch laufen, und haben 
${addedNodes} Knoten hinzugef?gt (${refusedNodes} Knoten haben uns abgelehnt). 
Wir sind gerade mit ${connectedSeednodes} Seednodes (Saat-Knoten) verbunden und 
versuchen uns mit weiteren ${disconnectedSeednodes} zu verbinden.
+Announcer.announceDisabledTooOld=Diese Knoten scheint zu alt zu sein um sich 
mit dem aktuellen Freenet-Netzwerk zu verbinden. Wir haben die Ank?ndigungen 
deaktiviert, da sie nichts bringen. Bitte aktualisieren Sie Ihren Knoten so 
bald wie m?glich (die automatische Aktualisierung k?nnte auf Ihre Eingabe 
warten oder deaktiviert sein).
+Announcer.announceDisabledTooOldTitle=Ank?ndigungen deaktiviert (zu alt)
 Announcer.announceLoading=Freenet l?dt gerade die Seednodes(Saat-Knoten)-Datei 
herunter, sodass es versuchen kann sich im Rest des Netzwerks bekannt zu 
machen. Die Bekanntgabe kann ein paar Minuten dauern.
 Announcer.coolingOff=In den n?chsten ${time} Sekunden wartet der Knoten auf 
die Knoten, bei denen er sich gerade gemeldet hat um sich zu verbinden. Wenn 
dies nicht gen?gend Knoten hervorbringt, wird er es mit einem anderen Knoten 
versuchen.
 BookmarkEditorToadlet.addBookmark=Lesezeichen hinzuf?gen
@@ -72,7 +74,7 @@
 ConfigToadlet.configNavTitle=Konfigurations-Navigation
 ConfigToadlet.console=Konsole
 ConfigToadlet.contributeTranslation=Zur ?bersetzung beitragen
-ConfigToadlet.defaultIs=Der Standard-Wert f?r diese Einstellung ist: 
"${default}".
+ConfigToadlet.defaultIs=Standard-Wert: "${default}".
 ConfigToadlet.false=nein
 ConfigToadlet.fcp=FCP
 ConfigToadlet.fproxy=FProxy
@@ -81,11 +83,11 @@
 ConfigToadlet.node=Knoten
 ConfigToadlet.pluginmanager=Plugin-Manager
 ConfigToadlet.pluginmanager2=Plugin-Manager 2
-ConfigToadlet.possibilitiesTitle=Ihre M?glichkeiten
+ConfigToadlet.possibilitiesTitle=Fortsetzen
 ConfigToadlet.reset=Zur?cksetzen
 ConfigToadlet.returnToNodeConfig=Zur Knoten-Konfiguration zur?ckkehren
 ConfigToadlet.shortTitle=Konfiguration
-ConfigToadlet.ssl=SSL (damit die Einstellungen ?bernommen werden, muss Freenet 
neu gestartet werden)
+ConfigToadlet.ssl=SSL (ben?tigt Neustart)
 ConfigToadlet.title=Freenet-Knoten-Konfiguration
 ConfigToadlet.toadletsymlinker=ToadletSymlinker
 ConfigToadlet.true=ja
@@ -315,12 +317,12 @@
 FcpServer.filenameToStorePDataLong=Name der Datei, in der Details zu 
persistenten (dauerhaften) Downloads gespeichert werden.
 FcpServer.intervalBetweenWrites=Zeitraum zwischen dem Schreiben persistenter 
Downloads auf den Datentr?ger.
 FcpServer.intervalBetweenWritesLong=Zeitraum zwischen dem Schreiben 
persistenter (dauerhafter) Downloads auf den Datentr?ger.
-FcpServer.isEnabled=Ist der FCP-Server aktiviert?
-FcpServer.isEnabledLong=Ist der FCP-Server aktiviert?
-FcpServer.portNumber=FCP-Port-Nummer
-FcpServer.portNumberLong=FCP-Port-Nummer.
+FcpServer.isEnabled=FCP aktivieren?
+FcpServer.isEnabledLong=Den "Freenet Client Protocol"-Server (Protokoll f?r 
Programme die auf Freenet zugreifen) aktivieren
+FcpServer.portNumber=FCP-Port
+FcpServer.portNumberLong=Der TCP-Port auf dem der FCP-Server Nachrichten 
empfangen soll.
 FcpServer.ssl=SSL aktivieren?
-FcpServer.sslLong=SSL aktivieren?
+FcpServer.sslLong=SSL auf dem FCP-Server aktivieren?
 FetchException.longError.1=Zu viele Ebenen von Rekursion (Schachtelung) in den 
Archiven
 FetchException.longError.10=Datei nicht im Archiv
 FetchException.longError.11=Zu viele Pfad-Komponenten - kein Manifest(???)? 
Versuchen Sie eine zu entfernen
@@ -406,8 +408,9 @@
 FirstTimeWizardToadlet.continue=Fortfahren
 FirstTimeWizardToadlet.continueEnd=Hier klicken, um anzufangen, Freenet zu 
benutzen!
 FirstTimeWizardToadlet.datastoreSize=Gr??e des Datenspeichers
-FirstTimeWizardToadlet.datastoreSizeLong=Bitte w?hlen Sie eine Gr??e f?r Ihren 
Datenspeicher. Der Datenspeicher verh?lt sich wie ein Zwischenspeicher (Cache); 
Daten f?r das Netzwerk zu speichern wird Ihnen zu einem besseren 
Daten-Durchsatz verhelfen, wenn Sie popul?re Dateien anfordern. Je mehr Platz 
Sie zur Verf?gung stellen k?nnen, desto besser ist es f?r die Gemeinschaft und 
desto schneller wird Ihr Knoten.
+FirstTimeWizardToadlet.datastoreSizeLong=Bitte w?hlen Sie eine Gr??e f?r Ihren 
Datenspeicher. Der Datenspeicher verh?lt sich wie ein Zwischenspeicher (Cache); 
Daten f?r das Netzwerk zu speichern wird Ihnen zu einem besseren 
Daten-Durchsatz verhelfen, wenn Sie popul?re Dateien anfordern. Je mehr Platz 
Sie zur Verf?gung stellen k?nnen, desto besser ist es f?r die Gemeinschaft und 
desto schneller wird Ihr Knoten, besonders das Herunterladen, sein.
 FirstTimeWizardToadlet.enableOpennet=Kennen Sie jemanden, der bereits Freenet 
benutzt?
+FirstTimeWizardToadlet.fivePercentDisk=(= 5% des freien Speicherplatzes)
 FirstTimeWizardToadlet.homepageTitle=Freenet-Einrichtungs-Assistent!
 FirstTimeWizardToadlet.iDoTrust=Trauen Sie Leuten die mit ${interface} (${ip}) 
verbunden sind?
 FirstTimeWizardToadlet.isNetworkTrusted=Ist Ihr lokales Netzwerk 
vertrauensw?rdig?
@@ -424,6 +427,7 @@
 FirstTimeWizardToadlet.step4Title=Freenet-Einrichtungs-Assistent! - Gr??e des 
Datenspeichers
 FirstTimeWizardToadlet.step5Title=Freenet-Einrichtungs-Assistent! - 
Netzwerk-Konfiguration
 FirstTimeWizardToadlet.step6Title=Freenet-Einrichtungs-Assistent! - Herzlichen 
Gl?ckwunsch, Ihr Knoten ist nun konfiguriert
+FirstTimeWizardToadlet.tenPercentDisk=(= 10% des freien Speicherplatzes)
 FirstTimeWizardToadlet.warningTitle=Warnung!
 FirstTimeWizardToadlet.welcomeInfoboxContent1=Willkommen beim 
Freenet-Einrichtungs-Assistent. Dieses Werkzeug erlaubt es Ihnen, Ihren Knoten 
schnell und einfach f?r den ersten Betrieb einzurichten.
 FirstTimeWizardToadlet.welcomeInfoboxTitle=Willkommen zum 
Freenet-Einrichtungs-Assistent!
@@ -962,10 +966,10 @@
 SimpleToadletServer.illegalCSSName=CSS-Namen d?rfen keine Schr?gstriche oder 
Doppelpunkte enthalten!
 SimpleToadletServer.panicButton=Den Alarm-Knopf anzeigen?
 SimpleToadletServer.panicButtonLong=Ob der Alarm-Knopf auf der 
Warteschlangen-Seite (/queue/) angezeigt werden soll oder nicht.
-SimpleToadletServer.port=FProxy-Port-Nummer
-SimpleToadletServer.portLong=FProxy-Port-Nummer
+SimpleToadletServer.port=FProxy-Port
+SimpleToadletServer.portLong=Der TCP-Port auf dem FProxy Nachrichten empfangen 
soll
 SimpleToadletServer.ssl=SSL aktivieren?
-SimpleToadletServer.sslLong=SSL aktivieren?
+SimpleToadletServer.sslLong=SSL auf FProxy aktivieren?
 StaticToadlet.pathInvalidChars=Der angegebene URI enth?lt nicht erlaubte 
Zeichen.
 StaticToadlet.pathNotFound=Der Pfad, den Sie angegeben haben, existiert nicht.
 StaticToadlet.pathNotFoundTitle=Pfad nicht gefunden
@@ -983,10 +987,10 @@
 StatisticsToadlet.fullTitle=Statistiken f?r ${name}
 StatisticsToadlet.getLogs=Letzte Log-Datei des Knotens abrufen
 StatisticsToadlet.inputRate=Eingehend: ${rate}/Sekunde (von ${max})
+StatisticsToadlet.javaVersion=Java-Version: ${version}
 StatisticsToadlet.jeDumpButton=JE-Dump generieren
 StatisticsToadlet.jvmInfoTitle=JVM-Info
 StatisticsToadlet.jvmVendor=JVM-Hersteller: ${vendor}
-StatisticsToadlet.javaVersion=Java-Version: ${version}
 StatisticsToadlet.jvmVersion=JVM-Version: ${version}
 StatisticsToadlet.maxMemory=Maximaler Java-Speicher: ${memory}
 StatisticsToadlet.noRequests=Ihr Knoten bearbeitet gerade keine Anfragen
@@ -1029,12 +1033,12 @@
 TextModeClientInterfaceServer.bindToLong=Zu benutzende IP-Adresse
 TextModeClientInterfaceServer.enableInputOutput=Auf stdout/stdin aktivieren?
 
TextModeClientInterfaceServer.enableInputOutputLong=Text-Modus-Client-Schnittstelle
 in der Standard-Ein-/Ausgabe aktivieren? (.enabled (.aktiviert) bezieht sich 
auf das stellen eines Servers im Telnet-Stil, dieser f?hrt es ?ber einen Sockel 
(Socket) aus)
-TextModeClientInterfaceServer.enabled=TMCI aktivieren
-TextModeClientInterfaceServer.enabledLong=Ob das TMCI aktiviert werden soll
+TextModeClientInterfaceServer.enabled=TMCI aktivieren?
+TextModeClientInterfaceServer.enabledLong=Das "Text Mode Client Interface" 
(textbasierte Schnittstelle f?r Programme die Freenet benutzen) aktivieren
 TextModeClientInterfaceServer.ssl=SSL aktivieren?
-TextModeClientInterfaceServer.sslLong=SSL aktivieren?
-TextModeClientInterfaceServer.telnetPortNumber=Telnet-Port
-TextModeClientInterfaceServer.telnetPortNumberLong=Telnet-Port-Nummer
+TextModeClientInterfaceServer.sslLong=SSL auf dem TMCI-Server aktivieren?
+TextModeClientInterfaceServer.telnetPortNumber=TMCI-Port
+TextModeClientInterfaceServer.telnetPortNumberLong=Der TCP-Port auf dem der 
TMCI-Server Nachrichten empfangen soll
 TimeSkewDetectedUserAlert.text=Es wurde vom Knoten eine Zeitversetzung 
erkannt. Das ist SEHR schlimm. Ihr Knoten wird nicht richtig laufen bis dies 
behoben ist; h?ufige Ursachen sind ein falsch konfigurierter 
Energie-Spar-Modus, Netzwerk-Zeit-Synchronisations-Programme, oder fehlerhafte 
Hardware.
 TimeSkewDetectedUserAlert.title=Zeitversetzung erkannt!
 Toadlet.cancel=Abbrechen

Modified: trunk/freenet/src/freenet/l10n/freenet.l10n.en.properties
===================================================================
--- trunk/freenet/src/freenet/l10n/freenet.l10n.en.properties   2008-04-14 
15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/l10n/freenet.l10n.en.properties   2008-04-14 
16:00:44 UTC (rev 19326)
@@ -800,6 +800,7 @@
 PproxyToadlet.loadOtherPluginText=Here you can enter the URL of a plugin you 
want to load. Other plugins than the ones listed above are not even remotely 
supported or checked for privacy leaks by us, so if you load a remote plugin 
here, you are basically on your own.
 PproxyToadlet.loadOtherURLLabel=Plugin URL
 PproxyToadlet.noPlugins=No plugins loaded
+PproxyToadlet.noVersion=N/A
 PproxyToadlet.pluginNotFoundReload=The specified plugin could not be located 
in order to reload it.
 PproxyToadlet.pluginNotFoundReloadTitle=Plugin Not Found (reloading)
 PproxyToadlet.pluginUnloaded=Plugin unloaded
@@ -830,18 +831,21 @@
 PproxyToadlet.unloadPluginTitle=Unload plugin?
 PproxyToadlet.unloadPluginWithName=Are you sure you wish to unload ${name}?
 PproxyToadlet.unloadPurge=Remove plugin from cache
-QueueToadlet.DUinProgress=Directory uploads in progress
-QueueToadlet.DinProgress=Downloads in progress
-QueueToadlet.UinProgress=Uploads in progress
+PproxyToadlet.versionTitle=Version
+QueueToadlet.DUinProgress=Directory uploads in progress (${size})
+QueueToadlet.DinProgress=Downloads in progress (${size})
+QueueToadlet.UinProgress=Uploads in progress (${size})
 QueueToadlet.change=Change
-QueueToadlet.completedDU=Completed directory uploads
-QueueToadlet.completedDinDownloadDirectory=Completed: Downloads to download 
directory (${size})
-QueueToadlet.completedDinTempDirectory=Completed: Downloads to temporary 
directory (${size})
-QueueToadlet.completedDtoDisk=Completed downloads to disk
-QueueToadlet.completedDtoTemp=Completed downloads to temp
-QueueToadlet.completedU=Completed: Uploads (${size})
-QueueToadlet.completedUDirectory=Completed: Directory Uploads (${size})
+QueueToadlet.completedDU=Completed directory uploads (${size})
+QueueToadlet.completedDinDownloadDirectory=Completed downloads to download 
directory (${size})
+QueueToadlet.completedDinTempDirectory=Completed downloads to temporary 
directory (${size})
+QueueToadlet.completedDtoDisk=Completed downloads to disk (${size})
+QueueToadlet.completedDtoTemp=Completed downloads to temp (${size})
+QueueToadlet.completedU=Completed uploads (${size})
+QueueToadlet.completedUDirectory=Completed directory uploads (${size})
 QueueToadlet.download=Download
+QueueToadlet.downloadSucceededTitle=Download succeeded: ${filename}
+QueueToadlet.downloadSucceeded=The file ${origlink}${filename}${/origlink} has 
been downloaded successfully. ${link}Click here${/link} to open the file 
(${size}).
 QueueToadlet.errorAccessDenied=Error: Access Denied!
 QueueToadlet.errorAccessDeniedFile=The current configuration of the node 
prohibits you from uploading the file "${file}".
 QueueToadlet.errorDToDisk=Cannot download to disk
@@ -857,17 +861,14 @@
 QueueToadlet.errorNoFileSelectedU=You did not select a file to upload.
 QueueToadlet.errorNoKey=No key specified to download
 QueueToadlet.errorNoKeyToD=You did not specify a key to download.
-QueueToadlet.failedD=Failed downloads
-QueueToadlet.failedD=Failed: Downloads (${size})
-QueueToadlet.failedDU=Failed directory uploads
-QueueToadlet.failedDU=Failed: Directory Uploads (${size})
+QueueToadlet.failedD=Failed downloads (${size})
+QueueToadlet.failedDU=Failed directory uploads (${size})
 QueueToadlet.failedToRemove=Failed to remove ${id}: ${message}
 QueueToadlet.failedToRemoveId=Failed to remove: ${id}
 QueueToadlet.failedToRemoveRequest=Failed to remove request
 QueueToadlet.failedToRestart=Failed to restart: ${id}
 QueueToadlet.failedToRestartRequest=Failed to restart request
-QueueToadlet.failedU=Failed uploads
-QueueToadlet.failedU=Failed: Uploads (${size})
+QueueToadlet.failedU=Failed uploads (${size})
 QueueToadlet.fcpIsMissing=FCP server is missing
 QueueToadlet.fileName=Filename
 QueueToadlet.files=Files
@@ -911,11 +912,15 @@
 QueueToadlet.remove=Remove
 QueueToadlet.requestNavigation=Request Navigation
 QueueToadlet.restart=Restart
+QueueToadlet.siteUploadSucceededTitle=Freesite insert succeeded: ${filename}
+QueueToadlet.siteUploadSucceeded=Your freesite ${filename} (${files} files, 
${size} total size) has been successfully uploaded to Freenet. ${link}Click 
here${/link} to open the site homepage.
 QueueToadlet.size=Size
 QueueToadlet.starting=STARTING
 QueueToadlet.title=Global queue of ${nodeName}
 QueueToadlet.totalSize=Total Size
 QueueToadlet.unknown=Unknown
+QueueToadlet.uploadSucceededTitle=Insert succeeded: ${filename}
+QueueToadlet.uploadSucceeded=Your file ${filename} (size ${size}) has been 
successfully uploaded to Freenet. ${link}Click here${/link} to open the file.
 QueueToadlet.warningUnsafeContent=Potentially Unsafe Content
 QueueToadlet.warningUnsafeContentExplanation=The file you want to download is 
currently not filtered by Freenet's content filter! That means that your 
anonymity can be compromised by opening the file!
 QueueToadlet.wipD=In Progress: Downloads (${size})

Modified: trunk/freenet/src/freenet/l10n/freenet.l10n.fr.properties
===================================================================
--- trunk/freenet/src/freenet/l10n/freenet.l10n.fr.properties   2008-04-14 
15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/l10n/freenet.l10n.fr.properties   2008-04-14 
16:00:44 UTC (rev 19326)
@@ -2,6 +2,8 @@
 Announcer.announceAlertNoSeednodes=Le fichier seednodes.fref est introuvable, 
le n?ud ne peut pas se connecter ? l'Opennet automatiquement. Veuillez ?tablir 
des connexions manuellement, ou t?l?charger le fichier 
http://downloads.freenetproject.org/alpha/opennet/seednodes.fref et le placer 
dans le r?pertoire freenet.
 Announcer.announceAlertTitle=Connexion au r?seau
 Announcer.announceDetails=Nous avons envoy? ${recentSentAnnouncements} 
annonces, dont ${runningAnnouncements} sont toujours en cours, et ajout? 
${addedNodes} n?uds (${refusedNodes} n?uds ont refus? de se connecter). Nous 
sommes actuellement connect?s ? ${connectedSeednodes} seednodes et essayons de 
nous connecter ? ${disconnectedSeednodes} autres.
+Announcer.announceDisabledTooOld=Votre noeud est trop ancien pour se connecter 
au r?seau Freenet actuel. Les annonces sont d?sactiv?es puisqu'elles ne m?nent 
nulle part. Veuillez mettre votre noeud ? jour (la mise ? jour automatique 
attend peut-?tre une confirmation ou alors vous l'avez d?sactiv?e).
+Announcer.announceDisabledTooOldTitle=Annonces d?sactiv?es (noeud trop ancien)
 Announcer.announceLoading=Freenet t?l?charge la liste des seednodes afin de 
pouvoir annoncer sa pr?sence sur le r?seau. Cela va prendre quelques minutes.
 Announcer.coolingOff=Pendant les ${time} prochaines secondes, le noeud va 
attendre que les noeuds auxquels il s'est annonc? se connectent. S'il n'y en a 
pas assez, il essaiera ? nouveau.
 BookmarkEditorToadlet.addBookmark=Ajouter un marque-page
@@ -72,7 +74,7 @@
 ConfigToadlet.configNavTitle=Sections de configuration
 ConfigToadlet.console=console
 ConfigToadlet.contributeTranslation=Aider ? la traduction
-ConfigToadlet.defaultIs=La valeur par d?faut est : '${default}'.
+ConfigToadlet.defaultIs=Par d?faut : '${default}'.
 ConfigToadlet.false=non
 ConfigToadlet.fcp=fcp
 ConfigToadlet.fproxy=fproxy
@@ -81,11 +83,11 @@
 ConfigToadlet.node=noeud
 ConfigToadlet.pluginmanager=gestionnaire de plugins
 ConfigToadlet.pluginmanager2=gestionnaire de plugins 2
-ConfigToadlet.possibilitiesTitle=Vos possibilit?s
+ConfigToadlet.possibilitiesTitle=Continuer
 ConfigToadlet.reset=R?initialiser
 ConfigToadlet.returnToNodeConfig=Retourner ? la page de configuration
 ConfigToadlet.shortTitle=Configuration
-ConfigToadlet.ssl=SSL (n'affecte pas les connexions d?ja ?tablies, red?marrez 
si n?cessaire)
+ConfigToadlet.ssl=SSL (red?marrage n?cessaire)
 ConfigToadlet.title=Configuration du noeud Freenet
 ConfigToadlet.toadletsymlinker=toadletsymlinker
 ConfigToadlet.true=oui
@@ -112,8 +114,18 @@
 ConnectionsToadlet.nodeStatus.TOO NEW=TROP RECENT
 ConnectionsToadlet.nodeStatus.TOO OLD=TROP ANCIEN
 ConnectionsToadlet.nodeStatus.UNKNOWN STATUS=ETAT INCONNU
+ConnectivityToadlet.addressTitle=Adresse
+ConnectivityToadlet.byIPTitle=Paquets pour ${ip} par adresse IP - ${status} 
(dur?e de vie minimum du tunnel ${tunnelLength})
+ConnectivityToadlet.byPortTitle=Paquets pour ${port} par port - ${status} 
(dur?e de vie minimum du tunnel ${tunnelLength})
 ConnectivityToadlet.connectivity=Raccordement ? Internet
 ConnectivityToadlet.connectivityTitle=Raccordement r?seau
+ConnectivityToadlet.firstReceiveLeadTime=D?lai avant la premi?re r?ception
+ConnectivityToadlet.firstSendLeadTime=D?lai avant le premier envoi
+ConnectivityToadlet.local=LOCAL
+ConnectivityToadlet.localRemoteTitle=Local/distant
+ConnectivityToadlet.noreply=PAS DE REPONSE
+ConnectivityToadlet.remote=DISTANT
+ConnectivityToadlet.sentReceivedTitle=Paquets envoy?s/re?us
 ConnectivityToadlet.summaryTitle=Raccordement r?seau
 ConnectivityToadlet.title=Raccordement internet de ${nodeName}
 ContentDataFilter.unknownCharset=La page que vous essayez d'afficher utilise 
un jeu de caract?re inconnu. Cela signifie que le noeud ne pourra pas la 
filtrer, et qu'elle pourait compromettre votre anonymat.
@@ -309,12 +321,12 @@
 FcpServer.filenameToStorePDataLong=Fichier o? stocker les d?tails sur les 
t?l?chargements persistants.
 FcpServer.intervalBetweenWrites=Intervalle entre les ?critures des 
t?l?chargements persistants sur le disque
 FcpServer.intervalBetweenWritesLong=Intervalle entre les ?critures des 
t?l?chargements persistants sur le disque.
-FcpServer.isEnabled=Le serveur FCP est-il activ? ?
-FcpServer.isEnabledLong=Le serveur FCP est-il activ? ?
+FcpServer.isEnabled=Activer FCP ?
+FcpServer.isEnabledLong=Activer le serveur de Protocole pour Client de Freenet
 FcpServer.portNumber=Port FCP
-FcpServer.portNumberLong=Port FCP.
-FcpServer.ssl=Activer ssl ?
-FcpServer.sslLong=Activer ssl ?
+FcpServer.portNumberLong=Le port TCP ?cout? par le serveur FCP
+FcpServer.ssl=Activer SSL ?
+FcpServer.sslLong=Activer SSL sur le serveur FCP
 FetchException.longError.1=Trop de niveau de r?cursivit? dans ces archives
 FetchException.longError.10=Le ficher ne se trouve pas dans l'archive
 FetchException.longError.11=Chemin trop long - pas un manifeste ? Essayez de 
raccourcir le chemin
@@ -389,7 +401,7 @@
 FileOffer.succeededReceiveHeader=Le transfert du fichier ${filename} de 
${node} a r?ussi.
 FileOffer.succeededReceiveTitle=Fichier re?u
 FirstTimeWizardToadlet.bandwidthLimit=Limites de d?bit
-FirstTimeWizardToadlet.bandwidthLimitLong=Veuillez choisir le type et la 
vitesse de votre connexion internet dans la liste ci-dessous.
+FirstTimeWizardToadlet.bandwidthLimitLong=Veuillez choisir le type et la 
vitesse de votre connexion internet (download/upload) dans la liste ci-dessous.
 FirstTimeWizardToadlet.chooseNodeName=Vous devez choisir un nom !
 FirstTimeWizardToadlet.chooseNodeNameLong=Veuillez entrer un nom pour votre 
n?ud (nous recommandons un pseudo ou une adresse email). Cela permettra ? vos 
amis (les n?uds de confiance que vous aurez ajout?s manuellement) de 
reconnaitre votre n?ud. Ce nom ne sera pas visible pour les Inconnus (ces n?uds 
ajout?s automatiquement par l'Opennet). Vous devez savoir que n'importe quel 
Ami ou Inconnu peut vous identifier facilement par votre IP, puisque vous ?tes 
connect? ? eux, mais il ne peuvent pas savoir ce que vous faites dans le r?seau.
 FirstTimeWizardToadlet.clickContinue=Cliquez ici pour continuer.
@@ -400,8 +412,9 @@
 FirstTimeWizardToadlet.continue=Continuer
 FirstTimeWizardToadlet.continueEnd=Cliquez ici pour commencer ? utiliser 
Freenet !
 FirstTimeWizardToadlet.datastoreSize=Taille du store
-FirstTimeWizardToadlet.datastoreSizeLong=Veuillez sp?cifier la taille de votre 
datastore. Le datastore agit comme un cache ; stocker les donn?es du r?seau 
am?liorera les performances quand vous t?l?chargerez des fichiers populaires. 
Plus vous stockez de donn?es, plus vous aidez la communaut? et plus votre noeud 
sera rapide.
+FirstTimeWizardToadlet.datastoreSizeLong=Veuillez sp?cifier la taille de votre 
datastore. Le datastore agit comme un cache ; stocker les donn?es du r?seau 
am?liorera les performances quand vous t?l?chargerez des fichiers populaires. 
Plus vous stockez de donn?es, plus vous aidez la communaut? et plus votre noeud 
sera rapide (surtout les t?l?chargements).
 FirstTimeWizardToadlet.enableOpennet=Connaissez-vous des gens qui utilisent 
Freenet ?
+FirstTimeWizardToadlet.fivePercentDisk=(= 5% de l'espace libre)
 FirstTimeWizardToadlet.homepageTitle=Assistant de d?marrage Freenet !
 FirstTimeWizardToadlet.iDoTrust=Faites-vous confiance aux personnes connect?s 
par ${interface} (${ip}) ?
 FirstTimeWizardToadlet.isNetworkTrusted=Le r?seau local est-il digne de 
confiance ?
@@ -418,6 +431,7 @@
 FirstTimeWizardToadlet.step4Title=Assistant de d?marrage Freenet ! - Taille du 
datastore
 FirstTimeWizardToadlet.step5Title=Assistant de d?marrage Freenet ! - 
Configuration r?seau
 FirstTimeWizardToadlet.step6Title=Assistant de d?marrage Freenet ! - 
F?licitations, votre noeud est configur?.
+FirstTimeWizardToadlet.tenPercentDisk=(= 10% de l'espace libre)
 FirstTimeWizardToadlet.warningTitle=Attention !
 FirstTimeWizardToadlet.welcomeInfoboxContent1=Bienvenue dans l'assistant de 
d?marrage Freenet . Cet outil vous permettra de configurer rapidement et 
facilement votre noeud.
 FirstTimeWizardToadlet.welcomeInfoboxTitle=Bienvenue sur l'assistant de 
d?marrage Freenet !
@@ -894,7 +908,7 @@
 QueueToadlet.priority6=ne se terminera jamais
 QueueToadlet.progress=Progression
 QueueToadlet.progressbarAccurate=Cette valeur est pr?cise
-QueueToadlet.progressbarNotAccurate=Cette valeur risque de changer car le 
traitement du fichier n'est pas termin?e
+QueueToadlet.progressbarNotAccurate=Cette valeur risque de changer quand de 
nouveaux blocs seront trouv?s
 QueueToadlet.reason=Raison
 QueueToadlet.remove=Supprimer
 QueueToadlet.requestNavigation=Parcours des requ?tes
@@ -904,13 +918,18 @@
 QueueToadlet.title=File d'attente globale de ${nodeName}
 QueueToadlet.totalSize=Taille totale
 QueueToadlet.unknown=Inconnu
+QueueToadlet.uploadProgressbarNotAccurate=Cette valeur risque de changer quand 
de nouveaux blocs seront ins?r?s
 QueueToadlet.warningUnsafeContent=Contenu potentiellement dangereux
 QueueToadlet.warningUnsafeContentExplanation=Ce fichier n'est pas filtr? par 
votre noeud Freenet ! Cela signifie qu'ouvrir ce fichier peut compromettre 
votre anonymat.
 QueueToadlet.wipD=En cours : T?l?chargements (${size})
 QueueToadlet.wipDU=En cours : Insertions de dossiers (${size})
 QueueToadlet.wipU=En cours : Insertions (${size})
-RequestStarterGroup.scheduler=Priorit?s de l'ordonnanceur
+RequestStarterGroup.scheduler=Priorit?s de l'ordonnanceur : hard (respect 
strict des priorit?s) ou soft (m?lange l?g?rement les priorit?s)
+RequestStarterGroup.schedulerCHKInserts=Gestion des priorit?s par 
l'ordonnanceur (insertions de CHK)
+RequestStarterGroup.schedulerCHKRequests=Gestion des priorit?s par 
l'ordonnanceur (requ?tes de CHK)
 RequestStarterGroup.schedulerLong=D?finir les r?gles de priorit? de 
l'ordonnanceur.
+RequestStarterGroup.schedulerSSKInserts=Gestion des priorit?s par 
l'ordonnanceur (insertions de SSK)
+RequestStarterGroup.schedulerSSKRequests=Gestion des priorit?s par 
l'ordonnanceur (requ?tes de SSK)
 RevocationKeyFoundUserAlert.text=Le noeud a trouv? la cl? de r?vocation des 
mises ? jour automatiques sur le r?seau. Cela signifie que le syst?me de mise ? 
jour automatique a certainement ?t? COMPROMIS ! En cons?quence, il a ?t? 
d?sactiv? sur votre noeud afin d'?viter que de "mauvaises choses" soient 
install?es. Nous vous conseillons fortement de rechercher les mises ? jour sur 
le site officiel. Assurez vous que le site n'a pas ?t? falsifi? lui aussi. Le 
message de r?vocation est : ${message}.
 RevocationKeyFoundUserAlert.title=La cl? priv?e du projet a ?t? compromise !
 SSL.enable=Activer le support SSL ?
@@ -953,10 +972,10 @@
 SimpleToadletServer.illegalCSSName=Le nom du CSS ne doit contenir ni slash, ni 
points !
 SimpleToadletServer.panicButton=Afficher le bouton panique ?
 SimpleToadletServer.panicButtonLong=Choisir d'afficher ou non le bouton 
panique sur la page /File d'attente/.
-SimpleToadletServer.port=Num?ro de port FProxy
-SimpleToadletServer.portLong=Num?ro de port FProxy
-SimpleToadletServer.ssl=Activer ssl?
-SimpleToadletServer.sslLong=Activer ssl ?
+SimpleToadletServer.port=Port FProxy
+SimpleToadletServer.portLong=Port TCP ?cout? par FProxy
+SimpleToadletServer.ssl=Activer SSL ?
+SimpleToadletServer.sslLong=Activer SSL pour FProxy
 StaticToadlet.pathInvalidChars=Cette URI contient des caract?res interdits.
 StaticToadlet.pathNotFound=Le chemin sp?cifi? n'existe pas.
 StaticToadlet.pathNotFoundTitle=Chemin introuvable.
@@ -974,6 +993,7 @@
 StatisticsToadlet.getLogs=R?cup?rer le dernier journal du noeud
 StatisticsToadlet.inputRate=R?ception : ${rate}/seconde (sur ${max}/seconde)
 StatisticsToadlet.insertOutput=Traffic d'insertion (traffic utile): CHK ${chk} 
SSK ${ssk}.
+StatisticsToadlet.javaVersion=Version de Java : ${version}
 StatisticsToadlet.jeDumpButton=G?n?rer un dump JE
 StatisticsToadlet.jvmInfoTitle=Informations sur la JVM
 StatisticsToadlet.jvmVendor=Distributeur de la JVM : ${vendor}
@@ -1022,12 +1042,12 @@
 TextModeClientInterfaceServer.bindToLong=Adresse IP o? ?couter
 TextModeClientInterfaceServer.enableInputOutput=Activer sur stdin/stdout ?
 TextModeClientInterfaceServer.enableInputOutputLong=Activer l'interface en 
mode texte ? (activer consiste ? fournir une interface ? la telnet, ? travers 
une socket)
-TextModeClientInterfaceServer.enabled=Activer TMCI
-TextModeClientInterfaceServer.enabledLong=Activer le serveur TMCI (interface 
en ligne de commande) ou non
-TextModeClientInterfaceServer.ssl=Activer ssl ?
-TextModeClientInterfaceServer.sslLong=Activer ssl ?
-TextModeClientInterfaceServer.telnetPortNumber=Port telnet
-TextModeClientInterfaceServer.telnetPortNumberLong=Num?ro de port telnet
+TextModeClientInterfaceServer.enabled=Activer TMCI ?
+TextModeClientInterfaceServer.enabledLong=Activer l'interface en mode texte
+TextModeClientInterfaceServer.ssl=Activer SSL ?
+TextModeClientInterfaceServer.sslLong=Activer SSL sur le serveur TMCI
+TextModeClientInterfaceServer.telnetPortNumber=Port TMCI
+TextModeClientInterfaceServer.telnetPortNumberLong=Le port TCP ?cout? par le 
serveur TMCI
 TimeSkewDetectedUserAlert.text=Un d?callage horaire a ?t? d?tect? par votre 
noeud. C'est TRES mauvais. Votre noeud ne fonctionnera pas correctement tant 
que ce ne sera pas corrig?. Les causes habituelles sont une mauvaise 
configuration de la mise en veille, du client de synchronisation horaire ou un 
probl?me mat?riel.
 TimeSkewDetectedUserAlert.title=D?callage horaire d?tect? !
 Toadlet.cancel=Annuler

Modified: trunk/freenet/src/freenet/node/DarknetPeerNode.java
===================================================================
--- trunk/freenet/src/freenet/node/DarknetPeerNode.java 2008-04-14 15:55:20 UTC 
(rev 19325)
+++ trunk/freenet/src/freenet/node/DarknetPeerNode.java 2008-04-14 16:00:44 UTC 
(rev 19326)
@@ -546,7 +546,7 @@
                        Logger.error(this, "Extra peer data directory for peer 
not a directory: "+extraPeerDataPeerDir.getPath());
                        return -1;
                }
-               Integer[] localFileNumbers = null;
+               Integer[] localFileNumbers;
                int nextFileNumber = 0;
                synchronized(extraPeerDataFileNumbers) {
                        // Find the first free slot
@@ -635,7 +635,7 @@
                        Logger.error(this, "Extra peer data directory for peer 
not a directory: "+extraPeerDataPeerDir.getPath());
                        return;
                }
-               Integer[] localFileNumbers = null;
+               Integer[] localFileNumbers;
                synchronized(extraPeerDataFileNumbers) {
                        localFileNumbers = (Integer[]) 
extraPeerDataFileNumbers.toArray(new Integer[extraPeerDataFileNumbers.size()]);
                }
@@ -731,7 +731,7 @@
        public void sendQueuedN2NMs() {
                if(logMINOR)
                        Logger.minor(this, "Sending queued N2NMs for 
"+shortToString());
-               Integer[] localFileNumbers = null;
+               Integer[] localFileNumbers;
                synchronized(queuedToSendN2NMExtraPeerDataFileNumbers) {
                        localFileNumbers = (Integer[]) 
queuedToSendN2NMExtraPeerDataFileNumbers.toArray(new 
Integer[queuedToSendN2NMExtraPeerDataFileNumbers.size()]);
                }

Modified: trunk/freenet/src/freenet/node/FNPPacketMangler.java
===================================================================
--- trunk/freenet/src/freenet/node/FNPPacketMangler.java        2008-04-14 
15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/node/FNPPacketMangler.java        2008-04-14 
16:00:44 UTC (rev 19326)
@@ -944,7 +944,7 @@
                }

                // sanity check
-               byte[] myNi = null;
+               byte[] myNi;
                synchronized (pn) {
                        myNi = (byte[]) pn.jfkNoncesSent.get(replyTo);
                }
@@ -1124,7 +1124,7 @@
                byte[] data = new byte[decypheredPayload.length - 
decypheredPayloadOffset];
                System.arraycopy(decypheredPayload, decypheredPayloadOffset, 
data, 0, decypheredPayload.length - decypheredPayloadOffset);
                long bootID = Fields.bytesToLong(data);
-               byte[] hisRef = new byte[data.length -8];
+               byte[] hisRef = new byte[data.length > 8 ? data.length -8 : 0];
                System.arraycopy(data, 8, hisRef, 0, hisRef.length);

                // construct the peernode

Modified: trunk/freenet/src/freenet/node/FailureTable.java
===================================================================
--- trunk/freenet/src/freenet/node/FailureTable.java    2008-04-14 15:55:20 UTC 
(rev 19325)
+++ trunk/freenet/src/freenet/node/FailureTable.java    2008-04-14 16:00:44 UTC 
(rev 19326)
@@ -154,18 +154,21 @@
                        if(logMINOR) Logger.minor(this, "Deleting "+offer+" 
from "+this);
                        synchronized(this) {
                                int idx = -1;
-                               for(int i=0;i<offers.length;i++) {
+                               final int offerLength = offers.length;
+                               for(int i=0;i<offerLength;i++) {
                                        if(offers[i] == offer) idx = i;
                                }
                                if(idx == -1) return;
-                               BlockOffer[] newOffers = new 
BlockOffer[offers.length-1];
-                               if(idx > 0)
-                                       System.arraycopy(offers, 0, newOffers, 
0, idx);
-                               if(idx < newOffers.length)
-                                       System.arraycopy(offers, idx+1, 
newOffers, idx, offers.length-idx-1);
-                               offers = newOffers;
+                               if(offers.length > 1) {
+                                       BlockOffer[] newOffers = new 
BlockOffer[offerLength > 1 ? offerLength-1 : 0];
+                                       if(idx > 0)
+                                               System.arraycopy(offers, 0, 
newOffers, 0, idx);
+                                       if(idx < newOffers.length)
+                                               System.arraycopy(offers, idx+1, 
newOffers, idx, offers.length-idx-1);
+                                       offers = newOffers;
+                               }
                        }
-                       if(offers.length == 0) {
+                       if(offers.length < 1) {
                                synchronized(FailureTable.this) {
                                        
blockOfferListByKey.removeKey(entry.key);
                                }

Modified: trunk/freenet/src/freenet/node/IPDetectorPluginManager.java
===================================================================
--- trunk/freenet/src/freenet/node/IPDetectorPluginManager.java 2008-04-14 
15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/node/IPDetectorPluginManager.java 2008-04-14 
16:00:44 UTC (rev 19326)
@@ -8,6 +8,7 @@
 import java.util.Set;
 import java.util.Vector;

+import freenet.io.AddressTracker;
 import freenet.io.comm.FreenetInetAddress;
 import freenet.io.comm.Peer;
 import freenet.l10n.L10n;
@@ -36,10 +37,12 @@

                final boolean suggestPortForward;
                private int[] portsNotForwarded;
+               boolean noDismiss;

-               public MyUserAlert(String title, String text, boolean 
suggestPortForward, short code) {
+               public MyUserAlert(String title, String text, boolean 
suggestPortForward, short code, boolean noDismiss) {
                        super(false, title, text, null, code, true, 
L10n.getString("UserAlert.hide"), false, null);
                        this.suggestPortForward = suggestPortForward;
+                       this.noDismiss = noDismiss;
                        portsNotForwarded = new int[] { };
                }

@@ -63,10 +66,10 @@
                        StringBuffer sb = new StringBuffer();
                        sb.append(super.getText());
                        if(portsNotForwarded.length == 1) {
-                               sb.append(l10n("suggestForwardPort", "port", 
Integer.toString(portsNotForwarded[0])));
+                               sb.append(l10n("suggestForwardPort", "port", 
Integer.toString(Math.abs(portsNotForwarded[0]))));
                        } else if(portsNotForwarded.length >= 2) {
                                sb.append(l10n("suggestForwardTwoPorts", new 
String[] { "port1", "port2" }, 
-                                               new String[] { 
Integer.toString(portsNotForwarded[0]), Integer.toString(portsNotForwarded[1]) 
}));
+                                               new String[] { 
Integer.toString(Math.abs(portsNotForwarded[0])), 
Integer.toString(Math.abs(portsNotForwarded[1])) }));
                                if(portsNotForwarded.length > 2)
                                        Logger.error(this, "Not able to tell 
user about more than 2 ports to forward! ("+portsNotForwarded.length+")");
                        }
@@ -88,7 +91,16 @@
                }

                public boolean userCanDismiss() {
-                       return !suggestPortForward;
+                       if(noDismiss) return true;
+                       // If no ports need forwarding, make it dismissable 
immediately.
+                       if(!suggestPortForward) return true;
+                       // Prevent NPE.
+                       if(portsNotForwarded == null) return false;
+                       // If any port definitely does need forwarding, make it 
non-dismissable.
+                       for(int i=0;i<portsNotForwarded.length;i++)
+                               if(portsNotForwarded[i] < 0) return false; // 
Port definitely needs to be forwarded
+                       // Otherwise it is dismissable.
+                       return true;
                }

        }
@@ -114,28 +126,31 @@
                this.node = node;
                this.detector = detector;
                noConnectionAlert = new MyUserAlert( 
l10n("noConnectivityTitle"), l10n("noConnectivity"), 
-                               true, UserAlert.ERROR);
+                               true, UserAlert.ERROR, true);
                symmetricAlert = new MyUserAlert(l10n("symmetricTitle"), 
l10n("symmetric"), 
-                               true, UserAlert.ERROR);                         
+                               true, UserAlert.ERROR, false);                  
        
                portRestrictedAlert = new 
MyUserAlert(l10n("portRestrictedTitle"), l10n("portRestricted"), 
-                               true, UserAlert.WARNING);
+                               true, UserAlert.WARNING, false);
                restrictedAlert = new MyUserAlert(l10n("restrictedTitle"), 
l10n("restricted"), 
-                               false, UserAlert.MINOR);
+                               false, UserAlert.MINOR, false);
        }

        public int[] getUDPPortsNotForwarded() {
                OpennetManager om = node.getOpennet();
-               if(om == null || om.crypto.definitelyPortForwarded()) {
-                       if(node.darknetDefinitelyPortForwarded()) {
+               int darknetStatus = 
node.darknetCrypto.getDetectedConnectivityStatus();
+               int opennetStatus = om == null ? AddressTracker.DONT_KNOW : 
om.crypto.getDetectedConnectivityStatus();
+               if(om == null || opennetStatus == 
AddressTracker.DEFINITELY_PORT_FORWARDED) {
+                       if(darknetStatus == 
AddressTracker.DEFINITELY_PORT_FORWARDED) {
                                return new int[] { };
                        } else {
-                               return new int[] { node.getDarknetPortNumber() 
};
+                               return new int[] { (darknetStatus < 
AddressTracker.DONT_KNOW ? -1 : 1) * node.getDarknetPortNumber() };
                        }
                } else {
-                       if(node.darknetDefinitelyPortForwarded()) {
-                               return new int[] { om.crypto.portNumber };
+                       if(darknetStatus == 
AddressTracker.DEFINITELY_PORT_FORWARDED) {
+                               return new int[] { (opennetStatus < 
AddressTracker.DONT_KNOW ? -1 : 1 ) * om.crypto.portNumber };
                        } else {
-                               return new int[] { node.getDarknetPortNumber(), 
om.crypto.portNumber };
+                               return new int[] { (darknetStatus < 
AddressTracker.DONT_KNOW ? -1 : 1 ) * node.getDarknetPortNumber(), 
+                                               (opennetStatus < 
AddressTracker.DONT_KNOW ? -1 : 1 ) * om.crypto.portNumber };
                        }
                }
        }

Modified: trunk/freenet/src/freenet/node/LocationManager.java
===================================================================
--- trunk/freenet/src/freenet/node/LocationManager.java 2008-04-14 15:55:20 UTC 
(rev 19325)
+++ trunk/freenet/src/freenet/node/LocationManager.java 2008-04-14 16:00:44 UTC 
(rev 19326)
@@ -10,8 +10,12 @@
 import java.text.DateFormat;
 import java.util.Date;
 import java.util.Enumeration;
+import java.util.HashSet;
 import java.util.Hashtable;
+import java.util.Iterator;
 import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
 import java.util.Vector;

 import freenet.crypt.RandomSource;
@@ -345,7 +349,7 @@
             }
             registerKnownLocation(hisLoc);

-            double[] hisFriendLocs = new double[hisBufLong.length-2];
+            double[] hisFriendLocs = new double[hisBufLong.length > 2 ? 
hisBufLong.length-2 : 0];
             for(int i=0;i<hisFriendLocs.length;i++) {
                 hisFriendLocs[i] = Double.longBitsToDouble(hisBufLong[i+2]);
                 if((hisFriendLocs[i] < 0.0) || (hisFriendLocs[i] > 1.0)) {
@@ -542,7 +546,7 @@
                 }
                 registerKnownLocation(hisLoc);

-                double[] hisFriendLocs = new double[hisBufLong.length-2];
+                double[] hisFriendLocs = new double[hisBufLong.length > 2 ? 
hisBufLong.length-2 : 0];
                 for(int i=0;i<hisFriendLocs.length;i++) {
                     hisFriendLocs[i] = 
Double.longBitsToDouble(hisBufLong[i+2]);
                     if((hisFriendLocs[i] < 0.0) || (hisFriendLocs[i] > 1.0)) {
@@ -1217,20 +1221,28 @@
     public void lostOrRestartedNode(PeerNode pn) {
         Vector v = new Vector();
         synchronized(recentlyForwardedIDs) {
-            Enumeration e = recentlyForwardedIDs.keys();
-            while(e.hasMoreElements()) {
-                Long l = (Long)e.nextElement();
-                RecentlyForwardedItem item = 
(RecentlyForwardedItem)recentlyForwardedIDs.get(l);
+               Set entrySet = recentlyForwardedIDs.entrySet();
+                       Iterator it = entrySet.iterator();
+                       while (it.hasNext()) {
+                               Map.Entry entry = (Map.Entry) it.next();
+                               Long l = (Long) entry.getKey();
+
+                               RecentlyForwardedItem item = 
(RecentlyForwardedItem) entry.getValue();
+                               
                 if(item == null) {
                        Logger.error(this, "Key is "+l+" but no value on 
recentlyForwardedIDs - shouldn't be possible");
                        continue;
                 }
                 if(item.routedTo != pn) continue;
                 if(item.successfullyForwarded) {
-                    removeRecentlyForwardedItem(item);
                     v.add(item);
                 }
             }
+                       
+                       // remove them
+                       Iterator it2 = v.iterator();
+                       while (it2.hasNext())
+                               
removeRecentlyForwardedItem((RecentlyForwardedItem) it2.next());
         }
                int dumped=v.size();
                if (dumped!=0 && logMINOR)

Modified: trunk/freenet/src/freenet/node/MemoryChecker.java
===================================================================
--- trunk/freenet/src/freenet/node/MemoryChecker.java   2008-04-14 15:55:20 UTC 
(rev 19325)
+++ trunk/freenet/src/freenet/node/MemoryChecker.java   2008-04-14 16:00:44 UTC 
(rev 19326)
@@ -47,7 +47,7 @@
                        ps.queueTimedJob(this, 120 * 250); // 30 sec
                        return;
                } else
-                       ps.queueTimedJob(this, 120 * sleeptime);
+                       ps.queueTimedJob(this, (long) 120 * sleeptime);

                // FIXME
                // Do not remove until all known memory issues fixed,

Modified: trunk/freenet/src/freenet/node/Node.java
===================================================================
--- trunk/freenet/src/freenet/node/Node.java    2008-04-14 15:55:20 UTC (rev 
19325)
+++ trunk/freenet/src/freenet/node/Node.java    2008-04-14 16:00:44 UTC (rev 
19326)
@@ -15,6 +15,7 @@
 import java.net.UnknownHostException;
 import java.security.MessageDigest;
 import java.util.Arrays;
+import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -107,6 +108,7 @@
 import freenet.support.LRUQueue;
 import freenet.support.Logger;
 import freenet.support.OOMHandler;
+import freenet.support.OOMHook;
 import freenet.support.PooledExecutor;
 import freenet.support.ShortBuffer;
 import freenet.support.SimpleFieldSet;
@@ -124,7 +126,7 @@
 /**
  * @author amphibian
  */
-public class Node implements TimeSkewDetectorCallback, GetPubkey {
+public class Node implements TimeSkewDetectorCallback, GetPubkey, OOMHook {

        private static boolean logMINOR;

@@ -475,7 +477,7 @@
         * to the datastore only if it's from an insert, and we are a sink, but 
when calculating whether
         * we are a sink we ignore nodes which have less uptime (percentage) 
than this parameter.
         */
-       private static final int MIN_UPTIME_STORE_KEY = 40;
+       private static final int MIN_UPTIME_STORE_KEY = 0;

        /**
         * Read all storable settings (identity etc) from the node file.
@@ -1468,33 +1470,33 @@
                        System.out.println("Initializing CHK Datastore 
("+maxStoreKeys+" keys)");
                        chkDatastore = new CHKStore();
                        BerkeleyDBFreenetStore.construct(storeDir, true, 
suffix, maxStoreKeys, FreenetStore.TYPE_CHK, 
-                                       storeEnvironment, storeShutdownHook, 
reconstructFile, chkDatastore);
+                                       storeEnvironment, storeShutdownHook, 
reconstructFile, chkDatastore, random);
                        Logger.normal(this, "Initializing CHK Datacache");
                        System.out.println("Initializing CHK Datacache 
("+maxCacheKeys+ ':' +maxCacheKeys+" keys)");
                        chkDatacache = new CHKStore();
                        BerkeleyDBFreenetStore.construct(storeDir, false, 
suffix, maxCacheKeys, FreenetStore.TYPE_CHK, 
-                                       storeEnvironment, storeShutdownHook, 
reconstructFile, chkDatacache);
+                                       storeEnvironment, storeShutdownHook, 
reconstructFile, chkDatacache, random);
                        Logger.normal(this, "Initializing pubKey Datastore");
                        System.out.println("Initializing pubKey Datastore");
                        pubKeyDatastore = new PubkeyStore();
                        BerkeleyDBFreenetStore.construct(storeDir, true, 
suffix, maxStoreKeys, FreenetStore.TYPE_PUBKEY, 
-                                       storeEnvironment, storeShutdownHook, 
reconstructFile, pubKeyDatastore);
+                                       storeEnvironment, storeShutdownHook, 
reconstructFile, pubKeyDatastore, random);
                        Logger.normal(this, "Initializing pubKey Datacache");
                        System.out.println("Initializing pubKey Datacache 
("+maxCacheKeys+" keys)");
                        pubKeyDatacache = new PubkeyStore();
                        BerkeleyDBFreenetStore.construct(storeDir, false, 
suffix, maxCacheKeys, FreenetStore.TYPE_PUBKEY, 
-                                       storeEnvironment, storeShutdownHook, 
reconstructFile, pubKeyDatacache);
+                                       storeEnvironment, storeShutdownHook, 
reconstructFile, pubKeyDatacache, random);
                        // FIXME can't auto-fix SSK stores.
                        Logger.normal(this, "Initializing SSK Datastore");
                        System.out.println("Initializing SSK Datastore");
                        sskDatastore = new SSKStore(this);
                        BerkeleyDBFreenetStore.construct(storeDir, true, 
suffix, maxStoreKeys, FreenetStore.TYPE_SSK, 
-                                       storeEnvironment, storeShutdownHook, 
reconstructFile, sskDatastore);
+                                       storeEnvironment, storeShutdownHook, 
reconstructFile, sskDatastore, random);
                        Logger.normal(this, "Initializing SSK Datacache");
                        System.out.println("Initializing SSK Datacache 
("+maxCacheKeys+" keys)");
                        sskDatacache = new SSKStore(this);
                        BerkeleyDBFreenetStore.construct(storeDir, false, 
suffix, maxStoreKeys, FreenetStore.TYPE_SSK, 
-                                       storeEnvironment, storeShutdownHook, 
reconstructFile, sskDatacache);
+                                       storeEnvironment, storeShutdownHook, 
reconstructFile, sskDatacache, random);
                } catch (FileNotFoundException e1) {
                        String msg = "Could not open datastore: "+e1;
                        Logger.error(this, msg, e1);
@@ -1620,6 +1622,8 @@
                        e.printStackTrace();
                        throw new 
NodeInitException(NodeInitException.EXIT_COULD_NOT_START_UPDATER, "Could not 
create Updater: "+e);
                }
+
+               OOMHandler.addOOMHook(this);

                Logger.normal(this, "Node constructor completed");
                System.out.println("Node constructor completed");
@@ -1648,8 +1652,8 @@
                }
                Logger.normal(this, "Freenet 0.7 Build 
#"+Version.buildNumber()+" r"+Version.cvsRevision);
                System.out.println("Freenet 0.7 Build 
#"+Version.buildNumber()+" r"+Version.cvsRevision);
-               Logger.normal(this, "FNP port is on "+darknetCrypto.bindto+ ':' 
+getDarknetPortNumber());
-               System.out.println("FNP port is on "+darknetCrypto.bindto+ ':' 
+getDarknetPortNumber());
+               Logger.normal(this, "FNP port is on 
"+darknetCrypto.getBindTo()+ ':' +getDarknetPortNumber());
+               System.out.println("FNP port is on "+darknetCrypto.getBindTo()+ 
':' +getDarknetPortNumber());
                // Start services

 //             SubConfig pluginManagerConfig = new SubConfig("pluginmanager3", 
config);
@@ -3116,9 +3120,9 @@
         */
        public synchronized boolean dontDetect() {
                // Only return true if bindTo is set on all ports which are in 
use
-               if(!darknetCrypto.bindto.isRealInternetAddress(false, true, 
false)) return false;
+               if(!darknetCrypto.getBindTo().isRealInternetAddress(false, 
true, false)) return false;
                if(opennet != null) {
-                       if(opennet.crypto.bindto.isRealInternetAddress(false, 
true, false)) return false;
+                       
if(opennet.crypto.getBindTo().isRealInternetAddress(false, true, false)) return 
false;
                }
                return true;
        }
@@ -3266,4 +3270,23 @@
        public void setDispatcherHook(NodeDispatcherCallback cb) {
                this.dispatcher.setHook(cb);
        }
+
+       /**
+        * Free some memory
+        */
+       public void handleOOM() throws Exception {
+               if (cachedPubKeys != null) {
+                       Object value;
+                       do {
+                               value = cachedPubKeys.popKey();
+                       } while (value != null);
+               }
+               if (recentlyCompletedIDs != null) {
+                       synchronized (recentlyCompletedIDs) {
+                               // half it size
+                               while (recentlyCompletedIDs.size() > 
MAX_RECENTLY_COMPLETED_IDS / 2)
+                                       recentlyCompletedIDs.pop();
+                       }
+               }
+    }
 }

Modified: trunk/freenet/src/freenet/node/NodeCrypto.java
===================================================================
--- trunk/freenet/src/freenet/node/NodeCrypto.java      2008-04-14 15:55:20 UTC 
(rev 19325)
+++ trunk/freenet/src/freenet/node/NodeCrypto.java      2008-04-14 16:00:44 UTC 
(rev 19326)
@@ -51,7 +51,6 @@
        /** The object which handles our specific UDP port, pulls messages from 
it, feeds them to the packet mangler for decryption etc */
        UdpSocketHandler socket;
        public FNPPacketMangler packetMangler;
-       final FreenetInetAddress bindto;
        // FIXME: abstract out address stuff? Possibly to something like 
NodeReference?
        final int portNumber;
        byte[] myIdentity; // FIXME: simple identity block; should be unique
@@ -100,7 +99,7 @@

                int port = config.getPort();

-               bindto = config.getBindTo();
+               FreenetInetAddress bindto = config.getBindTo();

                UdpSocketHandler u = null;

@@ -505,4 +504,12 @@
        public boolean definitelyPortForwarded() {
                return socket.getDetectedConnectivityStatus() == 
AddressTracker.DEFINITELY_PORT_FORWARDED;
        }
+       
+       public int getDetectedConnectivityStatus() {
+               return socket.getDetectedConnectivityStatus();
+       }
+
+       public FreenetInetAddress getBindTo() {
+               return config.getBindTo();
+       }
 }

Modified: trunk/freenet/src/freenet/node/NodeIPPortDetector.java
===================================================================
--- trunk/freenet/src/freenet/node/NodeIPPortDetector.java      2008-04-14 
15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/node/NodeIPPortDetector.java      2008-04-14 
16:00:44 UTC (rev 19326)
@@ -43,18 +43,13 @@
         * (for that port/NodeCrypto) list of IP addresses (still without port 
numbers).
         */
        FreenetInetAddress[] detectPrimaryIPAddress() {
-               FreenetInetAddress[] addresses = 
ipDetector.detectPrimaryIPAddress();
-               FreenetInetAddress addr = crypto.bindto;
+               FreenetInetAddress addr = crypto.getBindTo();
                if(addr.isRealInternetAddress(false, true, false)) {
-                       for(int i=0;i<addresses.length;i++) {
-                               if(addresses[i] == addr) return addresses;
-                       }
-                       FreenetInetAddress[] newAddresses = new 
FreenetInetAddress[addresses.length+1];
-                       System.arraycopy(addresses, 0, newAddresses, 0, 
addresses.length);
-                       newAddresses[addresses.length] = addr;
-                       return newAddresses;
+                       // Binding to a real internet address => don't want us 
to use the others, most likely
+                       // he is on a multi-homed box where only one IP can be 
used for Freenet.
+                       return new FreenetInetAddress[] { addr };
                }
-               return addresses;
+               return ipDetector.detectPrimaryIPAddress();
        }

        /**
@@ -82,7 +77,8 @@
                                if((p == null) || p.isNull()) continue;
                                // DNSRequester doesn't deal with our own node
                                if(!IPUtil.isValidAddress(p.getAddress(true), 
false)) continue;
-                               Logger.normal(this, "Peer 
"+peerList[i].getPeer()+" thinks we are "+p);
+                               if(Logger.shouldLog(Logger.MINOR, this))
+                                       Logger.minor(this, "Peer 
"+peerList[i].getPeer()+" thinks we are "+p);
                                if(countsByPeer.containsKey(p)) {
                                        Integer count = (Integer) 
countsByPeer.get(p);
                                        Integer newCount = new 
Integer(count.intValue()+1);

Modified: trunk/freenet/src/freenet/node/OpennetManager.java
===================================================================
--- trunk/freenet/src/freenet/node/OpennetManager.java  2008-04-14 15:55:20 UTC 
(rev 19325)
+++ trunk/freenet/src/freenet/node/OpennetManager.java  2008-04-14 16:00:44 UTC 
(rev 19326)
@@ -415,7 +415,7 @@
                        successCount++;
                        if(peersLRU.contains(pn)) {
                                peersLRU.push(pn);
-                               Logger.normal(this, "Opennet peer "+pn+" 
promoted to top of LRU because of successful request");
+                               if(logMINOR) Logger.minor(this, "Opennet peer 
"+pn+" promoted to top of LRU because of successful request");
                                return;
                        } else {
                                if(logMINOR) Logger.minor(this, "Success on 
opennet peer which isn't in the LRU!: "+pn, new Exception("debug"));

Modified: trunk/freenet/src/freenet/node/PacketSender.java
===================================================================
--- trunk/freenet/src/freenet/node/PacketSender.java    2008-04-14 15:55:20 UTC 
(rev 19325)
+++ trunk/freenet/src/freenet/node/PacketSender.java    2008-04-14 16:00:44 UTC 
(rev 19326)
@@ -385,7 +385,7 @@
                        lastClearedOldSwapChains = now;
                }

-               long oldNow = System.currentTimeMillis();
+               long oldNow = now;

                // Send may have taken some time
                now = System.currentTimeMillis();

Modified: trunk/freenet/src/freenet/node/PeerManager.java
===================================================================
--- trunk/freenet/src/freenet/node/PeerManager.java     2008-04-14 15:55:20 UTC 
(rev 19325)
+++ trunk/freenet/src/freenet/node/PeerManager.java     2008-04-14 16:00:44 UTC 
(rev 19326)
@@ -319,7 +319,8 @@
                        connectedPeers = newConnectedPeers;

                        // removing from myPeers
-                       PeerNode[] newMyPeers = new PeerNode[myPeers.length-1];
+                       int myPeerLength = myPeers.length;
+                       PeerNode[] newMyPeers = new PeerNode[myPeerLength > 1 ? 
myPeerLength-1 : 0];
                        int positionInNewArray = 0;
                        for(int i=0;i<myPeers.length;i++) {
                                if(myPeers[i]!=pn){

Modified: trunk/freenet/src/freenet/node/PeerNode.java
===================================================================
--- trunk/freenet/src/freenet/node/PeerNode.java        2008-04-14 15:55:20 UTC 
(rev 19325)
+++ trunk/freenet/src/freenet/node/PeerNode.java        2008-04-14 16:00:44 UTC 
(rev 19326)
@@ -817,7 +817,6 @@
                }
                if(logMINOR)
                        Logger.minor(this, "Updating handshake IPs for peer '" 
+ shortToString() + "' (" + ignoreHostnames + ')');
-               Peer[] localHandshakeIPs;
                Peer[] myNominalPeer;

                // Don't synchronize while doing lookups which may take a long 
time!
@@ -825,9 +824,9 @@
                        myNominalPeer = (Peer[]) nominalPeer.toArray(new 
Peer[nominalPeer.size()]);
                }

+               Peer[] localHandshakeIPs;
                if(myNominalPeer.length == 0) {
                        if(localDetectedPeer == null) {
-                               localHandshakeIPs = null;
                                synchronized(this) {
                                        handshakeIPs = null;
                                }
@@ -2266,7 +2265,7 @@
                                try {
                                        simpleVersion = 
Version.getArbitraryBuildNumber(version);
                                } catch (VersionParseException e) {
-                                       Logger.error(this, "Bad version: 
"+simpleVersion+" : "+e, e);
+                                       Logger.error(this, "Bad version: " + 
version + " : " + e, e);
                                }
                        }
                        Version.seenVersion(newVersion);
@@ -3638,8 +3637,9 @@
        }

        public synchronized boolean completedAnnounce(long uid) {
-               if(runningAnnounceUIDs.length == 0) return false;
-               long[] newList = new long[runningAnnounceUIDs.length - 1];
+               final int runningAnnounceUIDsLength = 
runningAnnounceUIDs.length;
+               if(runningAnnounceUIDsLength < 1) return false;
+               long[] newList = new long[runningAnnounceUIDsLength - 1];
                int x = 0;
                for(int i=0;i<runningAnnounceUIDs.length;i++) {
                        if(i == runningAnnounceUIDs.length) return false;

Modified: trunk/freenet/src/freenet/node/UptimeEstimator.java
===================================================================
--- trunk/freenet/src/freenet/node/UptimeEstimator.java 2008-04-14 15:55:20 UTC 
(rev 19325)
+++ trunk/freenet/src/freenet/node/UptimeEstimator.java 2008-04-14 16:00:44 UTC 
(rev 19326)
@@ -101,7 +101,7 @@
                        if(slot == wasOnline.length) slot = 0;
                }
                long now = System.currentTimeMillis();
-               if(logFile.length() > wasOnline.length*4) {
+               if(logFile.length() > (long) wasOnline.length*4) {
                        prevFile.delete();
                        logFile.renameTo(prevFile);
                }

Modified: trunk/freenet/src/freenet/node/fcp/ClientGet.java
===================================================================
--- trunk/freenet/src/freenet/node/fcp/ClientGet.java   2008-04-14 15:55:20 UTC 
(rev 19325)
+++ trunk/freenet/src/freenet/node/fcp/ClientGet.java   2008-04-14 16:00:44 UTC 
(rev 19326)
@@ -415,6 +415,7 @@
                if(!dontFree)
                        data.free();
                finish();
+               client.notifySuccess(this);
        }

        private void trySendDataFoundOrGetFailed(FCPConnectionOutputHandler 
handler) {
@@ -496,6 +497,7 @@
                        Logger.minor(this, "Caught "+e, e);
                trySendDataFoundOrGetFailed(null);
                finish();
+               client.notifyFailure(this);
                if(persistenceType != PERSIST_CONNECTION)
                        client.server.forceStorePersistentRequests();
        }

Modified: trunk/freenet/src/freenet/node/fcp/ClientPutBase.java
===================================================================
--- trunk/freenet/src/freenet/node/fcp/ClientPutBase.java       2008-04-14 
15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/node/fcp/ClientPutBase.java       2008-04-14 
16:00:44 UTC (rev 19326)
@@ -141,6 +141,7 @@
                freeData();
                finish();
                trySendFinalMessage(null);
+               client.notifySuccess(this);
                if(persistenceType != PERSIST_CONNECTION)
                        client.server.forceStorePersistentRequests();
        }
@@ -154,6 +155,7 @@
                freeData();
                finish();
                trySendFinalMessage(null);
+               client.notifyFailure(this);
                if(persistenceType != PERSIST_CONNECTION)
                        client.server.forceStorePersistentRequests();
        }

Modified: trunk/freenet/src/freenet/node/fcp/ClientPutComplexDirMessage.java
===================================================================
--- trunk/freenet/src/freenet/node/fcp/ClientPutComplexDirMessage.java  
2008-04-14 15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/node/fcp/ClientPutComplexDirMessage.java  
2008-04-14 16:00:44 UTC (rev 19326)
@@ -9,6 +9,7 @@
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedList;
+import java.util.Map;

 import freenet.client.async.ManifestElement;
 import freenet.node.Node;
@@ -151,10 +152,11 @@
         * HashMap's containing ManifestElement's.
         */
        private void convertFilesByNameToManifestElements(HashMap filesByName, 
HashMap manifestElements, Node node) throws MessageInvalidException {
-               Iterator i = filesByName.keySet().iterator();
+               Iterator i = filesByName.entrySet().iterator();
                while(i.hasNext()) {
-                       String tempName = (String) (i.next());
-                       Object val = filesByName.get(tempName);
+                       Map.Entry entry = (Map.Entry) i.next();
+                       String tempName = (String) entry.getKey();
+                       Object val = entry.getValue();
                        if(val instanceof HashMap) {
                                HashMap h = (HashMap) val;
                                HashMap manifests = new HashMap();

Modified: trunk/freenet/src/freenet/node/fcp/FCPClient.java
===================================================================
--- trunk/freenet/src/freenet/node/fcp/FCPClient.java   2008-04-14 15:55:20 UTC 
(rev 19325)
+++ trunk/freenet/src/freenet/node/fcp/FCPClient.java   2008-04-14 16:00:44 UTC 
(rev 19326)
@@ -18,7 +18,7 @@
  */
 public class FCPClient {

-       public FCPClient(String name2, FCPServer server, FCPConnectionHandler 
handler, boolean isGlobalQueue) {
+       public FCPClient(String name2, FCPServer server, FCPConnectionHandler 
handler, boolean isGlobalQueue, RequestCompletionCallback cb) {
                this.name = name2;
                if(name == null) throw new NullPointerException();
                this.currentConnection = handler;
@@ -35,6 +35,7 @@
                watchGlobalVerbosityMask = Integer.MAX_VALUE;
                toStart = new LinkedList();
                lowLevelClient = this;
+               completionCallback = cb;
        }

        /** The client's Name sent in the ClientHello message */
@@ -66,6 +67,7 @@
        private final LinkedList toStart;
        /** Low-level client object, for freenet.client.async. Normally == 
this. */
        final Object lowLevelClient;
+       private RequestCompletionCallback completionCallback;

        public synchronized FCPConnectionHandler getConnection() {
                return currentConnection;
@@ -254,4 +256,27 @@
        public String toString() {
                return super.toString()+ ':' +name;
        }
+
+       /**
+        * Callback called when a request succeeds.
+        */
+       public void notifySuccess(ClientRequest req) {
+               if(completionCallback != null)
+                       completionCallback.notifySuccess(req);
+       }
+
+       /**
+        * Callback called when a request fails
+        * @param get
+        */
+       public void notifyFailure(ClientRequest req) {
+               if(completionCallback != null)
+                       completionCallback.notifyFailure(req);
+       }
+       
+       public synchronized RequestCompletionCallback 
setRequestCompletionCallback(RequestCompletionCallback cb) {
+               RequestCompletionCallback old = completionCallback;
+               completionCallback = cb;
+               return old;
+       }
 }

Modified: trunk/freenet/src/freenet/node/fcp/FCPServer.java
===================================================================
--- trunk/freenet/src/freenet/node/fcp/FCPServer.java   2008-04-14 15:55:20 UTC 
(rev 19325)
+++ trunk/freenet/src/freenet/node/fcp/FCPServer.java   2008-04-14 16:00:44 UTC 
(rev 19326)
@@ -119,7 +119,7 @@
                defaultFetchContext = client.getFetchContext();
                defaultInsertContext = client.getInsertContext(false);

-               globalClient = new FCPClient("Global Queue", this, null, true);
+               globalClient = new FCPClient("Global Queue", this, null, true, 
null);

                logMINOR = Logger.shouldLog(Logger.MINOR, this);

@@ -517,7 +517,7 @@
                        oldClient = (FCPClient) clientsByName.get(name);
                        if(oldClient == null) {
                                // Create new client
-                               FCPClient client = new FCPClient(name, this, 
handler, false);
+                               FCPClient client = new FCPClient(name, this, 
handler, false, null);
                                clientsByName.put(name, client);
                                return client;
                        } else {
@@ -681,9 +681,10 @@
                        gis = new GZIPInputStream(fis);
                        bis = new BufferedInputStream(gis);
                        Logger.normal(this, "Loading persistent requests from 
"+file);
-                       if(file.length() > 0)
+                       if (file.length() > 0) {
                                loadPersistentRequests(bis);
-                       else
+                               haveLoadedPersistentRequests = true;
+                       } else
                                throw new IOException("File empty"); // If it's 
empty, try the temp file.
                } catch (IOException e) {
                        Logger.error(this, "IOE : " + e.getMessage(), e);
@@ -696,6 +697,7 @@
                                fis = new FileInputStream(file);
                                bis = new BufferedInputStream(fis);
                                loadPersistentRequests(bis);
+                               haveLoadedPersistentRequests = true;
                        } catch (IOException e1) {
                                Logger.normal(this, "It's corrupted too : Not 
reading any persistent requests from disk: "+e1);
                                return;
@@ -899,4 +901,9 @@
                return hasFinishedStart;
        }

+       public void setCompletionCallback(RequestCompletionCallback cb) {
+               if(globalClient.setRequestCompletionCallback(cb) != null)
+                       Logger.error(this, "Replacing request completion 
callback "+cb, new Exception("error"));
+       }
+       
 }

Modified: trunk/freenet/src/freenet/node/fcp/PersistentPutDir.java
===================================================================
--- trunk/freenet/src/freenet/node/fcp/PersistentPutDir.java    2008-04-14 
15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/node/fcp/PersistentPutDir.java    2008-04-14 
16:00:44 UTC (rev 19326)
@@ -86,7 +86,7 @@
                                } else if(data instanceof FileBucket) {
                                        subset.putSingle("UploadFrom", "disk");
                                        subset.putSingle("Filename", 
((FileBucket)data).getFile().getPath());
-                               } else if((data instanceof 
PaddedEphemerallyEncryptedBucket) || (data == null)) {
+                               } else if (data instanceof 
PaddedEphemerallyEncryptedBucket) {
                                        subset.putSingle("UploadFrom", 
"direct");
                                } else {
                                        throw new IllegalStateException("Don't 
know what to do with bucket: "+data);

Copied: trunk/freenet/src/freenet/node/fcp/RequestCompletionCallback.java (from 
rev 19322, trunk/freenet/src/freenet/node/fcp/RequestCompletionCallback.java)
===================================================================
--- trunk/freenet/src/freenet/node/fcp/RequestCompletionCallback.java           
                (rev 0)
+++ trunk/freenet/src/freenet/node/fcp/RequestCompletionCallback.java   
2008-04-14 16:00:44 UTC (rev 19326)
@@ -0,0 +1,15 @@
+package freenet.node.fcp;
+
+public interface RequestCompletionCallback {
+
+       /**
+        * Callback called when a request succeeds.
+        */
+       public void notifySuccess(ClientRequest req);
+       
+       /**
+        * Callback called when a request fails
+        */
+       public void notifyFailure(ClientRequest req);
+       
+}

Modified: trunk/freenet/src/freenet/node/updater/NodeUpdater.java
===================================================================
--- trunk/freenet/src/freenet/node/updater/NodeUpdater.java     2008-04-14 
15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/node/updater/NodeUpdater.java     2008-04-14 
16:00:44 UTC (rev 19326)
@@ -29,7 +29,6 @@
        private FetchContext ctx;
        private FetchResult result;
        private ClientGetter cg;
-       private boolean finalCheck;
        private FreenetURI URI;
        private final Ticker ticker;
        public final NodeClientCore core;
@@ -297,10 +296,6 @@
                return URI;
        }

-       public boolean inFinalCheck() {
-               return finalCheck;
-       }
-
        public void onMajorProgress() {
                // Ignore
        }

Modified: trunk/freenet/src/freenet/node/useralerts/OpennetUserAlert.java
===================================================================
--- trunk/freenet/src/freenet/node/useralerts/OpennetUserAlert.java     
2008-04-14 15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/node/useralerts/OpennetUserAlert.java     
2008-04-14 16:00:44 UTC (rev 19326)
@@ -9,12 +9,12 @@
        private final Node node;

        public OpennetUserAlert(Node node) {
-               super(true, L10n.getString("OpennetUserAlert.warningTitle"), 
L10n.getString("OpennetUserAlert.warning"), new HTMLNode("#", 
L10n.getString("OpennetUserAlert.warning")), UserAlert.WARNING, false, 
L10n.getString("UserAlert.hide"), false, null);
+               super(true, L10n.getString("OpennetUserAlert.warningTitle"), 
L10n.getString("OpennetUserAlert.warning"), new HTMLNode("#", 
L10n.getString("OpennetUserAlert.warning")), UserAlert.WARNING, true, 
L10n.getString("UserAlert.hide"), false, null);
                this.node = node;
        }

        public boolean isValid() {
-               return node.isOpennetEnabled();
+               return node.isOpennetEnabled() && valid;
        }

 }

Modified: trunk/freenet/src/freenet/node/useralerts/UserAlertManager.java
===================================================================
--- trunk/freenet/src/freenet/node/useralerts/UserAlertManager.java     
2008-04-14 15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/node/useralerts/UserAlertManager.java     
2008-04-14 16:00:44 UTC (rev 19326)
@@ -64,7 +64,7 @@
        }

        public UserAlert[] getAlerts() {
-               UserAlert[] a = null;
+               UserAlert[] a;
                synchronized (alerts) {
                        a = (UserAlert[]) alerts.toArray(new 
UserAlert[alerts.size()]);
                }

Copied: trunk/freenet/src/freenet/pluginmanager/FredPluginVersioned.java (from 
rev 19322, trunk/freenet/src/freenet/pluginmanager/FredPluginVersioned.java)
===================================================================
--- trunk/freenet/src/freenet/pluginmanager/FredPluginVersioned.java            
                (rev 0)
+++ trunk/freenet/src/freenet/pluginmanager/FredPluginVersioned.java    
2008-04-14 16:00:44 UTC (rev 19326)
@@ -0,0 +1,10 @@
+package freenet.pluginmanager;
+
+/**
+ * A Fred plugin that has a version
+ * @author dbkr
+ *
+ */
+public interface FredPluginVersioned {
+       public String getVersion();
+}

Modified: trunk/freenet/src/freenet/pluginmanager/PluginInfoWrapper.java
===================================================================
--- trunk/freenet/src/freenet/pluginmanager/PluginInfoWrapper.java      
2008-04-14 15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/pluginmanager/PluginInfoWrapper.java      
2008-04-14 16:00:44 UTC (rev 19326)
@@ -3,6 +3,7 @@
 import java.util.Date;
 import java.util.HashSet;

+import freenet.l10n.L10n;
 import freenet.support.Logger;
 import freenet.support.StringArray;

@@ -21,11 +22,11 @@
        private boolean isPortForwardPlugin;
        private boolean isMultiplePlugin;
        private boolean isFCPPlugin;
+       private boolean isVersionedPlugin;
        private String filename;
        private HashSet toadletLinks=new HashSet();
        private boolean stopping = false;
        private boolean unregistered = false;
-       //public String 

        public PluginInfoWrapper(FredPlugin plug, String filename) {
                this.plug = plug;
@@ -41,6 +42,7 @@
                isPortForwardPlugin = (plug instanceof FredPluginPortForward);
                isMultiplePlugin = (plug instanceof FredPluginMultiple);
                isFCPPlugin = (plug instanceof FredPluginFCP);
+               isVersionedPlugin = (plug instanceof FredPluginVersioned);
        }

        void setThread(Thread ps) {
@@ -66,6 +68,14 @@
                return plug.getClass().getName();
        }

+       public String getPluginVersion() {
+               if (isVersionedPlugin) {
+                       return ((FredPluginVersioned)plug).getVersion();
+               } else {
+                       return L10n.getString("PproxyToadlet.noVersion");
+               }
+       }
+       
        public synchronized String[] getPluginToadletSymlinks(){
                return StringArray.toArray(toadletLinks.toArray());
        }

Modified: trunk/freenet/src/freenet/pluginmanager/PluginManager.java
===================================================================
--- trunk/freenet/src/freenet/pluginmanager/PluginManager.java  2008-04-14 
15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/pluginmanager/PluginManager.java  2008-04-14 
16:00:44 UTC (rev 19326)
@@ -263,7 +263,7 @@
                File pluginDirectory = new File(node.getNodeDir(), "plugins");
                if (lastSlash == -1) {
                        /* it's an official plugin! */
-                       pluginFile = new File(pluginDirectory, 
pluginSpecification + ".jar.url");
+                       pluginFile = new File(pluginDirectory, 
pluginSpecification + ".jar");
                } else {
                        pluginFile = new File(pluginDirectory, 
pluginSpecification.substring(lastSlash + 1));
                }
@@ -491,6 +491,11 @@
                /* get plugin filename. */
                String completeFilename = pluginUrl.getPath();
                String filename = 
completeFilename.substring(completeFilename.lastIndexOf('/') + 1);
+               // The URL to the JAR file might end with .url because of the 
insane download server that redirects to a JAR file
+               // in response to a request for a file ending '.url'. Strip it 
off if so, since we want our JAR to end with '.jar'.
+               if (filename.endsWith(".url")) {
+                       filename = filename.substring(0, filename.length() - 4);
+               }
                pluginFile = new File(pluginDirectory, filename);

                /* check if file needs to be downloaded. */

Modified: trunk/freenet/src/freenet/store/BerkeleyDBFreenetStore.java
===================================================================
--- trunk/freenet/src/freenet/store/BerkeleyDBFreenetStore.java 2008-04-14 
15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/store/BerkeleyDBFreenetStore.java 2008-04-14 
16:00:44 UTC (rev 19326)
@@ -30,12 +30,16 @@
 import com.sleepycat.je.Transaction;
 import com.sleepycat.je.log.DbChecksumException;
 import com.sleepycat.je.log.LogFileNotFoundException;
+
+import freenet.crypt.RandomSource;
 import freenet.keys.KeyVerifyException;
 import freenet.node.NodeInitException;
 import freenet.node.SemiOrderedShutdownHook;
 import freenet.support.Fields;
 import freenet.support.HexUtil;
 import freenet.support.Logger;
+import freenet.support.OOMHandler;
+import freenet.support.OOMHook;
 import freenet.support.SortedLongSet;

 /**
@@ -46,7 +50,7 @@
  * @author tubbie
  * @author amphibian
  */
-public class BerkeleyDBFreenetStore implements FreenetStore {
+public class BerkeleyDBFreenetStore implements FreenetStore, OOMHook {

        private static boolean logMINOR;
        private static boolean logDEBUG;
@@ -55,6 +59,7 @@
        private final File reconstructFile;
        private final int dataBlockSize; 
        private final int headerBlockSize;
+       private final RandomSource random;

        private final Environment environment;
        private final TupleBinding storeBlockTupleBinding;
@@ -98,7 +103,7 @@
        public static FreenetStore construct(File baseStoreDir, boolean 
isStore, String suffix,
                        long maxStoreKeys, short type, 
                        Environment storeEnvironment, SemiOrderedShutdownHook 
storeShutdownHook, File reconstructFile, 
-                       StoreCallback callback) throws DatabaseException, 
IOException {
+                       StoreCallback callback, RandomSource random) throws 
DatabaseException, IOException {
                // Location of new store file
                String newStoreFileName = typeName(type) + suffix + '.' + 
(isStore ? "store" : "cache");
                File newStoreFile = new File(baseStoreDir, newStoreFileName);
@@ -116,17 +121,17 @@

                System.err.println("Opening database using "+newStoreFile);
                return openStore(storeEnvironment, newDBPrefix, newStoreFile, 
lruFile, keysFile, newFixSecondaryFile, maxStoreKeys, storeShutdownHook,
-                               reconstructFile, callback);
+                               reconstructFile, callback, random);
        }

        private static FreenetStore openStore(Environment storeEnvironment, 
String newDBPrefix, File newStoreFile, File lruFile,
                        File keysFile, File newFixSecondaryFile, long 
maxStoreKeys, SemiOrderedShutdownHook storeShutdownHook, 
-                       File reconstructFile, StoreCallback callback) throws 
DatabaseException, IOException {
+                       File reconstructFile, StoreCallback callback, 
RandomSource random) throws DatabaseException, IOException {
                try {
                        // First try just opening it.
                        return new BerkeleyDBFreenetStore(storeEnvironment, 
newDBPrefix, newStoreFile, lruFile, keysFile, newFixSecondaryFile,
                                        maxStoreKeys, false, storeShutdownHook, 
reconstructFile, 
-                                       callback);
+                                       callback, random);
                } catch (DatabaseException e) {

                        // Try a reconstruct
@@ -140,7 +145,7 @@
                        // Reconstruct

                        return new BerkeleyDBFreenetStore(storeEnvironment, 
newDBPrefix, newStoreFile, lruFile, keysFile, newFixSecondaryFile, 
-                                       maxStoreKeys, storeShutdownHook, 
reconstructFile, callback);
+                                       maxStoreKeys, storeShutdownHook, 
reconstructFile, callback, random);
                }
        }

@@ -167,7 +172,8 @@
         * @param lruFile
         *            LRU data file, flat file store for recovery
         * @param keysFile
-        *            Keys data file, flat file store for recvoery
+        *            Keys data file, flat file store for recovery, created 
only if
+        *            <code>callback.storeFullKeys()</code> is <code>true</code>
         * @param fixSecondaryFile
         *            Flag file. Created when secondary database error occur. If
         *            this file exist on start, delete it and recreate the 
secondary
@@ -189,10 +195,11 @@
        private BerkeleyDBFreenetStore(Environment env, String prefix, File 
storeFile, File lruFile, File keysFile,
                        File fixSecondaryFile, long maxChkBlocks, boolean wipe, 
SemiOrderedShutdownHook storeShutdownHook,
                        File reconstructFile,
-                       StoreCallback callback) throws IOException, 
DatabaseException {
+                       StoreCallback callback, RandomSource random) throws 
IOException, DatabaseException {
                logMINOR = Logger.shouldLog(Logger.MINOR, this);
                logDEBUG = Logger.shouldLog(Logger.DEBUG, this);

+               this.random = random;
                this.environment = env;
                this.name = prefix;
                this.fixSecondaryFile = fixSecondaryFile;
@@ -204,6 +211,8 @@
                this.headerBlockSize = callback.headerLength();
                this.keyLength = callback.fullKeyLength();
                callback.setStore(this);
+               
+               OOMHandler.addOOMHook(this);

                this.freeBlocks = new SortedLongSet();

@@ -325,7 +334,7 @@

        private long checkForHoles(long blocksInFile, boolean dontTruncate) 
throws DatabaseException {
                System.err.println("Checking for holes in database... 
"+blocksInFile+" blocks in file");
-               WrapperManager.signalStarting((int)Math.min(Integer.MAX_VALUE, 
5*60*1000 + blocksInFile*100)); // 10/sec
+               WrapperManager.signalStarting((int) Math.min(Integer.MAX_VALUE, 
5 * 60 * 1000 + blocksInFile * 100L)); // 10/sec
                long holes = 0;
                long maxPresent = 0;
                freeBlocks.clear();
@@ -444,7 +453,7 @@
                if(!dontCheckForHoles)
                        checkForHoles(maxBlocksInStore, true);

-               WrapperManager.signalStarting((int)(Math.min(Integer.MAX_VALUE, 
5*60*1000 + blocksInStore * 100))); // 10 per second
+               WrapperManager.signalStarting((int) 
(Math.min(Integer.MAX_VALUE, 5 * 60 * 1000 + blocksInStore * 100L))); // 10 per 
second

                long realSize = countCHKBlocksFromFile();

@@ -509,7 +518,7 @@
                                        }
                                        x++;
                                        if(x % 1024 == 0) {
-                                               System.out.println("Reading 
store prior to shrink: "+(x*100/realSize)+ "% ( "+x+ '/' +realSize+ ')');
+                                               System.out.println("Reading 
store prior to shrink: "+((long)x*100/realSize)+ "% ( "+x+ '/' +realSize+ ')');
                                        }
                                        if(x == Integer.MAX_VALUE) {
                                                System.err.println("Key number 
"+x+" - ignoring store after "+(x*(dataBlockSize+headerBlockSize)+" bytes"));
@@ -675,8 +684,12 @@
                System.out.println("Completing shrink"); // FIXME remove

                int totalUnwantedBlocks = 
unwantedMoveNums.length+freeEarlySlots.length;
-               WrapperManager.signalStarting(Math.min(Integer.MAX_VALUE, 
5*60*1000 + (totalUnwantedBlocks-wantedMoveNums.length) * 100));
+               WrapperManager.signalStarting((int) Math.min(Integer.MAX_VALUE, 
5*60*1000 + (totalUnwantedBlocks-wantedMoveNums.length) * 100L));
                // If there are any slots left over, they must be free.
+               
+               // FIXME put these into the database as we do in reconstruct().
+               // Not doing that now as its not immediately obvious how to 
deal with it...
+               
                freeBlocks.clear();
                t = environment.beginTransaction(null,null);
                for(int i=wantedMoveNums.length;i<totalUnwantedBlocks;i++) {
@@ -760,7 +773,7 @@
                try {
                        String msg = "Shrinking store: "+curBlocks+" -> 
"+maxBlocks+" (from db "+keysDB.count()+", highest 
"+highestBlockNumberInDatabase()+", from file "+countCHKBlocksFromFile()+ ')';
                        System.err.println(msg); Logger.normal(this, msg);
-                       
WrapperManager.signalStarting((int)Math.min(Integer.MAX_VALUE, (5*60*1000 + 100 
* (Math.max(0, curBlocks-maxBlocks)))));
+                       
WrapperManager.signalStarting((int)Math.min(Integer.MAX_VALUE, (5*60*1000 + 
100L * (Math.max(0, curBlocks-maxBlocks)))));
                        while(true) {
                                t = environment.beginTransaction(null,null);
                                long deleted = 0;
@@ -854,9 +867,9 @@
         */
        private BerkeleyDBFreenetStore(Environment env, String prefix, File 
storeFile, File lruFile, File keysFile,
                        File fixSecondaryFile, long maxChkBlocks, 
SemiOrderedShutdownHook storeShutdownHook, File reconstructFile,
-                       StoreCallback callback) throws DatabaseException, 
IOException {
+                       StoreCallback callback, RandomSource random) throws 
DatabaseException, IOException {
                this(env, prefix, storeFile, lruFile, keysFile, 
fixSecondaryFile, maxChkBlocks, true, storeShutdownHook,
-                               reconstructFile, callback);
+                               reconstructFile, callback, random);
        }

        private static void wipeOldDatabases(Environment env, String prefix) {
@@ -880,12 +893,51 @@
                }
        }

+       /**
+        * Reconstruct the database using flat file stores and other dark magic.
+        * 
+        * <strong>You are not expected to understand this.</strong>
+        * 
+        * <dl>
+        * <dt>header + data</dt>
+        * <dd>read from storeRAF, always available.</dd>
+        * 
+        * <dt>fullKey</dt>
+        * <dd>read from keyRAF, maybe null.</dd>
+        * 
+        * <dt>routingkey </dt>
+        * <dd>
+        * <ol>
+        * <li><code>callback.routingKeyFromFullKey(); </code></li>
+        * <li>if <code>null</code> or <code>KeyVerifyException</code>,<code> 
callback.construct().getRoutingKey()</code>,
+        * may throw <code>KeyVerifyException</code></li>
+        * <ol>
+        * <code>fullKey</code> (and hence
+        * <code>callback.routingKeyFromFullKey(); </code>) may be phantom, 
hence
+        * we must verify
+        * <code> callback.construct().getRoutingKey()  == routingkey </code> on
+        * <code>fetch()</code> </dd>
+        * </dl>
+        * 
+        * On <code>OperationStatus.KEYEXIST</code> or bad
+        * <code> callback.construct</code>:
+        * <ol>
+        * <li>insert a database entry with random key, (minimum lru - 1); </li>
+        * <li>if <code>op != OperationStatus.SUCCESS</code>,
+        * <code>addFreeBlock()</code>.</li>
+        * </ol>
+        * 
+        * 
+        * @throws DatabaseException
+        * @throws IOException
+        */
        private void reconstruct() throws DatabaseException, IOException {
                if(keysDB.count() != 0)
                        throw new IllegalStateException("Store must be empty 
before reconstruction!");
                System.err.println("Reconstructing store index from store file: 
callback="+callback);
                Logger.error(this, "Reconstructing store index from store file: 
callback="+callback);
-               WrapperManager.signalStarting((int)(Math.min(Integer.MAX_VALUE, 
5*60*1000+(storeRAF.length()/(dataBlockSize+headerBlockSize))*1000)));
+               WrapperManager.signalStarting((int) 
(Math.min(Integer.MAX_VALUE, 5 * 60 * 1000
+                       + (storeRAF.length() / (dataBlockSize + 
headerBlockSize)) * 1000L)));
                // Reusing the buffer is safe, provided we don't do anything 
with the resulting StoreBlock.
                byte[] header = new byte[headerBlockSize];
                byte[] data = new byte[dataBlockSize];
@@ -894,7 +946,20 @@
                long dupes = 0;
                long failures = 0;
                long expectedLength = 
storeRAF.length()/(dataBlockSize+headerBlockSize);
+               // Find minimum and maximum LRU.
+               long minLRU = Long.MAX_VALUE;
+               long maxLRU = Long.MIN_VALUE;
                try {
+                       lruRAF.seek(0);
+                       for(long i=0;i<lruRAF.length()/8;i++) {
+                               long lru = lruRAF.readLong();
+                               if(lru > maxLRU) maxLRU = lru;
+                               if(lru < minLRU) minLRU = lru;
+                       }
+               } catch (IOException e) {
+                       // We don't want this to be fatal...
+               }
+               try {
                        storeRAF.seek(0);
                        lruRAF.seek(0);
                        long lruRAFLength = lruRAF.length();
@@ -939,18 +1004,41 @@
                                }
                                try {
                                        byte[] routingkey = null;
-                                       try {
-                                               StorableBlock block = 
callback.construct(data, header, null, readKey ? keyBuf : null);
-                                               routingkey = 
block.getRoutingKey();
-                                       } catch (KeyVerifyException e) {
-                                               String err = "Bogus or 
unreconstructible key at slot "+l+" : "+e+" - lost block "+l;
-                                               Logger.error(this, err, e);
-                                               System.err.println(err);
-                                               addFreeBlock(l, true, "can't 
reconsturct key ("+callback+ ')');
-                                               routingkey = null;
-                                               failures++;
-                                               continue;
+                                       if(keyBuf != null && 
!isAllNull(keyBuf)) {
+                                               routingkey = 
callback.routingKeyFromFullKey(keyBuf);
+                                               if(routingkey == keyBuf) {
+                                                       // Copy it.
+                                                       byte[] newkey = new 
byte[routingkey.length];
+                                                       
System.arraycopy(routingkey, 0, newkey, 0, routingkey.length);
+                                                       routingkey = newkey;
+                                               }
                                        }
+                                       if(routingkey == null) {
+                                               try {
+                                                       StorableBlock block = 
callback.construct(data, header, null, readKey ? keyBuf : null);
+                                                       routingkey = 
block.getRoutingKey();
+                                               } catch (KeyVerifyException e) {
+                                                       String err = "Bogus or 
unreconstructible key at slot "+l+" : "+e+" - lost block "+l;
+                                                       Logger.error(this, err, 
e);
+                                                       System.err.println(err);
+                                                       failures++;
+                                                       t = 
environment.beginTransaction(null,null);
+                                                       StoreBlock storeBlock = 
new StoreBlock(l, --minLRU);
+                                                       byte[] buf = new 
byte[32];
+                                                       random.nextBytes(buf);
+                                                       DatabaseEntry 
routingkeyDBE = new DatabaseEntry(buf);
+                                                       DatabaseEntry blockDBE 
= new DatabaseEntry();
+                                                       
storeBlockTupleBinding.objectToEntry(storeBlock, blockDBE);
+                                                       OperationStatus op = 
keysDB.putNoOverwrite(t,routingkeyDBE,blockDBE);
+                                                       if(op != 
OperationStatus.SUCCESS) {
+                                                               
Logger.error(this, "Impossible operation status inserting bogus key to LRU: 
"+op);
+                                                               addFreeBlock(l, 
true, "Impossible to add (invalid) to LRU: "+op);
+                                                       }
+                                                       t.commitNoSync();
+                                                       t = null;
+                                                       continue;
+                                               }
+                                       } 
                                        t = 
environment.beginTransaction(null,null);
                                        StoreBlock storeBlock = new 
StoreBlock(l, lruVal);
                                        DatabaseEntry routingkeyDBE = new 
DatabaseEntry(routingkey);
@@ -958,8 +1046,23 @@
                                        
storeBlockTupleBinding.objectToEntry(storeBlock, blockDBE);
                                        OperationStatus op = 
keysDB.putNoOverwrite(t,routingkeyDBE,blockDBE);
                                        if(op == OperationStatus.KEYEXIST) {
-                                               addFreeBlock(l, true, 
"duplicate");
+                                               Logger.error(this, "Duplicate 
block: "+l);
+                                               System.err.println("Duplicate 
block: "+l);
                                                dupes++;
+                                               storeBlock = new StoreBlock(l, 
--minLRU);
+                                               byte[] buf = new byte[32];
+                                               random.nextBytes(buf);
+                                               routingkeyDBE = new 
DatabaseEntry(buf);
+                                               blockDBE = new DatabaseEntry();
+                                               
storeBlockTupleBinding.objectToEntry(storeBlock, blockDBE);
+                                               op = 
keysDB.putNoOverwrite(t,routingkeyDBE,blockDBE);
+                                               if(op != 
OperationStatus.SUCCESS) {
+                                                       Logger.error(this, 
"Impossible operation status inserting bogus key to LRU: "+op);
+                                                       addFreeBlock(l, true, 
"Impossible to add (dupe) to LRU: "+op);
+                                               }
+                                               t.commitNoSync();
+                                               t = null;
+                                               continue;
                                        } else if(op != 
OperationStatus.SUCCESS) {
                                                addFreeBlock(l, true, "failure: 
"+op);
                                                failures++;
@@ -996,6 +1099,12 @@
                }
        }

+       private boolean isAllNull(byte[] buf) {
+               for(int i=0;i<buf.length;i++)
+                       if(buf[i] != 0) return false;
+               return true;
+       }
+
        private int runningFetches;

        /**
@@ -1066,6 +1175,56 @@

                                block = callback.construct(data, header, 
routingkey, fullKey);

+                               if(!Arrays.equals(block.getRoutingKey(), 
routingkey)) {
+                                       
+                                       synchronized(this) {
+                                               misses++;
+                                       }
+                                       
+                                       keysDB.delete(t, routingkeyDBE);
+                                       
+                                       // Insert the block into the index.
+                                       // Set the LRU to minimum - 1.
+                                       
+                                       long lru = getMinRecentlyUsed(t) - 1;
+                                       
+                                       Logger.normal(this, "Does not verify 
(not the expected key), setting accessTime to "+lru+" for : 
"+HexUtil.bytesToHex(routingkey));
+                                       
+                                       storeBlock = new 
StoreBlock(storeBlock.offset, lru);
+                                       
+                                       routingkeyDBE = new 
DatabaseEntry(block.getRoutingKey());
+                                       
+                                       blockDBE = new DatabaseEntry();
+                                       
storeBlockTupleBinding.objectToEntry(storeBlock, blockDBE);
+                                       try {
+                                               
keysDB.put(t,routingkeyDBE,blockDBE);
+                                               synchronized(storeRAF) {
+                                                       if(keysRAF != null) {
+                                                               
keysRAF.seek(storeBlock.offset * keyLength);
+                                                               
keysRAF.write(fullKey);
+                                                               if(logDEBUG)
+                                                                       
Logger.debug(this, "Written full key length "+fullKey.length+" to block 
"+storeBlock.offset+" at "+(storeBlock.offset * keyLength)+" for "+callback);
+                                                       } else if(logDEBUG) {
+                                                               
Logger.debug(this, "Not writing full key length "+fullKey.length+" for block 
"+storeBlock.offset+" for "+callback);
+                                                       }
+                                               }
+                                       } catch (DatabaseException e) {
+                                               Logger.error(this, "Caught 
database exception "+e+" while replacing element");
+                                               addFreeBlock(storeBlock.offset, 
true, "Bogus key");
+                                               c.close();
+                                               c = null;
+                                               t.commit();
+                                               t = null;
+                                               return null;
+                                       }
+                                       Logger.normal(this, "Successfully 
replaced entry at block number "+storeBlock.offset+" lru "+lru);
+                                       c.close();
+                                       c = null;
+                                       t.commit();
+                                       t = null;
+                                       return null;
+                               }
+                               
                                if(!dontPromote) {
                                        storeBlock.updateRecentlyUsed();
                                        DatabaseEntry updateDBE = new 
DatabaseEntry();
@@ -1093,15 +1252,51 @@

                        } catch(KeyVerifyException ex) {
                                Logger.normal(this, "Does not verify ("+ex+"), 
setting accessTime to 0 for : "+HexUtil.bytesToHex(routingkey), ex);
+                               synchronized(this) {
+                                       misses++;
+                               }
+                               synchronized(storeRAF) {
+                                       // Clear the key in the keys file.
+                                       byte[] buf = new byte[fullKey.length];
+                                       for(int i=0;i<buf.length;i++) buf[i] = 
0; // FIXME unnecessary?
+                                       if(keysRAF != null) {
+                                               keysRAF.seek(storeBlock.offset 
* keyLength);
+                                               keysRAF.write(buf);
+                                       }
+                               }
+
                                keysDB.delete(t, routingkeyDBE);
+                               
+                               // Insert the block into the index with a 
random key, so that it's part of the LRU.
+                               // Set the LRU to minimum - 1.
+                               
+                               long lru = getMinRecentlyUsed(t) - 1;
+                               
+                               byte[] randomKey = new byte[fullKey.length];
+                               random.nextBytes(randomKey);
+                               
+                               storeBlock = new StoreBlock(storeBlock.offset, 
lru);
+                               
+                               routingkeyDBE = new DatabaseEntry(randomKey);
+                               
+                               blockDBE = new DatabaseEntry();
+                               
storeBlockTupleBinding.objectToEntry(storeBlock, blockDBE);
+                               try {
+                                       keysDB.put(t,routingkeyDBE,blockDBE);
+                               } catch (DatabaseException e) {
+                                       Logger.error(this, "Caught database 
exception "+e+" while adding corrupt element to LRU");
+                                       addFreeBlock(storeBlock.offset, true, 
"Bogus key");
+                                       c.close();
+                                       c = null;
+                                       t.commit();
+                                       t = null;
+                                       return null;
+                               }
+
                                c.close();
                                c = null;
                                t.commit();
                                t = null;
-                               addFreeBlock(storeBlock.offset, true, "Key does 
not verify");
-                               synchronized(this) {
-                                       misses++;
-                               }
                                return null;
                        }
                        synchronized(this) {
@@ -1414,6 +1609,15 @@
                                }
                                Logger.error(this, "Corrupt secondary database 
("+getName()+"). Should be cleaned up on restart.");
                                System.err.println("Corrupt secondary database 
("+getName()+"). Should be cleaned up on restart.");
+                               
+                               System.err.println("Flusing data store files (" 
+ getName() + ")");
+                               flushAndCloseRAF(storeRAF);
+                               storeRAF = null;
+                               flushAndCloseRAF(lruRAF);
+                               lruRAF = null;
+                               flushAndCloseRAF(keysRAF);
+                               keysRAF = null;
+                               
                                WrapperManager.restart();
                                
System.exit(freenet.node.NodeInitException.EXIT_DATABASE_REQUIRES_RESTART);
                        } else if(ex instanceof DbChecksumException || ex 
instanceof RunRecoveryException || ex instanceof LogFileNotFoundException ||
@@ -1430,13 +1634,22 @@
                                        System.err.println("Corrupt database 
("+getName()+") but could not create flag file "+reconstructFile);
                                        return; // Not sure what else we can do
                                }
+                               
+                               System.err.println("Flusing data store files (" 
+ getName() + ")");
+                               flushAndCloseRAF(storeRAF);
+                               storeRAF = null;
+                               flushAndCloseRAF(lruRAF);
+                               lruRAF = null;
+                               flushAndCloseRAF(keysRAF);
+                               keysRAF = null;
+                               
                                System.err.println("Restarting to fix corrupt 
store database...");
                                Logger.error(this, "Restarting to fix corrupt 
store database...");
                                WrapperManager.restart();
                        } else {
                                if(ex.getCause() != null)
                                        
checkSecondaryDatabaseError(ex.getCause());
-                       }
+                       } 
                }
        }

@@ -1583,7 +1796,17 @@

        private final Object closeLock = new Object();

-       private void closeRAF(RandomAccessFile file, boolean logError) {
+       private static void flushAndCloseRAF(RandomAccessFile file) {
+               try {
+                       if (file != null)
+                               file.getFD().sync();
+               } catch (IOException e) {
+                       // ignore
+               }
+               closeRAF(file, false);
+       }
+
+       private static void closeRAF(RandomAccessFile file, boolean logError) {
                        try {
                                                if (file != null)
                                                                file.close();
@@ -1595,7 +1818,7 @@
                                }
                }

-       private void closeDB(Database db, boolean logError) {
+       private static void closeDB(Database db, boolean logError) {
                                try {
                                                if (db != null)
                                                                db.close();
@@ -1723,6 +1946,35 @@
                return maxRecentlyUsed;
        }

+       private long getMinRecentlyUsed(Transaction t) {
+               long minRecentlyUsed = 0;
+               
+               Cursor c = null;
+               try {
+                       c = accessTimeDB.openCursor(t,null);
+                       DatabaseEntry keyDBE = new DatabaseEntry();
+                       DatabaseEntry dataDBE = new DatabaseEntry();
+                       
if(c.getFirst(keyDBE,dataDBE,null)==OperationStatus.SUCCESS) {
+                               StoreBlock storeBlock = (StoreBlock) 
storeBlockTupleBinding.entryToObject(dataDBE);
+                               minRecentlyUsed = storeBlock.getRecentlyUsed();
+                       }
+                       c.close();
+                       c = null;
+               } catch(DatabaseException ex) {
+                       ex.printStackTrace();
+               } finally {
+                       if(c != null) {
+                               try {
+                                       c.close();
+                               } catch (DatabaseException e) {
+                                       Logger.error(this, "Caught "+e, e);
+                               }
+                       }
+               }
+               
+               return minRecentlyUsed;
+       }
+       
        private long getNewRecentlyUsed() {
                synchronized(lastRecentlyUsedSync) {
                        lastRecentlyUsed++;
@@ -1786,7 +2038,7 @@
                System.err.println("Recreating secondary databases");
                System.err.println("This may take some time...");

-               WrapperManager.signalStarting((int)(Math.min(Integer.MAX_VALUE, 
5*60*1000+keysDB.count()*100)));
+               WrapperManager.signalStarting((int) 
(Math.min(Integer.MAX_VALUE, 5 * 60 * 1000 + keysDB.count() * 100L)));

                // Of course it's not a solution but a quick fix
                // Integer.MAX_VALUE seems to trigger an overflow or whatever 
...
@@ -1888,4 +2140,13 @@

                return db;
        }
+
+    public void handleOOM() throws Exception {
+               if (storeRAF != null)
+                       storeRAF.getFD().sync();
+               if (keysRAF != null)
+                       keysRAF.getFD().sync();
+               if (lruRAF != null)
+                       lruRAF.getFD().sync();
+       }
 }

Modified: trunk/freenet/src/freenet/store/CHKStore.java
===================================================================
--- trunk/freenet/src/freenet/store/CHKStore.java       2008-04-14 15:55:20 UTC 
(rev 19325)
+++ trunk/freenet/src/freenet/store/CHKStore.java       2008-04-14 16:00:44 UTC 
(rev 19326)
@@ -50,7 +50,18 @@
        }

        public boolean storeFullKeys() {
+               // Worth the extra two file descriptors, because if we have the 
keys we can do lazy 
+               // reconstruction i.e. don't construct each block, just 
transcode from the .keys file
+               // straight into the database.
+               return true;
+       }
+
+       public boolean constructNeedsKey() {
                return false;
        }

+       public byte[] routingKeyFromFullKey(byte[] keyBuf) {
+               return NodeCHK.routingKeyFromFullKey(keyBuf);
+       }
+
 }

Modified: trunk/freenet/src/freenet/store/FreenetStore.java
===================================================================
--- trunk/freenet/src/freenet/store/FreenetStore.java   2008-04-14 15:55:20 UTC 
(rev 19325)
+++ trunk/freenet/src/freenet/store/FreenetStore.java   2008-04-14 16:00:44 UTC 
(rev 19326)
@@ -22,15 +22,16 @@
         */
        StorableBlock fetch(byte[] routingKey, byte[] fullKey, boolean 
dontPromote) throws IOException;

-    /**
-     * Store a block.
-     * 
-     * @throws KeyCollisionException
-     *                 If the key already exists but has different contents.
-     * @param overwrite
-     *                If true, overwrite old content rather than throwing a
-     *                <code>KeyCollisionException</code>.
-     */
+       /**
+        * Store a block.
+        * 
+        * @throws KeyCollisionException
+        *             If the key already exists and 
<code>callback.collisionPossible()</code> is
+        *             <code>true</code>.
+        * @param overwrite
+        *            If true, overwrite old content rather than throwing a
+        *            <code>KeyCollisionException</code>.
+        */
     public void put(StorableBlock block, byte[] routingkey, byte[] fullKey, 
byte[] data, byte[] header, 
                boolean overwrite) throws IOException, KeyCollisionException;


Modified: trunk/freenet/src/freenet/store/PubkeyStore.java
===================================================================
--- trunk/freenet/src/freenet/store/PubkeyStore.java    2008-04-14 15:55:20 UTC 
(rev 19325)
+++ trunk/freenet/src/freenet/store/PubkeyStore.java    2008-04-14 16:00:44 UTC 
(rev 19326)
@@ -58,4 +58,12 @@
                return false;
        }

+       public boolean constructNeedsKey() {
+               return false;
+       }
+
+       public byte[] routingKeyFromFullKey(byte[] keyBuf) {
+               return keyBuf;
+       }
+
 }

Modified: trunk/freenet/src/freenet/store/RAMFreenetStore.java
===================================================================
--- trunk/freenet/src/freenet/store/RAMFreenetStore.java        2008-04-14 
15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/store/RAMFreenetStore.java        2008-04-14 
16:00:44 UTC (rev 19326)
@@ -81,10 +81,6 @@
                boolean storeFullKeys = callback.storeFullKeys();
                if(oldBlock != null) {
                        if(callback.collisionPossible()) {
-                               boolean equals = Arrays.equals(oldBlock.data, 
data) &&
-                                       Arrays.equals(oldBlock.header, header) 
&&
-                                       (storeFullKeys ? 
Arrays.equals(oldBlock.fullKey, fullKey) : true);
-                               if(equals) return;
                                if(overwrite) {
                                        oldBlock.data = data;
                                        oldBlock.header = header;

Modified: trunk/freenet/src/freenet/store/SSKStore.java
===================================================================
--- trunk/freenet/src/freenet/store/SSKStore.java       2008-04-14 15:55:20 UTC 
(rev 19325)
+++ trunk/freenet/src/freenet/store/SSKStore.java       2008-04-14 16:00:44 UTC 
(rev 19326)
@@ -60,4 +60,12 @@
                return true;
        }

+       public boolean constructNeedsKey() {
+               return true;
+       }
+
+       public byte[] routingKeyFromFullKey(byte[] keyBuf) {
+               return NodeSSK.routingKeyFromFullKey(keyBuf);
+       }
+
 }

Modified: trunk/freenet/src/freenet/store/StoreCallback.java
===================================================================
--- trunk/freenet/src/freenet/store/StoreCallback.java  2008-04-14 15:55:20 UTC 
(rev 19325)
+++ trunk/freenet/src/freenet/store/StoreCallback.java  2008-04-14 16:00:44 UTC 
(rev 19326)
@@ -26,6 +26,9 @@
        /** Whether we should create a .keys file to keep full keys in in order 
to reconstruct. */
        public abstract boolean storeFullKeys();

+       /** Whether we need the key in order to reconstruct a block. */
+       public abstract boolean constructNeedsKey();
+       
        /** Length of a full key. Full keys are stored in the .keys file. Also 
fixed. */
        public abstract int fullKeyLength();

@@ -41,7 +44,9 @@

        // Reconstruction

-       /** Construct a StorableBlock from the data, headers, and optionally 
routing key or full key 
+       /** Construct a StorableBlock from the data, headers, and optionally 
routing key or full key.
+        * IMPORTANT: Using the full key or routing key is OPTIONAL, and if we 
don't use them, WE DON'T
+        * CHECK THEM EITHER! You MUST check that the key is the one you 
expected.
         * @throws KeyVerifyException */
        abstract StorableBlock construct(byte[] data, byte[] headers, byte[] 
routingKey, byte[] fullKey) throws KeyVerifyException;

@@ -68,4 +73,7 @@
        public long keyCount() {
                return store.keyCount();
        }
+
+       /** Generate a routing key from a full key */
+       public abstract byte[] routingKeyFromFullKey(byte[] keyBuf);
 }

Modified: trunk/freenet/src/freenet/support/Base64.java
===================================================================
--- trunk/freenet/src/freenet/support/Base64.java       2008-04-14 15:55:20 UTC 
(rev 19325)
+++ trunk/freenet/src/freenet/support/Base64.java       2008-04-14 16:00:44 UTC 
(rev 19326)
@@ -1,7 +1,5 @@
 package freenet.support;

-import java.util.Random;
-
 /**
  * This class provides encoding of byte arrays into Base64-encoded strings,
  * and decoding the other way.

Modified: trunk/freenet/src/freenet/support/Buffer.java
===================================================================
--- trunk/freenet/src/freenet/support/Buffer.java       2008-04-14 15:55:20 UTC 
(rev 19325)
+++ trunk/freenet/src/freenet/support/Buffer.java       2008-04-14 16:00:44 UTC 
(rev 19326)
@@ -38,7 +38,9 @@
        private final int _length;

        /**
-        * Create a Buffer by reading a DataInputStream
+        * Create a Buffer by reading a DataInputStream. 
+        * Note that this a) expects that the first 4 bytes to be a length 
indicator of the rest of the byte stream and 
+        * b) these first 4 bytes are removed from the byte stream before 
storing the rest 
         *
         * @param dis
         * @throws IOException

Modified: trunk/freenet/src/freenet/support/Logger.java
===================================================================
--- trunk/freenet/src/freenet/support/Logger.java       2008-04-14 15:55:20 UTC 
(rev 19325)
+++ trunk/freenet/src/freenet/support/Logger.java       2008-04-14 16:00:44 UTC 
(rev 19326)
@@ -18,12 +18,12 @@

        public final static class OSThread {

-               public static boolean getPIDEnabled = false;
-               public static boolean getPPIDEnabled = false;
-               public static boolean logToFileEnabled = false;
-               public static int logToFileVerbosity = DEBUG;
-               public static boolean logToStdOutEnabled = false;
-               public static boolean procSelfStatEnabled = false;
+               private static boolean getPIDEnabled = false;
+               private static boolean getPPIDEnabled = false;
+               private static boolean logToFileEnabled = false;
+               private static int logToFileVerbosity = DEBUG;
+               private static boolean logToStdOutEnabled = false;
+               private static boolean procSelfStatEnabled = false;

                /**
                 * Get the thread's process ID or return -1 if it's unavailable 
for some reason

Modified: trunk/freenet/src/freenet/support/LoggerHookChain.java
===================================================================
--- trunk/freenet/src/freenet/support/LoggerHookChain.java      2008-04-14 
15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/support/LoggerHookChain.java      2008-04-14 
16:00:44 UTC (rev 19326)
@@ -59,10 +59,11 @@
      * Remove a hook from the logger.
      */
     public synchronized void removeHook(LoggerHook lh) {
-        LoggerHook[] newHooks = new LoggerHook[hooks.length-1];
+       final int hooksLength = hooks.length;
+        LoggerHook[] newHooks = new LoggerHook[hooksLength > 1 ? hooksLength-1 
: 0];
         int x=0;
         boolean removed = false;
-        for(int i=0;i<hooks.length;i++) {
+        for(int i=0;i<hooksLength;i++) {
             if(hooks[i] == lh) {
                 removed = true;
             } else {

Modified: trunk/freenet/src/freenet/support/NumberedItem.java
===================================================================
--- trunk/freenet/src/freenet/support/NumberedItem.java 2008-04-14 15:55:20 UTC 
(rev 19325)
+++ trunk/freenet/src/freenet/support/NumberedItem.java 2008-04-14 16:00:44 UTC 
(rev 19326)
@@ -2,6 +2,8 @@

 /**
  * An object with a number.
+ * 
+ * @see IntNumberedItem
  */
 public interface NumberedItem {


Modified: trunk/freenet/src/freenet/support/NumberedItemComparator.java
===================================================================
--- trunk/freenet/src/freenet/support/NumberedItemComparator.java       
2008-04-14 15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/support/NumberedItemComparator.java       
2008-04-14 16:00:44 UTC (rev 19326)
@@ -11,12 +11,6 @@
        final boolean wrapAround;

     public int compare(Object o1, Object o2) {
-        int x = ocompare(o1, o2);
-        //Logger.minor(this, "compare("+o1+","+o2+") = "+x);
-        return x;
-    }
-    
-    public int ocompare(Object o1, Object o2) {
         // Nulls at the end of the list
         if((o1 == null) && (o2 == null))
             return 0; // null == null

Modified: trunk/freenet/src/freenet/support/OOMHandler.java
===================================================================
--- trunk/freenet/src/freenet/support/OOMHandler.java   2008-04-14 15:55:20 UTC 
(rev 19325)
+++ trunk/freenet/src/freenet/support/OOMHandler.java   2008-04-14 16:00:44 UTC 
(rev 19326)
@@ -3,6 +3,9 @@
  * http://www.gnu.org/ for further details of the GPL. */
 package freenet.support;

+import java.util.Iterator;
+import java.util.Set;
+
 import org.tanukisoftware.wrapper.WrapperManager;

 import freenet.support.Logger;
@@ -11,16 +14,62 @@
  * Do this processing as a standard response to an OutOfMemoryError
  */
 public class OOMHandler {
-
-       public synchronized static void handleOOM(OutOfMemoryError e) {
+       private static volatile boolean isOOM = false;
+       
+       /**
+        * Emergency memory, freed when OOM occur. Marked <code>volatile</code> 
to make sure gc thread
+        * see it's free'd.
+        */
+       private static volatile byte[] emergencyPool = new byte[8192];
+       
+       /**
+        * List of {@link OOMHook}s
+        */
+       private static Set oomHooks = new WeakHashSet();
+       
+       public static void addOOMHook(OOMHook hook) {
+               synchronized (oomHooks) {
+                       oomHooks.add(hook);
+               }
+       }
+       
+       public static void handleOOM(OutOfMemoryError e) {
+               if (isOOM) {
+                       Logger.error(null, "Double OOM", e);
+                       return;
+               }
+               
+               isOOM = true;
+               
                Runtime r = null;
                try {
                        r = Runtime.getRuntime();
                        long usedAtStart = r.totalMemory() - r.freeMemory();
+                       
+                       if (emergencyPool != null)
+                               emergencyPool = null;
+                       
                        System.gc();
                        System.runFinalization();
+                       
+                       // iterate all oom hooks
+                       Iterator it = oomHooks.iterator();
+                       while (it.hasNext()) {
+                               OOMHook hook = ((OOMHook) it.next());
+                               if (hook != null) {
+                                       try {
+                                               hook.handleOOM();
+                                       } catch (Throwable t) {
+                                               //ignore
+                                       }
+                               }
+
+                               System.gc();
+                       }
+                       
                        System.gc();
                        System.runFinalization();
+                       
                        System.err.println(e.getClass());
                        System.err.println(e.getMessage());
                        e.printStackTrace();
@@ -46,6 +95,8 @@
                        while(tg.getParent() != null) tg = tg.getParent();
                        System.err.println("Running threads: 
"+tg.activeCount());
                        WrapperManager.requestThreadDump(); // Will probably 
crash, but never mind...
+               } finally {
+                       isOOM = false;
                }
        }
 }

Copied: trunk/freenet/src/freenet/support/OOMHook.java (from rev 19322, 
trunk/freenet/src/freenet/support/OOMHook.java)
===================================================================
--- trunk/freenet/src/freenet/support/OOMHook.java                              
(rev 0)
+++ trunk/freenet/src/freenet/support/OOMHook.java      2008-04-14 16:00:44 UTC 
(rev 19326)
@@ -0,0 +1,13 @@
+package freenet.support;
+
+/**
+ * @author sdiz
+ */
+public interface OOMHook {
+       /**
+        * Handle OutOfMemoryError
+        * 
+        * (try to free some cache, save the files, etc).
+        */
+       void handleOOM() throws Exception;
+}

Modified: trunk/freenet/src/freenet/support/SectoredRandomGrabArray.java
===================================================================
--- trunk/freenet/src/freenet/support/SectoredRandomGrabArray.java      
2008-04-14 15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/support/SectoredRandomGrabArray.java      
2008-04-14 16:00:44 UTC (rev 19326)
@@ -124,16 +124,17 @@
                        // Just because the item is cancelled does not 
necessarily mean the whole client is.
                        // E.g. a segment may return cancelled because it is 
decoding, that doesn't mean
                        // other segments are cancelled. So just go around the 
loop in that case.
+                       final int grabArraysLength = grabArrays.length;
                        if(rga.isEmpty()) {
                                if(logMINOR)
                                        Logger.minor(this, "Removing grab array 
"+x+" : "+rga+" for "+rga.getObject()+" (is empty)");
                                Object client = rga.getObject();
                                grabArraysByClient.remove(client);
-                               RemoveRandomWithObject[] newArray = new 
RemoveRandomWithObject[grabArrays.length-1];
+                               RemoveRandomWithObject[] newArray = new 
RemoveRandomWithObject[grabArraysLength > 1 ? grabArraysLength-1 : 0];
                                if(x > 0)
                                        System.arraycopy(grabArrays, 0, 
newArray, 0, x);
-                               if(x < grabArrays.length-1)
-                                       System.arraycopy(grabArrays, x+1, 
newArray, x, grabArrays.length - (x+1));
+                               if(x < grabArraysLength-1)
+                                       System.arraycopy(grabArrays, x+1, 
newArray, x, grabArraysLength - (x+1));
                                grabArrays = newArray;
                        }
                        if(item == null) {
@@ -141,7 +142,7 @@
                                        // Hmmm...
                                        excluded++;
                                        if(excluded > MAX_EXCLUDED) {
-                                               Logger.normal(this, "Too many 
sub-arrays are entirely excluded on "+this+" length = "+grabArrays.length, new 
Exception("error"));
+                                               Logger.normal(this, "Too many 
sub-arrays are entirely excluded on "+this+" length = "+grabArraysLength, new 
Exception("error"));
                                                return null;
                                        }
                                }

Modified: trunk/freenet/src/freenet/support/SizeUtil.java
===================================================================
--- trunk/freenet/src/freenet/support/SizeUtil.java     2008-04-14 15:55:20 UTC 
(rev 19325)
+++ trunk/freenet/src/freenet/support/SizeUtil.java     2008-04-14 16:00:44 UTC 
(rev 19326)
@@ -4,7 +4,7 @@
  * Size formatting utility.
  */
 public class SizeUtil {
-       public static String[] suffixes = {"B", 
"KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"};
+       public final static String[] suffixes = { "B", "KiB", "MiB", "GiB", 
"TiB", "PiB", "EiB", "ZiB", "YiB" };

        public static String formatSize(long sz) {
                return formatSize(sz, false);

Modified: trunk/freenet/src/freenet/support/SortedLongSet.java
===================================================================
--- trunk/freenet/src/freenet/support/SortedLongSet.java        2008-04-14 
15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/support/SortedLongSet.java        2008-04-14 
16:00:44 UTC (rev 19326)
@@ -48,7 +48,7 @@
         * @return <code>true</code>, if <code>num</code> exist.
         */
        public synchronized boolean contains(long num) {
-               int x = Arrays.binarySearch(data, num);
+               int x = binarySearch(num);
                if(x >= 0)
                        return true;
                else
@@ -62,7 +62,7 @@
         *            the item to be removed
         */
        public synchronized void remove(long item) {
-               int x = Arrays.binarySearch(data, item);
+               int x = binarySearch(item);
                if(x >= 0) {
                        if(x < length-1)
                                System.arraycopy(data, x+1, data, x, 
length-x-1);
@@ -98,12 +98,12 @@

        /**
         * Add the item, if it (or an item of the same number) is not already
-        * present. <strong>This method does not accept 
<code>Long.MAX_VALUE</code>.</strong>
+        * present.
         * 
         * @return <code>true</code>, if we added the item.
         */ 
        public synchronized boolean push(long num) {
-               int x = Arrays.binarySearch(data, num);
+               int x = binarySearch(num);
                if(x >= 0) return false;
                // insertion point
                x = -x-1;
@@ -114,14 +114,12 @@
        /**
         * Add the item.
         * 
-        * <strong>This method does not accept 
<code>Long.MAX_VALUE</code>.</strong>
-        * 
         * @throws {@link IllegalArgumentException}
         *             if the item already exist
         * @return <code>true</code>, if we added the item.
         */ 
        public synchronized void add(long num) {
-               int x = Arrays.binarySearch(data, num);
+               int x = binarySearch(num);
                if(x >= 0) throw new IllegalArgumentException(); // already 
exists
                // insertion point
                x = -x-1;
@@ -179,4 +177,21 @@
                return output;
        }

+       private int binarySearch(long key) {
+               int low = 0;
+               int high = length - 1;
+
+               while (low <= high) {
+                       int mid = (low + high) >>> 1;
+                       long midVal = data[mid];
+
+                       if (midVal < key)
+                               low = mid + 1;
+                       else if (midVal > key)
+                               high = mid - 1;
+                       else
+                               return mid; // key found
+               }
+               return -(low + 1); // key not found.
+       }
 }

Modified: trunk/freenet/src/freenet/support/WeakHashSet.java
===================================================================
--- trunk/freenet/src/freenet/support/WeakHashSet.java  2008-04-14 15:55:20 UTC 
(rev 19325)
+++ trunk/freenet/src/freenet/support/WeakHashSet.java  2008-04-14 16:00:44 UTC 
(rev 19326)
@@ -1,12 +1,11 @@
 package freenet.support;

+import java.util.AbstractSet;
 import java.util.Collection;
 import java.util.Iterator;
-import java.util.Set;
 import java.util.WeakHashMap;

-public class WeakHashSet implements Set {
-       
+public class WeakHashSet extends AbstractSet {
        private final WeakHashMap map;

        public WeakHashSet() {
@@ -17,14 +16,6 @@
                return map.put(key, null) == null;
        }

-       public boolean addAll(Collection arg0) {
-               boolean changed = false;
-               for(Iterator i=arg0.iterator();i.hasNext();) {
-                       changed |= add(i.next());
-               }
-               return changed;
-       }
-
        public void clear() {
                map.clear();
        }
@@ -34,10 +25,7 @@
        }

        public boolean containsAll(Collection arg0) {
-               for(Iterator i=arg0.iterator();i.hasNext();) {
-                       if(!map.containsKey(i.next())) return false;
-               }
-               return true;
+               return map.keySet().containsAll(arg0);
        }

        public boolean isEmpty() {
@@ -52,19 +40,6 @@
                return map.remove(key) != null;
        }

-       public boolean removeAll(Collection arg0) {
-               boolean changed = false;
-               for(Iterator i=arg0.iterator();i.hasNext();) {
-                       changed |= remove(i.next());
-               }
-               return changed;
-       }
-
-       public boolean retainAll(Collection arg0) {
-               // FIXME
-               throw new UnsupportedOperationException();
-       }
-
        public int size() {
                return map.size();
        }
@@ -76,5 +51,4 @@
        public Object[] toArray(Object[] arg0) {
                return map.keySet().toArray(arg0);
        }
-
 }

Modified: trunk/freenet/src/freenet/support/io/BaseFileBucket.java
===================================================================
--- trunk/freenet/src/freenet/support/io/BaseFileBucket.java    2008-04-14 
15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/support/io/BaseFileBucket.java    2008-04-14 
16:00:44 UTC (rev 19326)
@@ -219,13 +219,10 @@
        }

        class FileBucketInputStream extends FileInputStream {
-               Exception e;
                boolean closed;

                public FileBucketInputStream(File f) throws IOException {
                        super(f);
-                       if (Logger.shouldLog(Logger.DEBUG, this))
-                               e = new Exception("debug");
                }

                public void close() throws IOException {
@@ -280,7 +277,7 @@
                getFile().delete();
        }

-       public void finalize() {
+       protected void finalize() {
                if(deleteOnFinalize())
                        free(true);
        }
@@ -396,17 +393,27 @@

                if(toClose != null) {
                        Logger.error(this, "Streams open free()ing "+this+" : 
"+StringArray.toString(toClose), new Exception("debug"));
-                       for(int i=0;i<toClose.length;i++) {
-                               try {
-                                       if(toClose[i] instanceof 
FileBucketOutputStream) {
-                                               ((FileBucketOutputStream) 
toClose[i]).close();
-                                       } else {
-                                               ((FileBucketInputStream) 
toClose[i]).close();
+                       double toCloseLength = toClose.length;
+                       while(toCloseLength > 0) {
+                               int toCloseThisRound;
+                               if(toCloseLength <= Integer.MAX_VALUE) {
+                                       toCloseThisRound = (int) toCloseLength;
+                                       toCloseLength = 0;
+                               } else 
+                                       toCloseLength -= (toCloseThisRound = 
Integer.MAX_VALUE);
+
+                               for(int i=0; i<toCloseThisRound; i++) {
+                                       try {
+                                               if(toClose[i] instanceof 
FileBucketOutputStream) {
+                                                       
((FileBucketOutputStream) toClose[i]).close();
+                                               } else {
+                                                       
((FileBucketInputStream) toClose[i]).close();
+                                               }
+                                       } catch (IOException e) {
+                                               Logger.error(this, "Caught 
closing stream in free(): "+e, e);
+                                       } catch (Throwable t) {
+                                               Logger.error(this, "Caught 
closing stream in free(): "+t, t);
                                        }
-                               } catch (IOException e) {
-                                       Logger.error(this, "Caught closing 
stream in free(): "+e, e);
-                               } catch (Throwable t) {
-                                       Logger.error(this, "Caught closing 
stream in free(): "+t, t);
                                }
                        }
                }

Modified: trunk/freenet/src/freenet/support/io/FileBucket.java
===================================================================
--- trunk/freenet/src/freenet/support/io/FileBucket.java        2008-04-14 
15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/support/io/FileBucket.java        2008-04-14 
16:00:44 UTC (rev 19326)
@@ -22,7 +22,6 @@
        protected final boolean createFileOnly;
        // JVM caches File.size() and there is no way to flush the cache, so we
        // need to track it ourselves
-       protected long fileRestartCounter;

        protected static String tempDir = null;


Modified: trunk/freenet/src/freenet/support/io/MultiReaderBucket.java
===================================================================
--- trunk/freenet/src/freenet/support/io/MultiReaderBucket.java 2008-04-14 
15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/support/io/MultiReaderBucket.java 2008-04-14 
16:00:44 UTC (rev 19326)
@@ -120,7 +120,7 @@
                        return bucket.size();
                }

-               public void finalize() {
+               protected void finalize() {
                        free();
                }


Modified: 
trunk/freenet/src/freenet/support/io/PaddedEphemerallyEncryptedBucket.java
===================================================================
--- trunk/freenet/src/freenet/support/io/PaddedEphemerallyEncryptedBucket.java  
2008-04-14 15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/support/io/PaddedEphemerallyEncryptedBucket.java  
2008-04-14 16:00:44 UTC (rev 19326)
@@ -271,7 +271,7 @@
                if(size < minPaddedSize) size = minPaddedSize;
                if(size == minPaddedSize) return size;
                long min = minPaddedSize;
-               long max = minPaddedSize << 1;
+               long max = (long)minPaddedSize << 1;
                while(true) {
                        if(max < 0)
                                throw new Error("Impossible size: "+size+" - 
min="+min+", max="+max);

Modified: trunk/freenet/src/freenet/support/math/SimpleBinaryRunningAverage.java
===================================================================
--- trunk/freenet/src/freenet/support/math/SimpleBinaryRunningAverage.java      
2008-04-14 15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/support/math/SimpleBinaryRunningAverage.java      
2008-04-14 16:00:44 UTC (rev 19326)
@@ -160,7 +160,7 @@
                ba = new BitSet(maxSize);
                maximumSize = maxSize;
                byte[] b = new byte[HexUtil.countBytesForBits(size)];
-               dis.read(b);
+               dis.readFully(b);
                HexUtil.bytesToBits(b, ba, size);
                calculateTotalOnesZeros();
                defaultValue = 0.5; // not used

Modified: trunk/freenet/src/freenet/support/math/TrivialRunningAverage.java
===================================================================
--- trunk/freenet/src/freenet/support/math/TrivialRunningAverage.java   
2008-04-14 15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/freenet/support/math/TrivialRunningAverage.java   
2008-04-14 16:00:44 UTC (rev 19326)
@@ -40,7 +40,9 @@
        }

     public Object clone() {
-       return new TrivialRunningAverage(this);
+       synchronized (this) {
+                       return new TrivialRunningAverage(this);
+               }
     }



Modified: trunk/freenet/src/freenet/tools/AddRef.java
===================================================================
--- trunk/freenet/src/freenet/tools/AddRef.java 2008-04-14 15:55:20 UTC (rev 
19325)
+++ trunk/freenet/src/freenet/tools/AddRef.java 2008-04-14 16:00:44 UTC (rev 
19326)
@@ -108,7 +108,6 @@

        protected SimpleFieldSet getMessage(LineReadingInputStream lis){
                SimpleFieldSet sfs = new SimpleFieldSet(true);
-               sfs=new SimpleFieldSet(true);
                try {
                        while(lis.available()>0){
                                String line = lis.readLine(128, 128, true);

Modified: trunk/freenet/src/net/i2p/util/NativeBigInteger.java
===================================================================
--- trunk/freenet/src/net/i2p/util/NativeBigInteger.java        2008-04-14 
15:55:20 UTC (rev 19325)
+++ trunk/freenet/src/net/i2p/util/NativeBigInteger.java        2008-04-14 
16:00:44 UTC (rev 19326)
@@ -10,8 +10,6 @@

 import java.math.BigInteger;
 import java.util.Random;
-import java.security.SecureRandom;
-
 import java.net.URL;
 import java.io.FileOutputStream;
 import java.io.InputStream;
@@ -267,126 +265,7 @@
        public static boolean isNative() {
                return _nativeOk;
        }
-
        /**
-        * <p>Compare the BigInteger.modPow/doubleValue vs the 
NativeBigInteger.modPow/doubleValue of some 
-        * really big (2Kbit) numbers 100 different times and benchmark the 
-        * performance (or shit a brick if they don't match).  </p>
-        *
-        */
-       public static void main(String args[]) {
-               runModPowTest(100);
-               runDoubleValueTest(100);
-       }
-
-       /* the sample numbers are elG generator/prime so we can test with 
reasonable numbers */
-       private final static byte[] _sampleGenerator = new 
BigInteger("2").toByteArray();
-       private final static byte[] _samplePrime = new 
BigInteger("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + 
"29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + 
"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + 
"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + 
"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + 
"C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + 
"83655D23DCA3AD961C62F356208552BB9ED529077096966D" + 
"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + 
"E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + 
"DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + 
"15728E5A8AACAA68FFFFFFFFFFFFFFFF", 16).toByteArray();
-
-       private static void runModPowTest(int numRuns) {
-               System.out.println("DEBUG: Warming up the random number 
generator...");
-               SecureRandom rand = new SecureRandom();
-               rand.nextBoolean();
-               System.out.println("DEBUG: Random number generator warmed up");
-
-               BigInteger jg = new BigInteger(_sampleGenerator);
-               BigInteger jp = new BigInteger(_samplePrime);
-
-               long totalTime = 0;
-               long javaTime = 0;
-
-               int runsProcessed = 0;
-               for(runsProcessed = 0; runsProcessed < numRuns; 
runsProcessed++) {
-                       BigInteger bi = new BigInteger(2048, rand);
-                       NativeBigInteger g = new 
NativeBigInteger(_sampleGenerator);
-                       NativeBigInteger p = new NativeBigInteger(_samplePrime);
-                       NativeBigInteger k = new NativeBigInteger(1, 
bi.toByteArray());
-                       long beforeModPow = System.currentTimeMillis();
-                       BigInteger myValue = g.modPow(k, p);
-                       long afterModPow = System.currentTimeMillis();
-                       BigInteger jval = jg.modPow(bi, jp);
-                       long afterJavaModPow = System.currentTimeMillis();
-
-                       totalTime += (afterModPow - beforeModPow);
-                       javaTime += (afterJavaModPow - afterModPow);
-                       if(!myValue.equals(jval)) {
-                               System.err.println("ERROR: [" + runsProcessed + 
"]\tnative modPow != java modPow");
-                               System.err.println("ERROR: native modPow value: 
" + myValue.toString());
-                               System.err.println("ERROR: java modPow value: " 
+ jval.toString());
-                               System.err.println("ERROR: run time: " + 
totalTime + "ms (" + (totalTime / (runsProcessed + 1)) + "ms each)");
-                               break;
-                       } else
-                               System.out.println("DEBUG: current run time: " 
+ (afterModPow - beforeModPow) + "ms (total: " + totalTime + "ms, " + 
(totalTime / (runsProcessed + 1)) + "ms each)");
-               }
-               System.out.println("INFO: run time: " + totalTime + "ms (" + 
(totalTime / (runsProcessed + 1)) + "ms each)");
-               if(numRuns == runsProcessed)
-                       System.out.println("INFO: " + runsProcessed + " runs 
complete without any errors");
-               else
-                       System.out.println("ERROR: " + runsProcessed + " runs 
until we got an error");
-
-               if(_nativeOk) {
-                       System.out.println("native run time: \t" + totalTime + 
"ms (" + (totalTime / (runsProcessed + 1)) + "ms each)");
-                       System.out.println("java run time:   \t" + javaTime + 
"ms (" + (javaTime / (runsProcessed + 1)) + "ms each)");
-                       System.out.println("native = " + ((totalTime * 100.0d) 
/ (double) javaTime) + "% of pure java time");
-               } else {
-                       System.out.println("java run time: \t" + javaTime + "ms 
(" + (javaTime / (runsProcessed + 1)) + "ms each)");
-                       System.out.println("However, we couldn't load the 
native library, so this doesn't test much");
-               }
-       }
-
-       private static void runDoubleValueTest(int numRuns) {
-               System.out.println("DEBUG: Warming up the random number 
generator...");
-               SecureRandom rand = new SecureRandom();
-               rand.nextBoolean();
-               System.out.println("DEBUG: Random number generator warmed up");
-
-               BigInteger jg = new BigInteger(_sampleGenerator);
-
-               long totalTime = 0;
-               long javaTime = 0;
-
-               int MULTIPLICATOR = 50000; //Run the doubleValue() calls within 
a loop since they are pretty fast.. 
-               int runsProcessed = 0;
-               for(runsProcessed = 0; runsProcessed < numRuns; 
runsProcessed++) {
-                       NativeBigInteger g = new 
NativeBigInteger(_sampleGenerator);
-                       long beforeDoubleValue = System.currentTimeMillis();
-                       double dNative = 0;
-                       for(int mult = 0; mult < MULTIPLICATOR; mult++)
-                               dNative = g.doubleValue();
-                       long afterDoubleValue = System.currentTimeMillis();
-                       double jval = 0;
-                       for(int mult = 0; mult < MULTIPLICATOR; mult++)
-                               jval = jg.doubleValue();
-                       long afterJavaDoubleValue = System.currentTimeMillis();
-
-                       totalTime += (afterDoubleValue - beforeDoubleValue);
-                       javaTime += (afterJavaDoubleValue - afterDoubleValue);
-                       if(dNative != jval) {
-                               System.err.println("ERROR: [" + runsProcessed + 
"]\tnative double != java double");
-                               System.err.println("ERROR: native double value: 
" + dNative);
-                               System.err.println("ERROR: java double value: " 
+ jval);
-                               System.err.println("ERROR: run time: " + 
totalTime + "ms (" + (totalTime / (runsProcessed + 1)) + "ms each)");
-                               break;
-                       } else
-                               System.out.println("DEBUG: current run time: " 
+ (afterDoubleValue - beforeDoubleValue) + "ms (total: " + totalTime + "ms, " + 
(totalTime / (runsProcessed + 1)) + "ms each)");
-               }
-               System.out.println("INFO: run time: " + totalTime + "ms (" + 
(totalTime / (runsProcessed + 1)) + "ms each)");
-               if(numRuns == runsProcessed)
-                       System.out.println("INFO: " + runsProcessed + " runs 
complete without any errors");
-               else
-                       System.out.println("ERROR: " + runsProcessed + " runs 
until we got an error");
-
-               if(_nativeOk) {
-                       System.out.println("native run time: \t" + totalTime + 
"ms (" + (totalTime / (runsProcessed + 1)) + "ms each)");
-                       System.out.println("java run time:   \t" + javaTime + 
"ms (" + (javaTime / (runsProcessed + 1)) + "ms each)");
-                       System.out.println("native = " + ((totalTime * 100.0d) 
/ (double) javaTime) + "% of pure java time");
-               } else {
-                       System.out.println("java run time: \t" + javaTime + "ms 
(" + (javaTime / (runsProcessed + 1)) + "ms each)");
-                       System.out.println("However, we couldn't load the 
native library, so this doesn't test much");
-               }
-       }
-
-       /**
         * <p>Do whatever we can to load up the native library backing this 
BigInteger's native methods.
         * If it can find a custom built jbigi.dll / libjbigi.so, it'll use 
that.  Otherwise
         * it'll try to look in the classpath for the correct library (see 
loadFromResource).

Copied: trunk/freenet/test/freenet/support/BufferTest.java (from rev 19322, 
trunk/freenet/test/freenet/support/BufferTest.java)
===================================================================
--- trunk/freenet/test/freenet/support/BufferTest.java                          
(rev 0)
+++ trunk/freenet/test/freenet/support/BufferTest.java  2008-04-14 16:00:44 UTC 
(rev 19326)
@@ -0,0 +1,173 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package freenet.support;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+/**
+ * Test case for {@link freenet.support.Buffer} class.
+ * 
+ * @author stuart martin &lt;wavey at freenetproject.org&gt;
+ */
+public class BufferTest extends TestCase {
+
+       private static final String DATA_STRING_1 = 
"asldkjaskjdsakdhasdhaskjdhaskjhbkasbhdjkasbduiwbxgdoudgboewuydxbybuewyxbuewyuwe"
 + 
+               
"dasdkljasndijwnodhnqweoidhnaouidhbnwoduihwnxodiuhnwuioxdhnwqiouhnxwqoiushdnxwqoiudhxnwqoiudhxni";
+       
+       public void testByteArrayBuffer() {
+               
+               byte[] data = DATA_STRING_1.getBytes();
+               
+               Buffer buffer = new Buffer(data);
+               
+               assertEquals(data, buffer.getData());
+
+               doTestBuffer(data, buffer);
+       }
+
+       public void testByteArrayIndexBuffer() {
+               
+               // get content
+               byte[] data = DATA_STRING_1.getBytes();
+               
+               byte[] dataSub = new byte[5];
+               
+               // prepare 'substring'
+               System.arraycopy(data, 4, dataSub, 0, 5);
+
+               Buffer buffer = new Buffer(data, 4, 5);
+               
+               assertFalse(dataSub.equals(buffer.getData()));
+
+               doTestBuffer(dataSub, buffer);
+       }
+
+       
+       public void testDataInputStreamBuffer() {
+               
+               byte[] data = DATA_STRING_1.getBytes();   // get some content
+               
+               byte[] data2 = new byte[data.length + 4]; // make room for 4 
byte length indicator  
+               
+               int length = DATA_STRING_1.getBytes().length;
+               
+               // populate length as first 4 bytes
+               data2[0] = (byte)((length & 0xff000000) >> 24);
+               data2[1] = (byte)((length & 0xff0000)   >> 16);
+               data2[2] = (byte)((length & 0xff00)     >>  8);
+               data2[3] = (byte)((length & 0xff)            );
+               
+               System.arraycopy(data, 0, data2, 4, data.length); // populate 
rest of content
+               
+               DataInputStream dis = new DataInputStream(new 
ByteArrayInputStream(data2));
+               Buffer buffer = null;
+               
+               try {
+                       buffer = new Buffer(dis);
+               } catch (IOException e) {
+                       fail("unexpected exception: " + e.getMessage());
+               }
+               // perform rest of test with the *original* array because 
Buffer(DataInputStream) chomps first 4 bytes
+               doTestBuffer(data, buffer);
+       }
+       
+       private void doTestBuffer(byte[] data, Buffer buffer) {
+               assertEquals(data.length, buffer.getLength());
+               
+               for(int i = 0; i < buffer.getLength(); i++) {
+                       assertEquals(data[i], buffer.byteAt(i));
+               }
+               
+               try{
+                       buffer.byteAt(data.length + 1); // expect exception
+                       fail();
+               }
+               catch(ArrayIndexOutOfBoundsException e) {
+                       // expect this
+               }
+       }
+
+       public void testLongBufferToString() {
+               
+               Buffer buffer = new Buffer(DATA_STRING_1.getBytes());
+               String longString = buffer.toString();
+               assertEquals("Buffer {" + buffer.getLength() + "}", longString);
+       }
+
+       public void testShortBufferToString() {
+               String shortString = "feep";
+               Buffer shortBuffer = new Buffer(shortString.getBytes());
+               
+               String outString = shortBuffer.toString();
+               assertEquals(outString, "{4:102 101 101 112 "); // FIXME: final 
brace?
+       }
+       
+       public void testEquals() {
+               
+               Buffer b1 = new Buffer("Buffer1".getBytes());
+               Buffer b2 = new Buffer("Buffer2".getBytes());
+               Buffer b3 = new Buffer("Buffer1".getBytes());
+               
+               assertFalse(b1.equals(b2));
+               assertTrue(b1.equals(b3));
+               assertFalse(b2.equals(b3));
+               assertTrue(b1.equals(b1));
+               assertTrue(b2.equals(b2));
+               assertTrue(b3.equals(b1));                              
+       }
+       
+       public void testHashcode() {
+               
+               Buffer b1 = new Buffer("Buffer1".getBytes());
+               Buffer b2 = new Buffer("Buffer2".getBytes());
+               Buffer b3 = new Buffer("Buffer1".getBytes());
+               
+               Map hashMap = new HashMap();
+               
+               hashMap.put(b1, b1); 
+               hashMap.put(b2, b2);
+               hashMap.put(b3, b3); // should clobber b1 due to content
+
+               // see if b3 survived
+               Object o = hashMap.get(b3);
+               assertFalse(o == b1);
+               assertTrue(o == b3);
+               
+               // see if b1 survived
+               o = hashMap.get(b1);
+               assertFalse(o == b1);           
+               assertTrue(o == b3);
+       }
+       
+       public void testCopy() {
+               
+               byte[] oldBuf = DATA_STRING_1.getBytes();
+               Buffer b = new Buffer(oldBuf);
+               
+               byte[] newBuf = new byte[b.getLength()];
+               b.copyTo(newBuf, 0);
+               
+               for(int i = 0; i < oldBuf.length; i++) {
+                       assertEquals(newBuf[i], oldBuf[i]);
+               }
+       }
+}

Modified: trunk/freenet/test/freenet/support/SortedLongSetTest.java
===================================================================
--- trunk/freenet/test/freenet/support/SortedLongSetTest.java   2008-04-14 
15:55:20 UTC (rev 19325)
+++ trunk/freenet/test/freenet/support/SortedLongSetTest.java   2008-04-14 
16:00:44 UTC (rev 19326)
@@ -8,13 +8,7 @@
  * @author sdiz
  */
 public class SortedLongSetTest extends TestCase {
-       /*
-        * FIXME use Long.MAX_VALUE , not MAX_VALUE - 1
-        */
-       private final static long[] testArray  =  {
-               10, 8, 6, 2, 0, 1, 11,
-               Long.MAX_VALUE - 1, 4, 7, 5, 3, Long.MIN_VALUE 
-       };
+       private final static long[] testArray = { 10, 8, 6, 2, 0, 1, 11, 
Long.MAX_VALUE, 4, 7, 5, 3, Long.MIN_VALUE };

        protected SortedLongSet perpare(long[] array) {
                SortedLongSet set = new SortedLongSet();
@@ -60,7 +54,7 @@
                // Contain
                assertTrue(set.contains(0L));
                assertTrue(set.contains(3L));
-               assertTrue(set.contains(Long.MAX_VALUE - 1));
+               assertTrue(set.contains(Long.MAX_VALUE));
                assertTrue(set.contains(Long.MIN_VALUE));

                // Not contain

Modified: trunk/freenet/test/freenet/support/compress/GzipCompressorTest.java
===================================================================
--- trunk/freenet/test/freenet/support/compress/GzipCompressorTest.java 
2008-04-14 15:55:20 UTC (rev 19325)
+++ trunk/freenet/test/freenet/support/compress/GzipCompressorTest.java 
2008-04-14 16:00:44 UTC (rev 19326)
@@ -52,7 +52,10 @@

                // check GZIP is the first compressor
                assertEquals(gzipCompressor, compressorZero);
+       }

+       public void testCompress() {
+
                // do gzip compression
                byte[] compressedData = 
doCompress(UNCOMPRESSED_DATA_1.getBytes());

@@ -63,15 +66,48 @@
                for (int i = 0; i < compressedData.length; i++) {
                        assertEquals(COMPRESSED_DATA_1[i], compressedData[i]);
                }
+       }

-               // do gzip uncompression
-               byte[] uncompressedData = doUncompress(compressedData);
+       public void testBucketDecompress() {

+               byte[] compressedData = COMPRESSED_DATA_1;
+               
+               // do gzip decompression with buckets
+               byte[] uncompressedData = doBucketDecompress(compressedData);
+               
                // is the (round-tripped) uncompressed string the same as the 
original?
                String uncompressedString = new String(uncompressedData);
                assertEquals(uncompressedString, UNCOMPRESSED_DATA_1);
        }

+       public void testByteArrayDecompress() {
+               
+        // build 5k array 
+               byte[] originalUncompressedData = new byte[5 * 1024];
+               for(int i = 0; i < originalUncompressedData.length; i++) {
+                       originalUncompressedData[i] = 1;
+               }
+               
+               byte[] compressedData = doCompress(originalUncompressedData);
+               byte[] outUncompressedData = new byte[5 * 1024];
+               
+               int writtenBytes = 0;
+               
+               try {
+                       writtenBytes = 
Compressor.GZIP.decompress(compressedData, 0, compressedData.length, 
outUncompressedData);
+               } catch (CompressionOutputSizeException e) {
+                       fail("unexpected exception thrown : " + e.getMessage());
+               }
+               
+               assertEquals(writtenBytes, originalUncompressedData.length);
+               assertEquals(originalUncompressedData.length, 
outUncompressedData.length);
+               
+        // check each byte is exactly as expected
+               for (int i = 0; i < outUncompressedData.length; i++) {
+                       assertEquals(originalUncompressedData[i], 
outUncompressedData[i]);
+               }
+       }
+
        public void testCompressException() {

                byte[] uncompressedData = UNCOMPRESSED_DATA_1.getBytes();
@@ -109,7 +145,7 @@
                }
        }

-       private byte[] doUncompress(byte[] compressedData) {
+       private byte[] doBucketDecompress(byte[] compressedData) {

                Bucket inBucket = new ArrayBucket(compressedData);
                BucketFactory factory = new ArrayBucketFactory();

Copied: trunk/freenet/test/net/i2p/util/NativeBigIntegerTest.java (from rev 
19322, trunk/freenet/test/net/i2p/util/NativeBigIntegerTest.java)
===================================================================
--- trunk/freenet/test/net/i2p/util/NativeBigIntegerTest.java                   
        (rev 0)
+++ trunk/freenet/test/net/i2p/util/NativeBigIntegerTest.java   2008-04-14 
16:00:44 UTC (rev 19326)
@@ -0,0 +1,114 @@
+package net.i2p.util;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+import junit.framework.TestCase;
+
+public class NativeBigIntegerTest extends TestCase {
+       // Run with <code>ant -Dbenchmark=true</code> to do benchmark 
+       private static final boolean BENCHMARK = 
Boolean.getBoolean("benchmark");
+       private static int numRuns = BENCHMARK ? 200 : 5;
+
+       /*
+        * the sample numbers are elG generator/prime so we can test with 
reasonable
+        * numbers
+        */
+       private final static byte[] _sampleGenerator = new 
BigInteger("2").toByteArray();
+       private final static byte[] _samplePrime = new 
BigInteger("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+               + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + 
"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+               + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + 
"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
+               + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + 
"83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+               + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + 
"E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"
+               + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + 
"15728E5A8AACAA68FFFFFFFFFFFFFFFF", 16)
+               .toByteArray();
+
+       private SecureRandom rand;
+       private int runsProcessed;
+
+       private BigInteger jg;
+       private BigInteger jp;
+
+       private long totalTime = 0;
+       private long javaTime = 0;
+
+       protected void setUp() throws Exception {
+               if (!NativeBigInteger.isNative())
+                       printError("can't load native code");
+
+               printInfo("DEBUG: Warming up the random number generator...");
+               rand = new SecureRandom();
+               rand.nextBoolean();
+               printInfo("DEBUG: Random number generator warmed up");
+
+               jg = new BigInteger(_sampleGenerator);
+               jp = new BigInteger(_samplePrime);
+
+               totalTime = javaTime = 0;
+       }
+
+       protected void tearDown() throws Exception {
+               printInfo("INFO: run time: " + totalTime + "ms (" + (totalTime 
/ (runsProcessed + 1)) + "ms each)");
+               if (numRuns == runsProcessed)
+                       printInfo("INFO: " + runsProcessed + " runs complete 
without any errors");
+               else
+                       printError("ERROR: " + runsProcessed + " runs until we 
got an error");
+
+               printInfo("native run time: \t" + totalTime + "ms (" + 
(totalTime / (runsProcessed + 1)) + "ms each)");
+               printInfo("java run time:   \t" + javaTime + "ms (" + (javaTime 
/ (runsProcessed + 1)) + "ms each)");
+               printInfo("native = " + ((totalTime * 100.0d) / (double) 
javaTime) + "% of pure java time");
+       }
+
+       public void testModPow() {
+               for (runsProcessed = 0; runsProcessed < numRuns; 
runsProcessed++) {
+                       BigInteger bi = new BigInteger(2048, rand);
+                       NativeBigInteger g = new 
NativeBigInteger(_sampleGenerator);
+                       NativeBigInteger p = new NativeBigInteger(_samplePrime);
+                       NativeBigInteger k = new NativeBigInteger(1, 
bi.toByteArray());
+
+                       long beforeModPow = System.currentTimeMillis();
+                       BigInteger myValue = g.modPow(k, p);
+                       long afterModPow = System.currentTimeMillis();
+                       BigInteger jval = jg.modPow(bi, jp);
+                       long afterJavaModPow = System.currentTimeMillis();
+
+                       totalTime += (afterModPow - beforeModPow);
+                       javaTime += (afterJavaModPow - afterModPow);
+
+                       assertEquals(jval, myValue);
+               }
+       }
+
+       public void testDoubleValue() {
+               BigInteger jg = new BigInteger(_sampleGenerator);
+
+               int MULTIPLICATOR = 50000; //Run the doubleValue() calls within 
a loop since they are pretty fast.. 
+               for (runsProcessed = 0; runsProcessed < numRuns; 
runsProcessed++) {
+                       NativeBigInteger g = new 
NativeBigInteger(_sampleGenerator);
+                       long beforeDoubleValue = System.currentTimeMillis();
+                       double dNative = 0;
+                       for (int mult = 0; mult < MULTIPLICATOR; mult++)
+                               dNative = g.doubleValue();
+                       long afterDoubleValue = System.currentTimeMillis();
+                       double jval = 0;
+                       for (int mult = 0; mult < MULTIPLICATOR; mult++)
+                               jval = jg.doubleValue();
+                       long afterJavaDoubleValue = System.currentTimeMillis();
+
+                       totalTime += (afterDoubleValue - beforeDoubleValue);
+                       javaTime += (afterJavaDoubleValue - afterDoubleValue);
+
+                       assertEquals(jval, dNative, 0);
+               }
+       }
+
+       private static void printInfo(String info) {
+               if (BENCHMARK)
+                       System.out.println(info);
+       }
+
+       private static void printError(String info) {
+               if (BENCHMARK)
+                       System.err.println(info);
+       }
+}


Reply via email to