gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[GNUnet-SVN] r3286 - in freeway: . native src/org/gnu/freeway src/org/gn


From: mdonoughe
Subject: [GNUnet-SVN] r3286 - in freeway: . native src/org/gnu/freeway src/org/gnu/freeway/cwrappers src/org/gnu/freeway/cwrappers/util src/org/gnu/freeway/protocol src/org/gnu/freeway/protocol/fs src/org/gnu/freeway/protocol/fs/esed2 src/org/gnu/freeway/protocol/fs/swing src/org/gnu/freeway/server src/org/gnu/freeway/services src/org/gnu/freeway/services/c src/org/gnu/freeway/test src/org/gnu/freeway/util
Date: Mon, 21 Aug 2006 04:54:25 -0700 (PDT)

Author: mdonoughe
Date: 2006-08-21 04:53:42 -0700 (Mon, 21 Aug 2006)
New Revision: 3286

Added:
   freeway/src/org/gnu/freeway/cwrappers/CDatastoreDatum.java
   freeway/src/org/gnu/freeway/cwrappers/CDatastoreValue.java
   freeway/src/org/gnu/freeway/cwrappers/CHashCode512.java
   freeway/src/org/gnu/freeway/cwrappers/ConstCDatastoreDatum.java
   freeway/src/org/gnu/freeway/cwrappers/ConstCDatastoreValue.java
   freeway/src/org/gnu/freeway/cwrappers/ConstCHashCode512.java
   freeway/src/org/gnu/freeway/protocol/fs/
   freeway/src/org/gnu/freeway/protocol/fs/AFSProtocol.java
   freeway/src/org/gnu/freeway/protocol/fs/BloomFilter2.java
   freeway/src/org/gnu/freeway/protocol/fs/DBHandle.java
   freeway/src/org/gnu/freeway/protocol/fs/EntryCallback.java
   freeway/src/org/gnu/freeway/protocol/fs/FileIndex.java
   freeway/src/org/gnu/freeway/protocol/fs/Handler.java
   freeway/src/org/gnu/freeway/protocol/fs/IndexedFileNameCallback.java
   freeway/src/org/gnu/freeway/protocol/fs/IndirectionTableEntry.java
   freeway/src/org/gnu/freeway/protocol/fs/IterState.java
   freeway/src/org/gnu/freeway/protocol/fs/LFS.java
   freeway/src/org/gnu/freeway/protocol/fs/Manager.java
   freeway/src/org/gnu/freeway/protocol/fs/Migration.java
   freeway/src/org/gnu/freeway/protocol/fs/MySQLHandle.java
   freeway/src/org/gnu/freeway/protocol/fs/NativeFSProtocol.java
   freeway/src/org/gnu/freeway/protocol/fs/Policy2.java
   freeway/src/org/gnu/freeway/protocol/fs/QueryManager.java
   freeway/src/org/gnu/freeway/protocol/fs/QueryRecord.java
   freeway/src/org/gnu/freeway/protocol/fs/Routing.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/
   freeway/src/org/gnu/freeway/protocol/fs/esed2/AFSConstants.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/AFSUtils.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/Block.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/CSDelete3Hash.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/CSDeleteChk.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/CSGetAvgPriority.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/CSIndexBlock.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/CSIndexFile.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/CSIndexSuper.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/CSInsert3Hash.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/CSInsertChk.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/CSInsertSBlock.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/CSLinkFile.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/CSNSQuery.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/CSQuery.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/CSResult3Hash.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/CSResultChk.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/CSResultSBlock.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/CSUnindexBlock.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/CSUnindexFile.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/CSUnindexSuper.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/CSUploadFile.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/ChkHashes.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/ContentBlock.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/ContentEncoding.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/ContentIndex.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/DBlock.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/DeleteURI.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/DeleteUtil.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/DownloadURI.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/DownloadUtil.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/EncryptedSBlock.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/Extractor.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/FileIdentifier.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/GNDirectory.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/GNDirectoryDatabase.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/GeneralURI.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/IBlock.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/IBlockData.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/IOContext.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/InsertURI.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/InsertUtil.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/InsertWrapper.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/KeyWords.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/Listener.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/NSSearchResultCallback.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/Node.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/NodeContext.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/P2P3HashResult.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/P2PChkResult.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/P2PNSQuery.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/P2PQuery.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/P2PSBlockResult.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/Policy.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/Priority.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/ProgressModel.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/ProgressStats.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/Pseudonym.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/RequestContinuation.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/RequestEntry.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/RequestManager.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/RootNode.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/RootNodeCallback.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/SBlock.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/SearchResultCallback.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/SearchURI.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/SendNSQueryContext.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/SendQueriesContext.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/TestTerminateThread.java
   freeway/src/org/gnu/freeway/protocol/fs/esed2/URI.java
   freeway/src/org/gnu/freeway/protocol/fs/swing/
   freeway/src/org/gnu/freeway/protocol/fs/swing/CreatePseudonymDialog.java
   freeway/src/org/gnu/freeway/protocol/fs/swing/DaemonWindow.java
   freeway/src/org/gnu/freeway/protocol/fs/swing/DeletePseudonymDialog.java
   freeway/src/org/gnu/freeway/protocol/fs/swing/DownloadAdapter.java
   freeway/src/org/gnu/freeway/protocol/fs/swing/DownloadModel.java
   freeway/src/org/gnu/freeway/protocol/fs/swing/DownloadWindow.java
   freeway/src/org/gnu/freeway/protocol/fs/swing/InsertDialog.java
   freeway/src/org/gnu/freeway/protocol/fs/swing/SController.java
   freeway/src/org/gnu/freeway/protocol/fs/swing/SearchAdapter.java
   freeway/src/org/gnu/freeway/protocol/fs/swing/SearchOverviewPanel.java
   freeway/src/org/gnu/freeway/protocol/fs/swing/SearchPanel.java
   freeway/src/org/gnu/freeway/protocol/fs/swing/SearchWindow.java
   freeway/src/org/gnu/freeway/protocol/fs/swing/SelectDialog.java
   freeway/src/org/gnu/freeway/protocol/fs/swing/StatsWindow.java
   freeway/src/org/gnu/freeway/protocol/fs/swing/about.c
   freeway/src/org/gnu/freeway/protocol/fs/swing/about.h
   freeway/src/org/gnu/freeway/protocol/fs/swing/delete.c
   freeway/src/org/gnu/freeway/protocol/fs/swing/delete.h
   freeway/src/org/gnu/freeway/protocol/fs/swing/directory.c
   freeway/src/org/gnu/freeway/protocol/fs/swing/directory.h
   freeway/src/org/gnu/freeway/protocol/fs/swing/directorydisplay.c
   freeway/src/org/gnu/freeway/protocol/fs/swing/directorydisplay.h
   freeway/src/org/gnu/freeway/protocol/fs/swing/download.c
   freeway/src/org/gnu/freeway/protocol/fs/swing/download.h
   freeway/src/org/gnu/freeway/protocol/fs/swing/helper.c
   freeway/src/org/gnu/freeway/protocol/fs/swing/helper.h
   freeway/src/org/gnu/freeway/protocol/fs/swing/insert.c
   freeway/src/org/gnu/freeway/protocol/fs/swing/insert.h
   freeway/src/org/gnu/freeway/protocol/fs/swing/insertprogress.c
   freeway/src/org/gnu/freeway/protocol/fs/swing/insertprogress.h
   freeway/src/org/gnu/freeway/protocol/fs/swing/main.c
   freeway/src/org/gnu/freeway/protocol/fs/swing/main.h
   freeway/src/org/gnu/freeway/protocol/fs/swing/namespace.c
   freeway/src/org/gnu/freeway/protocol/fs/swing/namespace.h
   freeway/src/org/gnu/freeway/protocol/fs/swing/pseudonyms.c
   freeway/src/org/gnu/freeway/protocol/fs/swing/pseudonyms.h
   freeway/src/org/gnu/freeway/protocol/fs/swing/saveas.c
   freeway/src/org/gnu/freeway/protocol/fs/swing/saveas.h
   freeway/src/org/gnu/freeway/protocol/fs/swing/search.c
   freeway/src/org/gnu/freeway/protocol/fs/swing/search.h
   freeway/src/org/gnu/freeway/protocol/fs/swing/statistics.c
   freeway/src/org/gnu/freeway/protocol/fs/swing/statistics.h
   freeway/src/org/gnu/freeway/services/DatastoreService.java
   freeway/src/org/gnu/freeway/services/c/DatastoreService.java
   freeway/src/org/gnu/freeway/util/DatastoreValue.java
   freeway/src/org/gnu/freeway/util/DatumIterator.java
Removed:
   freeway/src/org/gnu/freeway/protocol/afs/
Modified:
   freeway/
   freeway/native/java.c
   freeway/native/switch-table.c
   freeway/native/switch-table.h
   freeway/native/util.c
   freeway/protocols.xml
   freeway/src/org/gnu/freeway/GNUNetDaemon.java
   freeway/src/org/gnu/freeway/GNUNetDirectory.java
   freeway/src/org/gnu/freeway/GNUNetDownload.java
   freeway/src/org/gnu/freeway/GNUNetInsert.java
   freeway/src/org/gnu/freeway/GNUNetPseudonym.java
   freeway/src/org/gnu/freeway/GNUNetSearch.java
   freeway/src/org/gnu/freeway/GNUNetSwing.java
   freeway/src/org/gnu/freeway/cwrappers/util/SwitchTableGenerator.java
   freeway/src/org/gnu/freeway/server/CPluginLoader.java
   freeway/src/org/gnu/freeway/server/NativeCallback.java
   freeway/src/org/gnu/freeway/services/c/StatsService.java
   freeway/src/org/gnu/freeway/test/MySQLTest.java
Log:

 fixes in SwitchTableGenerator
 work on native FS application and it's services



Property changes on: freeway
___________________________________________________________________
Name: svk:merge
   - 9da852c3-611b-0410-9456-b3f9f3a8f88d:/local/freeway:68
ca0d4bff-9018-0410-8ce9-c5d843b21c37:/local/freeway:73
   + 9da852c3-611b-0410-9456-b3f9f3a8f88d:/local/freeway:70
ca0d4bff-9018-0410-8ce9-c5d843b21c37:/local/freeway:73

Modified: freeway/native/java.c
===================================================================
--- freeway/native/java.c       2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/native/java.c       2006-08-21 11:53:42 UTC (rev 3286)
@@ -135,6 +135,7 @@
   MUTEX_CREATE(&threadsLock);
   MUTEX_CREATE(&modulesLock);
   registerThread(env, capi);
+  FREENONNULL(setConfigurationString("GNUNETD", "_MAGIC_", "NO"));
   char * * jargs = NULL;
   int jargLength;
   GNUNET_ASSERT(env != NULL);

Modified: freeway/native/switch-table.c
===================================================================
--- freeway/native/switch-table.c       2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/native/switch-table.c       2006-08-21 11:53:42 UTC (rev 3286)
@@ -2,13 +2,18 @@
 jobject * jargs;
 int jargLength = (*env)->GetArrayLength(env, arguments);
 if(jargLength > 0) {
-  jargs = MALLOC(sizeof(jobject) * jargLength);
+  jargs = malloc(sizeof(jobject) * jargLength);
   int jargsI;
   for(jargsI = 0; jargsI < jargLength; jargsI++) {
     jargs[jargsI] = (*env)->GetObjectArrayElement(env, arguments, jargsI);
   }
 }
 switch (functionType) {
+  case 4: {
+    long long cret = ((FunctionType4) 
((void**)m->moduleFptrStruct)[functionOffset])();
+    oret = convLongToCLong(cret, env);
+    break;
+  }
   case 13: {
     PointerObject * carg0 = convObjectToPtr(jargs[0], env);
     int cret = ((FunctionType13) 
((void**)m->moduleFptrStruct)[functionOffset])(carg0->pointer);
@@ -22,6 +27,15 @@
     ((FunctionType60) ((void**)m->moduleFptrStruct)[functionOffset])(carg0, 
carg1);
     break;
   }
+  case 85: {
+    PointerObject * carg0 = convObjectToPtr(jargs[0], env);
+    PointerObject * carg1 = convObjectToPtr(jargs[1], env);
+    int cret = ((FunctionType85) 
((void**)m->moduleFptrStruct)[functionOffset])(carg0->pointer, carg1->pointer);
+    updateObjectFromPtr(jargs[1], carg1, env);
+    updateObjectFromPtr(jargs[0], carg0, env);
+    oret = convIntToCInt(cret, env);
+    break;
+  }
   case 42: {
     int carg0 = convCIntToInt(jargs[0], env);
     int carg1 = convCIntToInt(jargs[1], env);
@@ -38,4 +52,4 @@
     GNUNET_ASSERT(0);
 }
 if(jargs != NULL)
-  FREE(jargs);
+  free(jargs);

Modified: freeway/native/switch-table.h
===================================================================
--- freeway/native/switch-table.h       2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/native/switch-table.h       2006-08-21 11:53:42 UTC (rev 3286)
@@ -1,5 +1,7 @@
 // This file was autogenerated by SwitchTableGenerator
+typedef long long (*FunctionType4)();
 typedef int (*FunctionType13)(void * arg0);
 typedef void (*FunctionType60)(int arg0, long long arg1);
+typedef int (*FunctionType85)(void * arg0, void * arg1);
 typedef void (*FunctionType42)(int arg0, int arg1);
 typedef long long (*FunctionType10)(int arg0);

Modified: freeway/native/util.c
===================================================================
--- freeway/native/util.c       2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/native/util.c       2006-08-21 11:53:42 UTC (rev 3286)
@@ -488,5 +488,6 @@
 
 //TODO: fix this
 int parseGnunetdCommandLine(int argc, char * argv[]) {
+  FREENONNULL(setConfigurationString("GNUNETD", "_MAGIC_", "YES"));
   return OK;
 }

Modified: freeway/protocols.xml
===================================================================
--- freeway/protocols.xml       2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/protocols.xml       2006-08-21 11:53:42 UTC (rev 3286)
@@ -1,8 +1,10 @@
-<jar destfile="${project.build}/protocol-afs.jar">
-       <fileset dir="${project.build}/classes" 
includes="org/gnu/freeway/protocol/afs/**" 
excludes="org/gnu/freeway/protocol/afs/MySQLHandle*" />
+<jar destfile="${project.build}/protocol-fs.jar">
+       <fileset dir="${project.build}/classes" 
includes="org/gnu/freeway/protocol/fs/**" 
excludes="org/gnu/freeway/protocol/fs/MySQLHandle*" />
        <fileset dir="${project.res}/swing" />
        <manifest>
-               <attribute name="Main-Class" 
value="org.gnu.freeway.protocol.afs.AFSProtocol" />
+               <attribute name="Freeway-Name" value="FS" />
+               <attribute name="Freeway-Java-Class" 
value="org.gnu.freeway.protocol.fs.AFSProtocol" />
+               <attribute name="Freeway-C-Class" 
value="org.gnu.freeway.protocol.fs.NativeFSProtocol" />
        </manifest>
 </jar>
 

Modified: freeway/src/org/gnu/freeway/GNUNetDaemon.java
===================================================================
--- freeway/src/org/gnu/freeway/GNUNetDaemon.java       2006-08-21 05:13:22 UTC 
(rev 3285)
+++ freeway/src/org/gnu/freeway/GNUNetDaemon.java       2006-08-21 11:53:42 UTC 
(rev 3286)
@@ -253,7 +253,7 @@
        public static void main( String[] args )
        {
                //TODO: use args
-               CPluginLoader._.initUtil(new String[] {}, VERSION);
+               CPluginLoader._.initUtil(new String[] {"gnunetd"}, VERSION);
                launch(GNUNetDaemon.class,args);
        }
 

Modified: freeway/src/org/gnu/freeway/GNUNetDirectory.java
===================================================================
--- freeway/src/org/gnu/freeway/GNUNetDirectory.java    2006-08-21 05:13:22 UTC 
(rev 3285)
+++ freeway/src/org/gnu/freeway/GNUNetDirectory.java    2006-08-21 11:53:42 UTC 
(rev 3286)
@@ -4,8 +4,8 @@
 
 package org.gnu.freeway;
 
-import org.gnu.freeway.protocol.afs.*;
-import org.gnu.freeway.protocol.afs.esed2.*;
+import org.gnu.freeway.protocol.fs.*;
+import org.gnu.freeway.protocol.fs.esed2.*;
 import org.gnu.freeway.util.*;
 import org.gnu.freeway.util.io.*;
 

Modified: freeway/src/org/gnu/freeway/GNUNetDownload.java
===================================================================
--- freeway/src/org/gnu/freeway/GNUNetDownload.java     2006-08-21 05:13:22 UTC 
(rev 3285)
+++ freeway/src/org/gnu/freeway/GNUNetDownload.java     2006-08-21 11:53:42 UTC 
(rev 3286)
@@ -4,8 +4,8 @@
 
 package org.gnu.freeway;
 
-import org.gnu.freeway.protocol.afs.*;
-import org.gnu.freeway.protocol.afs.esed2.*;
+import org.gnu.freeway.protocol.fs.*;
+import org.gnu.freeway.protocol.fs.esed2.*;
 import org.gnu.freeway.util.*;
 import org.gnu.freeway.util.io.*;
 import org.gnu.freeway.util.net.*;

Modified: freeway/src/org/gnu/freeway/GNUNetInsert.java
===================================================================
--- freeway/src/org/gnu/freeway/GNUNetInsert.java       2006-08-21 05:13:22 UTC 
(rev 3285)
+++ freeway/src/org/gnu/freeway/GNUNetInsert.java       2006-08-21 11:53:42 UTC 
(rev 3286)
@@ -4,8 +4,8 @@
 
 package org.gnu.freeway;
 
-import org.gnu.freeway.protocol.afs.*;
-import org.gnu.freeway.protocol.afs.esed2.*;
+import org.gnu.freeway.protocol.fs.*;
+import org.gnu.freeway.protocol.fs.esed2.*;
 import org.gnu.freeway.util.*;
 import org.gnu.freeway.util.crypto.*;
 import org.gnu.freeway.util.io.*;

Modified: freeway/src/org/gnu/freeway/GNUNetPseudonym.java
===================================================================
--- freeway/src/org/gnu/freeway/GNUNetPseudonym.java    2006-08-21 05:13:22 UTC 
(rev 3285)
+++ freeway/src/org/gnu/freeway/GNUNetPseudonym.java    2006-08-21 11:53:42 UTC 
(rev 3286)
@@ -4,7 +4,7 @@
 
 package org.gnu.freeway;
 
-import org.gnu.freeway.protocol.afs.esed2.*;
+import org.gnu.freeway.protocol.fs.esed2.*;
 import org.gnu.freeway.util.*;
 import org.gnu.freeway.util.crypto.*;
 import org.gnu.freeway.util.net.*;

Modified: freeway/src/org/gnu/freeway/GNUNetSearch.java
===================================================================
--- freeway/src/org/gnu/freeway/GNUNetSearch.java       2006-08-21 05:13:22 UTC 
(rev 3285)
+++ freeway/src/org/gnu/freeway/GNUNetSearch.java       2006-08-21 11:53:42 UTC 
(rev 3286)
@@ -4,7 +4,7 @@
 
 package org.gnu.freeway;
 
-import org.gnu.freeway.protocol.afs.esed2.*;
+import org.gnu.freeway.protocol.fs.esed2.*;
 import org.gnu.freeway.util.*;
 import org.gnu.freeway.util.crypto.*;
 import org.gnu.freeway.util.io.*;

Modified: freeway/src/org/gnu/freeway/GNUNetSwing.java
===================================================================
--- freeway/src/org/gnu/freeway/GNUNetSwing.java        2006-08-21 05:13:22 UTC 
(rev 3285)
+++ freeway/src/org/gnu/freeway/GNUNetSwing.java        2006-08-21 11:53:42 UTC 
(rev 3286)
@@ -4,8 +4,8 @@
 
 package org.gnu.freeway;
 
-import org.gnu.freeway.protocol.afs.esed2.*;
-import org.gnu.freeway.protocol.afs.swing.*;
+import org.gnu.freeway.protocol.fs.esed2.*;
+import org.gnu.freeway.protocol.fs.swing.*;
 import org.gnu.freeway.util.*;
 import org.gnu.freeway.util.io.*;
 import org.gnu.freeway.util.net.*;

Added: freeway/src/org/gnu/freeway/cwrappers/CDatastoreDatum.java
===================================================================
--- freeway/src/org/gnu/freeway/cwrappers/CDatastoreDatum.java  2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/cwrappers/CDatastoreDatum.java  2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,54 @@
+ /*
+      This file is part of Freeway
+
+      Freeway 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, or (at your
+      option) any later version.
+
+      Freeway 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 Freeway; see the file COPYING.  If not, write to the
+      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+      Boston, MA 02111-1307, USA.
+ */
+
+package org.gnu.freeway.cwrappers;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import org.gnu.freeway.cwrappers.util.CWrapper;
+import org.gnu.freeway.util.crypto.HostIdentity;
+import org.gnu.freeway.util.net.PersistentHelper;
+
+/**
+ * @file CDataStoreValue.java
+ * @brief 
+ * @author mdonoughe
+ */
+public class CDatastoreDatum extends ConstCDatastoreDatum implements CWrapper {
+
+       private static final int KIND = CWrapper.PTR_KIND;
+       
+       public CDatastoreDatum(byte[] serializedData) {
+               deserializeFromByteArray(serializedData);
+       }
+       
+       /* (non-Javadoc)
+        * @see 
org.gnu.freeway.cwrappers.util.CWrapper#deserializeFromByteArray(byte[])
+        */
+       public void deserializeFromByteArray(byte[] serializedData) {
+               byte[] keya = new byte[64];
+               byte[] valuea = new byte[24];
+               System.arraycopy(serializedData, 0, keya, 0, 64);
+               System.arraycopy(serializedData, 64, keya, 0, 24);
+               key = new CHashCode512(keya);
+               value = new CDatastoreValue(valuea);
+       }
+
+}

Added: freeway/src/org/gnu/freeway/cwrappers/CDatastoreValue.java
===================================================================
--- freeway/src/org/gnu/freeway/cwrappers/CDatastoreValue.java  2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/cwrappers/CDatastoreValue.java  2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,57 @@
+ /*
+      This file is part of Freeway
+
+      Freeway 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, or (at your
+      option) any later version.
+
+      Freeway 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 Freeway; see the file COPYING.  If not, write to the
+      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+      Boston, MA 02111-1307, USA.
+ */
+
+package org.gnu.freeway.cwrappers;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import org.gnu.freeway.cwrappers.util.CWrapper;
+import org.gnu.freeway.util.crypto.HostIdentity;
+import org.gnu.freeway.util.net.PersistentHelper;
+
+/**
+ * @file CDataStoreValue.java
+ * @brief 
+ * @author mdonoughe
+ */
+public class CDatastoreValue extends ConstCDatastoreValue implements CWrapper {
+
+       private static final int KIND = CWrapper.PTR_KIND;
+       
+       public CDatastoreValue(byte[] serializedData) {
+               super(null);
+               deserializeFromByteArray(serializedData);
+       }
+       
+       /* (non-Javadoc)
+        * @see 
org.gnu.freeway.cwrappers.util.CWrapper#deserializeFromByteArray(byte[])
+        */
+       public void deserializeFromByteArray(byte[] serializedData) {
+               ByteBuffer buf = ByteBuffer.wrap(serializedData);
+               buf.order(ByteOrder.nativeOrder());
+               value.setSize(buf.getInt());
+               value.setType(buf.getInt());
+               value.setPrio(buf.getInt());
+               value.setAnonymityLevel(buf.getInt());
+               buf.order(ByteOrder.BIG_ENDIAN);
+               value.setExpirationTime(buf.getLong());
+       }
+
+}

Added: freeway/src/org/gnu/freeway/cwrappers/CHashCode512.java
===================================================================
--- freeway/src/org/gnu/freeway/cwrappers/CHashCode512.java     2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/cwrappers/CHashCode512.java     2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,58 @@
+ /*
+      This file is part of Freeway
+
+      Freeway 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, or (at your
+      option) any later version.
+
+      Freeway 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 Freeway; see the file COPYING.  If not, write to the
+      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+      Boston, MA 02111-1307, USA.
+ */
+
+package org.gnu.freeway.cwrappers;
+
+import java.nio.ByteBuffer;
+
+import org.gnu.freeway.cwrappers.ConstCHostIdentity;
+import org.gnu.freeway.cwrappers.util.CWrapper;
+import org.gnu.freeway.util.crypto.HashCode512;
+import org.gnu.freeway.util.crypto.HostIdentity;
+import org.gnu.freeway.util.net.PersistentHelper;
+
+/**
+ * @file CHostIdentity.java
+ * @brief 
+ * @author mdonoughe
+ */
+public class CHashCode512 extends ConstCHashCode512 implements CWrapper {
+
+       private static final int KIND = CWrapper.PTR_KIND;
+       
+       public CHashCode512(byte[] serializedData) {
+               super(null);
+               deserializeFromByteArray(serializedData);
+       }
+       
+       public CHashCode512(HashCode512 value) {
+               super(value);
+       }
+       /* (non-Javadoc)
+        * @see 
org.gnu.freeway.cwrappers.util.CWrapper#deserializeFromByteArray(byte[])
+        */
+       public void deserializeFromByteArray(byte[] serializedData) {
+               value = (HashCode512) 
PersistentHelper.readFully(HashCode512.class, ByteBuffer.wrap(serializedData));
+       }
+       
+       public void setValue(HashCode512 value) {
+               this.value = value;
+       }
+
+}

Added: freeway/src/org/gnu/freeway/cwrappers/ConstCDatastoreDatum.java
===================================================================
--- freeway/src/org/gnu/freeway/cwrappers/ConstCDatastoreDatum.java     
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/cwrappers/ConstCDatastoreDatum.java     
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,59 @@
+ /*
+      This file is part of Freeway
+
+      Freeway 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, or (at your
+      option) any later version.
+
+      Freeway 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 Freeway; see the file COPYING.  If not, write to the
+      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+      Boston, MA 02111-1307, USA.
+ */
+
+package org.gnu.freeway.cwrappers;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import org.gnu.freeway.cwrappers.util.CWrapper;
+import org.gnu.freeway.cwrappers.util.ConstCWrapper;
+import org.gnu.freeway.util.crypto.HostIdentity;
+import org.gnu.freeway.util.net.PersistentHelper;
+
+/**
+ * @file ConstCDataStoreValue.java
+ * @brief 
+ * @author mdonoughe
+ */
+public class ConstCDatastoreDatum implements ConstCWrapper {
+       private static final int KIND = CWrapper.PTR_KIND;
+       
+       ConstCHashCode512 key;
+       ConstCDatastoreValue value;
+       
+       /* (non-Javadoc)
+        * @see org.gnu.freeway.cwrappers.util.ConstCWrapper#getSerializedSize()
+        */
+       public int getSerializedSize() {
+               return 88;
+       }
+
+       /* (non-Javadoc)
+        * @see 
org.gnu.freeway.cwrappers.util.ConstCWrapper#serializeToByteArray()
+        */
+       public byte[] serializeToByteArray() {
+               byte[] out = new byte[88];
+               ByteBuffer buf = ByteBuffer.wrap(out);
+               buf.put(key.serializeToByteArray());
+               buf.put(value.serializeToByteArray());
+               return buf.array();
+       }
+
+}

Added: freeway/src/org/gnu/freeway/cwrappers/ConstCDatastoreValue.java
===================================================================
--- freeway/src/org/gnu/freeway/cwrappers/ConstCDatastoreValue.java     
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/cwrappers/ConstCDatastoreValue.java     
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,72 @@
+ /*
+      This file is part of Freeway
+
+      Freeway 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, or (at your
+      option) any later version.
+
+      Freeway 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 Freeway; see the file COPYING.  If not, write to the
+      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+      Boston, MA 02111-1307, USA.
+ */
+
+package org.gnu.freeway.cwrappers;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import org.gnu.freeway.cwrappers.util.CWrapper;
+import org.gnu.freeway.cwrappers.util.ConstCWrapper;
+import org.gnu.freeway.util.DatastoreValue;
+import org.gnu.freeway.util.crypto.HostIdentity;
+import org.gnu.freeway.util.net.PersistentHelper;
+
+/**
+ * @file ConstCDataStoreValue.java
+ * @brief 
+ * @author mdonoughe
+ */
+public class ConstCDatastoreValue implements ConstCWrapper {
+       private static final int KIND = CWrapper.PTR_KIND;
+       
+       DatastoreValue value;
+       
+       public ConstCDatastoreValue(DatastoreValue value) {
+               this.value = value;
+       }
+       
+       /* (non-Javadoc)
+        * @see org.gnu.freeway.cwrappers.util.ConstCWrapper#getSerializedSize()
+        */
+       public int getSerializedSize() {
+               return 24;
+       }
+
+       /* (non-Javadoc)
+        * @see 
org.gnu.freeway.cwrappers.util.ConstCWrapper#serializeToByteArray()
+        */
+       public byte[] serializeToByteArray() {
+               byte[] out = new byte[24];
+               ByteBuffer buf = ByteBuffer.wrap(out);
+               buf.order(ByteOrder.nativeOrder());
+               buf.putInt(value.getSize());
+               buf.putInt(value.getType());
+               buf.putInt(value.getPrio());
+               buf.putInt(value.getAnonymityLevel());
+               buf.order(ByteOrder.BIG_ENDIAN);
+               buf.putLong(value.getExpirationTime());
+               return buf.array();
+       }
+       
+       public DatastoreValue getValue() {
+               return value;
+       }
+
+}

Added: freeway/src/org/gnu/freeway/cwrappers/ConstCHashCode512.java
===================================================================
--- freeway/src/org/gnu/freeway/cwrappers/ConstCHashCode512.java        
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/cwrappers/ConstCHashCode512.java        
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,60 @@
+ /*
+      This file is part of Freeway
+
+      Freeway 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, or (at your
+      option) any later version.
+
+      Freeway 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 Freeway; see the file COPYING.  If not, write to the
+      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+      Boston, MA 02111-1307, USA.
+ */
+
+package org.gnu.freeway.cwrappers;
+
+import org.gnu.freeway.cwrappers.util.CWrapper;
+import org.gnu.freeway.cwrappers.util.ConstCWrapper;
+import org.gnu.freeway.util.crypto.HashCode512;
+import org.gnu.freeway.util.crypto.HostIdentity;
+import org.gnu.freeway.util.net.PersistentHelper;
+
+/**
+ * @file ConstCHostIdentity.java
+ * @brief 
+ * @author mdonoughe
+ */
+public class ConstCHashCode512 implements ConstCWrapper {
+       
+       private static final int KIND = CWrapper.PTR_KIND;
+       
+       protected HashCode512 value;
+       
+       public ConstCHashCode512(HashCode512 value) {
+               this.value = value;
+       }
+       
+       /* (non-Javadoc)
+        * @see org.gnu.freeway.cwrappers.util.ConstCWrapper#getSerializedSize()
+        */
+       public int getSerializedSize() {
+               return 64;
+       }
+
+       /* (non-Javadoc)
+        * @see 
org.gnu.freeway.cwrappers.util.ConstCWrapper#serializeToByteArray()
+        */
+       public byte[] serializeToByteArray() {
+               return PersistentHelper.toBytes(value);
+       }
+
+       public HashCode512 getValue() {
+               return value;
+       }
+}

Modified: freeway/src/org/gnu/freeway/cwrappers/util/SwitchTableGenerator.java
===================================================================
--- freeway/src/org/gnu/freeway/cwrappers/util/SwitchTableGenerator.java        
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/cwrappers/util/SwitchTableGenerator.java        
2006-08-21 11:53:42 UTC (rev 3286)
@@ -553,6 +553,13 @@
                
                // write every method to the buffer
                for(int i = 0; i < methods.length; i++) {
+                       Iterator temp = null;
+                       String tempStr = null;
+                       for(temp = order.iterator(); temp.hasNext(); tempStr = 
(String) temp.next())
+                               if(methods[i].getName().equals(tempStr))
+                                       break;
+                       if(!methods[i].getName().equals(tempStr))
+                               continue;
                        StringBuffer buffer = new StringBuffer();
                        StringBuffer arrayBuffer = new StringBuffer();
                        buffer.append(" public " + 
cleanClassName(methods[i].getReturnType().getName(), imports) + " " + 
methods[i].getName() + "(");
@@ -644,6 +651,8 @@
        public static int getArgumentType(Class c) {
                if ("void".equals(c.getName()))
                        return CWrapper.VOID_KIND;
+               else if(c.isArray())
+                       return CWrapper.PTR_KIND;
                try {
                        Field f = getPrivateField(c, "KIND");
                        f.setAccessible(true);
@@ -657,14 +666,11 @@
        }
        
        public static Field getPrivateField(Class c, String name) throws 
NoSuchFieldException {
-               if("void".equals(c.getName())) {
-                       System.exit(0);
-               }
                Field[] fields = c.getDeclaredFields();
                for(int i = 0; i < fields.length; i++)
                        if(name.equals(fields[i].getName()))
                                return fields[i];
-               throw new NoSuchFieldException(name);
+               throw new NoSuchFieldException(name + " in " + 
c.getCanonicalName());
        }
        
        private static class SourceFilesFilter implements FilenameFilter {

Added: freeway/src/org/gnu/freeway/protocol/fs/AFSProtocol.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/AFSProtocol.java    2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/AFSProtocol.java    2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,240 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs;
+
+import org.gnu.freeway.protocol.*;
+import org.gnu.freeway.protocol.fs.esed2.*;
+import org.gnu.freeway.server.*;
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.net.*;
+
+import java.util.logging.*;
+
+/**
+ * main functions of the anonymous file sharing service
+ *
+ * AFS CORE. This is the code that is plugged into the GNUnet core to
+ * enable Anonymous File Sharing.
+ * support for ESED2 encoding of files
+ */
+
+public class AFSProtocol extends AbstractProtocol
+{
+       /**
+        * Just the version number of the AFS implementation.
+        * History:
+        *
+        * 1.x.x: initial version with triple hash and merkle tree
+        * 2.x.x: root node with mime-type, filename and version number
+        * 2.1.x: combined CHK/3HASH encoding with 25:1 super-nodes
+        * 2.2.x: with directories
+        * 3.0.x: with namespaces
+        */
+
+       public static final int VERSION =       Utils.makeVersion(3,0,4);
+
+       /** */
+       private BloomFilter2    bloomFilter;
+
+       /** */
+       private FileIndex               fileIndex;
+
+       /** */
+       private Policy                  policy;
+
+       /** */
+       private Handler                 handler;
+
+       /** */
+       private Manager                 manager;
+
+       /** */
+       private Routing                 routing;
+
+       /** */
+       private Migration               migration;
+
+       /** */
+       private QueryManager    queryManager;
+
+
+       public AFSProtocol()
+       {
+               super("AFS");
+               bloomFilter=new BloomFilter2();
+               fileIndex=new FileIndex();
+               policy=null;
+               handler=new Handler();
+               manager=new Manager();
+               routing=new Routing();
+               migration=new Migration(manager);
+               queryManager=new QueryManager();
+       }
+
+       public String toString()
+       {
+               return "AFS";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       /**
+        * Initialize the AFS module. This method name must match
+        * the library name (libgnunet_XXX => initialize_XXX).
+        * @param capi
+        * @return false on errors
+        */
+
+       public boolean init( CoreForProtocol capi )
+       {
+               Prefs   prefs;
+
+               super.init(capi);
+
+               prefs=capi.getApplication().getPreferences();
+               if (prefs.getInt("FS","QUOTA",0)<=0) {
+                       log(Level.SEVERE,"You must specify a postive number for 
the QUOTA in section FS.");
+                       return false;
+                       }
+
+               fileIndex.init(capi);
+
+               policy=new Policy(prefs,capi);
+
+               manager.init(capi);
+               bloomFilter.init(capi);
+               queryManager.init(capi);
+               routing.init(capi);
+               handler.init(capi);
+               migration.init(capi);
+
+               handler.policy=new Policy2(capi);
+               handler.singleBloomFilter=bloomFilter.singleBloomFilter;
+               handler.superBloomFilter=bloomFilter.superBloomFilter;
+               handler.fileIndex=fileIndex;
+               handler.manager=manager;
+               handler.routing=routing;
+
+               routing.policy=policy;
+               routing.manager=manager;
+               routing.queryManager=queryManager;
+               routing.superBloomFilter=bloomFilter.superBloomFilter;
+               routing.singleBloomFilter=bloomFilter.singleBloomFilter;
+               // **** fin beurk
+
+               // p2p handlers
+               addP2PHandler(P2PMessage.IS_QUERY,P2PQuery.class);
+               addP2PHandler(P2PMessage.IS_3HASH_RESULT,P2P3HashResult.class);
+               addP2PHandler(P2PMessage.IS_CHK_RESULT,P2PChkResult.class);
+               addP2PHandler(P2PMessage.IS_NSQUERY,P2PNSQuery.class);
+               
addP2PHandler(P2PMessage.IS_SBLOCK_RESULT,P2PSBlockResult.class);
+
+               // c/s handlers
+               addCSHandler(CSMessage.IS_QUERY,CSQuery.class);
+               addCSHandler(CSMessage.IS_INSERT_CHK,CSInsertChk.class);
+               addCSHandler(CSMessage.IS_INSERT_3HASH,CSInsert3Hash.class);
+               addCSHandler(CSMessage.IS_INDEX_BLOCK,CSIndexBlock.class);
+               addCSHandler(CSMessage.IS_INDEX_FILE,CSIndexFile.class);
+               addCSHandler(CSMessage.IS_INDEX_SUPER,CSIndexSuper.class);
+               addCSHandler(CSMessage.IS_DELETE_CHK,CSDeleteChk.class);
+               addCSHandler(CSMessage.IS_DELETE_3HASH,CSDelete3Hash.class);
+               addCSHandler(CSMessage.IS_UNINDEX_BLOCK,CSUnindexBlock.class);
+               addCSHandler(CSMessage.IS_UNINDEX_FILE,CSUnindexFile.class);
+               addCSHandler(CSMessage.IS_UNINDEX_SUPER,CSUnindexSuper.class);
+               addCSHandler(CSMessage.IS_NSQUERY,CSNSQuery.class);
+               addCSHandler(CSMessage.IS_INSERT_SBLOCK,CSInsertSBlock.class);
+               addCSHandler(CSMessage.IS_UPLOAD_FILE,CSUploadFile.class);
+               addCSHandler(CSMessage.IS_LINK_FILE,CSLinkFile.class);
+               
addCSHandler(CSMessage.IS_GET_AVG_PRIORITY,CSGetAvgPriority.class);
+               return true;
+       }
+
+       public void done()
+       {
+               super.done();
+               bloomFilter.done();
+               migration.done();
+               queryManager.done();
+               routing.done();
+               manager.done();
+               fileIndex.done();
+               policy.doneAnonymityPolicy();
+       }
+
+       protected boolean onCSMessage( CSSession client, CSMessage msg )
+       {
+               if (msg instanceof CSNSQuery) {
+                       return 
handler.csHandleRequestNSQuery(client,(CSNSQuery) msg);
+                       }
+               if (msg instanceof CSInsertSBlock) {
+                       return 
handler.csHandleRequestInsertSBlock(client,(CSInsertSBlock) msg);
+                       }
+               if (msg instanceof CSQuery) {
+                       return handler.csHandleRequestQuery(client,(CSQuery) 
msg);
+                       }
+               if (msg instanceof CSInsertChk) {
+                       return 
handler.csHandleRequestInsertCHK(client,(CSInsertChk) msg);
+                       }
+               if (msg instanceof CSInsert3Hash) {
+                       return 
handler.csHandleRequestInsert3HASH(client,(CSInsert3Hash) msg);
+                       }
+               if (msg instanceof CSIndexBlock) {
+                       return 
handler.csHandleRequestIndexBlock(client,(CSIndexBlock) msg);
+                       }
+               if (msg instanceof CSIndexFile) {
+                       return 
handler.csHandleRequestIndexFile(client,(CSIndexFile) msg);
+                       }
+               if (msg instanceof CSIndexSuper) {
+                       return 
handler.csHandleRequestIndexSuper(client,(CSIndexSuper) msg);
+                       }
+               if (msg instanceof CSDeleteChk) {
+                       return 
handler.csHandleRequestDeleteCHK(client,(CSDeleteChk) msg);
+                       }
+               if (msg instanceof CSDelete3Hash) {
+                       return 
handler.csHandleRequestDelete3HASH(client,(CSDelete3Hash) msg);
+                       }
+               if (msg instanceof CSUnindexBlock) {
+                       return 
handler.csHandleRequestUnindexBlock(client,(CSUnindexBlock) msg);
+                       }
+               if (msg instanceof CSUnindexFile) {
+                       return 
handler.csHandleRequestUnindexFile(client,(CSUnindexFile) msg);
+                       }
+               if (msg instanceof CSUnindexSuper) {
+                       return 
handler.csHandleRequestUnindexSuper(client,(CSUnindexSuper) msg);
+                       }
+               if (msg instanceof CSUploadFile) {
+                       return 
handler.csHandleRequestUploadFile(client,(CSUploadFile) msg);
+                       }
+               if (msg instanceof CSLinkFile) {
+                       return 
handler.csHandleRequestLinkFile(client,(CSLinkFile) msg);
+                       }
+               if (msg instanceof CSGetAvgPriority) {
+                       return 
routing.csHandleRequestAvgPriority(client,(CSGetAvgPriority) msg);
+                       }
+               return false;
+       }
+
+       protected boolean onP2PMessage( HostIdentity sender, P2PMessage msg )
+       {
+               if (msg instanceof P2PNSQuery) {
+                       return handler.handleNSQUERY(sender,msg);
+                       }
+               if (msg instanceof P2PSBlockResult) {
+                       return handler.handleSBLOCK_CONTENT(sender,msg);
+                       }
+               if (msg instanceof P2PQuery) {
+                       return handler.handleQUERY(sender,msg);
+                       }
+               if (msg instanceof P2P3HashResult) {
+                       return handler.handle3HASH_CONTENT(sender,msg);
+                       }
+               if (msg instanceof P2PChkResult) {
+                       return handler.handleCHK_CONTENT(sender,msg);
+                       }
+               return false;
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/BloomFilter2.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/BloomFilter2.java   2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/BloomFilter2.java   2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,107 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs;
+
+import org.gnu.freeway.protocol.fs.esed2.*;
+import org.gnu.freeway.server.*;
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.io.*;
+
+import java.nio.*;
+import java.util.logging.*;
+
+/**
+ * Bloomfilter implementation.
+ */
+
+public class BloomFilter2 extends LoggedObject
+{
+       public static final String      BLOOMFILTER1    =       
"content_bloomfilter";
+       public static final String      BLOOMFILTER2    =       
"keyword_bloomfilter";
+
+       /** Filters. */
+       public BloomFilter      superBloomFilter;
+       public BloomFilter      singleBloomFilter;
+
+
+       public BloomFilter2()
+       {
+               super(true);
+       }
+
+       public String toString()
+       {
+               return "xxx";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public void init( CoreForProtocol capi )
+       {
+               ByteBuffer      buf;
+               String          fn,bf;
+               int                     quota,superbf_size,singlbf_size;
+
+               Statistics              
stats=capi.getApplication().getStatistics();
+               Prefs   prefs=capi.getApplication().getPreferences();
+
+               fn = prefs.getString("AFS","AFSDIR",null);
+               if (fn==null) {
+                       trace("Configuration must specify directory for AFS 
data in section AFS under AFSDIR.");
+                       return;
+                       }
+               new DirLocation(fn).create();
+
+               /* read existing quota, check if it changed */
+               quota = prefs.getInt("AFS","DISKQUOTA",0);
+
+               buf=prefs.getContent("AFS-DISKQUOTA");
+               if (buf==null || buf.capacity()!=4) {
+                       buf=ByteBuffer.allocateDirect(4);
+                       buf.putInt(quota);
+                       prefs.putContent("AFS-DISKQUOTA",buf);
+                       }
+               else if (buf.getInt()!=quota) {
+                       log(Level.SEVERE,"AFS-Quota changed, run gnunet-convert 
!");
+                       return;
+                       }
+               quota = quota * 1024; /* convert to kb */
+               singlbf_size = quota;    /* 8 bit per entry/kb in DB */
+               superbf_size = quota;
+
+               bf=fn+"/"+BLOOMFILTER1;
+               superBloomFilter=BloomFilter.load(stats,bf,superbf_size,5); /* 
approx. 3% false positives at max use */
+
+               bf=fn+"/"+BLOOMFILTER2;
+               singleBloomFilter=BloomFilter.load(stats,bf,singlbf_size,5); /* 
approx. 3% false positives at max use */
+       }
+
+       public void done()
+       {
+               singleBloomFilter.free();
+               superBloomFilter.free();
+       }
+
+       public void bf_deleteEntryCallback( HashCode512 key, ContentIndex ce, 
Object data, int datalen, Object closure )
+       {
+               switch (ce.type) {
+                       case ContentIndex.LOOKUP_TYPE_CHK:
+                       case ContentIndex.LOOKUP_TYPE_3HASH:
+                       case ContentIndex.LOOKUP_TYPE_SBLOCK:
+                               singleBloomFilter.delete(key);
+                               break;
+                       case ContentIndex.LOOKUP_TYPE_SUPER:
+                               superBloomFilter.delete(key);
+                               break;
+                       case ContentIndex.LOOKUP_TYPE_CHKS:
+                               break;
+                       default:
+                               log(Level.WARNING,"Bloom filter notified of 
deletion of unexpected type of content entry: "+ce.type+".");
+                               break;
+                       }
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/DBHandle.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/DBHandle.java       2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/DBHandle.java       2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,170 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs;
+
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.protocol.fs.esed2.*;
+import org.gnu.freeway.util.crypto.*;
+
+/**
+ * Handle for a high-level database (mysql, simple)
+ * mysql wrapper
+ */
+
+public interface DBHandle
+{
+       /**
+        * Open the database.
+        *
+        * @param prefs
+        * @param backend       string used to distinguish multiple backends of 
the same type.
+        * @param name          parameter for naming the database configuration 
(e.g. quota)
+        * @return                      the database handle
+        */
+
+       public boolean open( Prefs prefs, String backend, String name );
+
+       /**
+        * Close the database.
+        */
+
+       public void close();
+
+       /**
+        * Remove the database (entirely!). Also implicitly close database.
+        * Close and delete the database.
+        */
+
+       public void drop();
+
+       /**
+        * Read the contents of a bucket to a buffer.
+        * Read the contents of a block to a buffer. In the case of 3HASH
+        * query, readContent must also return the respective 2HASH in
+        * ce.hash [so that hash(ce.hash)==3HASH].
+        *
+        * @param query the query hash (3HASH o CHK)/the hashcode representing 
the entry
+        * @param ce    what to look for (will be modified on return)/the 
meta-data of the entry (set)
+        * @param prio  the amount to change priority of the entry if found/by 
how much should the priority of the content be changed
+        * @return              the content on success, null on failure
+        */
+
+       public byte[] readContent( HashCode512 query, ContentIndex ce, int prio 
);
+
+       /**
+        * Write content to a file. Check for reduncancy and eventually
+        * append.
+        * Write content to the db.  Overwrites existing data.
+        * If ce.type is LOOKUP_TYPE_3HASH, ce.hash will contain
+        * a double hash which must be converted to 3HASH, later to be
+        * retrievable by 3HASH, but the 2HASH must be stored so it can
+        * be retrieved by readContent(). For indexed content,
+        * ce.fileOffset and ce.fileNameIndex must be stored.
+        * Note that block can be null for on-demand encoded content
+        * (in this case, len must also be 0).
+        * Write content to the db.  Overwrites existing data.
+        * If ce.type is LOOKUP_TYPE_3HASH, ce.hash will contain
+        * a double hash which must be converted to 3HASH, later to be
+        * retrievable by 3HASH, but the 2HASH must be stored so it can
+        * be retrieved by readContent(). For indexed content,
+        * ce.fileOffset and ce.fileNameIndex must be stored.
+        * Note that block can be null for on-demand encoded content
+        * (in this case, len must also be 0).
+        *
+        * @param ce information related to the block to store/the meta-data 
for the entry/the meta-data for the entry
+        * @param block the data to store
+        * @param offset offset
+        * @param length the size of the block
+        * @return false on error, true if ok.
+        */
+
+       public boolean writeContent( ContentIndex ce, byte[] block, int offset, 
int length );
+
+       /**
+        * Get the number of entries in the database.
+        * Get the number of entries in the database.
+        *
+        * @return -1 on error, otherwise the number of entries
+        */
+
+       public int countContentEntries();
+
+       /**
+        * Get the lowest priority of content in the DB.
+        * Get the lowest priority of content in the store.
+        * Get the lowest priority value of all content in the store.
+        *
+        * @return the lowest priority
+        */
+
+       public int getMinimumPriority();
+
+       /**
+        * Call a method for each key in the database and
+        * call the callback method on it.
+        * Call a method for each key in the database and
+        * call the callback method on it.
+        *
+        * @param callback the callback method
+        * @param data second argument to all callback calls
+        * @return the number of items stored in the content database
+        */
+
+       public int forEachEntry( EntryCallback callback, Object data );
+
+       /**
+        * Return a random key from the database (just the key, not the
+        * content!).
+        * Get a random content block from MySQL database.
+        * Tries to use indexes efficiently.
+        *
+        * Code supplied by H. Pagenhardt
+        *
+        * @param ce the meta-data of the random content (set)/output 
information about the key
+        * @return true on success, false on error
+        */
+
+       public boolean getRandomContent( ContentIndex ce );
+
+       /**
+        * Delete low-priority content from the database
+        * Deletes some least important content
+        *
+        * @param count the number of entries to delete/the number of 1kb 
blocks to free
+        * @param callback method to call on each deleted entry/method to call 
on each deleted item
+        * @param closure extra argument to callback
+        * @return true on success, false on error
+        */
+
+       public boolean deleteContent( int count, EntryCallback callback, Object 
closure );
+
+       /**
+        * Estimate how many blocks can be stored in the DB
+        * before the quota is reached.
+        * Estimate how many blocks can be stored in the DB
+        * before the quota is reached.
+        *
+        * NOTE: this function can not be performed relying on
+        * Data_length+Index_length from "SHOW TABLE STATUS" because
+        * those values seem not to be decreasing in real time.
+        * On mysql 4.0.16, Avg_row_len seems to be updating in real
+        * time w.r.t. insertions and deletions.
+        *
+        * @param quota the number of kb available for the DB
+        * @return number of blocks left
+        */
+
+       public int estimateAvailableBlocks( int quota );
+
+       /**
+        * Free space in the database by removing an entry.
+        *
+        * @param name the key of the entry to remove/the query of the entry to 
remove/hashcode for the block to be deleted
+        * @return false on error, true if ok.
+        * Free space in the database by removing one block
+        */
+
+       public boolean unlink( HashCode512 name );
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/EntryCallback.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/EntryCallback.java  2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/EntryCallback.java  2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,33 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs;
+
+import org.gnu.freeway.protocol.fs.esed2.*;
+import org.gnu.freeway.util.crypto.*;
+
+/**
+ *
+ */
+
+public interface EntryCallback
+{
+       /**
+        * Callback function type used by the iterator.  Contains the key,
+        * index information, the block (null if there is no block in the
+        * database), the length of the block and the closure.
+        *
+        * The callback is responsible for freeing data if data is not null.
+        *
+        * Note that the callback function may not perform additional
+        * read, write or delete operations on the database!
+        * @param key
+        * @param ce
+        * @param data
+        * @param dataLen
+        * @param closure
+        */
+
+       public void onEntry( HashCode512 key, ContentIndex ce, Object data, int 
dataLen, Object closure );
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/FileIndex.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/FileIndex.java      2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/FileIndex.java      2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,376 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs;
+
+import org.gnu.freeway.server.*;
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.io.*;
+
+import EDU.oswego.cs.dl.util.concurrent.*;
+
+import java.io.*;
+import java.util.*;
+import java.util.logging.*;
+
+/**
+ * This module is responsible for storing the names
+ * of indexed files.
+ * access to the list of indexed files
+ *
+ * This module is responsible for storing the names
+ * of indexed files. The index for a file is always
+ * >0, since 0 is reserved for "not indexed".
+ */
+
+public class FileIndex extends LoggedObject
+{
+       public static final String      DATABASELIST    =       "database.list";
+
+       /** names of indexed files */
+       private String[]                                indexed_files;
+
+       /** Size of the indexed_files list. */
+       private int                                             
indexed_files_size;
+
+       /** number of files that are indexed */
+       private int                                             
indexed_files_count;
+
+       /** Mutex for synced access to indexed_files */
+       private Sync                                    lock;
+
+       /** stat handle for indexed_files_count */
+       private Stat                            stat_indexed_files_count;
+
+       /** stat handle for total size of indexed files */
+       private Stat                            stat_indexed_files_size;
+
+       /** */
+       private String                                  shared_file_list;
+
+       /** */
+       private Statistics      stats;
+
+       /** */
+       private Prefs   prefs;
+
+
+       public FileIndex()
+       {
+               super(true);
+       }
+
+       public String toString()
+       {
+               return "File index";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       /**
+        * Initialize the fileindex module.
+        * @param capi
+        */
+
+       public void init( CoreForProtocol capi )
+       {
+               stats=capi.getApplication().getStatistics();
+               prefs=capi.getApplication().getPreferences();
+
+               shared_file_list = getSharedFileList();
+               stat_indexed_files_count=stats.getHandle("# indexed files");
+               stat_indexed_files_size= stats.getHandle("# size of indexed 
files");
+               lock=new Mutex();
+               if (!scanDatabaseList()) {
+                       trace("Could not initialize file index module !");
+                       }
+       }
+
+       /**
+        * Shutdown the fileindex module.
+        */
+
+       public void done()
+       {
+               indexed_files=null;
+               shared_file_list=null;
+               lock=null;
+       }
+
+       /**
+        * Get the name of the file where we store the
+        * list of indexed files.
+        * @return
+        */
+
+       protected String getSharedFileList()
+       {
+               String  str;
+
+               str=prefs.getString("AFS","AFSDIR",null);
+               if (str==null) {
+                       log(Level.SEVERE,"Configuration file must specify 
filename for storing AFS data in section AFS under AFSDIR.");
+                       return null;
+                       }
+               new DirLocation(str).create();  // important, the directory may 
not exist yet !
+               return new FileLocation(str+"/"+DATABASELIST).getPath();
+       }
+
+       /**
+        * Scan the list of on-demand shared files to initialize indexed_files
+        * @return OK on success, false on error
+        */
+
+       protected boolean scanDatabaseList()
+       {
+               BufferedReader  handle;
+               String                  str;
+               long                    totalSize;
+               List                    list;
+               FileLocation            f;
+
+               try {
+                       lock.acquire();
+                       try {
+                               indexed_files = null;
+                               indexed_files_count = 0;
+                               indexed_files_size = 0;
+                               stat_indexed_files_count.reset();
+                               stat_indexed_files_size.reset();
+                               totalSize = 0;
+
+                               try {
+                                       handle=new BufferedReader(new 
InputStreamReader(new FileInputStream(shared_file_list)));
+                                       try {
+                                               list=new ArrayList();
+                                               for (str=handle.readLine(); 
str!=null; str=handle.readLine()) {
+                                                       if (str.length()>0) {
+                                                               
indexed_files_count++;
+                                                               list.add(str);
+
+                                                               f=new 
FileLocation(str);
+                                                               totalSize += 
f.getSize();
+                                                               }
+                                                       else {
+                                                               list.add(null);
+                                                               }
+                                                       }
+
+                                               if (indexed_files_count == 0) {
+                                                       return true;
+                                                       }
+
+                                               indexed_files_size = 
list.size();
+                                               indexed_files = (String[]) 
list.toArray(new String[list.size()]);
+                                               }
+                                       finally {
+                                               handle.close();
+                                               }
+                                       }
+                               catch( FileNotFoundException x ) {
+                                       // file not found : not an error
+                                       }
+                               catch( IOException x ) {
+                                       err("Could not open 
"+shared_file_list+" !",x);
+                                       return false;
+                                       }
+
+                               
stat_indexed_files_count.set(indexed_files_count);
+                               stat_indexed_files_size.set(totalSize);
+                               }
+                       finally {
+                               lock.release();
+                               }
+                       }
+               catch( InterruptedException x ) {
+                       err("",x);
+                       return false;
+                       }
+               return true;
+       }
+
+       /**
+        * Get the name of an indexed file.
+        *
+        * @param index the index of the file (must be strictly positive)
+        * @return the filename
+        */
+
+       public String getIndexedFileName( int index )
+       {
+               if (index<=0 || index>indexed_files_size) {
+                       log(Level.WARNING,"getIndexedFileName called with index 
out of bounds ("+index+")");
+                       return null;
+                       }
+
+               try {
+                       lock.acquire();
+                       try {
+                               return indexed_files[index-1];
+                               }
+                       finally {
+                               lock.release();
+                               }
+                       }
+               catch( InterruptedException x ) {
+                       err("",x);
+                       return null;
+                       }
+       }
+
+       /**
+        * Invoke a method on each of the filenames of the indexed files. If
+        * the method returns false, the file is removed from the list of
+        * indexed files!
+        *
+        * @param method the method to invoke for each indexed file
+        * @param data the last argument to method
+        * @return the number of shared files (after changes caused by this 
call)
+        */
+
+       public int forEachIndexedFile( IndexedFileNameCallback method, Object 
data )
+       {
+               PrintWriter     handle;
+               int                     i;
+               boolean         changed,erase;
+
+               try {
+                       lock.acquire();
+                       try {
+                               changed=false;
+                               for (i=0; i<indexed_files_size; i++) {
+                                       if (indexed_files[i] != null) {
+                                               lock.release();
+                                               try {
+                                                       
erase=!method.onFile(indexed_files[i],i+1,data);
+                                                       }
+                                               finally {
+                                                       lock.acquire();
+                                                       }
+
+                                               if (erase) {
+                                                       indexed_files[i]=null;
+                                                       changed=true;
+                                                       }
+                                               }
+                                       }
+
+                               if (changed) {
+                                       /* write changed list to the drive */
+
+                                       try {
+                                               handle = new PrintWriter(new 
OutputStreamWriter(new FileOutputStream(shared_file_list)),true);
+                                               try {
+                                                       for (i=0; 
i<indexed_files_size; i++) {
+                                                               if 
(indexed_files[i]!=null) {
+                                                                       
handle.println(indexed_files[i]);
+                                                                       }
+                                                               else {
+                                                                       
handle.println();
+                                                                       }
+                                                               }
+                                                       }
+                                               finally {
+                                                       handle.close();
+                                                       }
+                                               }
+                                       catch( IOException x ) {
+                                               err("List "+shared_file_list+" 
of directly shared filenames not available !",x);
+                                               return -1;
+                                               }
+                                       }
+                               }
+                       finally {
+                               lock.release();
+                               }
+                       }
+               catch( InterruptedException x ) {
+                       err("",x);
+                       return -1;
+                       }
+               return indexed_files_count;
+       }
+
+       /**
+        * Add a name to the list of filenames.
+        *
+        * @param filename the name of the file to add
+        * @return the index of that file in the list in [1,65536], -1 on 
error. <em>NEVER</em> returns 0.
+        */
+
+       public int appendFilename( String filename )
+       {
+               BufferedReader  handle;
+               PrintWriter     out;
+               String          str;
+               int                     pos;
+
+               assert(filename!=null);
+
+               try {
+                       lock.acquire();
+                       try {
+                               pos=0;
+                               filename=new FileLocation(filename).getPath();
+
+                               try {
+                                       handle=new BufferedReader(new 
InputStreamReader(new FileInputStream(shared_file_list)));
+                                       try {
+                                               for (str=handle.readLine(); 
str!=null; str=handle.readLine()) {
+                                                       pos++;
+                                                       if 
(str.equals(filename)) {
+                                                               debug("File 
already at index "+pos+".");
+                                                               return pos;     
// already there !
+                                                               }
+                                                       }
+                                               }
+                                       finally {
+                                               handle.close();
+                                               }
+                                       }
+                               catch( FileNotFoundException x ) {
+                                       // file not found : not an error
+                                       }
+                               catch( IOException x ) {
+                                       err("List "+shared_file_list+" of 
directly shared filenames not available !",x);
+                                       return -1;
+                                       }
+
+                               if (pos > 0xFFFF) {
+                                       log(Level.WARNING,"Too many files 
indexed (limit is 65535).");
+                                       return -1;
+                                       }
+
+                               // not there, append
+                               try {
+                                       out = new PrintWriter(new 
OutputStreamWriter(new FileOutputStream(shared_file_list,true)),true);
+                                       try {
+                                               out.println(filename);
+                                               }
+                                       finally {
+                                               out.close();
+                                               }
+                                       }
+                               catch( IOException x ) {
+                                       err("List "+shared_file_list+" of 
directly shared filenames not available !",x);
+                                       return -1;
+                                       }
+                               }
+                       finally {
+                               lock.release();
+                               }
+                       }
+               catch( InterruptedException x ) {
+                       err("",x);
+                       return -1;
+                       }
+
+               scanDatabaseList();
+
+               debug("Added file to index at position "+pos+".");
+
+               return pos; /* return index */
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/Handler.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/Handler.java        2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/Handler.java        2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,897 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs;
+
+import org.gnu.freeway.protocol.fs.esed2.*;
+import org.gnu.freeway.server.*;
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.io.*;
+import org.gnu.freeway.util.net.*;
+
+import java.util.logging.*;
+
+/**
+ * Handlers for incoming AFS requests (p2p and CS).
+ * Handlers for AFS related messages (CS and p2p).
+ */
+
+public class Handler extends LoggedObject implements AFSConstants
+{
+       private CoreForProtocol coreAPI;
+
+       private Prefs   prefs;
+
+       private Stat    stat_p2p_query_count;
+       private Stat    stat_p2p_superquery_count;
+       private Stat    stat_p2p_chk_replies;
+       private Stat    stat_p2p_3hash_replies;
+       private Stat    stat_cs_query_count;
+       private Stat    stat_cs_insert_chk_count;
+       private Stat    stat_cs_insert_3hash_count;
+       private Stat    stat_cs_index_block_count;
+       private Stat    stat_cs_index_file_count;
+       private Stat    stat_cs_index_super_count;
+       private Stat    stat_cs_delete_chk_count;
+       private Stat    stat_cs_delete_3hash_count;
+       private Stat    stat_cs_unindex_block_count;
+       private Stat    stat_cs_unindex_file_count;
+       private Stat    stat_cs_unindex_super_count;
+       private Stat    stat_cs_upload_file_count;
+       private Stat    stat_cs_insert_sblock_count;
+       private Stat    stat_cs_nsquery_count;
+       private Stat    stat_p2p_nsquery_count;
+       private Stat    stat_p2p_sblock_replies;
+
+       public Policy2          policy;
+       public BloomFilter      singleBloomFilter;
+       public BloomFilter      superBloomFilter;
+       public FileIndex                fileIndex;
+       public Manager          manager;
+       public Routing          routing;
+
+
+       public Handler()
+       {
+               super(true);
+       }
+
+       public String toString()
+       {
+               return "xxx";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       /**
+        * Initialize the handler module. Registers counters
+        * with the statistics module.
+        * @param capi
+        */
+
+       public void init( CoreForProtocol capi )
+       {
+               Statistics      stats;
+
+               coreAPI=capi;
+
+               prefs=capi.getApplication().getPreferences();
+
+               stats=capi.getApplication().getStatistics();
+               stat_p2p_query_count=stats.getHandle("# p2p queries received");
+               stat_p2p_superquery_count=stats.getHandle("# p2p super queries 
received");
+               stat_p2p_chk_replies=stats.getHandle("# p2p CHK content 
received (kb)");
+               stat_p2p_3hash_replies=stats.getHandle("# p2p search results 
received (kb)");
+               stat_cs_query_count=stats.getHandle("# client queries 
received",Stat.VERBOSE);
+               stat_cs_insert_chk_count=stats.getHandle("# client CHK content 
inserted (kb)",Stat.VERBOSE);
+               stat_cs_insert_3hash_count=stats.getHandle("# client 3HASH 
search results inserted (kb)",Stat.VERBOSE);
+               stat_cs_index_block_count=stats.getHandle("# client file index 
requests received",Stat.VERBOSE);
+               stat_cs_index_file_count=stats.getHandle("# file index requests 
received",Stat.VERBOSE);
+               stat_cs_index_super_count=stats.getHandle("# super query index 
requests received",Stat.VERBOSE);
+               stat_cs_delete_chk_count=stats.getHandle("# client CHK content 
deleted (kb)",Stat.VERBOSE);
+               stat_cs_delete_3hash_count=stats.getHandle("# client 3HASH 
search results deleted (kb)",Stat.VERBOSE);
+               stat_cs_unindex_block_count=stats.getHandle("# client file 
unindex requests received",Stat.VERBOSE);
+               stat_cs_unindex_file_count=stats.getHandle("# file unindex 
requests received",Stat.VERBOSE);
+               stat_cs_unindex_super_count=stats.getHandle("# super query 
unindex requests received",Stat.VERBOSE);
+               stat_cs_insert_sblock_count=stats.getHandle("# client SBlock 
insert requests received",Stat.VERBOSE);
+               stat_cs_nsquery_count=stats.getHandle("# client namespace 
queries received",Stat.VERBOSE);
+               stat_cs_upload_file_count=stats.getHandle("# client file upload 
requests",Stat.VERBOSE);
+               stat_p2p_nsquery_count=stats.getHandle("# p2p namespace queries 
received");
+               stat_p2p_sblock_replies=stats.getHandle("# p2p SBlocks 
received");
+       }
+
+       /**
+        * Handle query for content. Depending on how we like the sender,
+        * lookup, forward or even indirect.
+        * @param sender
+        * @param msg
+        * @return
+        */
+
+       public boolean handleQUERY( HostIdentity sender, P2PMessage msg )
+       {
+               int                             qp;
+               P2PQuery        qmsg;
+               int                             queries;
+               int                             ttl;
+               int                             prio;
+               double                  preference;
+
+               queries = (msg.getByteSize() - P2PQuery.SIZE) / 
HashCode512.SIZE;       //todo: AFS_p2p_QUERY.getQueriesCount()
+               if (queries<=0 || (msg.getByteSize()!=P2PQuery.SIZE + queries * 
HashCode512.SIZE) ) {
+                       log(Level.WARNING,"Query received was malformed.");
+                       return false;
+                       }
+               if (queries>1)
+                       stat_p2p_superquery_count.inc();
+               stat_p2p_query_count.inc();
+               qmsg = (P2PQuery) msg;
+
+               debug("Received query "+qmsg.getQuery(0).toHex()+" 
("+queries+") TTL "+qmsg.getTTL()+" PR "+qmsg.getPriority()+" from 
"+sender.getName()+".");
+
+               /* decrement ttl (always) */
+               ttl = qmsg.getTTL();
+               debug("Received query for "+qmsg.getQuery(0).toHex()+" with ttl 
"+ttl+".");
+
+               if (ttl < 0) {
+                       ttl = (int) (ttl - 2*TTL_DECREMENT - 
Crypto.nextLong(TTL_DECREMENT));
+                       if (ttl > 0)
+                               return true; /* just abort */
+               } else
+                       ttl = (int) (ttl - 2*TTL_DECREMENT - 
Crypto.nextLong(TTL_DECREMENT));
+               qp = policy.evaluateQuery(sender,qmsg.getPriority());
+               if ((qp & Policy2.QUERY_DROPMASK) == 0)
+                       return true; /* straight drop. */
+
+               preference=qp & Policy2.QUERY_PRIORITY_BITMASK;
+               if (preference < Policy2.QUERY_BANDWIDTH_VALUE)
+                       preference = Policy2.QUERY_BANDWIDTH_VALUE;
+               coreAPI.preferTrafficFrom(sender,
+                               preference);
+
+               /* adjust priority */
+               prio = qmsg.getPriority();
+               if ( (qp & Policy2.QUERY_PRIORITY_BITMASK) < prio) {
+                       prio = qp & Policy2.QUERY_PRIORITY_BITMASK;
+                       qmsg.setPriorityAndTTL(prio,qmsg.getTTL());
+               }
+               prio = prio / queries; /* effective priority for ttl */
+
+               /* adjust TTL */
+               if ( (ttl > 0) &&
+                               (ttl > (prio+3)*TTL_DECREMENT) )
+                       ttl = (int) ((prio+3)*TTL_DECREMENT); /* bound! */
+               qmsg.setPriorityAndTTL(qmsg.getPriority(),ttl);
+
+               routing.execQuery(qp, qmsg, null);
+               return true;
+       }
+
+       /**
+        * Receive content, do something with it!  There are 3 basic
+        * possiblilities. Either our node did the request and we should send
+        * the result to a client via TCP, or the content was requested by
+        * another node and we forwarded the request (and thus we now have to
+        * fwd the reply) or 3rd somebody just send us some content we did NOT
+        * ask for - and we can choose to store it or just discard it.
+        * @param sender
+        * @param msg
+        * @return
+        */
+
+       public boolean handleCHK_CONTENT( HostIdentity sender, P2PMessage msg )
+       {
+               int                                     prio;
+               HashCode512                     queryHash;
+               ContentIndex            ce=new ContentIndex();
+               P2PChkResult    cmsg;
+               boolean                         ret;
+               double                          preference;
+               boolean[]               dupe=new boolean[1];
+
+               if (msg.getByteSize()!=P2PChkResult.SIZE) {
+                       log(Level.WARNING,"WARNING: CHK content message 
received was malformed");
+                       return false;
+                       }
+               stat_p2p_chk_replies.inc();
+               cmsg = (P2PChkResult) msg;
+               
queryHash=HashCode512.create(PersistentHelper.toBytes(cmsg.result));
+               prio = routing.useContent(sender,queryHash,msg);
+               if (sender == null) /* no migration, this is already content 
from the local node */
+                       return true;
+               preference = prio;
+               prio = policy.evaluateContent(queryHash,prio);
+               if (prio != -1)
+                       preference+=prio;
+               if (preference < Policy2.CONTENT_BANDWIDTH_VALUE)
+                       preference = Policy2.CONTENT_BANDWIDTH_VALUE;
+               coreAPI.preferTrafficFrom(sender,preference);
+
+               if (prio == -1)
+                       return true; /* straight drop */
+               ce.hash=(HashCode512) PersistentHelper.copy(queryHash);         
//todo: copie utile ?
+               ce.importance    = prio;
+               ce.type          = ContentIndex.LOOKUP_TYPE_CHK;
+               ce.fileNameIndex = 0;
+               ce.fileOffset    = 0;
+               ret = manager.insertContent(ce,
+                               ContentBlock.SIZE,
+                               cmsg.result,
+                               sender,
+                               dupe);
+               if (ret && !dupe[0])
+                       singleBloomFilter.add(queryHash);
+               return true;
+       }
+
+       /**
+        * Receive content, do something with it!  There are 3 basic
+        * possiblilities. Either our node did the request and we should send
+        * the result to a client via TCP, or the content was requested by
+        * another node and we forwarded the request (and thus we now have to
+        * fwd the reply) or 3rd somebody just send us some content we did NOT
+        * ask for - and we can choose to store it or just discard it.
+        * @param sender
+        * @param msg
+        * @return
+        */
+
+       public boolean handle3HASH_CONTENT( HostIdentity sender, P2PMessage msg 
)
+       {
+               int                                             prio;
+               P2P3HashResult  cmsg;
+               HashCode512                             tripleHash;
+               ContentIndex                    ce=new ContentIndex();
+               boolean                                 ret;
+               double                                  preference;
+               boolean[]               dupe=new boolean[1];
+
+               if (msg.getByteSize() != P2P3HashResult.SIZE) {
+                       log(Level.WARNING,"WARNING: content message received 
was malformed");
+                       return false;
+                       }
+               stat_p2p_3hash_replies.inc();
+               cmsg = (P2P3HashResult) msg;
+               tripleHash=cmsg.getTripleHash();
+
+               debug("DEBUG: received 3HASH search result for 
"+tripleHash.toHex()+" from peer");
+
+               prio = routing.useContent(sender,tripleHash,msg);
+               if (sender == null) { /* no migration, this is already content 
from the local node */
+                       //fixme: how is it possible to have sender == null ???
+                       debug("Content migration not needed, content is 
local.");
+                       return true;
+                       }
+               preference = prio;
+
+               debug("Content migration with preference "+prio);
+
+               prio = policy.evaluateContent(tripleHash,prio);
+               if (prio != -1)
+                       preference += prio;
+               if (preference < Policy2.CONTENT_BANDWIDTH_VALUE)
+                       preference = Policy2.CONTENT_BANDWIDTH_VALUE;
+               coreAPI.preferTrafficFrom(sender,preference);
+
+               if (prio == -1) {
+                       debug("DEBUG: content not important enough, not 
replicated");
+                       return true; /* straight drop */
+                       }
+               debug("DEBUG: content replicated with total preference "+prio);
+
+               ce.hash=cmsg.getDoubleHash();
+               ce.importance    = prio;
+               ce.type          = ContentIndex.LOOKUP_TYPE_3HASH;
+               ce.fileNameIndex = 0;
+               ce.fileOffset    = 0;
+
+               ret = 
manager.insertContent(ce,ContentBlock.SIZE,cmsg.getResult(),sender,dupe);
+               if (ret && !dupe[0])
+                       singleBloomFilter.add(tripleHash);
+               return true;
+       }
+
+       /**
+        * Process a query from the client. Forwards to the network.
+        * @param sock
+        * @param queryRequest
+        * @return false if the TCP connection should be closed, otherwise true
+        */
+
+       public boolean csHandleRequestQuery( CSSession sock, CSQuery 
queryRequest )
+       {
+               int                             qp = 
Policy2.QUERY_ANSWER|Policy2.QUERY_FORWARD|Policy2.QUERY_INDIRECT|Policy2.QUERY_PRIORITY_BITMASK;
+               P2PQuery        msg;
+               int                             queries;
+               int                             ttl;
+               boolean                 ret;
+
+               queries = (queryRequest.getByteSize() - CSQuery.SIZE) / 
HashCode512.SIZE;
+               if ( (queries <= 0) || (queryRequest.getByteSize() != 
CSQuery.SIZE + queries * HashCode512.SIZE) ) {
+                       log(Level.WARNING,"Received malformed query from 
client.");
+                       return false;
+                       }
+               stat_cs_query_count.inc();
+
+               debug("Received "+queries+" queries 
("+queryRequest.getQuery(0).toHex()+") with ttl "+queryRequest.getTTL()+" and 
priority "+queryRequest.getPriority()+".");
+
+               msg = new P2PQuery(coreAPI.getIdentity());
+               msg.setQueries(queryRequest.getQueries());
+               /* adjust TTL */
+               ttl = queryRequest.getTTL();
+               if ( (ttl > 0) && (ttl > (msg.getPriority()+8)*TTL_DECREMENT) )
+                       ttl = (int) ((msg.getPriority()+8)*TTL_DECREMENT); /* 
bound! */
+
+               msg.setPriorityAndTTL(queryRequest.getPriority(),ttl);
+
+               ret = routing.execQuery(qp, msg, sock);
+               debug("Executed "+queries+" queries with result "+ret+".");
+               return sock.send(new CSResult(ret));
+       }
+
+       /**
+        * Process a request to insert content from the client.
+        * @param sock
+        * @param insertRequest
+        * @return false if the TCP connection should be closed, otherwise true
+        */
+
+       public boolean csHandleRequestInsertCHK( CSSession sock, CSInsertChk 
insertRequest )
+       {
+               ContentIndex    entry=new ContentIndex();
+               boolean                 ret;
+               boolean[]               dupe=new boolean[1];
+
+               stat_cs_insert_chk_count.inc();
+
+               
entry.hash=HashCode512.create(PersistentHelper.toBytes(insertRequest.getContent()));
+
+               debug("DEBUG: received CHK insert request for block 
"+entry.hash.toHex());
+
+               entry.type= ContentIndex.LOOKUP_TYPE_CHK;
+               entry.importance= insertRequest.getImportance();
+               entry.fileNameIndex= 0; /* database */
+               entry.fileOffset = 0; /* data/content */
+
+               ret = 
manager.insertContent(entry,ContentBlock.SIZE,insertRequest.getContent(),null,dupe);
+               if (ret && !dupe[0])
+                       singleBloomFilter.add(entry.hash);
+               return sock.send(new CSResult(ret));
+       }
+
+       /**
+        * Process a request to insert content from the client.
+        * @param sock
+        * @param insertRequest
+        * @return false if the TCP connection should be closed, otherwise true
+        */
+
+       public boolean csHandleRequestInsert3HASH( CSSession sock, 
CSInsert3Hash insertRequest )
+       {
+               ContentIndex    entry=new ContentIndex();
+               HashCode512             tripleHash;
+               boolean                 ret;
+               boolean[]               dupe=new boolean[1];
+
+               stat_cs_insert_3hash_count.inc();
+
+               entry.hash=(HashCode512) 
PersistentHelper.copy(insertRequest.getDoubleHash());  //todo: copie utile ?
+               
tripleHash=HashCode512.create(PersistentHelper.toBytes(insertRequest.getDoubleHash()));
+
+               debug("Received 3HASH insert request for "+tripleHash.toHex()+" 
from client");
+
+               entry.type= ContentIndex.LOOKUP_TYPE_3HASH;
+               entry.importance= insertRequest.getImportance();
+               entry.fileNameIndex= 0; /* database */
+               entry.fileOffset = 0; /* data/content */
+               ret = 
manager.insertContent(entry,ContentBlock.SIZE,insertRequest.getContent(),null,dupe);
+               if (ret && !dupe[0]) {
+                       singleBloomFilter.add(tripleHash);
+                       }
+               return sock.send(new CSResult(ret));
+       }
+
+       /**
+        * Process a request to index content from the client.
+        * @param sock
+        * @param indexingRequest
+        * @return false if the TCP connection should be closed, otherwise true
+        */
+
+       public boolean csHandleRequestIndexBlock( CSSession sock, CSIndexBlock 
indexingRequest )
+       {
+               boolean[]               dupe=new boolean[1];
+
+               debug("Indexing content 
"+indexingRequest.getContentIndex().hash.toHex()+" at offset 
"+indexingRequest.getContentIndex().fileOffset);
+
+               stat_cs_index_block_count.inc();
+               return sock.send(new 
CSResult(manager.insertContent(indexingRequest.getContentIndex(),0,(byte[]) 
null,null,dupe)));
+       }
+
+       /**
+        * Process a query to list a file as on-demand encoded from the client.
+        * @param sock
+        * @param listFileRequest
+        *
+        * @return false if the TCP connection should be closed, otherwise true
+        */
+
+       public boolean csHandleRequestIndexFile( CSSession sock, CSIndexFile 
listFileRequest )
+       {
+               DirLocation     dir;
+               String          str;
+               long                    quota,usage;
+               int                     ret;
+
+               stat_cs_index_file_count.inc();
+
+               str = prefs.getString("AFS","INDEX-DIRECTORY",null);
+               if (str == null) {
+                       log(Level.WARNING,"Rejecting content-unindex request, 
INDEX-DIRECTORY option not set !");
+                       return sock.send(CSResult.ERR);
+                       }
+
+               dir=new DirLocation(str);
+               dir.create();
+
+               quota=prefs.getInt("AFS","INDEX-QUOTA",0) * 1024 * 1024;
+               if (quota != 0) {
+                       usage=dir.getSizeWithoutLinks();
+                       if (usage + listFileRequest.filesize > quota) {
+                               log(Level.WARNING,"Rejecting file index 
request, quota exeeded: "+(usage / 1024 / 1024)+" of "+(quota / 1024 / 1024)+" 
(MB)");
+                               return sock.send(CSResult.ERR);
+                               }
+                       }
+
+               str=dir.getPath()+"/"+listFileRequest.hash.toHex();
+               ret = fileIndex.appendFilename(str);
+               if (ret == 0)
+                       ret = -1;
+               return sock.send(new CSResult(ret));
+       }
+
+       /**
+        * Process a client request to upload a file (indexing).
+        * @param sock
+        * @param uploadRequest
+        * @return
+        */
+
+       public boolean csHandleRequestUploadFile( CSSession sock, CSUploadFile 
uploadRequest )
+       {
+               DirLocation     prefix;
+               MappedFile      fd;
+               FileLocation    filename;
+               String          str;
+
+               stat_cs_upload_file_count.inc();
+
+               str=prefs.getString("AFS","INDEX-DIRECTORY",null);
+               if (str==null) {
+                       log(Level.WARNING,"WARNING: rejecting content-upload 
request, INDEX-DIRECTORY option not set!");
+                       return sock.send(CSResult.ERR);
+                       }
+
+               prefix=new DirLocation(str);
+               prefix.create();
+
+               filename=prefix.getFile(uploadRequest.hash.toHex());
+               filename.create();
+
+               fd=filename.open();
+               if (fd==null) {
+                       log(Level.SEVERE,"ERROR: OPEN() failed on "+filename);
+                       return sock.send(CSResult.ERR);
+                       }
+
+               try {
+                       fd.seek(uploadRequest.pos);
+                       if (!fd.writeBytes(uploadRequest.getChunk())) {
+                               return sock.send(CSResult.ERR);
+                               }
+                       }
+               finally {
+                       fd.close();
+                       }
+               return sock.send(new CSResult(uploadRequest.getChunk().length));
+       }
+
+       /**
+        * Process a client request to extend our super-query bloom
+        * filter.
+        * @param sock
+        * @param superIndexRequest
+        * @return
+        */
+
+       public boolean csHandleRequestIndexSuper( CSSession sock, CSIndexSuper 
superIndexRequest )
+       {
+               ContentIndex entry;
+               boolean[]               dupe=new boolean[1];
+
+               stat_cs_index_super_count.inc();
+
+               superBloomFilter.add(superIndexRequest.getSuperHash());
+
+               entry=new ContentIndex();
+               entry.type= ContentIndex.LOOKUP_TYPE_SUPER;
+               entry.importance= superIndexRequest.getImportance();
+               entry.fileNameIndex     = 0; /* database */
+               entry.fileOffset = 0; /* data/content */
+               entry.hash=(HashCode512) 
PersistentHelper.copy(superIndexRequest.getSuperHash());       //todo: copie 
utile ???
+               return sock.send(new 
CSResult(manager.insertContent(entry,0,(byte[]) null,null,dupe)));
+       }
+
+       /**
+        * Process a request from the client to delete content.
+        * @param sock
+        * @param insertRequest
+        * @return false if the TCP connection should be closed, otherwise true
+        */
+
+       public boolean csHandleRequestDeleteCHK( CSSession sock, CSDeleteChk 
insertRequest )
+       {
+               HashCode512     hc;
+               boolean         ret;
+
+               stat_cs_delete_chk_count.inc();
+
+               
hc=HashCode512.create(PersistentHelper.toBytes(insertRequest.content));
+
+               debug("DEBUG: received CHK remove request for block 
"+hc.toHex());
+
+               ret = manager.removeContent(hc,-1);
+               if (ret)
+                       if (singleBloomFilter.test(hc))
+                               singleBloomFilter.delete(hc);
+               return sock.send(new CSResult(ret));
+       }
+
+       /**
+        * Process a request from the client to delete content.
+        * @param sock
+        * @param insertRequest
+        * @return false if the TCP connection should be closed, otherwise true
+        */
+
+       public boolean csHandleRequestDelete3HASH( CSSession sock, 
CSDelete3Hash insertRequest )
+       {
+               HashCode512     tripleHash;
+               boolean         ret;
+
+               stat_cs_delete_3hash_count.inc();
+
+               
tripleHash=HashCode512.create(PersistentHelper.toBytes(insertRequest.doubleHash));
+
+               debug("DEBUG: received 3HASH delete request for 
"+tripleHash.toHex()+" from client");
+
+               ret = manager.removeContent(tripleHash,-1);
+               if (ret)
+                       singleBloomFilter.delete(tripleHash);
+
+               return sock.send(new CSResult(ret));
+       }
+
+       /**
+        * Process a request from the client to unindex content.
+        * @param sock
+        * @param indexingRequest
+        * @return false if the TCP connection should be closed, otherwise true
+        */
+
+       public boolean csHandleRequestUnindexBlock( CSSession sock, 
CSUnindexBlock indexingRequest )
+       {
+               stat_cs_unindex_block_count.inc();
+               return sock.send(new 
CSResult(manager.removeContent(indexingRequest.contentIndex.hash,-1)));
+       }
+
+       /**
+        * Callback used to select the file in the fileindex
+        * that is to be removed.
+        * @param fn
+        * @param i
+        * @param search
+        * @return
+        */
+
+       protected boolean removeMatch( String fn, int i, String search )
+       {
+               return !fn.equals(search);
+       }
+
+       /**
+        * Process a query from the client to remove an on-demand encoded file.
+        * n.b. This function just zeroes the correct row in the list of
+        * on-demand encoded files, if match (deletion is done by 
forEachIndexedFile).
+        * The index of the filename that was removed is returned to the client.
+        *
+        * FIXME: It lookslike if listFileRequest.filename was NOT in 
database.list,
+        * it gets appended to it, removed from it, and client gets a false idx.
+        * This unnecessarily bloats the database.list by one empty line.
+        * @param sock
+        * @param listFileRequest
+        *
+        * @return false if the TCP connection should be closed, otherwise true
+        */
+
+       public boolean csHandleRequestUnindexFile( CSSession sock, 
CSUnindexFile listFileRequest )
+       {
+               DirLocation     dir;
+               String          str;
+               int                     idx;
+
+               stat_cs_unindex_file_count.inc();
+
+               str=prefs.getString("AFS","INDEX-DIRECTORY",null);
+               if (str==null) {
+                       log(Level.WARNING,"WARNING: rejecting content-unindex 
request, INDEX-DIRECTORY option not set!");
+                       return sock.send(CSResult.ERR);
+                       }
+
+               dir=new DirLocation(str);
+               dir.create();
+
+               str=dir.getPath()+"/"+listFileRequest.hash.toHex();
+               idx=fileIndex.appendFilename(str);
+               if (idx==-1) {
+                       return sock.send(CSResult.ERR);
+                       }
+
+               assert(idx!=0) : "Index can't be null !";
+
+               fileIndex.forEachIndexedFile(new IndexedFileNameCallback() {
+                       public boolean onFile( String fn, int idxx, Object data 
)
+                       {
+                               return removeMatch(fn,idxx,(String) data);
+                       }
+                       },str);
+
+               if (!new FileLocation(str).delete()) {
+                       log(Level.WARNING,"Could not remove indexed file");
+                       idx = -1; /* remove failed!? */
+                       }
+               return sock.send(new CSResult(idx));
+       }
+
+       /**
+        * @param sock
+        * @param linkFileRequest
+        * @return false if the TCP connection should be closed, otherwise true
+        */
+
+       public boolean csHandleRequestLinkFile( CSSession sock, CSLinkFile 
linkFileRequest )
+       {
+               String          filename,tname;
+               DirLocation     prefix;
+               HashCode512     hc;
+               FileLocation    loc;
+               LinkLocation    f;
+
+               /* stat_cs_link_file_count.inc(); */    //todo: statut ???
+
+               tname=linkFileRequest.getPath();
+               loc=new FileLocation(tname);
+               hc=loc.getHash();
+
+               if (hc==null || !hc.equals(linkFileRequest.hash)) {
+                       log(Level.WARNING,"WARNING: file link request "+tname+" 
from client pointed to file with the wrong data !");
+                       return sock.send(CSResult.ERR);
+                       }
+
+               filename=prefs.getString("AFS","INDEX-DIRECTORY",null);
+               if (filename==null) {
+                       log(Level.WARNING,"WARNING: rejecting content-unindex 
request, INDEX-DIRECTORY option not set!");
+                       return sock.send(CSResult.ERR);
+                       }
+
+               prefix=new DirLocation(filename);
+               prefix.create();
+
+               filename=prefix.getPath()+"/"+linkFileRequest.hash.toHex();
+
+               f=new LinkLocation(filename);
+               if (f.setTarget(new FileLocation(tname))) {
+                       if (f.create()) {
+                               return sock.send(CSResult.OKAY);
+                               }
+                       }
+
+               log(Level.WARNING,"Could not create link from \""+tname+"\" to 
\""+f.getLabel()+"\".");
+               return sock.send(CSResult.ERR);
+       }
+
+       /**
+        * Process a client request to limit our super-query bloom
+        * filter.
+        * @param sock
+        * @param superIndexRequest
+        * @return
+        */
+
+       public boolean csHandleRequestUnindexSuper( CSSession sock, 
CSUnindexSuper superIndexRequest )
+       {
+               stat_cs_unindex_super_count.inc();
+
+               superBloomFilter.delete(superIndexRequest.superHash);
+               return sock.send(new 
CSResult(manager.removeContent(superIndexRequest.superHash,-1)));
+       }
+
+       /**
+        * @param sock
+        * @param insertRequest
+        * @return
+        *
+        */
+
+       public boolean csHandleRequestInsertSBlock( CSSession sock, 
CSInsertSBlock insertRequest )
+       {
+               ContentIndex    entry;
+               HashCode512             ns;
+               boolean                 ret;
+               boolean[]               dupe=new boolean[1];
+
+               stat_cs_insert_sblock_count.inc();
+
+               ns=insertRequest.getContent().getNameSpace();
+               debug("Received SBlock for namespace "+ns.toHex()+" with 
routing ID "+insertRequest.getContent().getIdentifier().toHex()+".");
+
+               entry=new ContentIndex();
+               entry.type= ContentIndex.LOOKUP_TYPE_SBLOCK;
+               entry.importance= insertRequest.getImportance();
+               entry.fileNameIndex= 0; /* database */
+               entry.fileOffset = 0; /* data/content */
+               entry.hash=(HashCode512) 
PersistentHelper.copy(insertRequest.getContent().getIdentifier());     //todo: 
copie utile ?
+               dupe[0] = false;
+               ret = 
manager.insertContent(entry,ContentBlock.SIZE,insertRequest.getContent(),null,dupe);
+
+               debug("Received SBlock insert is dupe: "+dupe+" (insert 
"+ret+")");
+
+               if (ret && !dupe[0]) {
+                       
singleBloomFilter.add(insertRequest.getContent().getIdentifier());
+                       }
+               return sock.send(new CSResult(ret));
+       }
+
+       /**
+        * @param sock
+        * @param queryRequest
+        * @return
+        *
+        */
+
+       public boolean csHandleRequestNSQuery( CSSession sock, CSNSQuery 
queryRequest )
+       {
+               int                             qp = 
Policy2.QUERY_ANSWER|Policy2.QUERY_FORWARD|Policy2.QUERY_INDIRECT|Policy2.QUERY_PRIORITY_BITMASK;
+               P2PNSQuery      msg;
+
+               stat_cs_nsquery_count.inc();
+
+               debug("Received NS query 
("+queryRequest.namespace.toHex()+"/"+queryRequest.identifier.toHex()+") with 
ttl "+queryRequest.ttl+" and priority "+queryRequest.priority+".");
+
+               msg = new P2PNSQuery(coreAPI.getIdentity());
+               msg.setPriorityAndTTL(queryRequest.priority,queryRequest.ttl);
+               msg.identifier=(HashCode512) 
PersistentHelper.copy(queryRequest.identifier);    //todo: copie utile ???
+               msg.namespace=(HashCode512) 
PersistentHelper.copy(queryRequest.namespace);      //todo: copie utile ???
+               routing.execQuery(qp, msg, sock);
+               return true;
+       }
+
+       public boolean handleNSQUERY( HostIdentity sender, P2PMessage msg )
+       {
+               int                             qp;
+               P2PNSQuery      qmsg;
+               int ttl;
+               int prio;
+               double preference;
+
+               if (msg.getByteSize() != P2PNSQuery.SIZEX) {
+                       log(Level.WARNING,"WARNING: nsquery received was 
malformed");
+                       return false;
+                       }
+               stat_p2p_nsquery_count.inc();
+               qmsg = (P2PNSQuery) msg;
+               /* decrement ttl */
+               ttl = qmsg.getTTL();
+
+               debug("DEBUG: received NS query for "+qmsg.identifier.toHex()+" 
with ttl "+ttl);
+
+               if (ttl < 0) {
+                       ttl = (int) (ttl - 2*TTL_DECREMENT - 
Crypto.nextLong(TTL_DECREMENT));
+                       if (ttl > 0)
+                               return true; /* just abort */
+                       }
+               else
+                       ttl = (int) (ttl - 2*TTL_DECREMENT - 
Crypto.nextLong(TTL_DECREMENT));
+
+               qp = policy.evaluateQuery(sender,qmsg.getPriority());
+               if ((qp & Policy2.QUERY_DROPMASK) == 0)
+                       return true; /* straight drop. */
+
+               preference =  (qp & Policy2.QUERY_PRIORITY_BITMASK);
+               if (preference < Policy2.QUERY_BANDWIDTH_VALUE)
+                       preference = Policy2.QUERY_BANDWIDTH_VALUE;
+               coreAPI.preferTrafficFrom(sender,preference);
+
+               /* adjust priority */
+               prio = qmsg.getPriority();
+               if ( (qp & Policy2.QUERY_PRIORITY_BITMASK) < prio) {
+                       prio = qp & Policy2.QUERY_PRIORITY_BITMASK;
+                       qmsg.setPriorityAndTTL(prio,qmsg.getTTL());
+               }
+
+               /* adjust TTL */
+               if ( (ttl > 0) &&
+                               (ttl > (prio+3)*TTL_DECREMENT) )
+                       ttl = (int) ((prio+3)*TTL_DECREMENT); /* bound! */
+               qmsg.setPriorityAndTTL(qmsg.getPriority(),ttl);
+
+               routing.execQuery(qp,qmsg, null);
+               return true;
+       }
+
+       /**
+        * @param sender
+        * @param msg
+        * @return
+        *
+        */
+
+       public boolean handleSBLOCK_CONTENT( HostIdentity sender, P2PMessage 
msg )
+       {
+               int                             prio;
+               P2PSBlockResult cmsg;
+               ContentIndex    ce=new ContentIndex();
+               boolean                 ret;
+               double                  preference;
+               boolean[]               dupe=new boolean[1];
+
+               if (msg.getByteSize() != P2PSBlockResult.SIZE) {
+                       log(Level.WARNING,"WARNING: signed content message 
received was malformed");
+                       return false;
+                       }
+               stat_p2p_sblock_replies.inc();
+               cmsg = (P2PSBlockResult) msg;
+
+               if (!cmsg.getResult().verify())
+                       return false;
+               new 
Pseudonym(prefs).addNamespace(cmsg.getResult().getNameSpace());
+
+               debug("DEBUG: received SBLOCK search result for 
"+cmsg.getResult().getIdentifier().toHex()+" from peer");
+
+               prio = 
routing.useContent(sender,cmsg.getResult().getIdentifier(),msg);
+               if (sender == null) { /* no migration, this is already content 
from the local node */
+                       debug("DEBUG: content migration not needed, content is 
local");
+                       return true;
+                       }
+
+               debug("DEBUG: content migration with preference "+prio);
+
+               preference = prio;
+               prio = 
policy.evaluateContent(cmsg.getResult().getIdentifier(),prio);
+               if (prio == -1) {
+                       debug("DEBUG: content not important enough, not 
replicated");
+                       return true; /* straight drop */
+                       }
+
+               debug("DEBUG: content replicated with total preference "+prio);
+
+               if (prio != -1)
+                       preference += prio;
+               if (preference < Policy2.CONTENT_BANDWIDTH_VALUE)
+                       preference = Policy2.CONTENT_BANDWIDTH_VALUE;
+               coreAPI.preferTrafficFrom(sender,preference);
+               ce.hash=(HashCode512) 
PersistentHelper.copy(cmsg.getResult().getIdentifier());  //todo: copie utile 
???
+               ce.importance    = prio;
+               ce.type          = ContentIndex.LOOKUP_TYPE_SBLOCK;
+               ce.fileNameIndex = 0;
+               ce.fileOffset    = 0;
+
+               ret = 
manager.insertContent(ce,ContentBlock.SIZE,cmsg.getResult(),sender,dupe);
+               if (ret && !dupe[0])
+                       singleBloomFilter.add(cmsg.getResult().getIdentifier());
+               return true;
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/IndexedFileNameCallback.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/IndexedFileNameCallback.java        
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/IndexedFileNameCallback.java        
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,23 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs;
+
+/**
+ *
+ */
+
+public interface IndexedFileNameCallback
+{
+       /**
+        * Callback for each indexed file.
+        *
+        * @param fn the name of the file
+        * @param idx the index of the file
+        * @param data opaque context pointer for the callee
+        * @return false if the file should be removed from the list
+        */
+
+       public boolean onFile( String fn, int idx, Object data );
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/IndirectionTableEntry.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/IndirectionTableEntry.java  
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/IndirectionTableEntry.java  
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,177 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs;
+
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.net.*;
+
+import java.util.*;
+
+/**
+ * Indirection table entry. Lists what we're looking for,
+ * where to forward it, and how long to keep looking for it.
+ */
+
+public class IndirectionTableEntry extends Object
+{
+       /** what are we waiting for ? */
+       public HashCode512      hash;
+
+       /** Are we limited to a specific namespace ? Non-null if yes. */
+       public HashCode512      namespace;
+
+       /** when can we forget about this entry ? */
+       public long                     ttl;
+
+       /** How much is this query worth to us, that is, how much would this 
node be willing to "pay" for an answer that matches the
+        hash stored in this ITE? (This is NOT the inbound priority, it is the 
trust-adjusted inbound priority <B>divided</B> by the
+        number of queries (for a multi-query)). */
+       public int                      priority;
+
+       /** which replies have we already seen ? hashcodes of the encrypted (!) 
replies that we have forwarded so far */
+       private List                    seen;
+
+       /** How many hosts are waiting for an answer to this query / who are 
these hosts ? */
+       private List                    waiting;
+
+               /** How many tcpsocks are in use ? / local TCP clients to send 
the reply to, null if none */
+       private List                    tcpsocks;
+
+       /** Do we currently have a response in the delay loop (delays are 
introduced to make traffic analysis harder
+        and thus enable anonymity) ? This marker is set to avoid looking up 
content again before the first content
+        exits the delay loop.  Since this *not* looking up content again is 
not externally visible, it is ok to do this
+        optimization to reduce disk accesses (see Mantis bug #407). */
+       public boolean          successful_local_lookup_in_delay_loop;
+
+       /** Avoiding concurrent lookups for the same ITE: semaphore grants 
access to peers to perform a lookup that matches this ITE entry. */
+       public Object           lookup_exclusion;
+
+
+       public IndirectionTableEntry()
+       {
+               super();
+               seen=new ArrayList();
+               waiting=new ArrayList();
+               tcpsocks=new ArrayList();
+               hash=new HashCode512();
+       }
+
+       public String toString()
+       {
+               return "xxx";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public boolean hasSeen()
+       {
+               return seen.size()>0;
+       }
+
+       public int getSeenCount()
+       {
+               return seen.size();
+       }
+
+       public HashCode512 getSeen( int index )
+       {
+               return ((index>=0 && index<seen.size()) ? (HashCode512) 
seen.get(index) : null);
+       }
+
+       public void addSeen( HashCode512 h )
+       {
+               seen.add(PersistentHelper.copy(h));     //todo: copie utile ???
+       }
+
+       public void clearSeen()
+       {
+               seen.clear();
+       }
+
+       public boolean hasWaitingHost( HostIdentity hi )
+       {
+               return waiting.contains(hi);
+       }
+
+       public boolean hasWaitingHosts()
+       {
+               return waiting.size()>0;
+       }
+
+       public int getWaitingHostsCount()
+       {
+               return waiting.size();
+       }
+
+       public HostIdentity getWaitingHost( int index )
+       {
+               return ((index>=0 && index<waiting.size()) ? (HostIdentity) 
waiting.get(index) : null);
+       }
+
+       public void addWaitingHost( HostIdentity hi )
+       {
+               waiting.add(PersistentHelper.copy(hi)); //todo: copie utile ???
+       }
+
+       public void removeWaitingHost( HostIdentity hi )
+       {
+               while (waiting.remove(hi)) {}
+       }
+
+       public void clearWaitingHosts()
+       {
+               waiting.clear();
+       }
+
+       public boolean hasClients()
+       {
+               return tcpsocks.size()>0;
+       }
+
+       public boolean containsClient( CSSession c )
+       {
+               int     i;
+
+               for (i=0; i<tcpsocks.size(); i++) {
+                       if (tcpsocks.get(i)==c) {
+                               return true;
+                               }
+                       }
+               return false;
+       }
+
+       public int getClientsCount()
+       {
+               return tcpsocks.size();
+       }
+
+       public CSSession getClient( int index )
+       {
+               return ((index>=0 && index<tcpsocks.size()) ? (CSSession) 
tcpsocks.get(index) : null);
+       }
+
+       public void addClient( CSSession c )
+       {
+               tcpsocks.add(c);
+       }
+
+       public void removeClient( CSSession c )
+       {
+               int     i;
+
+               for (i=0; i<tcpsocks.size(); i++) {
+                       if (tcpsocks.get(i)==c) {
+                               tcpsocks.remove(i);
+                               i--;
+                               }
+                       }
+       }
+
+       public void clearClients()
+       {
+               tcpsocks.clear();
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/IterState.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/IterState.java      2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/IterState.java      2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,28 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs;
+
+import org.gnu.freeway.protocol.fs.esed2.*;
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.crypto.*;
+
+import EDU.oswego.cs.dl.util.concurrent.*;
+
+/**
+ *
+ */
+
+public class IterState extends Object
+{
+       public boolean          hasNext;
+       public Semaphore                wsem;
+       public Semaphore                sem;
+       public HashCode512      next;
+       public ContentIndex     ce;
+       public int                      bucket;
+       public Object           data;
+       public int                      len;
+       public Task                     db_iterator;
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/LFS.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/LFS.java    2006-08-21 05:13:22 UTC 
(rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/LFS.java    2006-08-21 11:53:42 UTC 
(rev 3286)
@@ -0,0 +1,325 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs;
+
+import org.gnu.freeway.protocol.fs.esed2.*;
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.io.*;
+import org.gnu.freeway.util.net.*;
+
+import java.io.*;
+import java.nio.*;
+import java.nio.channels.*;
+import java.util.logging.*;
+
+/**
+ * Support for special handling of very large (3HASH) reply sets.
+ *
+ * The databases (gdbm in particular, but also the others) do not
+ * handle very large entries very well.  This is no problem for CHK,
+ * but there can be several thousand (!)  results for a very popular
+ * keyword, like a mime-type.  These 3HASH codes with more than
+ * VERY_LARGE_SIZE (16) results are thus stored in separate files.
+ *
+ * The reason is, that gdbm would grow quadratic when the file is
+ * build and that it would also be very slow: every read or write to
+ * these very large content entries would require reading and writing
+ * the *entire* 2 MB block (2 MB for 2,000 entries).  This API allows
+ * a random access to one of the results and the use of "append" to
+ * add a single entry.  It also does not suffer from the quadratic
+ * explosion in space consumption that gdbm has.  So essentially, this
+ * is a crapload of code that does not add any real functionality but
+ * overcomes problems with the crude database implementations that we
+ * would have to use otherwise (and that would be really bad for
+ * performance without this).
+ *
+ * Support for special handling of very large (3HASH) reply sets.
+ *
+ * The databases (gdbm in particular, but also the others) do not
+ * handle very large entries very well.  This is no problem for CHK,
+ * but there can be several thousand (!)  results for a very popular
+ * keyword, like a mime-type.  These 3HASH codes with more than
+ * VERY_LARGE_SIZE (16) results are thus stored in separate files.
+ *
+ * The reason is, that gdbm would grow quadratic when the file is
+ * build and that it would also be very slow: every read or write to
+ * these very large content entries would require reading and writing
+ * the *entire* 2 MB block (2 MB for 2,000 entries).  This API allows
+ * a random access to one of the results and the use of "append" to
+ * add a single entry.  It also does not suffer from the quadratic
+ * explosion in space consumption that gdbm has.  So essentially, this
+ * is a crapload of code that does not add any real functionality but
+ * overcomes problems with the crude database implementations that we
+ * would have to use otherwise (and that would be really bad for
+ * performance without this).
+ */
+
+public class LFS extends LoggedObject
+{
+       public static final String      DIR_EXT =       ".lfs";
+
+       private DirLocation     dir;
+       private Object          lock;
+       private StatusCallsService      status;
+
+
+       public LFS()
+       {
+               super(true);
+       }
+
+       public String toString()
+       {
+               return "LFS";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       /**
+        * Initialize the storage module.
+        * @param str the name of the directory/file
+        *        containing the content database
+        * @param sc
+        */
+
+       public void init( String str, StatusCallsService sc )
+       {
+               status=sc;
+
+               dir=new DirLocation(str+DIR_EXT);
+               if (!dir.create()) {
+                       log(Level.SEVERE,"Failed to open directory 
"+dir.getLabel()+" !");
+                       return;
+                       }
+
+               log(Level.INFO,"Database directory is located at 
\""+dir.getLabel()+"\".");
+
+               lock=new Object();
+       }
+
+       /**
+        * Remove the lfs database.
+        * Remove the lfs database.
+        */
+
+       public void delete()
+       {
+               if (!dir.delete()) {
+                       log(Level.SEVERE,"Could not remove 
\""+dir.getName()+"\" !");
+                       }
+               dir=null;
+               lock=null;
+       }
+
+       /**
+        * Clean shutdown of the storage module.
+        */
+
+       public void done()
+       {
+               dir=null;
+               lock=null;
+       }
+
+       /**
+        * Read the contents of a bucket to a buffer.
+        * @param query
+        *
+        * @return read blocks on success, null on failure
+        */
+
+       public ContentBlock[] read( HashCode512 query )
+       {
+               ContentBlock[]  blocks;
+               FileChannel             fd;
+               FileLocation                    fil;
+               int                             fsize,i;
+               ByteBuffer              buf;
+
+               fil=dir.getFile(query.toHex());
+               if (fil==null) {
+                       log(Level.WARNING,"File \""+query.toHex()+"\" does not 
exist !");
+                       return null;
+                       }
+
+               blocks=null;
+               synchronized(lock) {
+                       try {
+                               fd=new 
RandomAccessFile(fil.getPath(),"r").getChannel();
+                               try {
+                                       fsize=(int) fd.size();
+                                       if ((fsize % ContentBlock.SIZE)!=0) {
+                                               log(Level.WARNING,"LFS database 
corrupted (file \""+query.toHex()+"\" has bad length), trying to fix.");
+                                               fsize = (fsize / 
ContentBlock.SIZE) * ContentBlock.SIZE;
+                                               fd.truncate(fsize);
+                                               }
+
+                                       buf=ByteBuffer.allocateDirect(fsize);
+                                       if (fd.read(buf)!=fsize) {
+                                               return null;
+                                               }
+                                       buf.flip();
+
+                                       blocks= new 
ContentBlock[fsize/ContentBlock.SIZE];
+                                       for (i=0; i<blocks.length; i++) {
+                                               blocks[i]=new ContentBlock();
+                                               blocks[i].readBytes(buf,new 
ErrorReporter());
+                                               }
+                                       }
+                               finally {
+                                       fd.close();
+                                       }
+                               }
+                       catch( IOException x ) {
+                               err("Failed to read on \""+query.toHex()+"\" 
!",x);
+                               return null;
+                               }
+                       }
+               return blocks;
+       }
+
+       /**
+        * Read one random block from an entry
+        * Read the contents of a bucket to a buffer.
+        *
+        * @param query the hashcode representing the entry
+        * @param prio
+        * @return read blocks on success, null on failure
+        */
+
+       public ContentBlock[] randomRead( HashCode512 query, int prio )
+       {
+               ContentBlock[]  blocks;
+               int[]                   perm;
+               FileChannel             fd;
+               FileLocation                    fil;
+               int                             size,fsize,max,i;
+
+               max = (50-status.getNetworkLoadUp())*(prio+1);
+               if (max <= 0)
+                       max = 1;
+
+               fil=dir.getFile(query.toHex());
+               if (fil==null) {
+                       log(Level.WARNING,"File \""+query.toHex()+"\" does not 
exist !");
+                       return null;
+                       }
+
+               blocks=null;
+               synchronized(lock) {
+                       try {
+                               fd=new 
RandomAccessFile(fil.getPath(),"r").getChannel();
+                               try {
+                                       fsize=(int) fd.size();
+                                       if ((fsize % ContentBlock.SIZE)!=0) {
+                                               log(Level.WARNING,"LFS database 
corrupted (file \""+query.toHex()+"\" has bad length), trying to fix.");
+                                               fsize = (fsize / 
ContentBlock.SIZE) * ContentBlock.SIZE;
+                                               fd.truncate(fsize);
+                                               }
+
+                                       fsize = fsize / ContentBlock.SIZE;
+                                       if (fsize == 0) {
+                                               return null;
+                                               }
+                                       if (max > fsize) {
+                                               max = fsize;
+                                               }
+                                       log(Level.FINEST,"Received query, have 
"+fsize+" results, adding "+max+" to queue.");
+
+                                       blocks=new ContentBlock[max];
+
+                                       perm = Crypto.permute(fsize);
+                                       for (i=0; i<max; i++) {
+                                               blocks[i]=new ContentBlock();
+
+                                               
fd.position(perm[i]*ContentBlock.SIZE);
+
+                                               
size=fd.read(ByteBuffer.wrap(blocks[i].content));
+                                               if (size!=ContentBlock.SIZE) {
+                                                       return null;
+                                                       }
+                                               }
+                                       }
+                               finally {
+                                       fd.close();
+                                       }
+                               }
+                       catch( IOException x ) {
+                               err("Failed to read on \""+query.toHex()+"\" 
!",x);
+                               return null;
+                               }
+                       }
+               return blocks;
+       }
+
+       /**
+        * Append content to file.
+        *
+        * @param query the key for the entry
+        * @param data what to append/the data to store
+        * @return false on error, true if ok.
+        */
+
+       public boolean append( HashCode512 query, ContentBlock data )
+       {
+               FileChannel     fd;
+               FileLocation            fil;
+               int                     offset;
+
+               fil=dir.getFile(query.toHex());
+               fil.create();
+
+               synchronized(lock) {
+                       try {
+                               fd=new 
RandomAccessFile(fil.getPath(),"rw").getChannel();
+                               try {
+                                       fd.position(fd.size());
+                                       offset=(int) fd.position();
+                                       if ((offset % ContentBlock.SIZE)!=0) {
+                                               log(Level.WARNING,"LFS database 
corrupted (file \""+query.toHex()+"\" has bad length), trying to fix.");
+                                               
offset=(offset/ContentBlock.SIZE)*ContentBlock.SIZE;
+                                               fd.position(offset);
+                                               fd.truncate(offset);
+                                               }
+
+                                       fd.write(ByteBuffer.wrap(data.content));
+                                       }
+                               finally {
+                                       fd.close();
+                                       }
+                               }
+                       catch( IOException x ) {
+                               err("Failed to append on \""+query.toHex()+"\" 
!",x);
+                               return false;
+                               }
+                       }
+               return true;
+       }
+
+       /**
+        * Remove an entry.
+        * Free space in the database by removing one file
+        *
+        * @param query the key for the entry/the hashcode representing the 
entry
+        * @return false on error, true if ok.
+        */
+
+       public boolean remove( HashCode512 query )
+       {
+               FileLocation            f;
+
+               f=dir.getFile(query.toHex());
+               if (f==null) {
+                       log(Level.WARNING,"File \""+query.toHex()+"\" does not 
exist !");
+                       return false;
+                       }
+
+               synchronized(lock) {
+                       return f.delete();
+                       }
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/Manager.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/Manager.java        2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/Manager.java        2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,1017 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs;
+
+import org.gnu.freeway.protocol.fs.esed2.*;
+import org.gnu.freeway.server.*;
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.io.*;
+import org.gnu.freeway.util.net.*;
+
+import EDU.oswego.cs.dl.util.concurrent.*;
+
+import java.io.*;
+import java.nio.*;
+import java.nio.channels.*;
+import java.util.*;
+import java.util.logging.*;
+
+/**
+ * This module is responsible to manage content, in particular it needs to 
decide what content to keep.
+ * This module is responsible for content management (what to
+ * keep, what to discard, content ageing, content migration).
+ *
+ * The manager.h header defines the external interface to the
+ * GNUnet databases.  The manager code is responsible for
+ * space management and on-demand encoding of blocks.  The
+ * high-level database abstraction defined in high_backend.h
+ * is responsible for lookup (3HASH and CHK) and block retrieval
+ * (ContentEntries and inserted blocks).
+ */
+
+public class Manager extends LoggedObject
+{
+       public static final String      VLS_DIR =       "large";
+       public static final String      AGEFILE =       "database.age";
+
+       public static final int         DB_DIRTY_AVAILABLE      =       
Integer.MIN_VALUE;
+
+
+       public static final boolean     TRACK_INDEXED_FILES     =       true;
+       public static final String      TRACKFILE                       =       
"indexed_requests.txt";
+
+
+       /** Entry length that indicates that the entry was too large for the 
usual DB and has been stored in a separate file instead. */
+       public static final int VERY_LARGE_FILE =       42;
+
+       /** How large is very large? (number of ContentEntries) Mysql seems to 
have some limit at 16k, so let's pick 15 to be on the good side for sure. */
+       public static final int VERY_LARGE_SIZE =       15;
+
+       /** The current base value for fresh content (used to time-out old 
content). */
+       private int                     MANAGER_age;
+
+       /** Is active migration allowed ? This is about us receiving data from 
the network,
+        actively pushing content out is always ok. */
+       private boolean         useActiveMigration;
+
+       /** Global database handle */
+//     private DatabaseAPI     dbAPI;
+       /** Handle of the database as returned by initContentDatabase() */
+       public DBHandle[]       dbHandles;
+
+       /** The number of buckets */
+       public int                      buckets;
+
+       /** cache estimated available blocks for each bucket */
+       public int[]            dbAvailableBlocks;
+
+       /** Large file handling. */
+       private LFS                     lfs;
+
+       /** Statistics handles */
+       private Stat    stat_handle_lookup_3hash;
+       private Stat    stat_handle_lookup_sblock;
+       private Stat    stat_handle_lookup_chk;
+       private Stat    stat_handle_lookup_ondemand;
+       private Stat    stat_handle_lookup_notfound;
+       private Stat    stat_handle_spaceleft;
+
+       private BloomFilter2    filter2;        //todo: VA PLANTER
+       private FileIndex               fileIndex;      //todo: VA PLANTER
+       private ContentEncoding encoding;       //todo: VA PLANTER
+
+       private Prefs           prefs;
+       private Statistics      stats;
+       private Scheduler               scheduler;
+       private ScheduledTask           reduceTask;
+
+
+       public Manager()
+       {
+               super(true);
+               lfs=new LFS();
+
+               reduceTask=new ScheduledTask("REDUCE-IMPORTANCE",new 
EvalAction(this,"cronReduceImportance"),Scheduler.HOURS_12);
+       }
+
+       public String toString()
+       {
+               return "Manager";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       /**
+        * Initialize the manager module.
+        * @param capi
+        */
+
+       public void init( CoreForProtocol capi )
+       {
+               String  dtype,afsdir;
+               int             delta,i;
+
+               prefs=capi.getApplication().getPreferences();
+               stats=capi.getApplication().getStatistics();
+
+               dtype = prefs.getString("AFS","DATABASETYPE",null);
+               initializeDatabaseAPI(dtype);
+               stat_handle_lookup_sblock=stats.getHandle("# lookup (SBlock, 
search results)");
+               stat_handle_lookup_3hash=stats.getHandle("# lookup (3HASH, 
search results)");
+               stat_handle_lookup_chk=stats.getHandle("# lookup (CHK, inserted 
or migrated content)");
+               stat_handle_lookup_ondemand=stats.getHandle("# lookup 
(ONDEMAND, indexed content)");
+               stat_handle_lookup_notfound=stats.getHandle("# lookup (data not 
found)");
+               stat_handle_spaceleft=stats.getHandle("# blocks AFS storage 
left (estimate)");
+
+               MANAGER_age = readAge();
+
+               useActiveMigration= 
prefs.testString("AFS","ACTIVEMIGRATION","YES");
+
+               scheduler=capi.getApplication().getScheduler();
+               scheduler.addJob(reduceTask,Scheduler.HOURS_6);
+
+               delta = estimateGlobalAvailableBlocks();
+               if (delta < 0) {
+                       int[]   perm = Crypto.permute(buckets);
+                       /* we permute to delete content in random order since
+                        users may interrupt the process (in particular at
+                        the beginning) and we want to make sure that the
+                        chances are distributed reasonably at random) */
+                       for (i=0; i<buckets; i++) {
+                               
dbHandles[perm[i]].deleteContent(16-delta/buckets,new EntryCallback() {
+                                       public void onEntry( HashCode512 key, 
ContentIndex ce, Object data, int dataLen, Object closure )
+                                       {
+                                               
filter2.bf_deleteEntryCallback(key,ce,data,dataLen,closure);
+                                       }
+                                       },null);
+                               dbAvailableBlocks[perm[i]]=DB_DIRTY_AVAILABLE;
+                               }
+                       delta = (16-delta/buckets)*buckets;
+                       }
+               stat_handle_spaceleft.set(delta);
+
+               afsdir = prefs.getString("AFS","AFSDIR",null);
+               if (afsdir==null) {
+                       trace("Configuration file must specify directory for 
storing AFS data in section AFS under AFSDIR !");
+                       return;
+                       }
+
+               lfs.init(afsdir+"/"+VLS_DIR,(StatusCallsService) 
capi.service(StatusCallsService.class));
+       }
+
+       /**
+        * Shutdown the manager module.
+        */
+
+       public void done()
+       {
+               int i;
+
+               scheduler.deleteJob(reduceTask);
+
+               for (i=0;i<buckets;i++)
+                       dbHandles[i].close();
+               dbHandles=null;
+               dbAvailableBlocks=null;
+               lfs.done();
+       }
+
+       /**
+        * calculates the global available space using
+        * cached bucket availability estimates
+        * @return
+        */
+
+       protected int estimateGlobalAvailableBlocks()
+       {
+               int i;
+               int ret = 0;
+               int perBucketQuota = prefs.getInt("AFS","DISKQUOTA",0) * 1024 / 
buckets;
+
+               for (i = 0; i < buckets; ++i) {
+                       if (dbAvailableBlocks[i] == DB_DIRTY_AVAILABLE) {
+                               dbAvailableBlocks[i] = 
dbHandles[i].estimateAvailableBlocks(perBucketQuota);
+                               }
+                       ret += dbAvailableBlocks[i];
+                       }
+               return ret;
+       }
+
+       /**
+        * Load the high-level database as specified by
+        * the given dtype.
+        * @param dtype
+        */
+
+       public void initializeDatabaseAPI( String dtype )
+       {
+               DBHandle        dbh;
+               String          odtype;
+               int                     i;
+
+               if (dtype == null) {
+                       log(Level.SEVERE,"AFS/DATABASETYPE not specified in 
config");
+                       return;
+                       }
+
+               odtype=prefs.getContentAsString("AFS-DATABASETYPE");
+               if (odtype==null) {
+                       prefs.putContent("AFS-DATABASETYPE",dtype);
+                       }
+               else if (!odtype.equals(dtype)) {
+                       log(Level.SEVERE,"FATAL: AFS database type was changed, 
run gnunet-convert");
+                       return;
+                       }
+
+               buckets= 4 *prefs.getInt("AFS","DISKQUOTA",0) / 1024; // one 
bucket per 256 MB
+               if (buckets == 0)
+                       buckets = 1; // at least 1 bucket!
+               dbHandles= new DBHandle[buckets];
+               dbAvailableBlocks=new int[buckets];
+
+               for (i=0;i<buckets;i++) {
+                       dbh=(DBHandle) new 
DynamicLibrary("protocol-afs-"+dtype+".jar").load(DBHandle.class,getClass().getClassLoader());
+                       if (dbh==null) {
+                               log(Level.SEVERE,"Failed to initialize AFS 
database "+i+" !");
+                               return;
+                               }
+                       if 
(!dbh.open(prefs,String.valueOf(i),String.valueOf(prefs.getInt("AFS","DISKQUOTA",0))))
 {
+                               dbh.close();
+                               trace("Should not happen !");
+                               return;
+                               }
+                       dbHandles[i]=dbh;
+
+                       if (dbHandles[i] == null) {
+                               log(Level.SEVERE,"Failed to initialize AFS 
database "+i+" !");
+                               return;
+                               }
+                       dbAvailableBlocks[i]=DB_DIRTY_AVAILABLE; /* not yet 
initialized */
+                       }
+       }
+
+       /**
+        * Store content (if the priority is high enough), potentially
+        * discarding less important content. If this method is called
+        * for indexed content, * data should be null and len==0 and
+        * fields of ce filled properly. For 3HASH inserts, 2HASH must
+        * be provided in ce.hash.
+        *
+        * @param ce the content entry describing the content
+        * @param len the length of the data in bytes/the length of the data in 
bytes, either 0 for no data (e.g. on-demand encoding
+        *         of indexed content or CONTENT_Block.SIZE for normal data.
+        * @param data the block itself
+        * @param sender from where does the content come? null for
+        *        from local client./from where does the content come? null for
+        *        from local client.
+        * @param duplicate output param, will be true if content was already 
there
+        * @return true if the block was stored, false if not
+        */
+
+       public boolean insertContent( ContentIndex ce, int len, byte[] data, 
HostIdentity sender, boolean[] duplicate )
+       {
+               byte[]                  old;
+               ContentIndex    oldce;
+               HashCode512             query;
+               int                             avail,importance;
+
+               if (ce.fileNameIndex>0) {
+                       log(Level.FINEST,"Using fileNameIndex 
"+ce.fileNameIndex);
+                       }
+
+               if (len!=0 && len!=ContentBlock.SIZE) {
+                       trace("Unexpected length "+len+" for insertContent !");
+                       return false;
+                       }
+
+               duplicate[0] = false;
+               if (sender!=null && !useActiveMigration) {
+                       return false; // forbidden!
+                       }
+               importance = ce.importance;
+               if (sender!=null && Crypto.nextInt(2 + importance)==0) {
+                       return false; // don't bother...
+                       }
+
+               ce.importance= importance + MANAGER_age;
+               switch (ce.type) {
+                       case ContentIndex.LOOKUP_TYPE_3HASH:
+                               
query=HashCode512.create(PersistentHelper.toBytes(ce.hash));
+                               break;
+                       case ContentIndex.LOOKUP_TYPE_CHK:
+                       case ContentIndex.LOOKUP_TYPE_CHKS:
+                       case ContentIndex.LOOKUP_TYPE_SUPER:
+                       case ContentIndex.LOOKUP_TYPE_SBLOCK:
+                               query=(HashCode512) 
PersistentHelper.copy(ce.hash);
+                               break;
+                       default:
+                               log(Level.WARNING,"Unexpected content type : 
"+ce.type);
+                               return false;
+                       }
+
+               oldce=(ContentIndex) PersistentHelper.copy(ce);
+
+               avail = estimateGlobalAvailableBlocks();
+               if (avail <= 0) {
+                       if (importance + MANAGER_age <= 
computeHighDB(query).getMinimumPriority())
+                               return false; // new content has such a low 
priority that we should not even bother!
+                       computeHighDB(query).deleteContent(16-avail,new 
EntryCallback() {
+                                       public void onEntry( HashCode512 key, 
ContentIndex ce2, Object data2, int dataLen, Object closure )
+                                       {
+                                               
filter2.bf_deleteEntryCallback(key,ce2,data2,dataLen,closure);
+                                       }
+                                       },null);
+                       stat_handle_spaceleft.set(16-avail);
+                       
dbAvailableBlocks[computeBucketGlobal(query)]=DB_DIRTY_AVAILABLE;
+                       }
+               else {
+                       stat_handle_spaceleft.set(avail);
+                       }
+
+               // try to read existing
+               old = computeHighDB(query).readContent(query,oldce,0);
+
+               // add the content
+               switch (ce.type) {
+                       case ContentIndex.LOOKUP_TYPE_3HASH:
+                               if (len != ContentBlock.SIZE ) {
+                                       trace("Unexpected length "+len+" for 
insertContent !");
+                                       return false;
+                                       }
+                               return 
handle3HSBInsert(query,ce,data,(old!=null ? old.length : 
-1),duplicate,len,old,oldce.importance);
+                       case ContentIndex.LOOKUP_TYPE_SBLOCK:
+                               if (len != ContentBlock.SIZE) {
+                                       trace("Unexpected length "+len+" for 
insertContent !");
+                                       return false;
+                                       }
+                               return 
handle3HSBInsert(query,ce,data,(old!=null ? old.length : 
-1),duplicate,len,old,oldce.importance);
+                       case ContentIndex.LOOKUP_TYPE_CHK:
+                       case ContentIndex.LOOKUP_TYPE_CHKS:
+                       case ContentIndex.LOOKUP_TYPE_SUPER:
+                       {
+                               boolean replace = false;
+                               /*
+                                * This is a bit messy. The intended idea is 
that missing blocks
+                                * are always replaced. Indexed blocks are 
replaced only if the
+                                * new one is indexed AND has a higher priority 
than the old one.
+                                * Nonindexed, existing blocks are replaced if 
the size differs OR if new
+                                * is more important OR if new is an indexed 
block. This scheme
+                                * should never replace an indexed block with a 
nonindexed block.
+                                * We are not setting *duplicate=YES in the 
true replace case because
+                                * we don't want the bloomfilters to be 
unnecessarily incremented
+                                * outside insertContent().
+                                */
+                               duplicate[0] = true;
+                               if (old == null) {
+                                       replace = true;
+                                       duplicate[0] = false;
+                                       }
+                               else if (oldce.fileNameIndex > 0) {
+                                       if (ce.fileNameIndex>0 && 
ce.importance>oldce.importance) {
+                                               replace = true;
+                                               }
+                                       else {
+                                               replace = false;
+                                               }
+                                       }
+                               else {
+                                       if ((old!=null ? old.length : -1) != 
len || ce.importance>oldce.importance || ce.fileNameIndex>0) {
+                                               replace = true;
+                                               }
+                                       else {
+                                               replace = false;
+                                               }
+                                       }
+
+                               if (!replace) {
+                                       return true;
+                                       }
+
+                               dbAvailableBlocks[computeBucketGlobal(query)]= 
DB_DIRTY_AVAILABLE;
+                               return 
computeHighDB(query).writeContent(ce,data,0,len);
+                               }
+//                             break;
+                       default:
+                               log(Level.WARNING,"Unexpected content type : 
"+ce.type);
+                               return false;
+                       }
+//             log(Level.SEVERE,"ERROR: insertContent code ended up where it 
never should");
+//             return false;
+       }
+
+       public boolean insertContent( ContentIndex ce, int len, Persistent 
data, HostIdentity sender, boolean[] duplicate )
+       {
+               return 
insertContent(ce,len,PersistentHelper.toBytes(data),sender,duplicate);
+       }
+
+       /**
+        * Locate content. This method locates the data matching the
+        * query.  The data is on-demand encrypted if it is
+        * indexed content or retrieved from the contentdatabase
+        * if it was inserted content.  The ContentIndex entry is
+        * filled with the appropriate values.
+        * Locate content.  This method locates the data matching the content
+        * entry.  The data is on-demand encrypted if it is indexed content or
+        * retrieved from the contentdatabase if it was inserted content.
+        *
+        * @param query         the CHK or the tripleHash key of the conten/the 
query for the content (CHK or 3HASH)
+        * @param ce            the content entry describing what to look 
for/the meta-data for the content
+        * @param prio          the amount to modify the priority of the 
entry/by how much should the priority of the content be changed (if it is 
found)?
+        * @param isLocal       is the request a local request? (true/false)
+        * @return                      the resulting content, null on error
+        */
+
+       public ContentBlock[] retrieveContent( HashCode512 query, ContentIndex 
ce, int prio, boolean isLocal )
+       {
+               ContentBlock[]  result;
+               byte[]                  tmp;
+               int                             len,i;
+
+               tmp=computeHighDB(query).readContent(query,ce,prio);
+               if (tmp==null) {
+                       stat_handle_lookup_notfound.inc();
+                       return null;
+                       }
+
+               if (tmp.length==VERY_LARGE_FILE) {
+                       result=(isLocal ? lfs.read(query) : 
lfs.randomRead(query,prio));
+                       if (result==null) {
+                               return null;
+                               }
+                       }
+               else {
+                       if ((tmp.length % ContentBlock.SIZE)!=0) {
+                               log(Level.SEVERE,"ERROR: retrieved content but 
size is not multiple of 1k!");
+                               return null;
+                               }
+
+                       result=new ContentBlock[tmp.length/ContentBlock.SIZE];
+                       for (i=0; i<result.length; i++) {
+                               result[i]=(ContentBlock) 
PersistentHelper.readFully(ContentBlock.class,ByteBuffer.wrap(tmp,i*ContentBlock.SIZE,ContentBlock.SIZE));
+                               }
+                       }
+
+               if (ce.fileNameIndex == 0) {
+                       switch (ce.type) {
+                               case ContentIndex.LOOKUP_TYPE_CHK:
+                               case ContentIndex.LOOKUP_TYPE_CHKS:
+                                       stat_handle_lookup_chk.inc();
+                                       break;
+                               case ContentIndex.LOOKUP_TYPE_3HASH:
+                                       stat_handle_lookup_3hash.inc();
+                                       break;
+                               case ContentIndex.LOOKUP_TYPE_SBLOCK:
+                                       stat_handle_lookup_sblock.inc();
+                                       break;
+                               case ContentIndex.LOOKUP_TYPE_SUPER:
+                                       // only gnunet-check will be doing 
this, don't bother to keep up stats
+                                       break;
+                               default:
+                                       log(Level.SEVERE,"Manager got 
unexpected content type : "+ce.type);
+                                       break;
+                               }
+                       return result;
+                       }
+
+               log(Level.SEVERE,"Retrieved content but index says on-demand 
encoded !");
+
+
+               stat_handle_lookup_ondemand.inc();
+
+               result=new ContentBlock[1];
+
+               len=encodeOnDemand(ce,result);
+               return (len>=0 ? result : null);
+       }
+
+       /**
+        * Explicitly delete content.  This method is not currently used
+        * (the manager discards data internally if we run out of space)
+        * but it could be used by a "gnunet-remove" application in the
+        * near future.
+        * <p>
+        *
+        * Explicitly remove some content from the database.
+        * Note that if multiple keywords correspond to the query, all are
+        * removed. To selectively remove one keyword, use retrieveContent and
+        * then insertContent if there are multiple results.
+        *
+        * @param query the query that corresponds to the block to remove
+        * @param bucket where to delete, <0 == autocompute/where to delete (<0 
== autocompute)
+        *               >=0 is used by gnunet-check.
+        * @return
+        */
+
+       public boolean removeContent( HashCode512 query, int bucket )
+       {
+               byte[]                  data;
+               ContentIndex    ce;
+               DBHandle                db;
+               boolean                 ok;
+
+               if (bucket < 0)
+                       db = computeHighDB(query);
+               else
+                       db = dbHandles[bucket];
+
+               ce=new ContentIndex();
+               data = db.readContent(query,ce,0);
+               if (data==null) {
+                       log(Level.WARNING,"removeContent ("+query.toHex()+") 
failed, readContent did not find content !");
+                       return false; /* not found! */
+                       }
+               if (data.length == VERY_LARGE_FILE) {
+                       // need to remove from VLS index, too -- this is 
unusual, but we can do it (should currently never happen in practice)
+                       ok = lfs.remove(query);
+                       if (!ok)
+                               log(Level.WARNING,"WARNING: removeContent 
failed on LFS content?");
+                       }
+
+               ok = db.unlink(query);
+               if (ok) {
+                       int delta;
+
+                       dbAvailableBlocks[computeBucketGlobal(query)] = 
DB_DIRTY_AVAILABLE;
+                       delta = estimateGlobalAvailableBlocks();
+                       if (delta < 0)  /* should not happen */
+                               delta = 0;
+                       stat_handle_spaceleft.set(delta);
+                       }
+               return ok;
+       }
+
+       /**
+        * Get some random contet.
+        * Return a random key from the database.
+        * @param ce output information about the key
+        * @return false on error, true if ok.
+        */
+
+       public boolean retrieveRandomContent( ContentIndex ce )
+       {
+               int     bucket;
+
+               bucket=Crypto.nextInt(buckets);
+               if (dbHandles[bucket] == null) {
+                       log(Level.SEVERE,"dbHandle at bucket "+bucket+" is 
null");
+                       return false;
+                       }
+               return dbHandles[bucket].getRandomContent(ce);
+       }
+
+       /**
+        * Iterator over all the queries in the database
+        * as needed by resizeBloomfilter.
+        *
+        * The idea is to use this code in the startup
+        * of the AFS module when the quota/memory limitations
+        * have changed and the bloomfilter needs to be
+        * resized. Note that the iterator is quite costly,
+        * but we can assume that the user is not going to
+        * change the configuration all the time :-).
+        * Iterator over all the queries in the database as needed by
+        * resizeBloomfilter (and gnunet-check).  Typical use:
+        * <code>
+        * state = makeDatabaseIteratorState();
+        * while (databaseIterator(state, x, y, z, t))
+        *   ...do something...
+        * </code>
+        *
+        * @param sss the iterator state as created by
+        *        makeDatabaseIteratorState
+        * @param hc next hash code (set)
+        * @param ce next content index (set)
+        * @param bucket where the data actually was (set)
+        * @param data corresponding data (set)
+        * @param datalen length of data (set)
+        * @return true if the iterator has filled in another element
+        *  from the database, false if there are no more elements
+        */
+
+       public boolean databaseIterator( IterState sss, HashCode512 hc, 
ContentIndex ce, int[] bucket, byte[][] data, int[] datalen )
+       {
+               IterState       st = sss;
+
+               try {
+                       st.sem.acquire();
+                       if (!st.hasNext) {
+                               st.sem=null;
+                               st.wsem=null;
+                               st.db_iterator.join();
+                               return false;
+                               }
+                       hc=(HashCode512) PersistentHelper.copy(st.next);
+                       ce=(ContentIndex) PersistentHelper.copy(st.ce);
+                       bucket[0] = st.bucket;
+                       data[0] = (byte[]) st.data;
+                       datalen[0] = st.len;
+                       st.wsem.release();
+                       return true;
+                       }
+               catch( InterruptedException x ) {
+                       err("",x);
+                       return false;
+                       }
+       }
+
+       /**
+        * Create the state required for a database iterator.
+        * Create the state required for a database iterator.  Calling this
+        * method requires to call databaseIterator with the state returned
+        * until "false" is returned.
+        * @return
+        */
+
+       public Object makeDatabaseIteratorState()
+       {
+               IterState  ret;
+
+               ret = new IterState();
+               ret.sem = new Semaphore(0);
+               ret.wsem = new Semaphore(1);
+
+               final IterState _ret = ret;
+
+               ret.db_iterator=new Task("DB-ITER",new AbstractAction() {
+                       public void perform()
+                       {
+                               iterator_helper(_ret);
+                       }
+                       });
+               ret.db_iterator.launch();
+               return ret;
+       }
+
+       /**
+        * Compute the database bucket id (for gnunet-check)
+        * This function is as crazy as it is since RIPE160 hashes do not seem
+        * to be quite random as we may want the to be...  So to get evenly
+        * distributed indices, we have to be a bit tricky. And no, there is
+        * high science but just a bit playing with the formula here.
+        * @param query
+        * @param maxBuckets
+        * @return
+        */
+
+       public int computeBucket( HashCode512 query, int maxBuckets )
+       {
+               HashCode512     qt;
+               long    r = 0;
+
+               qt=HashCode512.create(PersistentHelper.toBytes(query));
+
+               for(int i = 0; i < HashCode512.SIZE / 4; i++)
+                       r ^= query.getInt(i)-qt.getInt(i);
+               r = r >> 4;
+               r = r & 0x00000000FFFFFFFFL;
+               return (int) (r % maxBuckets);
+       }
+
+       /**
+        * Use this, if initManager() has been executed and
+        * the global dbAPI has the correct bucket count
+        * @param query
+        * @return
+        */
+
+       public int computeBucketGlobal( HashCode512 query )
+       {
+               return computeBucket(query,
+                               buckets);
+       }
+
+       /**
+        * @param query
+        * @return
+        *
+        */
+
+       protected DBHandle computeHighDB( HashCode512 query )
+       {
+               return dbHandles[computeBucket(query,buckets)];
+       }
+
+       /**
+        * Open the AGE file and return the handle.
+        * @return
+        */
+
+       protected FileLocation getAgeFileHandle()
+       {
+               DirLocation             dir;
+               FileLocation            f;
+
+               dir=prefs.getDirLocation("AFS","AFSDIR");
+               if (dir==null) {
+                       log(Level.SEVERE,"Configuration file must specify 
directory for storage of AFS data in section AFS under AFSDIR.");
+                       return null;
+                       }
+               f=dir.getFile(AGEFILE);
+               f.create();
+               return f;
+       }
+
+       /**
+        * Cron-job that decreases the importance-level of all
+        * files by 1. Runs 'not very often'.
+        */
+
+       public void cronReduceImportance()
+       {
+               FileLocation    f;
+               MappedFile      m;
+
+               log(Level.INFO,"Enter cronReduceImportance");
+
+               MANAGER_age++;
+
+               f=getAgeFileHandle();
+               if (f==null) {
+                       log(Level.WARNING,"Could not open agefile !");
+                       return;
+                       }
+
+               m=f.openNew();
+               m.writeInt(MANAGER_age);
+               m.close();
+
+               log(Level.INFO,"Exit cronReduceImportance");
+       }
+
+       protected int readAge()
+       {
+               FileLocation    f;
+               MappedFile      m;
+               int                     age;
+
+               f=getAgeFileHandle();
+               if (f==null) {
+                       log(Level.WARNING,"Could not open agefile !");
+                       return 0;
+                       }
+
+               m=f.open();
+               age=m.asInt(0);
+               m.close();
+               return age;
+       }
+
+       /**
+        * Encode a block from a file on the drive, put the
+        * result in the result buffer (allocate) and return
+        * the size of the block.
+        * @param ce
+        * @param result
+        * @return
+        */
+
+       protected int encodeOnDemand( ContentIndex ce, ContentBlock[] result )
+       {
+               String fn;
+               int blen;
+               HashCode512 hc;
+               ContentBlock  iobuf;
+               FileChannel     fc;
+
+               // on-demand encoding mechanism
+               fn = fileIndex.getIndexedFileName(ce.fileNameIndex);
+               if (fn == null) {
+                       log(Level.SEVERE,"Database inconsistent! (index points 
to invalid offset ("+ce.fileNameIndex+")");
+                       return -1;
+                       }
+
+               iobuf = new ContentBlock();
+
+               try {
+                       fc=new RandomAccessFile(fn,"r").getChannel();
+
+                       if (TRACK_INDEXED_FILES) {
+                               PrintWriter     fp;
+                               FileLocation    scratch;
+                               DirLocation     afsDir;
+
+                               afsDir=prefs.getDirLocation("AFS","AFSDIR");
+                               if (afsDir!=null) {
+                                       scratch=afsDir.getFile(TRACKFILE);
+
+                                       fp=new PrintWriter(new 
FileOutputStream(scratch.getPath(),true),true);
+                                       fp.println(ce.fileNameIndex+" 
"+Scheduler.toSeconds(Scheduler.now()));
+                                       fp.close();
+                                       }
+                               else {
+                                       log(Level.SEVERE,"Configuration file 
must specify directory for storage of AFS data in section AFS under AFSDIR.");
+                                       }
+                               }
+
+                       try {
+                               fc.position(ce.fileOffset);
+                               if (fc.position()!=ce.fileOffset) {
+                                       log(Level.WARNING,"WARNING: unable to 
seek to "+ce.fileOffset+" in "+fn);
+                                       return -1;
+                                       }
+                               Arrays.fill(iobuf.content,(byte) 0);
+                               blen=fc.read(ByteBuffer.wrap(iobuf.content));
+                               if (blen <= 0) {
+                                       if(blen == 0)
+                                               log(Level.WARNING,"WARNING: 
read 0 bytes from "+fn);
+                                       else
+                                               log(Level.SEVERE,"ERROR: could 
not read file");
+                                       return -1;
+                                       }
+
+                               debug("Read "+blen+" bytes from "+fn+" for 
on-demand encoding at "+ce.fileOffset);
+                               }
+                       finally {
+                               fc.close();
+                               }
+                       }
+               catch( IOException x ) {
+                       err("I/O exception !",x);
+                       return -1;
+                       }
+
+               hc=HashCode512.create(PersistentHelper.toBytes(iobuf),0,blen);
+
+               result[0] = new ContentBlock();
+               if (!encoding.encryptContent(iobuf,hc,result[0])) {
+                       log(Level.SEVERE,"ERROR: encryption of content failed");
+                       return -1;
+                       }
+
+               hc=HashCode512.create(PersistentHelper.toBytes(result[0]));
+               debug("DEBUG: on-demand encoded content has query "+hc.toHex());
+               return ContentBlock.SIZE;
+       }
+
+       protected void iterator_helper_callback( HashCode512 key, ContentIndex 
ce, Object data, int dataLen, IterState sss )
+       {
+               try {
+                       sss.wsem.acquire();
+                       sss.next=(HashCode512) PersistentHelper.copy(key);
+                       sss.ce=(ContentIndex) PersistentHelper.copy(ce);
+                       sss.data = data;
+                       sss.len = dataLen;
+                       sss.sem.release();
+                       }
+               catch( InterruptedException x ) {
+                       err("",x);
+                       }
+       }
+
+       /**
+        * Thread that fetches the next entry from the database.
+        * The thread is created by makeDatabaseIteratorState
+        * and exits once we're through the database.
+        * @param sss
+        */
+
+       protected void iterator_helper( IterState sss )
+       {
+               int i;
+
+               try {
+                       sss.hasNext = true;
+                       for (i=0;i<buckets;i++) {
+                               sss.wsem.acquire();
+                               sss.bucket = i;
+                               sss.wsem.release();
+                               dbHandles[i].forEachEntry(new EntryCallback() {
+                                       public void onEntry( HashCode512 key, 
ContentIndex ce, Object data, int dataLen, Object closure )
+                                       {
+                                               
iterator_helper_callback(key,ce,data,dataLen,(IterState) closure);
+                                       }
+                                       },sss);
+                               }
+                       sss.wsem.acquire();
+                       sss.hasNext = false;
+                       sss.sem.release();
+                       }
+               catch( InterruptedException x ) {
+                       err("",x);
+                       }
+       }
+
+       protected boolean handleVLSResultSet( HashCode512 query, byte[] data, 
boolean[] duplicate )
+       {
+               ContentBlock[]  blocks;
+               int i,j;
+               int ret;
+
+               blocks=lfs.read(query);
+               if (blocks==null) {
+                       log(Level.WARNING,"lfs database inconsistent, trying to 
fix");
+                       if (computeHighDB(query).unlink(query)) {
+                               
dbAvailableBlocks[computeBucketGlobal(query)]=DB_DIRTY_AVAILABLE;
+                               }
+                       else {
+                               log(Level.WARNING,"Failed to fix lfs database 
inconsistency.");
+                               }
+                       return false;
+                       }
+
+               // check if the content is already present
+               ret=blocks.length;
+               for (i=0; i<ret; i++) {
+                       for (j=0; j<ContentBlock.SIZE && 
data[j]==blocks[i].content[j]; j++) {}
+
+                       if (j==ContentBlock.SIZE) {
+                               duplicate[0] = true;
+                               return true;
+                               }
+                       }
+               return lfs.append(query,(ContentBlock) 
PersistentHelper.readFully(ContentBlock.class,ByteBuffer.wrap(data)));
+       }
+
+       protected boolean migrateToVLS( byte[] old, int oldLen, HashCode512 
query, byte[] data, ContentIndex ce )
+       {
+               int             i;
+               boolean ret;
+               ContentBlock    block;
+
+               ret = true;
+
+               i = 0;
+               while ( (i < oldLen / ContentBlock.SIZE ) && ret) {
+                       block=(ContentBlock) 
PersistentHelper.readFully(ContentBlock.class,ByteBuffer.wrap(old,i*ContentBlock.SIZE,ContentBlock.SIZE));
+                       ret = lfs.append(query,block);
+                       i++;
+                       }
+
+               if (ret) {
+                       block=(ContentBlock) 
PersistentHelper.readFully(ContentBlock.class,ByteBuffer.wrap(data));
+                       ret = lfs.append(query,block);
+                       }
+
+               if (!ret) {
+                       lfs.remove(query);
+                       return ret;
+                       }
+               // put forwarding content (marked by size)
+               
ret=computeHighDB(query).writeContent(ce,data,0,VERY_LARGE_FILE); // data: 
random bits
+               
dbAvailableBlocks[computeBucketGlobal(query)]=DB_DIRTY_AVAILABLE;
+               return ret;
+       }
+
+       /**
+        * 3HASH and SBlock results require special treatment
+        * since multiple results are possible.
+        * @param query
+        * @param ce
+        * @param data
+        * @param oldLen
+        * @param duplicate
+        * @param len
+        * @param old
+        * @param oldImportance
+        * @return
+        */
+
+       protected boolean handle3HSBInsert( HashCode512 query, ContentIndex ce, 
byte[] data, int oldLen, boolean[] duplicate, int len, byte[] old, int 
oldImportance )
+       {
+               boolean ret;
+               byte[]  tmp;
+               int             n,i;
+
+               if (oldLen == -1) {
+                       // no old content, just write
+                       
dbAvailableBlocks[computeBucketGlobal(query)]=DB_DIRTY_AVAILABLE;
+                       return computeHighDB(query).writeContent(ce,data,0,len);
+                       }
+
+               // check if content is ALREADY in VLS store
+               if (oldLen == VERY_LARGE_FILE) {
+                       return handleVLSResultSet(query,data,duplicate);
+                       }
+
+               // Not VLS, check if the content is already present
+               for (n=0; n<oldLen/len; n++) {
+                       for (i=0; i<len && old[len*n+i]==data[i]; i++) {}
+                       if (i==len) {
+                               // content already there, abort
+                               duplicate[0] = true;
+                               return true;
+                               }
+                       }
+
+               // check if we need to *move* the content to VLS store
+               if (oldLen / ContentBlock.SIZE >= VERY_LARGE_SIZE) {
+                       return migrateToVLS(old, oldLen, query, data, ce);
+                       }
+
+               // else: default behavior: append
+
+               tmp = new byte[oldLen + len];
+               System.arraycopy(old,0,tmp,0,oldLen);
+               System.arraycopy(data,0,tmp,oldLen,len);
+               // Discussion: perhaps we should use eg max(a,b)? Otherwise n 
local inserts throws this through the ceiling...
+               // OTOH, if the same block was part of two files, it is really 
twice as important, so adding makes sense in some cases.
+               ce.importance = oldImportance + ce.importance;
+               ret = computeHighDB(query).writeContent(ce,tmp,0,oldLen+len);
+               
dbAvailableBlocks[computeBucketGlobal(query)]=DB_DIRTY_AVAILABLE;
+               return ret;
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/Migration.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/Migration.java      2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/Migration.java      2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,273 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs;
+
+import org.gnu.freeway.protocol.fs.esed2.*;
+import org.gnu.freeway.server.*;
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.net.*;
+
+import EDU.oswego.cs.dl.util.concurrent.*;
+
+import java.nio.*;
+import java.util.*;
+import java.util.logging.*;
+
+/**
+ * This module is responsible for pushing content out
+ * into the network.
+ */
+
+public class Migration extends LoggedObject
+{
+       /** use a 64-entry RCB buffer */
+       public static final int RCB_SIZE        =       128;
+
+       /** */
+       private CoreForProtocol coreAPI;
+
+       /** */
+       private StatusCallsService                      status;
+
+       /** */
+       private Stat            stat_handle_content_pushed;
+
+       /** Semaphore on which the RCB aquire thread waits if the RCB buffer is 
full. */
+       private Semaphore               aquireMoreSignal;
+
+       /** */
+       private Semaphore               doneSignal;
+
+       /** Lock for the RCB buffer. */
+       private Object                  lock;
+
+       /** Buffer with pre-fetched random content for migration. */
+       private ContentIndex[]  randomContentBuffer;
+
+       /** Highest index in RCB that is valid. */
+       private int                             rCBPos;
+
+       /** */
+       private Task                            gather_thread;
+
+       /** */
+       private Manager                 manager;
+
+
+       public Migration( Manager mgr )
+       {
+               super(true);
+               randomContentBuffer=new ContentIndex[RCB_SIZE];
+               manager=mgr;
+       }
+
+       public String toString()
+       {
+               return "xxx";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       /**
+        * Initialize the migration module.
+        * @param capi
+        */
+
+       public void init( CoreForProtocol capi )
+       {
+               Statistics      stats;
+
+               coreAPI=capi;
+
+               stats=capi.getApplication().getStatistics();
+               status=(StatusCallsService) 
capi.service(StatusCallsService.class);
+               stat_handle_content_pushed=stats.getHandle("# kb content pushed 
out as padding",Stat.VERBOSE);
+
+               Arrays.fill(randomContentBuffer,null);
+
+               aquireMoreSignal = new Semaphore(RCB_SIZE);
+               doneSignal = null;
+               lock=new Object();
+
+               gather_thread=new Task("MIGRATION-GATHER",new AbstractAction() {
+                       public void perform()
+                       {
+                               rcbAquire();
+                       }
+                       });
+               gather_thread.launch();
+
+               coreAPI.registerSendCallback(P2PChkResult.SIZE,new 
BufferFillCallback() {
+                       public boolean fillBuffer( HostIdentity receiver, 
ByteBuffer buf )
+                       {
+                               return activeMigrationCallback(receiver,buf);
+                       }
+                       });
+       }
+
+       /**
+        *
+        */
+
+       public void done()
+       {
+               coreAPI.unregisterSendCallback(P2PChkResult.SIZE,new 
BufferFillCallback() {
+                       public boolean fillBuffer( HostIdentity receiver, 
ByteBuffer buf )
+                       {
+                               return activeMigrationCallback(receiver,buf);
+                       }
+                       });
+
+               try {
+                       doneSignal = new Semaphore(0);
+                       aquireMoreSignal.release();
+                       doneSignal.acquire();
+                       aquireMoreSignal=null;
+                       doneSignal=null;
+                       lock=null;
+                       gather_thread.join();
+                       }
+               catch( InterruptedException x ) {
+                       err("",x);
+                       }
+       }
+
+       protected void rcbAquire()
+       {
+               ContentIndex    ce,cp;
+               boolean                 ok;
+
+               try {
+                       while (true) {
+                               aquireMoreSignal.acquire();
+                               if (doneSignal != null)
+                                       break;
+
+                               ce=new ContentIndex();
+                               ok = manager.retrieveRandomContent(ce);
+                               if (ok)
+                                       if 
(ce.type==ContentIndex.LOOKUP_TYPE_3HASH || 
ce.type==ContentIndex.LOOKUP_TYPE_SUPER)
+                                               ok = false;     // can not 
migrate these
+                               if (ok) {
+                                       cp=(ContentIndex) 
PersistentHelper.copy(ce);
+                                       synchronized(lock) {
+                                               randomContentBuffer[rCBPos++] = 
cp;
+                                               }
+                                       }
+                               else {
+                                       int load = status.getCPULoad();
+                                       if (load < 10)
+                                               load = 10;
+                                       
Scheduler.sleep(Scheduler.seconds(load/5));     // the higher the load, the 
longer the sleep, but at least 2 seconds
+                                       aquireMoreSignal.release();     // send 
myself signal to go again !
+                                       }
+                               }
+                       doneSignal.release();
+                       }
+               catch( InterruptedException x ) {
+                       err("",x);
+                       }
+       }
+
+       /**
+        * Select content for active migration.  Takes the best match from the
+        * randomContentBuffer (if the RCB is non-empty) and returns it.
+        * @param receiver
+        * @param ce
+        *
+        * @return false if the RCB is empty
+        */
+
+       protected boolean selectMigrationContent( HostIdentity receiver, 
ContentIndex ce )
+       {
+               int dist;
+               int minDist;
+               int minIdx;
+               int i;
+
+               minIdx = -1;
+               minDist = -1;   // max
+
+               synchronized(lock) {
+                       for (i=0;i<rCBPos;i++) {
+                               dist = 
receiver.distance(randomContentBuffer[i].hash);
+                               if (dist < minDist) {
+                                       minIdx = i;
+                                       minDist = dist;
+                                       }
+                               }
+                       if (minIdx == -1) {
+                               return false;
+                               }
+                       ce=(ContentIndex) 
PersistentHelper.copy(randomContentBuffer[minIdx]);
+                       randomContentBuffer[minIdx] = 
randomContentBuffer[--rCBPos];
+                       randomContentBuffer[rCBPos] = null;
+                       }
+               aquireMoreSignal.release();
+               return true;
+       }
+
+       /**
+        * Callback method for pushing content into the network.
+        * The method chooses either a "recently" deleted block
+        * or content that has a hash close to the receiver ID
+        * (randomized to guarantee diversity, unpredictability
+        * etc.).<p>
+        *
+        * @param receiver the receiver of the message
+        * @param buf
+        * @return the number of bytes written to
+        *   that buffer (must be a positive number).
+        */
+
+       protected boolean activeMigrationCallback( HostIdentity receiver, 
ByteBuffer buf )
+       {
+               ContentIndex    ce;
+
+               ce=new ContentIndex();
+               while (buf.remaining()>P2PChkResult.SIZE) {
+                       if (!selectMigrationContent(receiver,ce)) {
+                               return true;    // nothing selected, that's the 
end
+                               }
+
+                       // append it !
+                       if (!buildCHKReply(ce,buf)) {
+                               return false;   // abort early after any error
+                               }
+
+                       stat_handle_content_pushed.inc();
+                       }
+               return true;
+       }
+
+       /**
+        * Build a CHK reply message for some content
+        * selected for migration.
+        * @param ce
+        * @param buf
+        * @return OK on success, false on error
+        */
+
+       protected boolean buildCHKReply( ContentIndex ce, ByteBuffer buf )
+       {
+               ContentBlock[]  data;
+
+               if (ce.type==ContentIndex.LOOKUP_TYPE_3HASH || 
ce.type==ContentIndex.LOOKUP_TYPE_SUPER) {
+                       return false;
+                       }
+
+               data=manager.retrieveContent(ce.hash,ce,0,false);       // low 
prio! & should not matter for CHK anyway
+               if (data==null) {       // can happen if we're concurrently 
inserting, _should be_ rare but is OK !
+                       return false;
+                       }
+               if (data.length != ContentBlock.SIZE) {
+                       log(Level.WARNING,"BuildCHKReply got unsuitable block 
from db (len="+data.length+",type="+ce.type+")");
+                       return false;
+                       }
+               return PersistentHelper.write(data[0],buf);
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/MySQLHandle.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/MySQLHandle.java    2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/MySQLHandle.java    2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,1030 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs;
+
+import org.gnu.freeway.protocol.fs.esed2.*;
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+import java.sql.*;
+import java.util.logging.*;
+
+/**
+ * Handle for a high-level database (mysql, simple)
+ * mysql wrapper
+ *
+ * API for the "high-level" database libraries.
+ * Equivalent to what is specified in high_backend.h.
+ * A header specifying the interfaces that each (high-level) database backend 
(gdbm, tdb, mysql, etc.) must provide.
+ * Database: MySQL
+ *
+ * HIGHLIGHTS
+ *
+ * Pros
+ * + On up-to-date hardware where mysql can be used comfortably, this
+ *   module will have better performance than the other db choices
+ *   (according to our tests).
+ * + Its often possible to recover the mysql database from internal
+ *   inconsistencies. The other db choices do not support repair
+ *   (gnunet-check cannot fix problems internal to the dbmgr!).
+ *   For example, we have seen several cases where power failure
+ *   has ruined a gdbm database beyond repair.
+ * Cons
+ * - Memory usage (Comment: "I have 1G and it never caused me trouble")
+ * - Manual setup
+ *
+ * MANUAL SETUP INSTRUCTIONS
+ *
+ * 1) in /etc/gnunet.conf, set
+ *    <pre>
+ *
+ *    DATABASETYPE = "mysql"
+ *
+ *    </pre>
+ * 2) Then access mysql as root,
+ *    <pre>
+ *
+ *    # mysql -u root -p
+ *
+ *    </pre>
+ *    and do the following. [You should replace $USER with the username
+ *    that will be running the gnunetd process].
+ *    <pre>
+ *
+         CREATE DATABASE gnunet;
+         GRANT select,insert,update,delete,create,alter,drop
+                ON gnunet.* TO address@hidden;
+         SET PASSWORD FOR address@hidden('$the_password_you_like');
+         FLUSH PRIVILEGES;
+ *
+ *    </pre>
+ * 3) In the $HOME directory of $USER, create a ".my.cnf" file
+ *    with the following lines
+ *    <pre>
+
+         [client]
+         user=$USER
+         password=$the_password_you_like
+
+ *    </pre>
+ *
+ * Thats it. Note that .my.cnf file is a security risk unless its on
+ * a safe partition etc. The $HOME/.my.cnf can of course be a symbolic
+ * link. Even greater security risk can be achieved by setting no
+ * password for $USER.  Luckily $USER has only priviledges to mess
+ * up GNUnet's tables, nothing else (unless you give him more,
+ * of course).<p>
+ *
+ * 4) Still, perhaps you should briefly try if the DB connection
+ *    works. First, login as $USER. Then use,
+ *
+ *    <pre>
+ *    # mysql -u $USER
+ *    mysql> use gnunet
+ *    </pre>
+ *
+ *    If you get the message &quot;Database changed&quot; it probably works.
+ *
+ *    [If you get &quot;ERROR 2002: Can't connect to local MySQL server
+ *     through socket '/tmp/mysql.sock' (2)&quot; it may be resolvable by
+ *     &quot;ln -s /var/run/mysqld/mysqld.sock /tmp/mysql.sock&quot;
+ *     so there may be some additional trouble depending on your mysql setup.]
+ *
+ * REPAIRING TABLES
+ *
+ * - Its probably healthy to check your tables for inconsistencies
+ *   every now and then.
+ * - If you get odd SEGVs on gnunetd startup, it might be that the mysql
+ *   databases have been corrupted.
+ * - The tables can be verified/fixed in two ways;
+ *   1) by shutting down mysqld (mandatory!) and running
+ *   # myisamchk -r *.MYI
+ *   in /var/lib/mysql/gnunet/ (or wherever the tables are stored).
+ *   Another repair command is "mysqlcheck". The usable command
+ *   may depend on your mysql build/version. Or,
+ *   2) by executing
+ *   mysql> REPAIR TABLE data1024of
+ *   for each table in the gnunet database (USE gnunet; SHOW TABLES;)
+ *
+ * If you have problems related to the mysql module, your best
+ * friend is probably the mysql manual. The first thing to check
+ * is that mysql is basically operational, that you can connect
+ * to it, create tables, issue queries etc.
+ *
+ * Database: MySQL
+ *
+ * NOTE: This db module does NOT work with mysql v3.23.49 due to a bug
+ * in mysql.  All later versions should be fine, including the 4.0.x
+ * series. Current devel version is 4.0.16-log on debian/unstable.
+ *
+ * HIGHLIGHTS
+ *
+ * Pros
+ * + On up-to-date hardware where mysql can be used comfortably, this
+ *   module will have better performance than the other db choices
+ *   (according to our tests).
+ * + Its often possible to recover the mysql database from internal
+ *   inconsistencies. The other db choices do not support repair
+ *   (gnunet-check cannot fix problems internal to the dbmgr!).
+ *   For example, we have seen several cases where power failure
+ *   has ruined a gdbm database beyond repair.
+ * Cons
+ * - Memory usage (Comment: "I have 1G and it never caused me trouble")
+ * - Manual setup
+ *
+ * MANUAL SETUP INSTRUCTIONS
+ *
+ * 1) in /etc/gnunet.conf, set
+ *    <pre>
+ *
+ *    DATABASETYPE = "mysql"
+ *
+ *    </pre>
+ * 2) Then access mysql as root,
+ *    <pre>
+ *
+ *    # mysql -u root -p
+ *
+ *    </pre>
+ *    and do the following. [You should replace $USER with the username
+ *    that will be running the gnunetd process].
+ *    <pre>
+ *
+ CREATE DATABASE gnunet;
+ GRANT select,insert,update,delete,create,alter,drop
+ ON gnunet.* TO address@hidden;
+ SET PASSWORD FOR address@hidden('$the_password_you_like');
+ FLUSH PRIVILEGES;
+ *
+ *    </pre>
+ * 3) In the $HOME directory of $USER, create a ".my.cnf" file
+ *    with the following lines
+ *    <pre>
+
+ [client]
+ user=$USER
+ password=$the_password_you_like
+
+ *    </pre>
+ *
+ * Thats it. Note that .my.cnf file is a security risk unless its on
+ * a safe partition etc. The $HOME/.my.cnf can of course be a symbolic
+ * link. Even greater security risk can be achieved by setting no
+ * password for $USER.  Luckily $USER has only priviledges to mess
+ * up GNUnet's tables, nothing else (unless you give him more,
+ * of course).<p>
+ *
+ * 4) Still, perhaps you should briefly try if the DB connection
+ *    works. First, login as $USER. Then use,
+ *
+ *    <pre>
+ *    # mysql -u $USER
+ *    mysql> use gnunet
+ *    </pre>
+ *
+ *    If you get the message &quot;Database changed&quot; it probably works.
+ *
+ *    [If you get &quot;ERROR 2002: Can't connect to local MySQL server
+ *     through socket '/tmp/mysql.sock' (2)&quot; it may be resolvable by
+ *     &quot;ln -s /var/run/mysqld/mysqld.sock /tmp/mysql.sock&quot;
+ *     so there may be some additional trouble depending on your mysql setup.]
+ *
+ * REPAIRING TABLES
+ *
+ * - Its probably healthy to check your tables for inconsistencies
+ *   every now and then.
+ * - If you get odd SEGVs on gnunetd startup, it might be that the mysql
+ *   databases have been corrupted.
+ * - The tables can be verified/fixed in two ways;
+ *   1) by shutting down mysqld (mandatory!) and running
+ *   # myisamchk -r *.MYI
+ *   in /var/lib/mysql/gnunet/ (or wherever the tables are stored).
+ *   Another repair command is "mysqlcheck". The usable command
+ *   may depend on your mysql build/version. Or,
+ *   2) by executing
+ *   mysql> REPAIR TABLE data1024of
+ *   for each table in the gnunet database (USE gnunet; SHOW TABLES;)
+ *
+ * EFFICIENCY ISSUES
+ *
+ * If you suffer from too slow index/insert speeds,
+ * you might try to define /etc/gnunet.conf option
+ *
+ *   [AFS]
+ *   MYSQL_DELAYED = YES
+ *
+ * for small efficiency boost. The option will let MySQL bundle multiple
+ * inserts before actually writing them to disk. You shouldn't use this
+ * option unless you're an (my)sql expert and really know what you're doing.
+ * Especially, if you run into any trouble due to this, you're on your own.
+ *
+ * PROBLEMS?
+ *
+ * If you have problems related to the mysql module, your best
+ * friend is probably the mysql manual. The first thing to check
+ * is that mysql is basically operational, that you can connect
+ * to it, create tables, issue queries etc.
+ */
+
+public class MySQLHandle extends LoggedObject implements DBHandle
+{
+       private static long                     ramdomSpentTime;
+       private static int                              randomCalls;
+
+       /** */
+       private Connection      dbf;
+
+       private String  tableName;
+       /** database index */
+//     private int;
+
+       /** total number of databases */
+//     private int;
+
+       /** */
+       private Object          DATABASE_Lock_;
+
+       /** which column contains the Avg_row_length in SHOW TABLE STATUS 
resultset */
+       private int                     avgLength_ID;
+
+       /** use potentially unsafe delayed inserts? */
+       private boolean         useDelayed;
+
+
+       /**
+        * Popularity of _known_ 3hash keywords can be tracked by creating
+        * the following table and enabling TRACK_3HASH_QUERIES. In addition,
+        * you'll have to fill the table with <name,hash> pairs yourself
+        * (not provided). Tracking is not generally recommended as
+        * it may harm your deniability.
+        *
+        USE gnunet;
+        CREATE TABLE `dictionary` (
+        `name` tinyblob NOT NULL,
+        `hash` varchar(40) binary NOT NULL default '',
+        `hits` smallint(5) unsigned NOT NULL default '0',
+        PRIMARY KEY  (`hash`),
+        UNIQUE KEY `unique_name` (`name`(32))
+        ) TYPE=MyISAM
+        *
+        */
+
+       public static final boolean TRACK_3HASH_QUERIES =       false;
+
+
+       public MySQLHandle()
+       {
+               super(true);
+               avgLength_ID=-1;
+       }
+
+       public String toString()
+       {
+               return "xxx";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public boolean open( Prefs prefs, String backend, String name )
+       {
+               Connection                      conn;
+               Statement                       stmt;
+               ResultSet                       rset;
+               ResultSetMetaData       meta;
+               StringBuffer            sql;
+               boolean                         found;
+               int     i;
+
+               tableName="data"+name+"of"+backend;
+
+               try {
+                       
Class.forName(prefs.getString("AFS","MYSQL_DRIVER",null));
+                       conn=DriverManager.getConnection(
+                               
"jdbc:mysql://"+prefs.getString("AFS","MYSQL_HOST",null)+"/gnunet",
+                               prefs.getString("AFS","MYSQL_USER",null),
+                               prefs.getString("AFS","MYSQL_PASSWORD",null)
+                               );
+                       conn.setAutoCommit(false);
+                       }
+               catch( SQLException x ) {
+                       err("Unable to init MySQL !",x);
+                       return false;
+                       }
+               catch( ClassNotFoundException x ) {
+                       err("Unable to init MySQL !",x);
+                       return false;
+                       }
+
+               dbf=conn;
+
+               useDelayed=prefs.testString("AFS","MYSQL_DELAYED","YES");
+               DATABASE_Lock_=new Object();
+
+               found=false;
+               try {
+                       stmt=dbf.createStatement();
+                       try {
+                               sql=new StringBuffer();
+                               sql.append("CREATE TABLE IF NOT EXISTS 
"+tableName+" (");
+                               sql.append("  hash tinyblob NOT NULL 
default'',");
+                               sql.append("  priority int(11) NOT NULL default 
0,");
+                               sql.append("  type tinyint NOT NULL default 
0,");
+                               sql.append("  fileIndex smallint NOT NULL 
default 0,");
+                               sql.append("  fileOffset int(11) NOT NULL 
default 0,");
+                               sql.append("  doubleHash tinyblob NOT NULL 
default '',");
+                               sql.append("  content mediumblob NOT NULL 
default '',");
+                               sql.append("  PRIMARY KEY (hash(20)),");
+                               sql.append("  KEY priority (priority)");
+                               sql.append(") TYPE=MyISAM");
+                               stmt.executeUpdate(sql.toString());
+
+                               // find out which column contains the avg row 
length field and assume
+                               // that mysqld always gives it in the same 
order across calls :)
+                               sql.setLength(0);
+                               sql.append("SHOW TABLE STATUS ");
+                               sql.append("FROM gnunet ");
+                               sql.append("LIKE '"+tableName+"'");
+
+                               rset=stmt.executeQuery(sql.toString());
+                               if (rset.next()) {
+                                       meta=rset.getMetaData();
+
+                                       for (i=0; i<meta.getColumnCount() && 
!found; i++) {
+                                               if 
(meta.getColumnName(i+1).equals("Avg_row_length")) {
+                                                       avgLength_ID=i;
+                                                       found=true;
+                                                       }
+                                               }
+                                       if (!found) {
+                                               log(Level.SEVERE,"ERROR: mysql 
Avg_row_length not found in SHOW TABLE STATUS");
+                                               return false;
+                                               }
+                                       }
+                               rset.close();
+
+                               if (avgLength_ID == -1) {
+                                       log(Level.SEVERE,"FATAL: could not find 
row avg_row_length");
+                                       return false;
+                                       }
+                               }
+                       finally {
+                               stmt.close();
+                               }
+                       }
+               catch( SQLException x ) {
+                       err("Failed to init !",x);
+                       return false;
+                       }
+               return true;
+       }
+
+       public void close()
+       {
+               DATABASE_Lock_=null;
+               try {
+                       dbf.close();
+                       dbf=null;
+                       }
+               catch( SQLException x ) {
+                       err("Failed to close connection !",x);
+                       }
+       }
+
+       public void drop()
+       {
+               Statement               stmt;
+               StringBuffer    sql;
+
+               try {
+                       sql=new StringBuffer();
+                       sql.append("DROP TABLE "+tableName);
+
+                       stmt=dbf.createStatement();
+                       try {
+                               stmt.executeUpdate(sql.toString());
+                               }
+                       finally {
+                               stmt.close();
+                               }
+                       }
+               catch( SQLException x ) {
+                       err("Failed to close connection !",x);
+                       }
+
+               close();
+       }
+
+       public byte[] readContent( HashCode512 query, ContentIndex ce, int prio 
)
+       {
+               byte[]                  tmp,result;
+               ResultSet               rset;
+               Statement               stmt;
+               StringBuffer    sql;
+               int                             _prio=-1;
+
+               result=null;
+               synchronized(DATABASE_Lock_) {
+                       sql=new StringBuffer();
+                       sql.append("SELECT content, type, priority, doubleHash, 
fileOffset, fileIndex ");
+                       sql.append("FROM "+tableName+" ");
+                       sql.append("WHERE hash='"+escape(query)+"'");
+
+                       try {
+                               
stmt=dbf.createStatement(ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_UPDATABLE);
+                               try {
+                                       rset=stmt.executeQuery(sql.toString());
+
+                                       if (rset.next()) {
+                                               result=rset.getBytes(1);
+
+                                               ce.type = rset.getInt(2);
+                                               ce.importance = rset.getInt(3);
+                                               if 
(ce.type==ContentIndex.LOOKUP_TYPE_3HASH) {
+                                                       tmp=rset.getBytes(4);
+                                                       if (tmp!=null && 
tmp.length==HashCode512.SIZE) {
+                                                               
ce.hash=(HashCode512) 
PersistentHelper.readFully(HashCode512.class,ByteBuffer.wrap(tmp));
+                                                               }
+                                                       }
+                                               else {
+                                                       ce.hash=(HashCode512) 
PersistentHelper.copy(query);
+                                                       }
+                                               ce.fileOffset=rset.getInt(5);
+                                               ce.fileNameIndex=rset.getInt(6);
+
+                                               if (prio != 0) {
+                                                       //todo: debugguer le 
UPDATE qui devrait se faire ICI
+//                                                     
rset.updateInt(3,rset.getInt(3)+prio);
+//                                                     rset.updateRow();
+
+                                                       
_prio=rset.getInt(3)+prio;
+                                                       }
+                                               }
+
+                                       rset.close();
+                                       }
+                               finally {
+                                       stmt.close();
+                                       }
+
+                               if (TRACK_3HASH_QUERIES) {
+                                       if 
(ce.type==ContentIndex.LOOKUP_TYPE_3HASH) {
+                                               stmt=dbf.createStatement();
+                                               try {
+                                                       
stmt.executeUpdate("UPDATE dictionary SET hits=hits+1 WHERE 
hash='"+escape(query)+"'");
+                                                       }
+                                               finally {
+                                                       stmt.close();
+                                                       }
+                                               }
+                                       }
+
+                               //tempo (a supprimer apres debug)
+                               if (_prio!=0) {
+                                       sql=new StringBuffer();
+                                       sql.append("UPDATE "+tableName+" ");
+                                       sql.append("SET priority="+_prio+" ");
+                                       sql.append("WHERE 
hash='"+escape(query)+"'");
+
+                                       stmt=dbf.createStatement();
+                                       try {
+                                               
stmt.executeUpdate(sql.toString());
+                                               }
+                                       finally {
+                                               stmt.close();
+                                               }
+                                       }
+                               }
+                       catch( SQLException x ) {
+                               err("Failed to read content !",x);
+                               result=null;
+                               }
+                       }
+               return result;
+       }
+
+       public boolean writeContent( ContentIndex ce, byte[] block, int offset, 
int length )
+       {
+               Statement               stmt;
+               HashCode512             tripleHash;
+               StringBuffer    sql;
+               String                  doubleHash,escapedBlock,escapedHash;
+
+               synchronized(DATABASE_Lock_) {
+                       if (ce.type== ContentIndex.LOOKUP_TYPE_3HASH) {
+                               
tripleHash=HashCode512.create(PersistentHelper.toBytes(ce.hash));
+                               escapedHash=escape(tripleHash);
+                               doubleHash = escape(ce.hash);
+                               }
+                       else {
+                               doubleHash = null;
+                               escapedHash=escape(ce.hash);
+                               }
+
+                       escapedBlock=escape(block,offset,length);
+
+                       sql=new StringBuffer();
+                       sql.append("REPLACE "+(useDelayed ? "DELAYED" : "")+" 
INTO "+tableName+" ");
+                       
sql.append("(content,hash,priority,fileOffset,fileIndex,doubleHash,type)");
+                       sql.append(" VALUES (");
+                       sql.append("'"+(length > 0 ? escapedBlock : "")+"',");
+                       sql.append("'"+escapedHash+"',");
+                       sql.append("'"+ce.importance+"',");
+                       sql.append("'"+ce.fileOffset+"',");
+                       sql.append("'"+ce.fileNameIndex+"',");
+                       sql.append("'"+(doubleHash!=null ? doubleHash : 
"")+"',");
+                       sql.append(ce.type);
+                       sql.append(")");
+
+                       try {
+                               stmt=dbf.createStatement();
+                               try {
+                                       stmt.executeUpdate(sql.toString());
+                                       }
+                               finally {
+                                       stmt.close();
+                                       }
+                               }
+                       catch( SQLException x ) {
+                               err("Failed to write content !",x);
+
+                               //todo: a supprimer apres debug
+                               
log(Level.SEVERE,Utils.toString(block,offset,offset+length,"SEQ."));
+                               return false;
+                               }
+                       }
+               return true;
+       }
+
+       /**
+        * Get the number of entries in the database.
+        * Get the number of entries in the database.
+        *
+        * @return -1 on error, otherwise the number of entries
+        */
+
+       public int countContentEntries()
+       {
+               Statement               stmt;
+               ResultSet               rset;
+               StringBuffer    sql;
+               int                             count;
+
+               count=0;
+               synchronized(DATABASE_Lock_) {
+                       sql=new StringBuffer();
+                       sql.append("SELECT count(*) ");
+                       sql.append("FROM "+tableName);
+
+                       try {
+                               stmt=dbf.createStatement();
+                               try {
+                                       rset=stmt.executeQuery(sql.toString());
+                                       if (rset.next()) {
+                                               count=rset.getInt(1);
+                                               }
+                                       rset.close();
+                                       }
+                               finally {
+                                       stmt.close();
+                                       }
+                               }
+                       catch( SQLException x ) {
+                               err("Failed to get minimum priority !",x);
+                               count=-1;
+                               }
+                       }
+               return count;
+       }
+
+       /**
+        * Get the lowest priority of content in the DB.
+        * Get the lowest priority of content in the store.
+        * Get the lowest priority value of all content in the store.
+        *
+        * @return the lowest priority
+        */
+
+       public int getMinimumPriority()
+       {
+               Statement               stmt;
+               ResultSet               rset;
+               StringBuffer    sql;
+               int                             minPrio;
+
+               minPrio=0;
+               synchronized(DATABASE_Lock_) {
+                       sql=new StringBuffer();
+                       sql.append("SELECT MIN(priority) ");
+                       sql.append("FROM "+tableName);
+
+                       try {
+                               stmt=dbf.createStatement();
+                               try {
+                                       rset=stmt.executeQuery(sql.toString());
+                                       if (rset.next()) {
+                                               minPrio=rset.getInt(1);
+                                               }
+                                       rset.close();
+                                       }
+                               finally {
+                                       stmt.close();
+                                       }
+                               }
+                       catch( SQLException x ) {
+                               err("Failed to get minimum priority !",x);
+                               }
+                       }
+               return minPrio;
+       }
+
+       /**
+        * Call a method for each key in the database and
+        * call the callback method on it.
+        * Call a method for each key in the database and
+        * call the callback method on it.
+        *
+        * @param callback the callback method
+        * @param data second argument to all callback calls
+        * @return the number of items stored in the content database
+        */
+
+       public int forEachEntry( EntryCallback callback, Object data )
+       {
+               byte[]                  result,tmp;
+               Statement               stmt;
+               ResultSet               rset;
+               ContentIndex    ce;
+               HashCode512             h;
+               StringBuffer    sql;
+               int                             count;
+
+               count=0;
+               synchronized(DATABASE_Lock_) {
+                       sql=new StringBuffer();
+                       sql.append("SELECT content, type, priority, doubleHash, 
fileOffset, fileIndex, hash ");
+                       sql.append("FROM "+tableName);
+
+                       try {
+                               stmt=dbf.createStatement();
+                               try {
+                                       rset=stmt.executeQuery(sql.toString());
+                                       while (rset.next()) {
+                                               ce=new ContentIndex();
+
+                                               result=rset.getBytes(1);
+                                               ce.type=rset.getInt(2);
+                                               ce.importance=rset.getInt(3);
+                                               if 
(ce.type==ContentIndex.LOOKUP_TYPE_3HASH) {
+                                                       tmp=rset.getBytes(4);
+                                                       if (tmp!=null && 
tmp.length==HashCode512.SIZE) {
+                                                               
ce.hash=(HashCode512) 
PersistentHelper.readFully(HashCode512.class,ByteBuffer.wrap(tmp));
+                                                               }
+                                                       }
+                                               else {
+                                                       tmp=rset.getBytes(7);
+                                                       ce.hash=(HashCode512) 
PersistentHelper.readFully(HashCode512.class,ByteBuffer.wrap(tmp));
+                                                       }
+                                               ce.fileOffset=rset.getInt(5);
+                                               ce.fileNameIndex=rset.getInt(6);
+
+                                               tmp=rset.getBytes(7);
+                                               h=(HashCode512) 
PersistentHelper.readFully(HashCode512.class,ByteBuffer.wrap(tmp));
+
+                                               
callback.onEntry(h,ce,result,(result!=null ? result.length : 0),data);
+                                               count++;
+                                               }
+                                       rset.close();
+                                       }
+                               finally {
+                                       stmt.close();
+                                       }
+                               }
+                       catch( SQLException x ) {
+                               err("Failed to iterate over entries !",x);
+                               count=-1;
+                               }
+                       }
+               return count;
+       }
+
+       /**
+        * Return a random key from the database (just the key, not the
+        * content!).
+        * Get a random content block from MySQL database.
+        * Tries to use indexes efficiently.
+        *
+        * Code supplied by H. Pagenhardt
+        *
+        * @param ce the meta-data of the random content (set)/output 
information about the key
+        * @return true on success, false on error
+        */
+
+       public boolean getRandomContent( ContentIndex ce )
+       {
+               byte[]                  tmp;
+               Statement               stmt;
+               ResultSet               rset;
+               HashCode512             hash;
+               StringBuffer    sql;
+               long                    startTime,endTime;
+               boolean                 found;
+
+               startTime=Scheduler.now();
+
+               synchronized(DATABASE_Lock_) {
+                       found=false;
+
+                       hash=HashCode512.random();
+
+                       sql=new StringBuffer();
+                       sql.append("SELECT hash, type, priority, fileOffset, 
fileIndex ");
+                       sql.append("FROM "+tableName+" ");
+                       sql.append("WHERE hash >= '"+escape(hash)+"' ");
+                       sql.append("AND (type = 
"+ContentIndex.LOOKUP_TYPE_CHK+" OR type = "+ContentIndex.LOOKUP_TYPE_CHKS+") 
");
+                       sql.append("LIMIT 1");
+
+                       try {
+                               stmt=dbf.createStatement();
+                               try {
+                                       rset=stmt.executeQuery(sql.toString());
+
+                                       found=rset.next();
+                                       if (!found) {
+                                               rset.close();
+
+                                               sql.setLength(0);
+                                               sql.append("SELECT 
hash,type,priority,fileOffset,fileIndex ");
+                                               sql.append("FROM "+tableName+" 
");
+                                               sql.append("WHERE hash >= '' ");
+                                               sql.append("AND (type = 
"+ContentIndex.LOOKUP_TYPE_CHK+" OR type = "+ContentIndex.LOOKUP_TYPE_CHKS+") 
");
+                                               sql.append("LIMIT 1");
+
+                                               
rset=stmt.executeQuery(sql.toString());
+                                               found=rset.next();
+                                               }
+
+                                       if (found) {
+                                               tmp=rset.getBytes(1);
+                                               ce.hash=(HashCode512) 
PersistentHelper.readFully(HashCode512.class,ByteBuffer.wrap(tmp));
+                                               ce.type=rset.getInt(2);
+                                               ce.importance=rset.getInt(3);
+                                               ce.fileOffset=rset.getInt(4);
+                                               ce.fileNameIndex=rset.getInt(5);
+                                               }
+
+                                       rset.close();
+                                       }
+                               finally {
+                                       stmt.close();
+                                       }
+                               }
+                       catch( SQLException x ) {
+                               err("Failed to get random content !",x);
+                               found=false;
+                               }
+                       }
+
+               endTime=Scheduler.now();
+               randomCalls++;
+               ramdomSpentTime+=(endTime-startTime);
+               debug("getRandomContent spent total 
"+Scheduler.toMillis(ramdomSpentTime)+"ms / "+randomCalls+" calls.");
+
+               if (!found) {
+                       log(Level.FINEST,"MySQL random didn't find anything !");
+                       }
+               return found;
+       }
+
+       /**
+        * Delete low-priority content from the database
+        * Deletes some least important content
+        *
+        * @param count the number of entries to delete/the number of 1kb 
blocks to free
+        * @param callback method to call on each deleted entry/method to call 
on each deleted item
+        * @param closure extra argument to callback
+        * @return true on success, false on error
+        */
+
+       public boolean deleteContent( int count, EntryCallback callback, Object 
closure )
+       {
+               HashCode512[]   deleteThese;
+               byte[]                  data,tmp;
+               ContentIndex    ce;
+               Statement               stmt;
+               ResultSet               rset;
+               StringBuffer    sql;
+               int                             k;
+
+               synchronized(DATABASE_Lock_) {
+                       try {
+                               stmt=dbf.createStatement();
+                               try {
+                                       // collect hashes to delete
+                                       sql=new StringBuffer();
+                                       sql.append("SELECT hash FROM 
"+tableName+" ");
+                                       sql.append("ORDER BY priority ASC LIMIT 
"+count);
+
+                                       deleteThese=new HashCode512[count];
+
+                                       k=0;
+                                       rset=stmt.executeQuery(sql.toString());
+                                       while (rset.next()) {
+                                               tmp=rset.getBytes(1);
+                                               deleteThese[k++]=(HashCode512) 
PersistentHelper.readFully(HashCode512.class,ByteBuffer.wrap(tmp));
+                                               }
+                                       rset.close();
+                                       }
+                               finally {
+                                       stmt.close();
+                                       }
+
+                               // delete collected hashes
+                               count=k;
+                               for(k=0; k<count; k++) {
+                                       ce=new ContentIndex();
+
+                                       data=readContent(deleteThese[k],ce,0);
+                                       if (data!=null && callback!=null) {
+                                               
callback.onEntry(deleteThese[k],ce,data,data.length,closure);
+                                               }
+
+                                       sql.setLength(0);
+                                       sql.append("DELETE FROM "+tableName+" 
");
+                                       sql.append("WHERE 
hash='"+escape(deleteThese[k])+"'");
+
+                                       stmt=dbf.createStatement();
+                                       try {
+                                               
stmt.executeUpdate(sql.toString());
+                                               }
+                                       finally {
+                                               stmt.close();
+                                               }
+                                       }
+                               }
+                       catch( SQLException x ) {
+                               err("Failed to delete content !",x);
+                               return false;
+                               }
+                       }
+               return true;
+       }
+
+       /**
+        * Estimate how many blocks can be stored in the DB
+        * before the quota is reached.
+        * Estimate how many blocks can be stored in the DB
+        * before the quota is reached.
+        *
+        * NOTE: this function can not be performed relying on
+        * Data_length+Index_length from "SHOW TABLE STATUS" because
+        * those values seem not to be decreasing in real time.
+        * On mysql 4.0.16, Avg_row_len seems to be updating in real
+        * time w.r.t. insertions and deletions.
+        *
+        * @param quota the number of kb available for the DB
+        * @return number of blocks left
+        */
+
+       public int estimateAvailableBlocks( int quota )
+       {
+               Statement               stmt;
+               ResultSet               rset;
+               StringBuffer    sql;
+               long                    avgRowLen,rowsInTable;
+               int                             kbUsed;
+
+               avgRowLen=-1;
+               rowsInTable=0;
+               synchronized(DATABASE_Lock_) {
+                       try {
+                               stmt=dbf.createStatement();
+                               try {
+                                       // find out average row length in bytes
+                                       // FIXME: probably unnecessary to check 
avg row length every time
+                                       sql=new StringBuffer();
+                                       sql.append("SHOW TABLE STATUS ");
+                                       sql.append("FROM gnunet ");
+                                       sql.append("LIKE '"+tableName+"'");
+
+                                       rset=stmt.executeQuery(sql.toString());
+                                       if (!rset.next()) {
+                                               log(Level.SEVERE,"Unable to 
estimate available blocks (query \""+sql+"\" had no results).");
+                                               return 0;
+                                               }
+
+                                       if 
(avgLength_ID>=rset.getMetaData().getColumnCount() || avgLength_ID<0) {
+                                               log(Level.SEVERE,"Unable to 
estimate available blocks (query \""+sql+"\" had 
"+rset.getMetaData().getColumnCount()+" rows, expected > "+avgLength_ID+").");
+                                               return 0;
+                                               }
+
+                                       avgRowLen=rset.getLong(avgLength_ID+1);
+                                       if (rset.wasNull()) {
+                                               avgRowLen=-1;
+                                               }
+                                       
debug("******************************************* avgRowLen = "+avgRowLen);
+                                       rset.close();
+
+                                       if (avgRowLen<0) {
+                                               log(Level.SEVERE,"FATAL: mysql 
claimed avgRowLen<0 (or failed)");
+                                               return 0;
+                                               }
+
+                                       // find number of entries (rows)
+                                       sql.setLength(0);
+                                       sql.append("SELECT count(*) ");
+                                       sql.append("FROM "+tableName);
+
+                                       rset=stmt.executeQuery(sql.toString());
+
+                                       //todo: if (!rset.next()) {}
+                                       if (rset.next()) {
+                                               rowsInTable=rset.getLong(1);
+                                               }
+                                       rset.close();
+                                       }
+                               finally {
+                                       stmt.close();
+                                       }
+                               }
+                       catch( SQLException x ) {
+                               err("Failed to estimate available blocks !",x);
+                               return 0;
+                               }
+                       }
+
+               kbUsed=(int) ((rowsInTable * avgRowLen) / 1024);
+               debug("Estimate content available 
(q="+quota+",u="+kbUsed+",rem="+(quota-kbUsed)+")");
+               return quota - kbUsed;
+       }
+
+       /**
+        * Free space in the database by removing an entry.
+        *
+        * @param name the key of the entry to remove/the query of the entry to 
remove/hashcode for the block to be deleted
+        * @return false on error, true if ok.
+        * Free space in the database by removing one block
+        */
+
+       public boolean unlink( HashCode512 name )
+       {
+               Statement               stmt;
+               StringBuffer    sql;
+
+               synchronized(DATABASE_Lock_) {
+                       sql=new StringBuffer();
+                       sql.append("DELETE FROM "+tableName+" ");
+                       sql.append("WHERE hash='"+escape(name)+"'");
+
+                       try {
+                               stmt=dbf.createStatement();
+                               try {
+                                       stmt.executeUpdate(sql.toString());
+                                       }
+                               finally {
+                                       stmt.close();
+                                       }
+                               }
+                       catch( SQLException x ) {
+                               err("Failed to unlink "+name.toHex()+" !",x);
+                               return false;
+                               }
+                       }
+               return true;
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public static String escape( HashCode512 h )
+       {
+               return escape(PersistentHelper.toBytes(h));
+       }
+
+       public static String escape( byte[] b )
+       {
+               return escape(b,0,b.length);
+       }
+
+       public static String escape( byte[] b, int offset, int length )
+       {
+               StringBuffer    buf;
+               int                             i;
+               char                    c;
+
+               buf=new StringBuffer();
+               for (i=0; i<length; i++) {
+                       c=(char) (b[offset+i] & 0x000000ff);
+                       switch (c) {
+                               case '\'': buf.append("''"); break;
+                               case '\\': buf.append("\\\\"); break;
+                               default: buf.append(c); break;
+                               }
+                       }
+               return buf.toString();
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/NativeFSProtocol.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/NativeFSProtocol.java       
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/NativeFSProtocol.java       
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,68 @@
+ /*
+      This file is part of Freeway
+
+      Freeway 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, or (at your
+      option) any later version.
+
+      Freeway 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 Freeway; see the file COPYING.  If not, write to the
+      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+      Boston, MA 02111-1307, USA.
+ */
+
+package org.gnu.freeway.protocol.fs;
+
+import org.gnu.freeway.protocol.AbstractProtocol;
+import org.gnu.freeway.protocol.Protocol;
+import org.gnu.freeway.server.CPluginLoader;
+import org.gnu.freeway.server.CoreAPI;
+import org.gnu.freeway.server.CoreForProtocol;
+
+/**
+ * @file NativeFSProtocol.java
+ * @brief 
+ * @author mdonoughe
+ */
+public class NativeFSProtocol extends AbstractProtocol implements Protocol {
+       private CPluginLoader loader;
+       
+       public NativeFSProtocol() {
+               super();
+               
+               loader = CPluginLoader._;
+       }
+       
+       /* (non-Javadoc)
+        * @see 
org.gnu.freeway.protocol.Protocol#init(org.gnu.freeway.server.CoreForProtocol)
+        */
+       public boolean init(CoreForProtocol api) {
+               boolean ok = super.init(api);
+               CoreAPI._.setProtocolCore(api);
+               ok &= CoreAPI.OK.getValue() == 
loader.loadApplicationModule(CPluginLoader.classToLibraryName(getName()));
+               return ok;
+       }
+       
+       /* (non-Javadoc)
+        * @see org.gnu.freeway.protocol.Protocol#done()
+        */
+       public void done() {
+               
loader.unloadApplicationModule(CPluginLoader.classToLibraryName(getName()));
+               super.done();
+       }
+       
+       public String toString() {
+               String ret = this.getClass().getSimpleName();
+               if(ret.startsWith("Native"))
+                       ret = ret.substring(6);
+               if(ret.endsWith("Protocol"))
+                       ret = ret.substring(0, ret.length() - 8);
+               return ret.toLowerCase();
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/Policy2.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/Policy2.java        2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/Policy2.java        2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,117 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs;
+
+import org.gnu.freeway.server.*;
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.crypto.*;
+
+/**
+ * Policy interface. This is the interface to the C part of the policy.
+ * resource allocation (storage space, routing) implementation
+ */
+
+public class Policy2 extends Object
+{
+       /** Drop the query if & with this bitmask is 0 */
+       public static final int QUERY_DROPMASK  =       0x00FF0000;
+
+       /** Send answer if local files match */
+       public static final int QUERY_ANSWER    =       0x00020000;
+
+       /** Forward the query, priority is encoded in QUERY_PRIORITY_BITMASK */
+       public static final int QUERY_FORWARD   =       0x00040000;
+
+       /** Indirect the query (use this as the originating node) */
+       public static final int QUERY_INDIRECT  =       0x00080000;
+
+       /** Maximum priority to use (apply this bitmask to the QueryPolicy) */
+       public static final int QUERY_PRIORITY_BITMASK  =       0x0000FFFF;
+
+       /** Bandwidth value of an (effectively) 0-priority query. */
+       public static final double      QUERY_BANDWIDTH_VALUE   =       0.01;
+
+       /** Bandwidth value of a 0-priority content (must be fairly high 
compared to query since content is
+        typically significantly larger -- and more valueable since it can take 
many queries to get one piece of content). */
+       public static final double      CONTENT_BANDWIDTH_VALUE =       0.8;
+
+       private CoreForProtocol coreAPI;
+
+
+       public Policy2( CoreForProtocol capi )
+       {
+               super();
+               coreAPI=capi;
+       }
+
+       public String toString()
+       {
+               return "xxx";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       /**
+        * A query has been received. The question is, if it should be forwarded
+        * and if with which priority. Routing decisions(to whom) are to be 
taken elsewhere.
+        * <p>
+        *
+        * @param sender the host sending us the query
+        * @param priority the priority the query had when it came in, may be 
an arbitrary number if the
+        *        sender is malicious! Cap by trustlevel first!
+        * @return binary encoding: QUERY_XXXX constants
+        */
+
+       public int evaluateQuery( HostIdentity sender, int priority )
+       {
+               int netLoad = ((StatusCallsService) 
coreAPI.service(StatusCallsService.class)).getNetworkLoadUp();
+
+               if (netLoad < 50)
+                       return 0 /* minimum priority */ |
+                       QUERY_ANSWER | QUERY_FORWARD | QUERY_INDIRECT;
+               /* charge! */
+               priority = - coreAPI.changeTrust(sender, -priority);
+               if (netLoad < 100 + priority)
+                       return ((priority) & QUERY_DROPMASK) |
+                       QUERY_ANSWER | QUERY_FORWARD | QUERY_INDIRECT;
+               else if (netLoad < 100 + 10 * priority)
+                       return ((priority) & QUERY_DROPMASK) |
+                       QUERY_ANSWER | QUERY_FORWARD;
+               else if (netLoad < 100)
+                       return ((priority) & QUERY_DROPMASK) | QUERY_ANSWER;
+               else
+                       return 0; /* drop entirely */
+       }
+
+       /**
+        * Some content dropped by. We may want to store it locally, or not.
+        * The policy adjusts the priority and returns the effective
+        * importance for the content.
+        *
+        * @param hc the query
+        * @param priority of the original query
+        * @return -1 if the content should be dropped, the
+        *   priority for keeping it otherwise/-1 if the content should not be 
replicated,
+        *   otherwise the new priority for the lookup database
+        */
+
+       public int evaluateContent( HashCode512 hc, int priority )
+       {
+               int distance;
+               int j;
+
+               distance = coreAPI.getIdentity().distance(hc);
+               /* compute 'log' of distance */
+               j = 16;
+               while (distance > 0) {
+                       distance = distance>>1;
+                       j--;
+               }
+               if (j < 0)
+                       return -1;
+               return priority * j;
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/QueryManager.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/QueryManager.java   2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/QueryManager.java   2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,694 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs;
+
+import org.gnu.freeway.protocol.fs.esed2.*;
+import org.gnu.freeway.server.*;
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+
+/**
+ * The query manager is responsible for queueing queries
+ * forwarding of queries
+ *
+ * The query manager is responsible for queueing queries.  Queued
+ * queries are used to fill buffers (instead of using noise). The QM
+ * is also responsible for selecting the initial set of nodes that
+ * will receive the query. For a good choice, it keeps track of which
+ * nodes were recently hot in answering queries. Some randomness is
+ * preserved to ensure that we potentially find a better path.<p>
+ *
+ * Routing is an incredibly hard problem, so please consider
+ * consulting with other gnunet-developers before making any
+ * significant changes here, even if you have CVS write access.
+ */
+
+public class QueryManager extends LoggedObject implements AFSConstants
+{
+       /** Set to 'YES' to play with 0.6.2b (and earlier) behavior. */
+       private static final boolean    TRADITIONAL_SELECTION   =       false;
+
+       /** of how many outbound queries do we simultaneously keep track ? */
+       public static final int QUERY_RECORD_COUNT      =       512;
+
+       public static final double      FINAL_GOAL      =       3.0;
+
+       /** how many peers should be selected on average? */
+       public static final double      SELECT_GOAL     =       2.0;
+
+       /** How much is a query worth 'in general' (even if there is no trust 
relationship between the peers !).
+        Multiplied by the number of queries in the request.  20 is for '20 
bytes / hash', so this is kind of the base unit. */
+       public static final int BASE_QUERY_PRIORITY     =       20;
+
+       /** Array of the queries we are currently sending out. */
+       private QueryRecord[]   queries;
+       private int                             zePos;
+
+       /** Mutex for all query manager structures. */
+       private Object                  queryManagerLock;
+
+       private int             Xaverage        = 0;
+       private double  Xweight = 4.0;
+       private double  Yweight = 4.0;
+       private Scheduler               scheduler;
+       private ScheduledTask           ageTask;
+       private CoreForProtocol coreAPI;
+
+       /** Linked list tracking reply statistics.  Synchronize access using 
the queryManagerLock! */
+       private ReplyTrackData  rtdList;
+
+
+       public QueryManager()
+       {
+               super(true);
+               queries=new QueryRecord[QUERY_RECORD_COUNT];
+               ageTask=new ScheduledTask("AGE-RTD",new 
EvalAction(this,"ageRTD"),Scheduler.MINUTES_2);
+       }
+
+       public String toString()
+       {
+               return "Query manager";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       /**
+        * Initialize the query management.
+        * @param capi
+        */
+
+       public void init( CoreForProtocol capi )
+       {
+               int     i;
+
+               coreAPI=capi;
+               scheduler=capi.getApplication().getScheduler();
+
+               for (i=0;i<QUERY_RECORD_COUNT;i++) {
+                       queries[i]=new QueryRecord();
+                       queries[i].expires = 0; /* all expired */
+                       queries[i].msg = null;
+               }
+               queryManagerLock = coreAPI.getConnectionModuleLock();
+               coreAPI.registerSendCallback(P2PQuery.SIZE+HashCode512.SIZE,new 
BufferFillCallback() {
+                       public boolean fillBuffer( HostIdentity receiver, 
ByteBuffer buf )
+                       {
+                               return fillInQuery(receiver,buf);
+                       }
+               });
+
+               scheduler.addJob(ageTask,Scheduler.MINUTES_2);
+       }
+
+       /**
+        * Shutdown query management.
+        */
+
+       public void done()
+       {
+               int     i;
+
+               scheduler.deleteJob(ageTask);
+
+               for (i=0; i<QUERY_RECORD_COUNT; i++) {
+                       queries[i]=null;
+                       }
+
+               
coreAPI.unregisterSendCallback(P2PQuery.SIZE+HashCode512.SIZE,new 
BufferFillCallback() {
+                       public boolean fillBuffer( HostIdentity receiver, 
ByteBuffer buf )
+                       {
+                               return fillInQuery(receiver,buf);
+                       }
+               });
+       }
+
+       /**
+        * Take a query and forward it to the appropriate number of nodes 
(depending on load, queue, etc).
+        *
+        * @param msg   the query
+        * @param origin        where did the query come from?
+        * @param client        where did the query come from? (if it was a 
client)
+        */
+
+       public void forwardQuery( P2PQuery msg, HostIdentity origin, CSSession 
client )
+       {
+               long now;
+               QueryRecord  qr;
+               QueryRecord             dummy=new QueryRecord();
+               long oldestTime;
+               long expirationTime;
+               int oldestIndex;
+               int i;
+               boolean noclear = false;
+
+               debug("DEBUG: forwarding query for "+msg.getQuery(0).toHex()+" 
with ttl "+msg.getTTL());
+
+               now=Scheduler.now();
+
+               synchronized(queryManagerLock) {
+                       oldestIndex = -1;
+                       expirationTime = now + msg.getTTL();
+                       oldestTime = expirationTime;
+
+                       for (i=0; i<QUERY_RECORD_COUNT; i++) {
+                               if (queries[i].expires<oldestTime) {
+                                       oldestTime=queries[i].expires;
+                                       oldestIndex=i;
+                                       }
+
+                               if (queries[i].msg==null) {
+                                       continue;
+                                       }
+
+                               if (    queries[i].msg.sameQueries(msg)) {
+                                       /* We have exactly this query pending 
already. Replace existing query! */
+
+                                       oldestIndex = i;
+                                       if ( (queries[i].expires > now - 4 * 
TTL_DECREMENT) && /* not long expired */ (Crypto.nextInt(4) != 0) ) {
+                                               /* do not clear the bitmap 
describing which peers we have
+                                                forwarded the query to 
already; but do this only with high
+                                                probability since we may want 
to try again if the query is
+                                                retransmitted lots (this can 
happen if this is the only
+                                                query; we may forward it to 
all connected peers and get no
+                                                reply.  If the initiator keeps 
retrying, we want to
+                                                eventually forward it again.
+
+                                                Note that the initial 
probability here (0.6.0/0.6.1) was
+                                                very low (1:64), which is far 
too low considering that the
+                                                clients do an exponential 
back-off.  The rule is a pure
+                                                optimization, and as such the 
probability that we
+                                                eventually forward must be 
significant.  25% seems to work
+                                                better... (extra-note: in 
small testbeds, the problem
+                                                is bigger than in a larger 
network where the case that
+                                                a query stays in the QM 
indefinitely might be much more
+                                                rare; so don't just trust a 
micro-scale benchmark when
+                                                trying to figure out an 
'optimal' threshold). */
+                                               noclear = true;
+
+                                               debug("DEBUG: QM noclear rule 
applied!\n");
+                                               }
+                                       break; /* this is it, do not scan for 
other 'oldest' entries */
+                                       }
+                               }
+
+                       if (oldestIndex == -1) {
+                               debug("DEBUG: keeping track of 
"+QUERY_RECORD_COUNT+" queries already, will not manage this one");
+                               qr = dummy;
+                               }
+                       else {
+                               qr = queries[oldestIndex];
+                               qr.msg = null;
+                               }
+                       qr.expires = expirationTime;
+                       qr.transmissionCount = 0;
+                       qr.msg=(P2PQuery) PersistentHelper.copy(msg);
+
+                       if (!noclear) {
+                               qr.clearBits();
+                               qr.noTarget=(origin != null ? origin : 
coreAPI.getIdentity());
+                               qr.localClient=client;
+                               qr.totalDistance=0;
+
+                               if (TRADITIONAL_SELECTION) {
+                                       
qr.activeConnections=coreAPI.forAllConnectedNodes(new PerNodeCallback() {
+                                               public void callback( 
HostIdentity identity, Object data )
+                                               {
+                                                       
selectActiveNodes(identity,(QueryRecord) data);
+                                               }
+                                               },qr);
+
+                                       if (qr.activeConnections>0) {
+                                               selectActiveNodes(null, qr); /* 
give SAN chance to adjust weight at the end of the iteration! */
+
+                                               for 
(i=BITMAP_SIZE*4/qr.activeConnections;i>=0;i--) {
+                                                       
qr.setBit(Crypto.nextInt(BITMAP_SIZE)*8); /* select 4 random nodes */
+                                                       }
+
+                                               
coreAPI.forAllConnectedNodes(new PerNodeCallback() {
+                                                       public void callback( 
HostIdentity identity, Object data )
+                                                       {
+                                                               
selectRandomNodes(identity,(QueryRecord) data);
+                                                       }
+                                                       },qr);
+                                               }
+                                       }
+                               else {
+                                       qr.rankings = new int[8*BITMAP_SIZE];
+                                       
qr.activeConnections=coreAPI.forAllConnectedNodes(new PerNodeCallback() {
+                                               public void callback( 
HostIdentity identity, Object data )
+                                               {
+                                                       
newSelectCode(identity,(QueryRecord) data);
+                                               }
+                                               },qr);
+
+                                       /* actual selection, proportional to 
rankings assigned by newSelectCode ... */
+                                       {
+                                               int j;
+                                               long rankingSum = 0;
+                                               for (i=0;i<8*BITMAP_SIZE;i++)
+                                                       rankingSum += 
qr.rankings[i];
+
+                                               if ( (rankingSum != 0) && /* 
doppelt haelt besser */ (qr.activeConnections > 0) ) {
+                                                       /* select 4 peers for 
forwarding */
+                                                       for (i=0;i<4;i++) {
+                                                               long sel;
+                                                               long pos;
+                                                               sel = 
Crypto.nextLong(rankingSum);
+                                                               pos = 0;
+                                                               for 
(j=0;j<8*BITMAP_SIZE;j++) {
+                                                                       pos += 
qr.rankings[j];
+                                                                       if (pos 
> sel) {
+                                                                               
qr.setBit(j);
+                                                                               
break;
+                                                                               
}
+                                                                       }
+                                                               }
+                                                       }
+                                               else {
+                                                       /* no bias available, 
go random! */
+                                                       if 
(qr.activeConnections > 0) {
+                                                               for 
(i=4*BITMAP_SIZE*8/qr.activeConnections-1;i>=0;i--) {
+                                                                       
qr.setBit(Crypto.nextInt(BITMAP_SIZE)*8); /* select 4 random nodes */
+                                                                       }
+                                                               }
+                                                       }
+                                       }
+
+                                       qr.rankings = null;
+                                       }
+                               /* now forward to a couple of selected nodes */
+                               coreAPI.forAllConnectedNodes(new 
PerNodeCallback() {
+                                       public void callback( HostIdentity 
identity, Object data )
+                                       {
+                                               
sendToSelected(identity,(QueryRecord) data);
+                                       }
+                                       },qr);
+                               }
+                       }
+       }
+
+       /**
+        * Stop transmitting a certain query (we don't route it anymore or we 
have learned the answer).
+        * @param query
+        */
+
+       public void dequeueQuery( HashCode512 query )
+       {
+               int                     i,j;
+               QueryRecord     qr;
+
+               synchronized(queryManagerLock) {
+                       for (i=0;i<QUERY_RECORD_COUNT;i++) {
+                               qr = queries[i];
+                               if( qr.msg != null ) {
+                                       for 
(j=NUMBER_OF_QUERIES(qr.msg)-1;j>=0;j--) {
+                                               if 
(query.equals(qr.msg.getQuery(j))) {
+                                                       qr.expires = 0; /* 
expire NOW! */
+                                                       break;
+                                               }
+                                       }
+                               }
+                       }
+               }
+       }
+
+       /**
+        * How many queries are in the given query (header given).
+        * @param p
+        * @return
+        */
+
+       protected int NUMBER_OF_QUERIES( Persistent p )
+       {
+               return ((p.getByteSize()-P2PQuery.SIZE)/HashCode512.SIZE);
+       }
+
+       /**
+        * Map the id to an index into the bitmap array.
+        * @param id
+        * @return
+        */
+
+       protected int getIndex( HostIdentity id )
+       {
+               int index;
+
+               index = coreAPI.computeIndex(id);
+               if (index > 8*BITMAP_SIZE)
+                       index = index & (8*BITMAP_SIZE-1);
+               return index;
+       }
+
+       /**
+        * Callback method for filling buffers. This method is invoked by the
+        * core if a message is about to be send and there is space left for a
+        * 3QUERY.  We then search the pending queries and fill one (or more)
+        * in if possible.
+        *
+        * Note that the same query is not transmitted twice to a peer and that
+        * queries are not queued more frequently than 2 TTL_DECREMENT.
+        *
+        * @param receiver the receiver of the message
+        * @param buf
+        * @return the number of bytes written to
+        *   that buffer (must be a positive number).
+        */
+
+       protected boolean fillInQuery( HostIdentity receiver, ByteBuffer buf )
+       {
+               long    now;
+               int             start,delta;
+
+               now=Scheduler.now();
+               synchronized(queryManagerLock) {
+                       start = zePos;
+                       delta = 0;
+                       while (buf.remaining()>P2PQuery.SIZE+HashCode512.SIZE) {
+                               if (queries[zePos].expires>now &&
+                                               
!queries[zePos].getBit(getIndex(receiver)) &&
+                                               buf.remaining()>= 
queries[zePos].msg.getByteSize()
+                               ) {
+
+                                       debug("Adding 
"+NUMBER_OF_QUERIES(queries[zePos].msg)+" queries 
("+queries[zePos].msg.getQuery(0).toHex()+") to outbound buffer of 
"+receiver.getName());
+
+                                       
queries[zePos].setBit(getIndex(receiver));
+                                       if 
(!PersistentHelper.write(queries[zePos].msg,buf)) {
+                                               return false;
+                                       }
+                                       queries[zePos].sendCount++;
+                                       delta += 
queries[zePos].msg.getByteSize();
+                               }
+                               zePos++;
+                               if (zePos >= QUERY_RECORD_COUNT)
+                                       zePos = 0;
+                               if (zePos == start)
+                                       break;
+                       }
+               }
+               return true;
+       }
+
+       /**
+        * A "PerNodeCallback" method that selects the most
+        * active nodes for forwarding (with some randomness).
+        *
+        * Also computes the sum of the distances of the
+        * other node IDs as a side-effect.
+        * @param id
+        * @param qr
+        */
+
+       protected void selectActiveNodes( HostIdentity id, QueryRecord qr )
+       {
+               int trf;
+
+               if (id==null) {
+                       // special call for weight adjustment
+                       
Xweight=Xweight/Math.sqrt((qr.transmissionCount+1.0)/(SELECT_GOAL+1.0));
+                       return;
+               }
+
+               trf = coreAPI.queryBPMfromPeer(id);
+               Xaverage  = (Xaverage * 15 + trf) / 16; /* approximate average 
over time...*/
+
+               debug("Selecting from active nodes "+id.getName()+" as 
rand("+(trf+1)+") > rand("+(Xaverage+1)+")*"+Xweight+".");
+
+               /* Forward the query to peers that have on average lots of 
bandwidth
+                assigned to them (and are thus recently very productive...) */
+               if (Crypto.nextInt(trf+1)>Crypto.nextInt(Xaverage+1)*Xweight) {
+                       qr.setBit(getIndex(id));
+                       qr.transmissionCount++;
+
+                       debug("Node selected for forwarding due to activity: 
"+id.getName()+".");
+               }
+               else {
+                       qr.totalDistance+=id.distance(qr.msg.getQuery(0));
+               }
+       }
+
+       /**
+        * A "PerNodeCallback" method that selects some
+        * random nodes (biased according to proximity).
+        * @param id
+        * @param qr
+        */
+
+       protected void selectRandomNodes( HostIdentity id, QueryRecord qr )
+       {
+               int avgDist;
+               int peerDist;
+
+               if (id == null) {
+                       Yweight = Yweight / Math.sqrt( 
(qr.transmissionCount+1.0) / (FINAL_GOAL+1.0) );
+                       return;
+               }
+
+               if (qr.totalDistance==0 || qr.activeConnections==0) {
+                       return; /* activeConnections should never be 0, if 
totalDistance is 0, this is caused by us selectAcitveNodes selecting all nodes 
already */
+               }
+
+               if (qr.getBit(getIndex(id))) {
+                       return;
+               }
+
+               avgDist = (int) (qr.totalDistance / qr.activeConnections);
+               peerDist = id.distance(qr.msg.getQuery(0));
+
+               debug("Selecting at random from active nodes "+id.getName()+" 
using rand("+(peerDist+1)+")*"+qr.transmissionCount+"*"+Yweight+" < 
rand("+(avgDist+1)+").");
+
+               /* Select 2 random nodes for forwarding. Give preference
+                to nodes that are close.  Division by 4 to ensure
+                that we are in the range of a signed int. */
+               if (Crypto.nextInt(1+peerDist) * qr.transmissionCount * Yweight 
< Crypto.nextInt(1+avgDist) ) {
+                       debug("Node selected for forwarding from random set: 
"+id.getName()+".");
+
+                       qr.setBit(getIndex(id));
+                       qr.transmissionCount++;
+               }
+       }
+
+       protected void newSelectCode( HostIdentity id, QueryRecord qr )
+       {
+               ReplyTrackData  pos;
+               ResponseList  rp;
+               int ranking = 0;
+               int distance;
+
+               pos = rtdList;
+               while (pos != null) {
+                       if ((qr.localClient==null && 
pos.queryOrigin.equals(qr.noTarget)) || qr.localClient == pos.localQueryOrigin) 
{
+                               break;
+                               }
+                       pos=pos.next;
+                       }
+
+               if (pos != null) {
+                       rp = pos.responseList;
+                       while (rp != null) {
+                               if (rp.responder.equals(id)) {
+                                       break;
+                                       }
+                               rp = rp.next;
+                               }
+
+                       if (rp != null) {
+                               if (rp.responseCount < 0xFFFF)
+                                       ranking = 0x7FFF * rp.responseCount;
+                               else
+                                       ranking = 0x7FFFFFF;
+                               }
+                       }
+
+               distance=id.distance(qr.msg.getQuery(0));
+               if (distance <= 0)
+                       distance = 1;
+
+               ranking += 0xFFFF / (1 + Crypto.nextInt(distance));
+               ranking += Crypto.nextInt(0xFF); /* small random chance for 
everyone */
+               qr.rankings[getIndex(id)]=ranking;
+       }
+
+       /**
+        * A "PerNodeCallback" method that forwards
+        * the query to the selected nodes.
+        * @param id
+        * @param qr
+        */
+
+       protected void sendToSelected( HostIdentity id, QueryRecord qr )
+       {
+               if (id.equals(qr.noTarget)) {
+                       return;
+                       }
+
+               if (qr.getBit(getIndex(id))) {
+                       debug("Queueing query "+qr.msg.getQuery(0).toHex()+" in 
buffer of selected node "+id.getName()+".");
+
+                       coreAPI.sendToNode(id,
+                                       qr.msg,
+                                       BASE_QUERY_PRIORITY 
*(qr.msg.getPriority()*2+NUMBER_OF_QUERIES(qr.msg)),
+                                       (int) TTL_DECREMENT);
+                       }
+       }
+
+       /**
+        * Cron job that ages the RTD data and that frees memory for entries 
that reach 0.
+        */
+
+       public void ageRTD()
+       {
+               ReplyTrackData  pos;
+               ReplyTrackData  prev;
+               ResponseList  rpos;
+               ResponseList  rprev;
+
+               synchronized(queryManagerLock) {
+                       prev = null;
+                       pos = rtdList;
+                       while (pos != null) {
+                               /* after 10 minutes, always discard everything 
*/
+                               if (pos.lastReplyReceived < 
Scheduler.toSeconds(Scheduler.now()) - 600) {
+                                       while (pos.responseList != null) {
+                                               rpos = pos.responseList;
+                                               pos.responseList = rpos.next;
+                                       }
+                               }
+                               /* otherwise, age reply counts */
+                               rprev = null;
+                               rpos = pos.responseList;
+                               while (rpos != null) {
+                                       rpos.responseCount = rpos.responseCount 
/ 2;
+                                       if (rpos.responseCount == 0) {
+                                               if (rprev == null)
+                                                       pos.responseList = 
rpos.next;
+                                               else
+                                                       rprev.next = rpos.next;
+                                               if (rprev == null)
+                                                       rpos = pos.responseList;
+                                               else
+                                                       rpos = rprev.next;
+                                               continue;
+                                       }
+                               }
+                               /* if we have no counts for a peer anymore,
+                                free pos entry */
+                               if (pos.responseList == null) {
+                                       if (prev == null)
+                                               rtdList = pos.next;
+                                       else
+                                               prev.next = pos.next;
+                                       if (prev == null)
+                                               pos = rtdList;
+                                       else
+                                               pos = prev.next;
+                                       continue;
+                               }
+                               prev = pos;
+                               pos = pos.next;
+                               }
+                       }
+       }
+
+       /**
+        * We received a reply from 'responder' to a query received from
+        * 'origin' (or 'localOrigin').  Update reply track data!
+        *
+        * @param origin only valid if localOrigin == null
+        * @param localOrigin origin if query was initiated by local client
+        * @param responder peer that send the reply
+        */
+
+       public void updateResponseData( HostIdentity origin, CSSession 
localOrigin, HostIdentity responder )
+       {
+               ReplyTrackData  pos;
+               ReplyTrackData  prev;
+               ResponseList  rpos;
+               ResponseList  rprev;
+
+               if (responder == null)
+                       return; /* we don't track local responses */
+
+               synchronized(queryManagerLock) {
+                       pos = rtdList;
+                       prev = null;
+                       while (pos != null) {
+                               if (pos.localQueryOrigin==localOrigin && 
(localOrigin!=null || origin.equals(pos.queryOrigin))) {
+                                       break; /* found */
+                                       }
+                               prev = pos;
+                               pos = pos.next;
+                       }
+                       if (pos == null) {
+                               pos = new ReplyTrackData();
+                               pos.next = null;
+                               pos.localQueryOrigin = localOrigin;
+                               if (localOrigin == null)
+                                       pos.queryOrigin = origin;
+                               pos.responseList = null;
+                               if (prev == null)
+                                       rtdList = pos;
+                               else
+                                       prev.next = pos;
+                       }
+                       pos.lastReplyReceived=(int) 
Scheduler.toSeconds(Scheduler.now());
+                       rpos = pos.responseList;
+                       rprev = null;
+                       while (rpos != null) {
+                               if (responder.equals(rpos.responder)) {
+                                       rpos.responseCount++;
+                                       return;
+                                       }
+                               rprev = rpos;
+                               rpos = rpos.next;
+                               }
+
+                       rpos = new ResponseList();
+                       rpos.responseCount = 1;
+                       rpos.responder = responder;
+                       rpos.next = null;
+                       if (rprev == null)
+                               pos.responseList = rpos;
+                       else
+                               rprev.next = rpos;
+                       }
+       }
+}
+
+/**
+ * Linked list of peer ids with number of replies received.
+ */
+
+class ResponseList extends Object
+{
+       public HostIdentity     responder;
+       public int                      responseCount;
+       public ResponseList     next;
+}
+
+/**
+ * Structure for tracking from which peer we got valueable replies for which 
clients / other peers.
+ */
+
+class ReplyTrackData extends Object
+{
+       /** For which client does this entry track replies ? Only valid if 
localQueryOrigin == null! */
+       public HostIdentity             queryOrigin;
+
+       /** For which client does this entry track replies ? */
+       public CSSession                localQueryOrigin;
+
+       /** Time at which we received the last reply for this client (in 
seconds). Used to discard old entries eventually. */
+       public int                              lastReplyReceived;
+
+       /** Linked list of peers that responded, with number of responses. */
+       public ResponseList             responseList;
+
+       /** Linked list. */
+       public ReplyTrackData           next;
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/QueryRecord.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/QueryRecord.java    2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/QueryRecord.java    2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,91 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs;
+
+import org.gnu.freeway.protocol.fs.esed2.*;
+
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.net.CSSession;
+
+import java.util.*;
+
+/**
+ * In this struct, we store information about a
+ * query that is being send from the local node to
+ * optimize the sending strategy.
+ */
+
+public class QueryRecord extends Object implements AFSConstants
+{
+       /** How often did we send this query so far ? */
+       public int                              sendCount;
+
+       /** The message that we are sending. */
+       public P2PQuery msg;
+
+       /** Bit-map marking the hostIndices (computeIndex) of nodes that have 
received this query already.
+        Note that the bit-map has a maximum size, if the index is 
out-of-bounds, it is hashed into
+        the smaller size of the bitmap. There may thus be nodes with identical 
indices, in that case, only one of
+        the nodes will receive the query. */
+       public byte[]                   bitmap;
+
+       /** When do we stop forwarding (!) this query ? */
+       public long                             expires;
+
+       /** How many nodes were connected when we initated sending this query ? 
*/
+       public int                              activeConnections;
+
+       /** What is the total distance of the query to the connected nodes ? */
+       public long                             totalDistance;
+
+       /** To how many peers has / will this query be transmitted ? */
+       public int                              transmissionCount;
+
+       /** To which peer will we never send this message ? */
+       public HostIdentity             noTarget;
+
+       /** Sender identity, for a local client. */
+       public CSSession                localClient;
+
+       /** How important would it be to send the message to all peers in this 
bucket ? */
+       public int[]                            rankings;
+
+
+       public QueryRecord()
+       {
+               super();
+               bitmap=new byte[BITMAP_SIZE];
+               msg=new P2PQuery();
+       }
+
+       public String toString()
+       {
+               return "Query record";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public boolean getBit( int bit )
+       {
+               byte    theBit;
+
+               theBit=(byte) (1 << (bit & 7));
+               return (bitmap[bit>>3] & theBit)!=0;
+       }
+
+       public void setBit( int bit )
+       {
+               byte    theBit;
+
+               theBit=(byte) (1 << (bit & 7));
+               bitmap[bit>>3] |= theBit;
+       }
+
+       public void clearBits()
+       {
+               Arrays.fill(bitmap,(byte) 0);
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/Routing.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/Routing.java        2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/Routing.java        2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,1328 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs;
+
+import org.gnu.freeway.*;
+import org.gnu.freeway.protocol.fs.esed2.*;
+import org.gnu.freeway.server.*;
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.io.*;
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+import java.util.logging.*;
+
+/**
+ * Routing interface. This is the interface that does the routing.
+ * routing of AFS queries and replies
+ *
+ * The routing code is responsible for deciding which replies
+ * need to be forwarded to which peers. While the querymanager
+ * decides where to forward queries, it needs to negotiate with
+ * the routing code which queries can be forwarded since we may
+ * not be able to keep track of all queries.
+ */
+
+public class Routing extends LoggedObject implements AFSConstants
+{
+       private static final boolean    DEBUG_WRITE_INDTABLE    =       true;
+
+       /** How much is a response worth 'in general'. Since replies are 
roughly 1k and should be much (factor of 4)
+        preferred over queries (which have a base priority of 20, which yields 
a base unit of roughly 1 per byte).
+        Thus if we set this value to 4092 we'd rather send a reply instead of 
a query unless the queries have
+        (on average) a priority that is more than double the reply priority 
(note that querymanager multiplies the
+        query priority with 2 to compute the scheduling priority). */
+
+       public static final int BASE_REPLY_PRIORITY     =       4092;
+
+       /**
+        * minimum indirection table size, defaults to 8192 entries, reduce if
+        * you have very little memory, enlarge if you start to overflow often
+        * and have memory available.<p>
+        *
+        * If the average query lives for say 1 minute (10 hops), and you have
+        * a 56k connection (= 420 kb/minute, or approximately 8000
+        * queries/minute) the maximum reasonable routing table size would
+        * thus be 8192 entries.  Every entry takes about 68 bytes.<p>
+        *
+        * The larger the value is that you pick here, the greater your
+        * anonymity can become.  It also can improve your download speed.<p>
+        *
+        * Memory consumption:
+        * <ul>
+        * <li>8192 => 560k indirection table => approx. 6 MB gnunetd</li>
+        * <li>65536 => 4456k indirection table => approx. 10 MB gnuentd</li>
+        * </ul>
+        * <p>
+        * THE VALUE YOU PICK MUST BE A POWER OF 2, for example:
+        * 128, 256, 512, 1024, 2048, 4092, 8192, 16384, 32768, 65536
+        */
+
+       public static final int MIN_INDIRECTION_TABLE_SIZE      =       8192;
+
+       /**
+        * Under certain cirumstances, two peers can interlock in their
+        * routing such that both have a slot that is blocked exactly until
+        * the other peer will make that slot available.  This is the
+        * probability that one will give in.  And yes, it's a hack.  It
+        * may not be needed anymore once we add collision-resistance to
+        * the routing hash table.
+        */
+
+       public static final int TIE_BREAKER_CHANCE      =       4;
+
+       /** ITE modes for addToSlot. */
+       public static final int ITE_REPLACE     =       0;
+       public static final int ITE_GROW        =       1;
+
+       /** Size of the indirection table specified in gnunet.conf */
+       private int     indirectionTableSize;
+
+       /** The routing table. This table has entries for all queries that we 
have recently send out.
+        It helps GNUnet to route the replies back to the respective sender. */
+
+       private IndirectionTableEntry[] ROUTING_indTable_;
+
+       /** Stats handle for how much content replies we have send back to 
clients. */
+       private Stat    stat_cs_reply_content_out;
+
+       /** Stats handles about incoming blocks */
+       private Stat    stat_content_in_ok;
+       private Stat    stat_content_in_dupe;
+       private Stat    stat_content_in_orphan;
+       private Stat    stat_routingFull;
+       private Stat    stat_routingReplaced;
+       private Stat    stat_routingPresent;
+       private Stat    stat_p2p_query_out;
+       private Stat    stat_concurrent_route_replacement;
+       private Stat    stat_delaytime_route_replacement;
+
+       private int                     random_qsel;
+
+       private CoreForProtocol         coreAPI;
+       private Prefs   prefs;
+       private Statistics              stats;
+       private Scheduler                       scheduler;
+       private int             round = 0;
+
+       public Policy           policy;
+       public Manager          manager;
+       public QueryManager     queryManager;
+       public BloomFilter      superBloomFilter;
+       public BloomFilter      singleBloomFilter;
+
+       private ScheduledTask           writeTask;
+
+
+       public Routing()
+       {
+               super(true);
+               coreAPI=null;
+
+               writeTask=new ScheduledTask("WRITE-ID-TABLE",new 
EvalAction(this,"writeIDtable"),Scheduler.MINUTES_1);
+       }
+
+       public String toString()
+       {
+               return "xxx";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       /**
+        * This function will write numeric entries of the indirectiontable
+        * to a textfile. With idtablesize of (8192*8), it will generate
+        * >1200 kb per run. The output will not be an accurate snapshot
+        * of the idtable at any moment as the locking is done on per-entry
+        * basis. Naturally this function might also twist the routing
+        * itself a little by locking the entries. The hope is that analyzing
+        * this data in eg octave or matlab might help understand the
+        * routing behaviour better. Or not.
+        */
+
+       public void writeIDtable()
+       {
+               IndirectionTableEntry           ite;
+               MappedFile                              fp;
+               FileLocation                            f;
+               DirLocation                             dir;
+               long                                            now;
+               int                                             i;
+
+               now=Scheduler.now();
+
+               dir=prefs.getDirLocation("","GNUNETD_HOME");
+               if (dir==null) {
+                       dir=new DirLocation("~");
+                       }
+
+               f=dir.getFile("gn_idstats.txt");
+               f.create();
+
+               fp=f.open();
+               fp.seekEnd();
+
+               for(i=0; i<indirectionTableSize; i++) {
+                       ite=ROUTING_indTable_[i];
+                       synchronized(ite.lookup_exclusion) {
+                               fp.writeString(round+" "+i+" 
"+(ite.namespace!=null ? 1 : 0)+" "+
+                                       (ite.ttl == 0 ? 0 : 
Scheduler.toSeconds(ite.ttl-now))+" "+ite.priority+" "+
+                                       ite.getSeenCount()+" 
"+ite.getWaitingHostsCount()+" "+ite.getClientsCount()+"\n");
+                               }
+                       }
+
+               fp.close();
+               round++;
+       }
+
+       /**
+        * Initialize routing module.
+        * Initialize routing module (initializes indirection table)
+        * @param capi
+        */
+
+       public void init( CoreForProtocol capi )
+       {
+               int i;
+
+               coreAPI=capi;
+               prefs=capi.getApplication().getPreferences();
+               stats=capi.getApplication().getStatistics();
+               scheduler=capi.getApplication().getScheduler();
+
+               random_qsel = Crypto.nextInt(HashCode512.SIZE/4);
+               stat_cs_reply_content_out=stats.getHandle("# kb downloaded by 
clients",Stat.VERBOSE);
+               stat_delaytime_route_replacement=stats.getHandle("# routing 
entry replaced during delaytime",Stat.VERBOSE);
+               stat_concurrent_route_replacement=stats.getHandle("# routing 
entry replaced during lookup",Stat.VERBOSE);
+
+               stat_content_in_ok=stats.getHandle("# kb ok content in");
+               stat_content_in_dupe=stats.getHandle("# kb dupe content in");
+               stat_content_in_orphan=stats.getHandle("# kb orphan or pushed 
content in");
+               stat_routingFull=stats.getHandle("# routing table full");
+               stat_routingReplaced=stats.getHandle("# routing table entry 
replaced");
+               stat_routingPresent=stats.getHandle("# routing table entry 
already in place");
+               stat_p2p_query_out=stats.getHandle("# p2p queries sent");
+               indirectionTableSize 
=prefs.getInt("AFS","INDIRECTIONTABLESIZE",0);
+               if (indirectionTableSize < MIN_INDIRECTION_TABLE_SIZE)
+                       indirectionTableSize = MIN_INDIRECTION_TABLE_SIZE;
+               i = 1;
+               while (i < indirectionTableSize)
+                       i*=2;
+               indirectionTableSize = i; // make sure it's a power of 2
+
+               debug("Set indirection table size to 
"+indirectionTableSize+".");
+
+               ROUTING_indTable_= new 
IndirectionTableEntry[indirectionTableSize];
+               for (i=0;i<indirectionTableSize;i++) {
+                       ROUTING_indTable_[i]=new IndirectionTableEntry();
+                       ROUTING_indTable_[i].namespace = null;
+                       ROUTING_indTable_[i].ttl = 0; // expired / free
+                       
ROUTING_indTable_[i].successful_local_lookup_in_delay_loop = false;
+                       ROUTING_indTable_[i].lookup_exclusion=new Object();
+                       }
+               ((Server) coreAPI.getApplication()).registerCSExitHandler(new 
ClientExitHandler() {
+                       public void handle( CSSession client )
+                       {
+                               cancelTCP_routing(client);
+                       }
+                       });
+
+               if (DEBUG_WRITE_INDTABLE) {
+                       scheduler.addJob(writeTask,0);
+                       }
+       }
+
+       /**
+        * Shutdown the routing module.
+        */
+
+       public void done()
+       {
+               ((Server) coreAPI.getApplication()).unregisterCSExitHandler(new 
ClientExitHandler() {
+                       public void handle( CSSession client )
+                       {
+                               cancelTCP_routing(client);
+                       }
+                       });
+               ROUTING_indTable_=null;
+
+               if (DEBUG_WRITE_INDTABLE) {
+                       scheduler.deleteJob(writeTask);
+                       }
+       }
+
+       /**
+        * Print the current routing table.
+        * Print the routing table.
+        */
+
+       public void printRoutingTable()
+       {
+               int i;
+               IndirectionTableEntry  ite;
+               long now;
+
+               now=Scheduler.now();
+               log(Level.INFO,"Routing TABLE:");
+               for (i=0;i<indirectionTableSize;i++) {
+                       ite = ROUTING_indTable_[i];
+                       synchronized(ite.lookup_exclusion) {
+                               // if (ite.ttl >= now)
+                               log(Level.FINEST,i+": hash "+ite.hash.toHex()+" 
ttl "+Scheduler.toSeconds(ite.ttl - now)+"s hostsWaiting 
"+ite.getWaitingHostsCount()+" prio "+ite.priority+" seenIndex: 
"+ite.getSeenCount());
+                               }
+                       }
+       }
+
+       /**
+        * Execute the query. <p>
+        *
+        * Execute means to test if we can route the query (or, in the case
+        * of a multi-query, any of the sub-queries). If yes, we lookup the
+        * content locally and potentially route it deferred. Regardless if
+        * the content was found or not, the queries that we can route are
+        * forwarded to other peers (by the querymanager code).<p>
+        *
+        * The decision if we can route is made by "needsForwarding". Note that
+        * queries that we are already routing do not "need forwarding". If
+        * we do route the query, execQuery decides if we are going to do source
+        * rewriting or not.<p>
+        *
+        * If we route a query, execSingleQuery will use the bloom filters and
+        * the databases to locate the content and queue a cron job that will
+        * pass the response to "useContent" as if it came from another peer.
+        * Note that if the query originated from a local client, the response
+        * is instant (no cron job scheduled).
+        *
+        * @param qp the polciy (priority) for the query
+        * @param msg the query message (with host identity for the reply)
+        * @param sock the TCP socket to send the answer to if it is
+        *        a query from the local host, otherwise null.
+        * @return true if the query was routed (at least in part), false if it 
was dropped
+        */
+
+       public boolean execQuery( int qp, P2PQuery msg, CSSession sock )
+       {
+               HostIdentity  sender;
+               HostIdentity senderCpy;
+               int prio;
+               int count;
+               int routeCount;
+
+               count = (msg.getByteSize()-P2PQuery.SIZE) / HashCode512.SIZE;
+               prio = msg.getPriority() / count; // per-query priority
+
+               // source rewriting (or not...)
+               if (sock == null) {
+                       if (msg.getReturnTo().equals(coreAPI.getIdentity()))
+                               return false; // A to B, B sends back to A 
without (!) source rewriting, in this case, A must just drop
+                       senderCpy=(HostIdentity) 
PersistentHelper.copy(msg.getReturnTo());      //todo: copie utile ???
+                       sender = senderCpy;
+               } else {
+                       sender = null;
+                       senderCpy=(HostIdentity) 
PersistentHelper.copy(coreAPI.getIdentity());  //todo: copie utile ???
+               }
+               if ((qp & Policy2.QUERY_INDIRECT) != 0) {
+                       msg.setReturnTo((HostIdentity) 
PersistentHelper.copy(coreAPI.getIdentity()));   //todo: copie utile ???
+                       }
+               else {
+                       msg.setPriorityAndTTL(0,msg.getTTL());
+                       }
+
+               debug("Received "+count+"-query "+msg.getQuery(0).toHex()+" 
with ttl "+msg.getTTL()+" and priority "+msg.getPriority()+".");
+
+               if (msg instanceof P2PNSQuery) {
+                       if 
(execNSQuery(sender,sock,prio,msg.getTTL(),((P2PNSQuery) 
msg).identifier,((P2PNSQuery) msg).namespace))
+                               routeCount = 2; // NAMESPACE + IDENTIFIER!
+                       else
+                               routeCount = 0;
+                       }
+               else {
+                       if (count > 1) { // MULTI-QUERY, take apart for 
individual routing, but reassemble for forwarding
+                               int i;
+                               boolean superBF;
+
+                               superBF = 
superBloomFilter.test(msg.getQuery(0));
+                               routeCount = 1;
+                               for (i=1;i<count;i++) {
+                                       if 
(execSingleQuery(sender,sock,prio,msg.getTTL(),msg.getQuery(i),superBF)) {
+                                               // route this query !
+                                               
msg.setQuery(routeCount,msg.getQuery(i));
+                                               routeCount++;
+                                               }
+                                       }
+                               if (routeCount == 1)
+                                       routeCount = 0; // nothing to forward
+                               }
+                       else { // single query or 3hash search
+                               if 
(execSingleQuery(sender,sock,prio,msg.getTTL(),msg.getQuery(0),false))
+                                       routeCount = 1;
+                               else
+                                       routeCount = 0;
+                               }
+                       }
+
+               if (routeCount >= 1) {
+                       stat_p2p_query_out.add(routeCount);
+                       queryManager.forwardQuery(msg,((sock == null) ? sender 
: null),sock);
+
+                       debug("Slots free in routing table for "+((sock==null) 
? "remote" : "LOCAL")+
+                               " query "+msg.getQuery(0).toHex()+"; forwarded 
"+routeCount+" out of "+count+" queries.");
+                       return true;
+                       }
+
+               debug("0 slots free in routing table for "+((sock==null) ? 
"remote" : "LOCAL")+
+                       " query "+msg.getQuery(0).toHex()+" with "+count+" hash 
codes, none forwarded.");
+               return false;
+       }
+
+       /**
+        * Content has arrived. We must decide if we want to
+        *   a) forward it to our clients
+        *   b) indirect it to other nodes.
+        * The routing module should know what to do.
+        *
+        * This method checks the routing table if we have a matching route and 
if yes queues the reply.
+        * It also makes sure that we do not send the same reply back on the 
same route more than once.
+        *
+        * @param hostId        who sent the message
+        * @param queryHash     the hashcode from the query
+        * @param msg           the p2p message we received, good for 
indirecting, must potentially be turned into the adequate CS message.
+        * @return                      how good this content was (priority of 
the original request)
+        */
+
+       public int useContent( HostIdentity hostId, HashCode512 queryHash, 
P2PMessage msg )
+       {
+               int i;
+               ContentBlock  content;
+               HashCode512 contentHC;
+               IndirectionTableEntry  ite;
+               int prio = -1;
+
+               //log(Level.FINEST,"DEBUG: useContent - prints routing table");
+               //printRoutingTable();
+
+               debug("Received content "+queryHash.toHex()+" from peer 
"+((hostId == null) ? "self" : hostId.getName())+".");
+
+               ite = ROUTING_indTable_[computeRoutingIndex(queryHash)];
+
+               synchronized(ite.lookup_exclusion) {
+                       if (!ite.hash.equals(queryHash) ) {
+                               stat_content_in_orphan.inc();
+                               debug("No matching query pending for content 
"+queryHash.toHex()+" (not indirected).");
+                               return 0; // no indirection pending: was useless
+                               }
+
+                       if (msg instanceof P2P3HashResult) {
+                               content=new ContentBlock(((P2P3HashResult) 
msg).getResult());
+                               if (ite.namespace != null) {
+                                       return 0;
+                                       }
+                               }
+                       else if (msg instanceof P2PChkResult) {
+                               queryManager.dequeueQuery(ite.hash);
+                               content = ((P2PChkResult)msg).result;
+                               if (ite.namespace != null) {
+                                       return 0;
+                                       }
+
+                               // remove the sender from the waiting list (if 
the sender was waiting for a response)
+                               if (hostId != null) {
+                                       ite.removeWaitingHost(hostId);
+                                       }
+                               }
+                       else if (msg instanceof P2PSBlockResult) {
+                               content=(ContentBlock) 
PersistentHelper.readFully(ContentBlock.class,PersistentHelper.toBuffer(((P2PSBlockResult)msg).getResult()));
+                               if (ite.namespace == null) {
+                                       return 0;
+                                       }
+                               HashCode512 hc;
+                               
hc=((P2PSBlockResult)msg).getResult().getNameSpace();
+                               if (! ite.namespace.equals(hc) ) {
+                                       return 0;
+                                       }
+                               }
+                       else {
+                               log(Level.WARNING,"Unexpected p2p result : 
"+msg);
+                               return 0;
+                               }
+
+                       
contentHC=HashCode512.create(PersistentHelper.toBytes(content));
+
+                       for (i=0;i<ite.getSeenCount();i++) {
+                               if (contentHC.equals(ite.getSeen(i))) {
+                                       stat_content_in_dupe.inc();
+                                       debug("Content is not new (slot: 
"+computeRoutingIndex(queryHash)+").");
+                                       return 0; // seen before, useless
+                                       }
+                               }
+                       // new reply, adjust credits !
+                       if (hostId != null) // if we are the sender, hostId 
will be null
+                               coreAPI.changeTrust(hostId, ite.priority);
+                       prio = ite.priority;
+                       ite.priority = 0; // no priority for further replies, 
because we don't get paid for those...
+
+                       debug("Indirecting new content matching query 
"+ite.hash.toHex()+".");
+
+                       for (i=0; i<ite.getClientsCount(); i++) {
+                               
queryManager.updateResponseData(null,ite.getClient(i),hostId);
+                               }
+
+                       for (i=0; i<ite.getWaitingHostsCount(); i++) {
+                               
queryManager.updateResponseData(ite.getWaitingHost(i),null,hostId);
+                               }
+
+                       sendReply(ite,msg);
+                       ite.addSeen(contentHC);
+                       stat_content_in_ok.inc();
+                       }
+               return prio;
+       }
+
+       /**
+        * Compute the hashtable index of a host id.
+        * @param query
+        * @return
+        */
+
+       protected int computeRoutingIndex( HashCode512 query )
+       {
+               int     res;
+
+               res = query.getInt(random_qsel) & (indirectionTableSize - 1);
+               if (res>=indirectionTableSize) {
+                       trace("Indirection table size not power of 2 ? 
("+indirectionTableSize+")");
+                       }
+               return res;
+       }
+
+       /**
+        * Queue a CHK reply with cron to simulate
+        * another peer returning the response with
+        * some latency (and then route as usual).
+        *
+        * @param sender the next hop
+        * @param result the content that was found
+        */
+
+       protected void queueCHKReply( HostIdentity sender, ContentBlock result )
+       {
+               final P2PChkResult      _msg;
+               IndirectionTableEntry   ite;
+               HashCode512                             hc;
+
+               hc=HashCode512.create(PersistentHelper.toBytes(result));
+
+               ite=ROUTING_indTable_[computeRoutingIndex(hc)];
+               if (!ite.hash.equals(hc)) {
+                       debug("Concurrent route replacement : "+hc.toHex()+".");
+
+                       stat_concurrent_route_replacement.inc();
+                       return;
+                       }
+               if (ite.successful_local_lookup_in_delay_loop) {
+                       debug("Unexpected concurrent CHK lookup of 
"+hc.toHex()+".");
+                       return; // wow, really bad concurrent DB lookup and 
processing for the same query.  Well, at least we should not also queue the 
delayed reply twice...
+                       }
+               ite.successful_local_lookup_in_delay_loop = true;
+
+               _msg=new P2PChkResult();
+               _msg.result=(ContentBlock) PersistentHelper.copy(result);       
//todo: copie utile ???
+
+               // delay reply, delay longer if we are busy (makes it harder to 
predict / analyze, too).
+               scheduler.addJob(new ScheduledTask("DELAYED-REPLY",new 
AbstractAction() {
+                       public void perform()
+                       {
+                               HashCode512                             hc2;
+                               IndirectionTableEntry           ite2;
+
+                               
hc2=HashCode512.create(PersistentHelper.toBytes(_msg.result));
+
+                               ite2 = 
ROUTING_indTable_[computeRoutingIndex(hc2)];
+                               synchronized(ite2.lookup_exclusion) {
+                                       if (hc2.equals(ite2.hash)) {
+                                               
ite2.successful_local_lookup_in_delay_loop = false;
+                                               }
+                                       else {
+                                               
stat_delaytime_route_replacement.inc();
+                                               }
+                                       }
+                               useContent(null,hc2,_msg);
+                       }
+                       }),Crypto.nextLong(TTL_DECREMENT));
+       }
+
+       /**
+        * Queue an SBLOCK reply with cron to simulate
+        * another peer returning the response with
+        * some latency (and then route as usual).
+        *
+        * @param sender the next hop
+        * @param result the content that was found
+        */
+
+       protected void queueSBLOCKReply( HostIdentity sender, EncryptedSBlock 
result )
+       {
+               final P2PSBlockResult   _msg;
+
+               _msg=new P2PSBlockResult(result);
+
+               // delay reply, delay longer if we are busy (makes it harder to 
predict / analyze, too).
+               scheduler.addJob(new ScheduledTask("SBLOCK-REPLY",new 
AbstractAction() {
+                       public void perform()
+                       {
+                               
useContent(null,_msg.getResult().getIdentifier(),_msg);
+                       }
+                       }),Crypto.nextLong(TTL_DECREMENT));
+       }
+
+       /**
+        * Queue a 3Hash reply with cron to simulate
+        * another peer returning the response with
+        * some latency (and then route as usual).
+        *
+        * @param sender the next hop
+        * @param hc the double (!) hash
+        * @param result the content that was found
+        */
+
+       protected void queue3HashReply( HostIdentity sender, HashCode512 hc, 
ContentBlock result )
+       {
+               final P2P3HashResult    _msg;
+
+               _msg=new P2P3HashResult(hc,result);
+
+               // delay reply, delay longer if we are busy (makes it harder to 
predict / analyze, too).
+               scheduler.addJob(new ScheduledTask("3HASH-REPLY",new 
AbstractAction() {
+                       public void perform()
+                       {
+                               useContent(null,_msg.getTripleHash(),_msg);
+                       }
+                       }),Crypto.nextLong(TTL_DECREMENT));
+       }
+
+       /**
+        * Hand a CHK reply to the client.
+        * @param sock the client socket
+        * @param result the response
+        */
+
+       protected void tellClientCHKReply( CSSession sock, ContentBlock result )
+       {
+               CSResultChk  reply;
+               HashCode512 hc;
+
+               hc=HashCode512.create(PersistentHelper.toBytes(result));
+
+               debug("Sending client response to CHK query "+hc.toHex()+".");
+
+               reply = new CSResultChk();
+               reply.result=(ContentBlock) PersistentHelper.copy(result);      
//todo: copie utile ???
+
+               stat_cs_reply_content_out.inc();
+
+               sock.send(reply);
+       }
+
+       /**
+        * Hand an SBLOCK reply to the client.
+        * @param sock the client socket
+        * @param result the response
+        */
+
+       protected void tellClientSBLOCKReply( CSSession sock, EncryptedSBlock 
result )
+       {
+               CSResultSBlock  reply;
+
+               reply=new CSResultSBlock(result);
+
+               stat_cs_reply_content_out.inc();
+
+               sock.send(reply);
+       }
+
+       /**
+        * Hand a 3Hash reply to the client.
+        * @param sock the client socket
+        * @param hc the double hash
+        * @param result the response
+        */
+
+       protected void tellClient3HashReply( CSSession sock, HashCode512 hc, 
ContentBlock result )
+       {
+               CSResult3Hash  reply;
+
+               reply = new CSResult3Hash(hc,result);
+
+               stat_cs_reply_content_out.inc();
+
+               sock.send(reply);
+       }
+
+       /**
+        * Add an entry to the routing table. The lock on the ite
+        * must be held and is being released.
+        *
+        * @param mode replace or extend an existing entry?
+        * @param ite slot in the routing table that is manipulated
+        * @param query the query to look for
+        * @param namespace the namespace to look in (null for global namespace)
+        * @param ttl how long to keep the new entry, relative ttl
+        * @param priority how important is the new entry
+        * @param sender for which node is the entry (null for local client)
+        * @param sock for which local client is the entry (null for peer)
+        * @return true if sock or sender was added, false if both are null or 
existed already
+        *            in the queue
+        */
+
+       protected boolean addToSlot( int mode, IndirectionTableEntry ite, 
HashCode512 query, HashCode512 namespace, int ttl, int priority, HostIdentity 
sender, CSSession sock )
+       {
+               long    now;
+               boolean ret = false;
+
+               // namespace handling: always override with the new value 
(query collisions are supposed to be 'impossible',
+               // so this should always be correct.  Either we replace the 
existing slot with something new, or it
+               // should not make a difference since the old and the new 
namespace will be the same.
+               if (ite.namespace != null) {
+                       if (namespace == null) {
+                               ite.namespace = null;
+                       } else {
+                               ite.namespace=(HashCode512) 
PersistentHelper.copy(namespace);   //todo: copie utile ???
+                       }
+               } else {
+                       if (namespace != null) {
+                               ite.namespace=(HashCode512) 
PersistentHelper.copy(namespace);   //todo: copie utile ???
+                       }
+               }
+               now=Scheduler.now();
+               if (mode == ITE_REPLACE) {
+                       ite.clearSeen();
+                       if (query.equals(ite.hash)) {
+                               stat_routingPresent.inc();
+                               ite.ttl = now + ttl;
+                               ite.priority = priority;
+                               }
+                       else {
+                               if (ite.hasClients() && sender==null && 
!ite.hasSeen()) {
+                                       debug("Replacing local query 
"+query.toHex()+" without results with foreign query !");
+                                       }
+
+                               ite.successful_local_lookup_in_delay_loop = 
false;
+                               // different request, flush pending queues
+                               stat_routingReplaced.inc();
+                               queryManager.dequeueQuery(ite.hash);
+                               ite.hash=(HashCode512) 
PersistentHelper.copy(query);    //todo: copie utile ???
+                               ite.clearWaitingHosts();
+                               ite.clearClients();
+                               ite.ttl = now + ttl;
+                               ite.priority = priority;
+                       }
+               } else { // GROW mode
+                       if (!query.equals(ite.hash)) {
+                               trace("Assert failed !");
+                               return false;
+                               }
+                       if (sender != null) {
+                               if (ite.hasWaitingHost(sender)) {
+                                       sender=null;
+                                       }
+                               }
+                       stat_routingPresent.inc();
+
+                       if (sock!=null && ite.containsClient(sock)) {
+                               sock=null;
+                               }
+
+                       if ( (sock == null) &&
+                                       (sender == null) ) {
+                               return ret; // already there!
+                       }
+                       // extend lifetime
+                       if (ite.ttl < now + ttl)
+                               ite.ttl = now + ttl;
+                       ite.priority += priority;
+                       }
+
+               if (sock!=null) {
+                       if (ite.containsClient(sock)) {
+                               sock=null;
+                               }
+
+                       if (sock != null) {
+                               ite.addClient(sock);
+                               ite.clearSeen(); // new listener, flush "seen" 
list
+                               ret = true;
+                               }
+                       }
+
+               if (sender!=null && ite.hasWaitingHost(sender)) {
+                       sender=null;
+                       }
+
+               if (sender!=null) {
+                       ite.addWaitingHost(sender);
+                       ret = true;
+                       // again: new listener, flush seen list
+                       ite.clearSeen();
+                       }
+               return ret;
+       }
+
+       /**
+        * Find out, if this query is already pending. If the ttl of
+        * the new query is higher than the ttl of an existing query,
+        * false is returned since we should re-send the query.<p>
+        *
+        * If true is returned, the slot is also marked as used by
+        * the query and the sender (HostId or socket) is added.<p>
+        *
+        * This method contains a heuristic that attempts to do its best to
+        * route queries without getting too many cycles, send a query and
+        * then drop it from the routing table without sending a response,
+        * etc.  Before touching this code, definitely consult Christian
+        * (address@hidden) who has put more bugs in these five lines
+        * of code than anyone on this planet would think is possible.
+        *
+        *
+        * @param query the hash to look for
+        * @param namespace the namespace to look in, null for the global 
namespace
+        * @param ttl how long would the new query last
+        * @param priority the priority of the query
+        * @param sender which peer transmitted the query? (null for this peer)
+        * @param sock which client transmitted the query? (null for other peer)
+        * @param isRouted set to true if we can route this query, false if we 
can not
+        * @param doForward is set to true if we should forward the query, 
false if not
+        * @return a case ID for debugging
+        */
+
+       protected int needsForwarding( HashCode512 query, HashCode512 
namespace, int ttl, int priority, HostIdentity sender, CSSession sock, 
boolean[] isRouted, boolean[] doForward )
+       {
+               IndirectionTableEntry  ite;
+               long now;
+
+               now=Scheduler.now();
+               ite = ROUTING_indTable_[computeRoutingIndex(query)];
+               // released either in here or by addToSlot!
+
+               if ( ( ite.ttl < now - TTL_DECREMENT * 10) &&
+                               ( ttl > - TTL_DECREMENT * 5) ) {
+                       addToSlot(ITE_REPLACE, ite, query, namespace, ttl, 
priority, sender, sock);
+                       isRouted[0] = true;
+                       doForward[0] = true;
+                       return 21;
+                       }
+               if (ttl<0 && query.equals(ite.hash)) {
+                       // if ttl is "expired" and we have the exact query 
pending, route replies but do NOT forward _again_!
+
+                       debug("GROW: ttl < 0 and existing query is equal 
("+ttl+", "+(ite.ttl-now)+").");
+
+                       addToSlot(ITE_GROW, ite, query, namespace, ttl, 
priority, sender, sock);
+                       // don't go again, we are not even going to reset the 
seen list, so why bother looking locally again, if we would find
+                       //something, the seen list would block sending the 
reply anyway since we're not resetting that (ttl too small!)!
+                       isRouted[0] = false;
+                       doForward[0] = false;
+                       return 0;
+               }
+
+               if ( (ite.ttl + (TTL_DECREMENT * coreAPI.estimateNetworkSize()) 
<
+                               (now + ttl)) &&
+                               (ite.ttl < now) ) {
+                       // expired AND is significantly (!) longer expired than 
new query
+
+                       debug("REPLACE and reset SEEN: existing query expired 
and older than new query ("+ttl+", "+(ite.ttl-now)+").");
+
+                       // previous entry relatively expired, start using the 
slot -- and kill the old seen list!
+                       ite.clearSeen();
+                       if ( query.equals(ite.hash) &&
+                                       (ite. 
successful_local_lookup_in_delay_loop) ) {
+                               isRouted[0] = false;
+                               doForward[0] = false;
+                               addToSlot(ITE_GROW, ite, query, namespace, ttl, 
priority, sender, sock);
+                               return 1;
+                               }
+                       isRouted[0] = true;
+                       doForward[0] = true;
+                       addToSlot(ITE_REPLACE, ite, query, namespace, ttl, 
priority, sender, sock);
+                       return 2;
+                       }
+
+               if (query.equals(ite.hash) ) {
+                       if (!ite.hasSeen()) {
+                               // can not tell if CHK/3HASH/NSQUERY
+                               if (ite.ttl + TTL_DECREMENT < (now + ttl)) { // 
ttl of new is SIGNIFICANTLY     longer?
+                                       // query again
+
+                                       debug("REPLACE (seen was empty): 
existing query and TTL higher ("+(ite.ttl-now)+", "+ttl+").");
+
+                                       addToSlot(ITE_REPLACE, ite, query, 
namespace, ttl, priority, sender, sock);
+                                       if 
(ite.successful_local_lookup_in_delay_loop) {
+                                               isRouted[0] = false; // don't 
go again, we are already processing a local lookup!
+                                               doForward[0] = false;
+                                               return 3;
+                                               }
+                                       isRouted[0] = true;
+                                       doForward[0] = true;
+                                       return 4;
+                                       }
+
+                               // new TTL is lower than the old one, thus just 
wait for the reply that may come back
+
+                               debug("GROW - equal existing query exists 
without replies ("+(ite.ttl-now)+", "+ttl+").");
+
+                               if (addToSlot(ITE_GROW, ite, query, namespace, 
ttl, priority, sender, sock)) {
+                                       if 
(ite.successful_local_lookup_in_delay_loop) {
+                                               isRouted[0] = false; // don't 
go again, we are already processing a local lookup!
+                                               doForward[0] = false;
+                                               return 5;
+                                               }
+                                       isRouted[0] = true;
+                                       doForward[0] = false;
+                                       return 6;
+                                       }
+
+                               // same query with _higher_ TTL has already 
been processed FOR THE SAME recipient! Do NOT do
+                               // the lookup *again*.
+                               isRouted[0] = false;
+                               doForward[0] = false;
+                               return 7;
+                               }
+                       // ok, seen reply before, can judge type of query!
+
+                       // pending == new!
+                       if ( ite.hash.equals(ite.getSeen(0)) &&
+                                       (ite.namespace == null) ) { // CHK
+                               if (ite.ttl < (now + ttl)) { // ttl of new is 
longer?
+                                       // go again
+                                       ite.clearSeen();
+
+                                       debug("REPLACE and reset SEEN: existing 
query equal but we've seen the response already ("+(ite.ttl-now)+", "+ttl+").");
+
+                                       addToSlot(ITE_REPLACE, ite, query, 
namespace, ttl, priority, sender, sock);
+                                       if 
(ite.successful_local_lookup_in_delay_loop) {
+                                               isRouted[0] = false; // don't 
go again, we are already processing a local lookup!
+                                               doForward[0] = false;
+                                               return 8;
+                                               }
+                                       isRouted[0] = true;
+                                       // only forward if new TTL is 
significantly higher
+                                       if (ite.ttl + TTL_DECREMENT < (now + 
ttl))
+                                               doForward[0] = true;
+                                       else
+                                               doForward[0] = false;
+                                       return 9;
+                                       }
+
+                               // new TTL is lower than the old one, thus just 
wait for the reply that may come back
+                               debug("GROW - equal existing query exists 
without replies ("+(ite.ttl-now)+", "+ttl+").");
+
+                               if (addToSlot(ITE_GROW, ite, query, namespace, 
ttl, priority, sender, sock)) {
+                                       if 
(ite.successful_local_lookup_in_delay_loop) {
+                                               isRouted[0] = false;
+                                               doForward[0] = false;
+                                               return 10;
+                                               }
+                                       isRouted[0] = true;
+                                       doForward[0] = false;
+                                       return 11;
+                                       }
+                               isRouted[0] = false;
+                               doForward[0] = false;
+                               return 12;
+                               }
+
+                       // 3HASH or SQUERY, multiple results possible!
+                       // It's a pending 3HASH or SQUERY that can have 
multiple replies.  Do not re-send, just forward the
+                       // answers that we get from now on to this additional 
receiver
+                       boolean isttlHigher;
+
+                       debug("GROW - equal existing query exists without 
replies ("+(ite.ttl-now)+", "+ttl+").");
+
+                       if (ite.ttl <  now+ttl)
+                               isttlHigher = false;
+                       else
+                               isttlHigher = true;
+                       if (addToSlot(ITE_GROW, ite, query, namespace, ttl, 
priority, sender, sock)) {
+                               isRouted[0] = true;
+                               doForward[0] = false;
+                               return 13;
+                               }
+                       // receiver is the same as the one that already got the 
answer, do not bother to do this again,
+                       // IF the TTL is not higher!
+                       isRouted[0] = isttlHigher;
+                       doForward[0] = false;
+                       return 14;
+                       }
+               // a different query that is expired a bit longer is using the 
slot; but if it is a CHK query that has received
+               // a response already, we can eagerly throw it out anyway, 
since the request has been satisfied completely
+               if ( (ite.ttl + TTL_DECREMENT < (now + ttl) ) &&
+                               (ite.ttl < now) &&
+                               (ite.getSeenCount() == 1) &&
+                               (ite.namespace == null) &&
+                               (ite.hash.equals(ite.getSeen(0))) ) {
+                       // is CHK and we have seen the answer, get rid of it 
early
+
+                       debug("CHK "+ite.hash.toHex()+" with reply already 
seen, replacing eagerly ("+(ite.ttl-now)+", "+ttl+").");
+
+                       addToSlot(ITE_REPLACE, ite, query, namespace, ttl, 
priority, sender, sock);
+                       isRouted[0] = true;
+                       doForward[0] = true;
+                       return 15;
+               }
+               // Another still valid query is using the slot.  Now we need a 
_really_ good reason to discard it...
+               if (ttl < 0) {
+                       isRouted[0] = false;
+                       doForward[0] = false;
+                       return 16; // if new ttl is "expired", don't bother 
with priorities
+               }
+
+               // Finally try to find a _strong_ reason looking at 
priority/ttl relationships to replace the existing query. A low ttl with high
+               // priority should be preferred, so we do a 
cross-multiplication (!). Also, we want a _strong_ reason, so we add a "magic" 
factor
+               // of 10 for the additional work that the replacement would 
make (the network needs a certain amount of resilience to changes in
+               // the routing table, otherwise it might happen that query A 
replaces query B which replaces query A which could happen so
+               // quickly that no response to either query ever makes it 
through...
+
+               if ( ((ite.ttl - now) * priority) >
+                                10 * (ttl * ite.priority) ) {
+
+                       debug("Priority of new query is much higher, overriding 
("+(ite.ttl-now)+", "+ttl+").");
+
+                       addToSlot(ITE_REPLACE, ite, query, namespace, ttl, 
priority, sender, sock);
+                       isRouted[0] = true;
+                       doForward[0] = true;
+                       return 17;
+               }
+
+               if (Crypto.nextInt(TIE_BREAKER_CHANCE) == 0) {
+                       debug("TIE-BREAKER.  Overriding ("+(ite.ttl-now)+", 
"+ttl+").");
+
+                       addToSlot(ITE_REPLACE, ite, query, namespace, ttl, 
priority, sender, sock);
+                       isRouted[0] = true;
+                       doForward[0] = true;
+                       return 20;
+                       }
+
+               // sadly, the slot is busy with something else; we can not even 
add ourselves to the reply set
+               stat_routingFull.inc();
+               isRouted[0] = false;
+               doForward[0] = false;
+
+               debug("Existing "+(!ite.hasClients() ? "remote" : "LOCAL")+
+                       " query "+ite.hash.toHex()+" 
("+computeRoutingIndex(ite.hash)+") is more important (EP: "+
+                       ite.priority+", ET: "+(ite.ttl-now)+"; NP: 
"+priority+", NT: "+ttl+")");
+               return 18;
+       }
+
+       /**
+        * Send a reply to a host.  Distinguishes between local and remote
+        * delivery, converts the reply into the appropriate format and sends
+        * it out.
+        *
+        * @param ite the matching slot in the indirection table
+        * @param msg the message to route
+        */
+
+       protected void sendReply( IndirectionTableEntry ite, P2PMessage msg )
+       {
+               int j;
+               int maxDelay;
+               long now;
+
+               now=Scheduler.now();
+               if (now < ite.ttl)
+                       maxDelay = (int) (ite.ttl - now);
+               else
+                       maxDelay = (int) TTL_DECREMENT; // for expired queries
+
+               // send to peers
+               for (j=0; j<ite.getWaitingHostsCount(); j++) {
+                       
coreAPI.sendToNode(ite.getWaitingHost(j),msg,BASE_REPLY_PRIORITY 
*(ite.priority+1),maxDelay);// weigh priority
+                       }
+
+               for (j=0; j<ite.getClientsCount(); j++) {
+                       if (msg instanceof P2P3HashResult) {
+                               tellClient3HashReply(ite.getClient(j),
+                                               ((P2P3HashResult) 
msg).getDoubleHash(),
+                                               new 
ContentBlock(((P2P3HashResult) msg).getResult())
+                                               );
+                               }
+                       else if (msg instanceof P2PChkResult) {
+                               
tellClientCHKReply(ite.getClient(j),((P2PChkResult)msg).result);
+                               }
+                       else if (msg instanceof P2PSBlockResult) {
+                               
tellClientSBLOCKReply(ite.getClient(j),((P2PSBlockResult) msg).getResult());
+                               }
+                       else {
+                               log(Level.WARNING,"Unexpected p2p result : 
"+msg);
+                               }
+                       }
+       }
+
+       /**
+        * TCP connection is shut down, cancel all replies to that client.
+        * @param sock
+        */
+
+       protected void cancelTCP_routing( CSSession sock )
+       {
+               IndirectionTableEntry   ite;
+               int                                             i;
+
+               for (i=0; i<indirectionTableSize; i++) {
+                       ite=ROUTING_indTable_[i];
+                       synchronized(ite.lookup_exclusion) {
+                               ite.removeClient(sock);
+                               }
+                       }
+       }
+
+       /**
+        * Execute a single query. Tests if the query can be routed. If yes,
+        * the query is added to the routing table and the content is looked
+        * for locally. If the content is available locally, a deferred
+        * response is simulated with a cron job and the local content is
+        * marked as valueable. The method returns true if the query should
+        * subsequently be routed to other peers.
+        *
+        * @param sender next hop in routing of the reply
+        * @param sock client socket if we are ultimate receiver
+        * @param prio the effective priority of the query
+        * @param ttl the relative ttl of the query
+        * @param query the query itself
+        * @param superHash true if the super-hash test has indicated that we#
+        *        have a reply locally available
+        * @return true if the query should be routed further, false if not.
+        */
+
+       protected boolean execSingleQuery( HostIdentity sender, CSSession sock, 
int prio, int ttl, HashCode512 query, boolean superHash )
+       {
+               ContentIndex                    ce=new ContentIndex();
+               ContentBlock[]                  result;
+               int                                             len;
+               boolean[]                               isRouted=new boolean[1];
+               boolean[]                               doForward=new 
boolean[1];
+               IndirectionTableEntry   ite;
+               int                                             nfCase,i,rcount;
+               HashCode512                             hc;
+
+               ite = ROUTING_indTable_[computeRoutingIndex(query)];
+               synchronized(ite.lookup_exclusion) {
+                       nfCase = needsForwarding(query,
+                                       null,
+                                       ttl,
+                                       prio,
+                                       sender,
+                                       sock,
+                                       isRouted,
+                                       doForward);
+
+                       debug("Needs forwarding decided for "+((sock == null) ? 
"remote" : "LOCAL")+
+                               " query "+query.toHex()+" 
("+computeRoutingIndex(query)+", ttl "+ttl+
+                               ", pri "+prio+"): case "+nfCase+" 
("+(doForward[0] ? "FWD" : "")+", "+(isRouted[0] ? "ROUTE" : "")+")");
+
+                       if ( (sender != null) && !isRouted[0]) {
+                               return false; // if we can't route, forwarding 
never makes any sense
+                       }
+
+                       if (!superHash && !singleBloomFilter.test(query)) {
+                               return doForward[0]; // content not available 
locally,  just route
+                               }
+
+                       result = manager.retrieveContent(query,ce,prio,sender 
== null);
+                       if (result==null) {
+                               return doForward[0]; // bloomfilter was wrong, 
content not there
+                               }
+
+                       len=result.length;
+                       if (len == ContentBlock.SIZE) {
+                               
hc=HashCode512.create(PersistentHelper.toBytes(result[0]));
+                               if (ite.hasSeen()) {
+                                       if (hc.equals(ite.getSeen(0))) {
+                                               log(Level.SEVERE,"Lookup 
produced result already seen. Case: "+nfCase+".");
+                                               }
+                                       }
+                               }
+
+                       if (sender != null) {
+                               if (ce.type == ContentIndex.LOOKUP_TYPE_3HASH) {
+                                       if 
(!policy.checkAnonymityPolicy(CSMessage.IS_RESULT_3HASH,P2P3HashResult.SIZE)) {
+                                               return doForward[0]; // policy 
says: no direct response, but routing is ok!
+                                               }
+                                       }
+                               else {
+                                       if 
(!policy.checkAnonymityPolicy(CSMessage.IS_RESULT_CHK,P2PChkResult.SIZE)) {
+                                               return doForward[0]; // policy 
says: no direct response, but routing is ok
+                                               }
+                                       }
+                               }
+
+                       switch (ce.type) {
+                               case ContentIndex.LOOKUP_TYPE_CHK:
+                               case ContentIndex.LOOKUP_TYPE_CHKS:
+                                       if (len != ContentBlock.SIZE) {
+                                               log(Level.WARNING,"Local CHK 
content had bad size : "+len+".");
+                                               break;
+                                               }
+                                       if (sock != null) {
+                                               
tellClientCHKReply(sock,result[0]);
+                                               doForward[0]=false; // purely 
local handling!
+                                               }
+                                       if (sender!=null) {
+                                               queueCHKReply(sender,result[0]);
+                                               }
+                                       doForward[0]=false; // we have the one 
and only answer
+                                       break;
+
+                               case ContentIndex.LOOKUP_TYPE_3HASH:
+                                       byte[]  
tmp=PersistentHelper.toBytes(result[0]);
+                                       ContentBlock    res;
+
+                                       rcount = len / ContentBlock.SIZE;
+                                       if (rcount * ContentBlock.SIZE != len) {
+                                               log(Level.WARNING,"Database 
returned a 3HASH result that is not multiple of 1k ("+len+") ! Database 
corrupted ?");
+                                               break;
+                                               }
+
+                                       if (sock!=null) {
+                                               for (i=0; i<rcount; i++) {
+                                                       res=(ContentBlock) 
PersistentHelper.readFully(ContentBlock.class,ByteBuffer.wrap(tmp,i*ContentBlock.SIZE,ContentBlock.SIZE));
+                                                       
tellClient3HashReply(sock,ce.hash,res);
+                                                       }
+                                               }
+
+                                       if (sender!=null) {
+                                               for (i=0; i<rcount; i++) {
+                                                       res=(ContentBlock) 
PersistentHelper.readFully(ContentBlock.class,ByteBuffer.wrap(tmp,i*ContentBlock.SIZE,ContentBlock.SIZE));
+                                                       
queue3HashReply(sender,ce.hash,res);
+                                                       }
+                                               }
+                                       break;
+
+                               default:
+                                       log(Level.FINEST,"Query lookup produced 
unexpected type "+ce.type+" !");
+                                       break;
+                               }
+                       }
+               return doForward[0];
+       }
+
+       /**
+        * Execute a namespace query. Tests if the query can be routed. If yes,
+        * the query is added to the routing table and the content is looked
+        * for locally. If the content is available locally, a deferred
+        * response is simulated with a cron job and the local content is
+        * marked as valueable. The method returns true if the query should
+        * subsequently be routed to other peers.
+        *
+        * @param sender next hop in routing of the reply
+        * @param sock client socket if we are ultimate receiver
+        * @param prio the effective priority of the query
+        * @param ttl the relative ttl of the query
+        * @param query the query itself
+        * @param namespace for which namespace
+        * @return true if the query should be routed further, false if not.
+        */
+
+       protected boolean execNSQuery( HostIdentity sender, CSSession sock, int 
prio, int ttl, HashCode512 query, HashCode512 namespace )
+       {
+               ContentIndex                    ce=new ContentIndex();
+               ContentBlock[]                  result;
+               int                                             len;
+               HashCode512                             hc;
+               boolean[]                               isRouted=new boolean[1];
+               boolean[]                               doForwarding=new 
boolean[1];
+               int                                             k;
+               IndirectionTableEntry   ite;
+
+               debug("Received NS query for 
"+namespace.toHex()+"/"+query.toHex()+".");
+
+               ite = ROUTING_indTable_[computeRoutingIndex(query)];
+               synchronized(ite.lookup_exclusion) {
+                       
needsForwarding(query,namespace,ttl,prio,sender,sock,isRouted,doForwarding);
+                       }
+               if (!isRouted[0]) {
+                       return false;
+                       }
+               if (!singleBloomFilter.test(query)) {
+                       debug("Bloomfilter test says content not available 
locally.");
+                       return doForwarding[0]; // content not available 
locally, just route
+                       }
+
+               result=manager.retrieveContent(query,ce,prio,sender == null);
+               if (result==null) {
+                       debug("Bloomfilter test wrong, DB lookup failed.");
+                       return doForwarding[0]; // bloomfilter was wrong, 
content not there
+                       }
+
+               len=result.length;
+               if (ce.type != ContentIndex.LOOKUP_TYPE_SBLOCK) {
+                       return doForwarding[0];
+                       }
+
+               if (sender != null) {
+                       if 
(!policy.checkAnonymityPolicy(CSMessage.IS_RESULT_SBLOCK,P2PSBlockResult.SIZE)) 
{
+                               debug("Anonymity policy denies reply at this 
time.");
+                               return doForwarding[0]; // policy says: no 
direct response, but routing is ok
+                               }
+                       }
+
+               if (0!=(len % ContentBlock.SIZE)) {
+                       log(Level.WARNING,"Local SBLOCK content had bad size 
"+len+".");
+                       return doForwarding[0];
+                       }
+
+               EncryptedSBlock sblock;
+               byte[]  tmp;
+
+               for (k=(len/ContentBlock.SIZE)-1; k>=0; k--) {
+                       tmp=PersistentHelper.toBytes(result[k]);
+                       sblock=(EncryptedSBlock) 
PersistentHelper.readFully(EncryptedSBlock.class,ByteBuffer.wrap(tmp,k*ContentBlock.SIZE,ContentBlock.SIZE));
+
+                       hc=sblock.getNameSpace();
+                       if (! namespace.equals(hc)) {
+                               log(Level.WARNING,"WARNING: namespace mismatch 
(should be rare but can theoretically happen).");
+                               return doForwarding[0];
+                               }
+                       if (sender != null) {
+                               queueSBLOCKReply(sender,sblock);
+                               }
+                       if (sock != null) {
+                               tellClientSBLOCKReply(sock,sblock);
+                               doForwarding[0] = false;
+                               }
+                       }
+               return doForwarding[0];
+       }
+
+       /**
+        * Handle query for current average routing priority.
+        *
+        * @param sock
+        * @param msg
+        * @return
+        */
+
+       public boolean csHandleRequestAvgPriority( CSSession sock, 
CSGetAvgPriority msg )
+       {
+               int i;
+               IndirectionTableEntry  ite;
+               int j = 0;
+               long priSum = 0;
+
+               for (i=0; i<MIN_INDIRECTION_TABLE_SIZE; i++) {
+                       ite = ROUTING_indTable_[i];
+                       synchronized(ite.lookup_exclusion) {
+                               if (ite.ttl!=0 && ite.hasWaitingHosts() && 
!ite.hasClients() ) {
+                                       /* only count entries that do NOT 
correspond to local requests in any way... */
+                                       priSum += ite.priority;
+                                       j++;
+                                       }
+                               }
+                       }
+               if (j > 0)
+                       priSum = priSum / j;
+               return sock.send(new CSResult((int) priSum));
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/AFSConstants.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/AFSConstants.java     
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/AFSConstants.java     
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,87 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.*;
+
+/**
+ *
+ */
+
+public interface AFSConstants
+{
+       public static final String      GNUNET_URI_PREFIX_AFS   =       
"gnunet://afs/";
+
+       /* major/minor format versions (current) */
+       public static final int ROOT_MINOR_VERSION              =       0;
+       public static final int ROOT_MAJOR_VERSION              =       1;
+
+       /* major/minor format versions (current) */
+       public static final int SBLOCK_MINOR_VERSION    =       0;
+       public static final int SBLOCK_MAJOR_VERSION    =       2;
+
+       /** Fixed SBlock updateInterval codes. Positive values are interpreted 
as durations (in seconds) for periodical updates. */
+       public static final int SBLOCK_UPDATE_SPORADIC  =       -1;
+       public static final int SBLOCK_UPDATE_NONE              =       0;
+
+
+       /** Size of the Blocks we slice file data into (DBlocks and IBlocks). 
Never change this ! */
+       public static final int CONTENT_SIZE    =       1024;
+
+
+       /** Block is freshly created, nothing has been done. */
+       public static final int BLOCK_CREATED                           =       
0;
+
+       /** We know the correct block data and is is on the drive (and in 
memory if data != null) */
+       public static final int BLOCK_PRESENT                           =       
1;
+
+       /** We do not know the correct data, but we have not done a request yet.
+        It may be that we can construct the data from the children (if they 
are present). */
+       public static final int BLOCK_NOT_PRESENT                       =       
2;
+
+       /** We have a request pending for this block (either with the parent if 
parent != null)
+        or a direct request if parent == null. */
+       public static final int BLOCK_PENDING                           =       
3;
+
+       /** This block is present and all children (transitively) are also 
present. */
+       public static final int BLOCK_CHILDREN_PRESENT          =       4;
+
+       /** This iblock has a super-query pending. */
+       public static final int BLOCK_SUPERQUERY_PENDING        =       5;
+
+       /** This block is done (about to be freed). */
+       public static final int BLOCK_DONE                                      
=       6;
+
+       /** This block shall not be freed, even if all children are dead. */
+       public static final int BLOCK_PERSISTENT                        =       
7;
+
+
+       /** by which amount do we decrement the TTL for simple forwarding / 
indirection of the query; in milli-seconds.
+        Set somewhat in accordance to your network latency (above the time 
it'll take you to send a packet and get a reply). */
+       public static final long        TTL_DECREMENT   =       
Scheduler.SECS_5;
+
+       /** Highest TTL allowed ? (equivalent of 25-50 HOPS distance !) */
+       public static final long        MAX_TTL                 =       (100 * 
TTL_DECREMENT);
+
+       /** After how many retries do we print a warning ? */
+       public static final int         MAX_TRIES               =       50;
+
+       /* what is the context in which a root-node was discovered? */
+       public static final int DIR_CONTEXT_SEARCH              =       1;
+       public static final int DIR_CONTEXT_INSERT              =       2;
+       public static final int DIR_CONTEXT_DIRECTORY   =       4;
+       public static final int DIR_CONTEXT_INSERT_SB   =       8;
+
+       public static final int DIR_CONTEXT_ALL                 =       
(DIR_CONTEXT_SEARCH|DIR_CONTEXT_INSERT|DIR_CONTEXT_DIRECTORY|DIR_CONTEXT_INSERT_SB);
+
+       /* see also: http://www.w3.org/TR/PNG#R.PNG-file-signature */
+       public static final String      GNUNET_DIRECTORY_MAGIC  =       
"\211GND\r\n\032\n";
+       public static final String      GNUNET_DIRECTORY_EXT    =       ".gnd";
+       public static final String      GNUNET_DIRECTORY_MIME   =       
"application/gnunet-directory";
+
+       /** default size of the query record bitmap: 16 byte = 128 bit */
+       public static final int BITMAP_SIZE     =       16;
+
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/AFSUtils.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/AFSUtils.java 2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/AFSUtils.java 2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,121 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+import java.util.*;
+import java.util.logging.*;
+
+/**
+ *
+ */
+
+public class AFSUtils extends Object
+{
+       private static Logger   logger;
+
+       static {
+               logger=Logger.getLogger(AFSUtils.class.getName());
+               }
+
+
+       /**
+        * Parse the keywords (join at spaces, separate at AND).
+        * @param keywords      the list of keywords
+        * @return                      the hashcodes of the keywords
+        */
+
+       public static HashCode512[] parseKeywords( String[] keywords )
+       {
+               HashCode512[]   h;
+               List                    list;
+               StringBuffer    buf;
+               String                  str;
+               int                             i;
+
+               list=new ArrayList();
+
+               buf=new StringBuffer();
+               for (i=0; i<keywords.length; i++) {
+                       buf.setLength(0);
+                       while (i<keywords.length && (!keywords[i].equals("AND") 
|| i==keywords.length-1)) {
+                               buf.append(keywords[i++]);
+                               buf.append(" ");
+                               }
+
+                       if (buf.length()>0) {
+                               buf.setLength(buf.length()-1);
+                               list.add(buf.toString());
+                               }
+                       }
+
+               h=new HashCode512[list.size()];
+               for (i=0; i<h.length; i++) {
+                       str=(String) list.get(i);
+
+                       logger.log(Level.FINEST,"Keyword : \""+str+"\"");
+                       h[i]=HashCode512.create(str);
+                       }
+               return h;
+       }
+
+       /**
+        * Build an initial set of query messages from the list of keywords.
+        *
+        * @param keywords      the keywords (or keys)
+        * @return                      the resulting query messages
+        */
+
+       public static CSQuery[] buildMessages( HashCode512[] keywords )
+       {
+               HashCode512     doubleHash;
+               CSQuery[]       messages;
+               int                     i;
+
+               messages=new CSQuery[keywords.length];
+               for (i=0; i<messages.length; i++) {
+                       messages[i]=new CSQuery();
+                       messages[i].setPriorityAndTTL(
+                               5+Crypto.nextInt(20),
+                               (int) 
(AFSConstants.TTL_DECREMENT*4+Crypto.nextInt((int) 
Scheduler.seconds(messages.length*5)))
+                               );
+
+                       
doubleHash=HashCode512.create(PersistentHelper.toBytes(keywords[i]));
+                       
messages[i].addQuery(HashCode512.create(PersistentHelper.toBytes(doubleHash)));
+                       }
+               return messages;
+       }
+
+       public static String get( ByteBuffer buf, int len )
+       {
+               byte[]  b;
+               int             n;
+
+               b=new byte[len];
+               buf.get(b);
+
+               for (n=0; n<b.length && b[n]!=0; n++) {}
+               return new String(b,0,n);
+       }
+
+       public static void put( String str, ByteBuffer buf, int len )
+       {
+               byte[]  b;
+               int             n;
+
+               b=str.getBytes();
+               n=Math.min(b.length,len-1);
+
+               buf.put(b,0,n);
+               while (n<len) {
+                       buf.put((byte) 0);
+                       n++;
+                       }
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/Block.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/Block.java    2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/Block.java    2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,552 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+import java.util.*;
+import java.util.logging.*;
+
+/**
+ * Shared structure used in the internal objectish representation
+ * of all blocks (DBlocks and IBlocks) in the merkle-tree.
+ * Merkle-tree-CHK file encoding for anonymous file sharing
+ *
+ * Note that the current implementation no longer uses the exact
+ * scheme from the ESED paper. Extensive documentation is forthcoming,
+ * for now see http://www.ovmj.org/GNUnet/encoding.php3
+ */
+
+public abstract class Block extends LoggedObject implements AFSConstants
+{
+       /** The parent node in the file-tree, null for the node on top of the 
file-tree. */
+       private IBlock                  parent;
+
+       /** The total size of the file. */
+       private int                             filesize;
+
+       /** Position of the block relative to the beginning of the file. */
+       private int                             pos;
+
+       /** How many bytes in data are actual data (not padding)? Set to 0 to 
indicate that the download of this block is complete. */
+       private int                             len;
+
+       /** Pointer to the data of this block, null if the data is not yet 
available. */
+       private Persistent              data;
+
+       /** Hashes of the plaintext block (key) and the encrypted block 
(query). */
+       private ChkHashes               chk;
+
+       /** See BLOCK_XXX constants. */
+       private int                             status;
+
+
+       protected Block( int size, int length )
+       {
+               super(true);
+               parent=null;
+               filesize=size;
+               pos=0;
+               len=length;
+               data=null;
+               chk=null;
+               status=BLOCK_CREATED;
+
+               assert(length>0 && length<=ContentBlock.SIZE) : "Invalid block 
size ("+length+").";
+       }
+
+       protected Block( Block b, int offset, int length )
+       {
+               this(b.filesize,length);
+               parent=(IBlock) b;
+               pos=offset;
+       }
+
+       protected Block( Block b )
+       {
+               super(true);
+               filesize=b.filesize;
+               pos=b.pos;
+               chk=(ChkHashes) PersistentHelper.copy(b.chk);
+               len=b.len;
+               data=PersistentHelper.copy(b.data);
+               parent=b.parent;
+               status=b.status;
+       }
+
+       public String toString()
+       {
+               return "Block";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public int getDataLength()
+       {
+               return len;
+       }
+
+       public boolean hasData()
+       {
+               return data!=null;
+       }
+
+       public byte[] getRawData()
+       {
+               //todo: bufferiser
+               return (data!=null ? PersistentHelper.toBytes(data) : new 
byte[] {});
+       }
+
+       public int getDataCRC()
+       {
+               return Crypto.crc32(getRawData(),0,len);
+       }
+
+       public Persistent getData()
+       {
+               return data;
+       }
+
+       public Persistent getData( Class c )
+       {
+               //todo: optimiser
+               return 
PersistentHelper.readFully(c,ByteBuffer.wrap(getRawData()));
+       }
+
+       public void setData( Persistent p )
+       {
+               data=p;
+               //todo: len par rapport a p.getByteSize() ???
+       }
+
+       public void clearData()
+       {
+               data=null;
+       }
+
+       protected boolean loadData( NodeContext nc, boolean warning )
+       {
+               int     n;
+               ContentBlock    tmp;
+
+               tmp=new ContentBlock();
+
+               n=nc.ioc.read(getDepth(),pos,tmp.content,len);
+//debug("... load data (pos="+pos+",len="+len+") = "+n);
+               if (n!=len) {
+                       if (warning) {
+                               log(Level.WARNING,"Read from file did not 
return expected size "+len+", but "+n+".");
+                               }
+                       return false;
+                       }
+               data=tmp;
+               return true;
+       }
+
+       public boolean writeData( NodeContext nc )
+       {
+               int     n;
+
+               n=nc.ioc.write(getDepth(),pos,getRawData(),len);
+               if (n!=len) {
+                       log(Level.SEVERE,"Writing to file failed 
("+len+","+n+") !");
+                       return false;
+                       }
+               return true;
+       }
+
+       public IBlock getParent()
+       {
+               return parent;
+       }
+
+       public void detach()
+       {
+               parent=null;
+       }
+
+       public boolean checkTopCRC( int crc )
+       {
+               return Crypto.crc32(getRawData(),0,len)==crc;
+       }
+
+       public HashCode512 getKey()
+       {
+               return chk.key;
+       }
+
+       public HashCode512 getQuery()
+       {
+               return chk.query;
+       }
+
+       public void setKeyAndQuery( ChkHashes c )
+       {
+               chk=(ChkHashes) PersistentHelper.copy(c);
+       }
+
+       public void setKeyAndQuery( FileIdentifier fid )
+       {
+               chk=new ChkHashes();
+               chk.query=fid.getFileQuery();
+               chk.key=fid.getFileKey();
+       }
+
+       public ChkHashes getKeyAndQuery()
+       {
+               return chk;
+       }
+
+       public boolean isStatus( int s )
+       {
+               return status==s;
+       }
+
+       public int getStatus()
+       {
+               return status;
+       }
+
+       public void setStatus( int s )
+       {
+               status=s;
+       }
+
+       public abstract int getDepth();
+
+       public int getFilePosition()
+       {
+               return pos;
+       }
+
+       public int getFileSize()
+       {
+               return filesize;
+       }
+
+       /**
+        * Encrypt this block and initialize
+        * this.chk and return the encrpyted data (edata)
+        * @return
+        */
+
+       protected ContentBlock encrypt()
+       {
+               ContentBlock    edata;
+               byte[]                  bdata;
+
+               bdata=getRawData();
+//System.out.println("CHK="+chk+" len = "+len);
+               chk=new ChkHashes();
+               chk.key=HashCode512.create(bdata,0,len);
+
+               Arrays.fill(bdata,len,ContentBlock.SIZE,(byte) 0);
+               edata = new ContentBlock();
+               if (!new ContentEncoding().encryptContent(bdata,chk.key,edata)) 
{
+                       trace("Encryption failed !?");
+                       return null;
+                       }
+               
chk.query=HashCode512.create(PersistentHelper.toBytes(edata),0,ContentBlock.SIZE);
+               return edata;
+       }
+
+       /**
+        * This block has received a CHK reply for a block. Decrypt.
+        *
+        * @param query the query for which reply is the answer
+        * @param reply the reply
+        * @return OK if the reply was valid, false on error
+        */
+
+       protected boolean receivedChk( HashCode512 query, CSResultChk reply )
+       {
+               byte[]          tmp;
+               HashCode512     hc;
+
+               assert(chk.query.equals(query)) : "invoked with reply for a 
different block. This should not be.";
+
+               tmp=reply.decrypt(chk.key);
+               if (tmp==null) {
+                       trace("Decryption failed !?");
+                       return false;
+                       }
+               setData(new ContentBlock(tmp));
+
+               hc=HashCode512.create(getRawData(),0,len);
+               if (!chk.key.equals(hc)) {
+                       data = null;
+                       log(Level.SEVERE,"Decrypted content does not match key. 
This is either a bug or a maliciously inserted file. Download aborted.");
+                       return false;
+                       }
+               return true;
+       }
+
+       /**
+        * Delete a block (send appropriate message to gnunetd).
+        *
+        * @param nc the context
+        * @param sock the socket to talk to gnunetd
+        * @return OK on success, false on error
+        */
+
+       public boolean delete( NodeContext nc, CSSession sock )
+       {
+               ContentBlock            edata;
+               CSUnindexBlock  request;
+               CSResult                        rv;
+
+               edata = encrypt();
+               if (sock == null) {
+                       return true;    // fake insert only
+                       }
+
+               if (nc.index != 0) {
+                       request=new CSUnindexBlock();
+                       request.contentIndex.importance= nc.priority;
+                       request.contentIndex.type= 
ContentIndex.LOOKUP_TYPE_CHKS;
+                       request.contentIndex.fileNameIndex= nc.index;
+                       request.contentIndex.fileOffset= pos;
+                       request.contentIndex.hash=(HashCode512) 
PersistentHelper.copy(chk.query);
+                       if (!sock.send(request)) {
+                               log(Level.WARNING,"Could not send unindex 
information to gnunetd. Is gnunetd running ?");
+                               return false;
+                               }
+
+                       rv=(CSResult) sock.receive(CSResult.class);
+                       if (rv==null) {
+                               log(Level.WARNING,"Server did not send 
confirmation of deletion.");
+                               return false;
+                               }
+                       if (!rv.isOkay()) {
+                               log(Level.WARNING,"Server could not perform 
unindexing (content already removed ?).");
+                               return false;
+                               }
+                       return true;
+                       }
+               return deleteCHKBlock(sock,edata,nc.priority);
+       }
+
+       /**
+        * Insert a CHK block (insert, not index!)
+        *
+        * @param eblock the block to insert
+        * @param priority the priority to use
+        * @param sock the socket to talk to gnunetd
+        * @return OK on success, false on error
+        */
+
+       protected boolean insertCHKBlock( CSSession sock, ContentBlock eblock, 
int priority )
+       {
+               CSInsertChk     request;
+               CSResult                rv;
+
+               if (sock == null)
+                       return true;    // fake insert
+
+               request = new CSInsertChk(priority,eblock.content);
+
+               if (!sock.send(request)) {
+                       log(Level.WARNING,"Could not send index information to 
gnunetd. Is gnunetd running ?");
+                       return false;
+                       }
+
+               rv=(CSResult) sock.receive(CSResult.class);
+               if (rv==null) {
+                       log(Level.WARNING,"Server did not send confirmation of 
insertion.");
+                       return false;
+                       }
+               if (!rv.isOkay()) {
+                       log(Level.WARNING,"Server could not perform 
insertion.");
+                       return false;
+                       }
+               return true;
+       }
+
+       /**
+        * Delete a CHK block.
+        *
+        * @param eblock the block to insert
+        * @param priority the priority to use
+        * @param sock the socket to talk to gnunetd
+        * @return OK on success, false on error
+        */
+
+       protected boolean deleteCHKBlock( CSSession sock, ContentBlock eblock, 
int priority )
+       {
+               CSDeleteChk     request;
+               CSResult                rv;
+
+               if (sock == null)
+                       return true;    // fake insert
+
+               request =new CSDeleteChk();
+               request.importance = priority;
+               
System.arraycopy(eblock.content,0,request.content,0,ContentBlock.SIZE);
+
+               if (!sock.send(request)) {
+                       log(Level.WARNING,"Could not send delete information to 
gnunetd. Is gnunetd running ?");
+                       return false;
+                       }
+
+               rv=(CSResult) sock.receive(CSResult.class);
+               if (rv==null) {
+                       log(Level.WARNING,"Server did not send confirmation of 
deletion.");
+                       return false;
+                       }
+
+               if (!rv.isOkay()) {
+                       log(Level.WARNING,"Server could not perform deletion.");
+                       return false;
+                       }
+               return true;
+       }
+
+       /**
+        * Send a single query via the RequestManager to gnunetd.
+        *
+        * @param rm the rm used to issue the query
+        * @param receiver the receiver to call on the reply
+        * @param nc the context
+        * @param query the query to perform
+        */
+
+       protected void issueQuery( RequestManager rm, Listener receiver, 
NodeContext nc, HashCode512 query )
+       {
+               CSQuery msg;
+
+               msg = new CSQuery();
+               msg.setPriorityAndTTL(1,1);
+               msg.addQuery(query);
+
+debug("issueQuery +++ CSQuery (2): "+msg);
+
+               rm.request(this,receiver,nc,msg);
+       }
+
+       /**
+        * Print the block summary (for debugging)
+        * @param indent
+        */
+
+       public abstract void print( int indent );
+
+       /**
+        * Free the associated resources of this Block. DOES ALSO free the
+        * memory occupied by the Block struct itself!
+        *
+        * @param rm reference to the RequestManager for requests
+        */
+
+       public void destroy( RequestManager rm )
+       {
+               /* better make sure that we have no request pending... */
+               if (rm != null) {       /* rm == null for gnunet-insert! */
+                       rm.assertDead(this);
+                       if (rm.top == this) {
+                               rm.top=null;
+                               }
+                       }
+
+               if (parent != null) {
+                       if (parent.childrenDestroyed(this)) {
+                               parent.destroy(rm);
+                               }
+                       }
+               data=null;
+       }
+
+       /**
+        * Check if this block is already present, if yes, loads it.
+        * @param nc the context
+        * @return YES if the block is present, NO if not
+        */
+
+       public abstract boolean check( NodeContext nc );
+
+       /**
+        * Download this node (and the children below). Note that the
+        * processing is asynchronous until the pmodel is called with position
+        * == total (and thus no more requests are pending) or the request
+        * manager is aborted by the user.
+        * @param nc the context
+        * @param rm the request manager
+        */
+
+       public abstract void download( NodeContext nc, RequestManager rm );
+
+       /**
+        * Insert the current block into the network. Implementations
+        * are also responsible for updating the corresponding fields
+        * of the parent node (of course, except if the parent is
+        * null in the case of the top-node in the tree).<p>
+        * Inner nodes first call the respective inserter methods for
+        * their children.<p>
+        *
+        * Insert a block (send appropriate message to gnunetd).
+        * This method encrypts the block and then sends an
+        * index or insertion request to gnunetd, depending on
+        * the configuration.
+        *
+        * @param nc the node context/the context (gives us the priority)
+        * @param sock the socket to use to talk to the core/the socket to talk 
to gnunetd, null if
+        *        we just do a "fake" insert to compute the tree in memory
+        * @return OK on success, false on error
+        */
+
+       public boolean insert( NodeContext nc, CSSession sock )
+       {
+               ContentBlock    edata;
+               CSIndexBlock    request;
+               CSResult                rv;
+               ContentIndex    index;
+
+               edata = encrypt();
+               if (sock == null) {
+                       return true;    // fake insert only
+                       }
+
+               if (nc.index != 0) {
+                       index=new ContentIndex();
+                       index.importance=nc.priority;
+                       index.type=ContentIndex.LOOKUP_TYPE_CHKS;
+                       index.fileNameIndex=nc.index;
+                       index.fileOffset=pos;
+                       index.hash=(HashCode512) 
PersistentHelper.copy(chk.query);
+
+                       request=new CSIndexBlock(index);
+                       if (!sock.send(request)) {
+                               log(Level.WARNING,"Could not send index 
information to gnunetd. Is gnunetd running ?");
+                               return false;
+                               }
+                       rv=(CSResult) sock.receive(CSResult.class);
+                       if (rv==null) {
+                               log(Level.WARNING,"Server did not send 
confirmation of insertion.");
+                               return false;
+                               }
+                       if (!rv.isOkay()) {
+                               log(Level.WARNING,"Server could not perform 
indexing.");
+                               return false;
+                               }
+                       return true;
+                       }
+               return insertCHKBlock(sock,edata,nc.priority);
+       }
+
+       public abstract boolean receive( HashCode512 query, CSResultChk reply, 
RequestManager rm, NodeContext nc );
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public static Block create( int size )
+       {
+               assert(size>0) : "size ("+size+") not allowed to be <= 0";
+
+               return (size>ContentBlock.SIZE ? (Block) new IBlock(size) : 
(Block) new DBlock(size));
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/CSDelete3Hash.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/CSDelete3Hash.java    
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/CSDelete3Hash.java    
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,75 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+
+/**
+ * Structure for an incoming request messages from the local TCP link
+ * to add content to the node.
+ */
+
+public class CSDelete3Hash extends CSMessage
+{
+       public static final int SIZE    =       
8+HashCode512.SIZE+ContentBlock.SIZE;
+
+       /** The (initial) priority of the data. */
+       public int                              importance;
+
+       /** The doubleHash of the plaintext. */
+       public HashCode512              doubleHash;
+
+       /** The data to insert */
+       public ContentBlock             content;
+
+
+       public CSDelete3Hash()
+       {
+               super(IS_DELETE_3HASH);
+               importance=0;
+               doubleHash=new HashCode512();
+               content=new ContentBlock();
+       }
+
+       public String toString()
+       {
+               return "xxx";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public int getByteSize()
+       {
+               return SIZE;
+       }
+
+       public void readBytes( ByteBuffer buf, ErrorReporter err )
+       {
+               int     size,type;
+
+               size=buf.getShort() & 0x0000ffff;
+               err.reportIf(size!=SIZE,"bad size");
+
+               type=buf.getShort() & 0x0000ffff;
+               err.reportIf(type!=IS_DELETE_3HASH,"bad type !");
+
+               importance=buf.getInt();
+               doubleHash.readBytes(buf,err);
+               content.readBytes(buf,err);
+       }
+
+       public void writeBytes( ByteBuffer buf )
+       {
+               buf.putShort((short) SIZE);
+               buf.putShort((short) IS_DELETE_3HASH);
+               buf.putInt(importance);
+               doubleHash.writeBytes(buf);
+               content.writeBytes(buf);
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/CSDeleteChk.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/CSDeleteChk.java      
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/CSDeleteChk.java      
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,68 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+
+/**
+ * Structure for an incoming request messages from the local TCP link
+ * to add content to the node.
+ */
+
+public class CSDeleteChk extends CSMessage
+{
+       public static final int SIZE    =       8+ContentBlock.SIZE;
+
+       /** The (initial) priority of the data */
+       public int                      importance;
+
+       /** The data to insert */
+       public ContentBlock     content;
+
+
+       public CSDeleteChk()
+       {
+               super(IS_DELETE_CHK);
+               importance=0;
+               content=new ContentBlock();
+       }
+
+       public String toString()
+       {
+               return "xxx";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public int getByteSize()
+       {
+               return SIZE;
+       }
+
+       public void readBytes( ByteBuffer buf, ErrorReporter err )
+       {
+               int     size,type;
+
+               size=buf.getShort() & 0x0000ffff;
+               err.reportIf(size!=SIZE,"bad size");
+
+               type=buf.getShort() & 0x0000ffff;
+               err.reportIf(type!=IS_DELETE_CHK,"bad type !");
+
+               importance=buf.getInt();
+               content.readBytes(buf,err);
+       }
+
+       public void writeBytes( ByteBuffer buf )
+       {
+               buf.putShort((short) SIZE);
+               buf.putShort((short) IS_DELETE_CHK);
+               buf.putInt(importance);
+               content.writeBytes(buf);
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/CSGetAvgPriority.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/CSGetAvgPriority.java 
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/CSGetAvgPriority.java 
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,53 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+
+/**
+ */
+
+public class CSGetAvgPriority extends CSMessage
+{
+       public static final int SIZE            =       4;
+
+
+       public CSGetAvgPriority()
+       {
+               super(IS_GET_AVG_PRIORITY);
+       }
+
+       public String toString()
+       {
+               return "Get average priority message";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public int getByteSize()
+       {
+               return SIZE;
+       }
+
+       public void readBytes( ByteBuffer buf, ErrorReporter err )
+       {
+               int     size,type;
+
+               size=buf.getShort() & 0x0000ffff;
+               err.reportIf(size!=SIZE,"invalid size !");
+
+               type=buf.getShort() & 0x0000ffff;
+               err.reportIf(type!=IS_GET_AVG_PRIORITY,"invalid type !");
+       }
+
+       public void writeBytes( ByteBuffer buf )
+       {
+               buf.putShort((short) SIZE);
+               buf.putShort((short) IS_GET_AVG_PRIORITY);
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/CSIndexBlock.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/CSIndexBlock.java     
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/CSIndexBlock.java     
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,73 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+
+/**
+ * Structure for an incoming request messages from the local TCP link
+ * to add content to the INDEX of the node.
+ */
+
+public class CSIndexBlock extends CSMessage
+{
+       public static final int SIZE    =       4+ContentIndex.SIZE;
+
+       /** indexing information */
+       private ContentIndex    contentIndex;
+
+
+       public CSIndexBlock()
+       {
+               super(IS_INDEX_BLOCK);
+               contentIndex=new ContentIndex();
+       }
+
+       public CSIndexBlock( ContentIndex idx )
+       {
+               this();
+               contentIndex=idx;
+       }
+
+       public String toString()
+       {
+               return "Client/server index block 
[contentIndex="+contentIndex+"]";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public ContentIndex getContentIndex()
+       {
+               return contentIndex;
+       }
+
+       public int getByteSize()
+       {
+               return SIZE;
+       }
+
+       public void readBytes( ByteBuffer buf, ErrorReporter err )
+       {
+               int     size,type;
+
+               size=buf.getShort() & 0x0000ffff;
+               err.reportIf(size!=SIZE,"bad size");
+
+               type=buf.getShort() & 0x0000ffff;
+               err.reportIf(type!=IS_INDEX_BLOCK,"bad type !");
+
+               contentIndex.readBytes(buf,err);
+       }
+
+       public void writeBytes( ByteBuffer buf )
+       {
+               buf.putShort((short) SIZE);
+               buf.putShort((short) IS_INDEX_BLOCK);
+               contentIndex.writeBytes(buf);
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/CSIndexFile.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/CSIndexFile.java      
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/CSIndexFile.java      
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,79 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.io.*;
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+
+/**
+ * Structure for an incoming request messages from the local TCP link
+ * to add a filename to the list of directly shared files
+ */
+
+public class CSIndexFile extends CSMessage
+{
+       public static final int SIZE            =       8+HashCode512.SIZE;
+
+       /** Size of the file in bytes. */
+       public long                     filesize;
+
+       /** RIPE160 hash of the entire file (to avoid duplicates !). */
+       public HashCode512      hash;
+
+
+       public CSIndexFile()
+       {
+               super(IS_INDEX_FILE);
+               filesize=0;
+               hash=new HashCode512();
+       }
+
+       public CSIndexFile( FileLocation f )
+       {
+               this();
+               filesize=f.getSize();
+               hash=f.getHash();
+       }
+
+       public String toString()
+       {
+               return "Client/server index file [filesize="+filesize+", 
hash="+hash+"]";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public int getByteSize()
+       {
+               return SIZE;
+       }
+
+       public void readBytes( ByteBuffer buf, ErrorReporter err )
+       {
+               int     size,type;
+
+               size=buf.getShort() & 0x0000ffff;
+               err.reportIf(size!=SIZE,"bad size !");
+
+               type=buf.getShort() & 0x0000ffff;
+               err.reportIf(type!=IS_INDEX_FILE,"bad type !");
+
+               filesize=buf.getInt();  //fixme: convert to a long value
+
+               hash=new HashCode512();
+               hash.readBytes(buf,err);
+       }
+
+       public void writeBytes( ByteBuffer buf )
+       {
+               buf.putShort((short) SIZE);
+               buf.putShort((short) IS_INDEX_FILE);
+               buf.putInt((int) filesize);     //fixme: convert to a long value
+               hash.writeBytes(buf);
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/CSIndexSuper.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/CSIndexSuper.java     
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/CSIndexSuper.java     
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,87 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+
+/**
+ * Structure for an incoming request messages from the local TCP link
+ * to add a super-query to the bloom filter.
+ */
+
+public class CSIndexSuper extends CSMessage
+{
+       public static final int SIZE    =       HashCode512.SIZE+8;
+
+       /** The super-hash for the bloom-filter. */
+       private HashCode512     superHash;
+
+       /** The (initial) priority of the data. */
+       private int                     importance;
+
+
+       public CSIndexSuper()
+       {
+               super(IS_INDEX_SUPER);
+               superHash=new HashCode512();
+               importance=0;
+       }
+
+       public CSIndexSuper( HashCode512 h, int pri )
+       {
+               this();
+               superHash=(HashCode512) PersistentHelper.copy(h);
+               importance=pri;
+       }
+
+       public String toString()
+       {
+               return "Client/server index super [superHash="+superHash+", 
importance="+importance+"]";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public HashCode512 getSuperHash()
+       {
+               return superHash;
+       }
+
+       public int getImportance()
+       {
+               return importance;
+       }
+
+       public int getByteSize()
+       {
+               return SIZE;
+       }
+
+       public void readBytes( ByteBuffer buf, ErrorReporter err )
+       {
+               int     size,type;
+
+               size=buf.getShort() & 0x0000ffff;
+               err.reportIf(size!=SIZE,"bad size");
+
+               type=buf.getShort() & 0x0000ffff;
+               err.reportIf(type!=IS_INDEX_SUPER,"bad type !");
+
+               superHash.readBytes(buf,err);
+
+               importance=buf.getInt();
+       }
+
+       public void writeBytes( ByteBuffer buf )
+       {
+               buf.putShort((short) SIZE);
+               buf.putShort((short) IS_INDEX_SUPER);
+               superHash.writeBytes(buf);
+               buf.putInt(importance);
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/CSInsert3Hash.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/CSInsert3Hash.java    
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/CSInsert3Hash.java    
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,98 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+
+/**
+ * Structure for an incoming request messages from the local TCP link
+ * to add content to the node.
+ */
+
+public class CSInsert3Hash extends CSMessage
+{
+       public static final int SIZE    =       
HashCode512.SIZE+ContentBlock.SIZE+8;
+
+       /** The (initial) priority of the data  (network byte order) */
+       private int                             importance;
+
+       /** The doubleHash of the plaintext. */
+       private HashCode512             doubleHash;
+
+       /** The data to insert */
+       private ContentBlock    content;
+
+
+       public CSInsert3Hash()
+       {
+               super(IS_INSERT_3HASH);
+               importance=0;
+               doubleHash=new HashCode512();
+               content=new ContentBlock();
+       }
+
+       public CSInsert3Hash( int prio, HashCode512 h, ContentBlock c )
+       {
+               this();
+               importance=prio;
+               doubleHash=h;
+               content=c;
+       }
+
+       public String toString()
+       {
+               return "Insert 3-hash (c/s) [importance="+importance+", 
doubleHash="+doubleHash+"]";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public int getImportance()
+       {
+               return importance;
+       }
+
+       public HashCode512 getDoubleHash()
+       {
+               return doubleHash;
+       }
+
+       public ContentBlock getContent()
+       {
+               return content;
+       }
+
+       public int getByteSize()
+       {
+               return SIZE;
+       }
+
+       public void readBytes( ByteBuffer buf, ErrorReporter err )
+       {
+               int     size,type;
+
+               size=buf.getShort() & 0x0000ffff;
+               err.reportIf(size!=SIZE,"bad size");
+
+               type=buf.getShort() & 0x0000ffff;
+               err.reportIf(type!=IS_INSERT_3HASH,"bad type !");
+
+               importance=buf.getInt();
+               doubleHash.readBytes(buf,err);
+               content.readBytes(buf,err);
+       }
+
+       public void writeBytes( ByteBuffer buf )
+       {
+               buf.putShort((short) SIZE);
+               buf.putShort((short) IS_INSERT_3HASH);
+               buf.putInt(importance);
+               doubleHash.writeBytes(buf);
+               content.writeBytes(buf);
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/CSInsertChk.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/CSInsertChk.java      
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/CSInsertChk.java      
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,87 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+
+/**
+ * Structure for an incoming request messages from the local TCP link
+ * to add content to the node.
+ */
+
+public class CSInsertChk extends CSMessage
+{
+       private static final int        SIZE    =       ContentBlock.SIZE+8;
+
+       /** The (initial) priority of the data. */
+       private int                             importance;
+
+       /** The data to insert */
+       private ContentBlock    content;
+
+
+       public CSInsertChk()
+       {
+               super(IS_INSERT_CHK);
+               importance=0;
+               content=new ContentBlock();
+       }
+
+       public CSInsertChk( int prio, byte[] b )
+       {
+               this();
+               importance=prio;
+               assert(b.length==ContentBlock.SIZE);
+               System.arraycopy(b,0,content.content,0,b.length);
+       }
+
+       public String toString()
+       {
+               return "Client/server insert chk [importance="+importance+", 
content="+content+"]";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public int getImportance()
+       {
+               return importance;
+       }
+
+       public ContentBlock getContent()
+       {
+               return content;
+       }
+
+       public int getByteSize()
+       {
+               return SIZE;
+       }
+
+       public void readBytes( ByteBuffer buf, ErrorReporter err )
+       {
+               int     size,type;
+
+               size=buf.getShort() & 0x0000ffff;
+               err.reportIf(size!=SIZE,"bad size");
+
+               type=buf.getShort() & 0x0000ffff;
+               err.reportIf(type!=IS_INSERT_CHK,"bad type !");
+
+               importance=buf.getInt();
+
+               content.readBytes(buf,err);
+       }
+
+       public void writeBytes( ByteBuffer buf )
+       {
+               buf.putShort((short) SIZE);
+               buf.putShort((short) IS_INSERT_CHK);
+               buf.putInt(importance);
+               content.writeBytes(buf);
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/CSInsertSBlock.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/CSInsertSBlock.java   
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/CSInsertSBlock.java   
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,85 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+
+/**
+ * Structure for an incoming request messages from the local TCP link
+ * to add content to the node.
+ */
+
+public class CSInsertSBlock extends CSMessage
+{
+       public static final int SIZE    =       SBlock.SIZE+8;
+
+       /** The (initial) priority of the data. */
+       private int                             importance;
+
+       /** The data to insert. */
+       private EncryptedSBlock content;
+
+
+       public CSInsertSBlock()
+       {
+               super(IS_INSERT_SBLOCK);
+               importance=0;
+               content=null;
+       }
+
+       public CSInsertSBlock( int i, EncryptedSBlock eb )
+       {
+               this();
+               importance=i;
+               content=eb;
+       }
+
+       public String toString()
+       {
+               return "C/S insert SBlock [importance="+importance+", 
content="+content+"]";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public int getImportance()
+       {
+               return importance;
+       }
+
+       public EncryptedSBlock getContent()
+       {
+               return content;
+       }
+
+       public int getByteSize()
+       {
+               return SIZE;
+       }
+
+       public void readBytes( ByteBuffer buf, ErrorReporter err )
+       {
+               int     size,type;
+
+               size=buf.getShort() & 0x0000ffff;
+               err.reportIf(size!=SIZE,"bad size");
+
+               type=buf.getShort() & 0x0000ffff;
+               err.reportIf(type!=IS_INSERT_SBLOCK,"bad type !");
+
+               importance=buf.getInt();
+               content.readBytes(buf,err);
+       }
+
+       public void writeBytes( ByteBuffer buf )
+       {
+               buf.putShort((short) SIZE);
+               buf.putShort((short) IS_INSERT_SBLOCK);
+               buf.putInt(importance);
+               content.writeBytes(buf);
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/CSLinkFile.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/CSLinkFile.java       
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/CSLinkFile.java       
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,89 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.io.*;
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+
+/**
+ * AFS message sent for linking to a file.
+ */
+
+public class CSLinkFile extends CSMessage
+{
+       public static final int SIZE            =       4+HashCode512.SIZE;
+
+       /** RIPE160 hash of the entire file (to avoid duplicates !) */
+       public HashCode512      hash;
+
+       /** Path of the file. */
+       public String           data;
+
+
+       public CSLinkFile()
+       {
+               super(IS_LINK_FILE);
+               hash=new HashCode512();
+               data="";
+       }
+
+       public CSLinkFile( FileLocation f )
+       {
+               this();
+               hash=f.getHash();
+               data=f.getPath();
+       }
+
+       public String toString()
+       {
+               return "Client/server link file [hash="+hash+", data="+data+"]";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public String getPath() //todo: use FileLocation
+       {
+               return data;
+       }
+
+       public int getByteSize()
+       {
+               return SIZE+data.length();
+       }
+
+       public void readBytes( ByteBuffer buf, ErrorReporter err )
+       {
+               byte[]  b;
+               int             size,type;
+
+               size=buf.getShort() & 0x0000ffff;
+               err.reportIf(size<SIZE,"bad size !");
+
+               type=buf.getShort() & 0x0000ffff;
+               err.reportIf(type!=IS_LINK_FILE,"bad type !");
+
+               hash=new HashCode512();
+               hash.readBytes(buf,err);
+
+               //fixme: character set encoding
+               b=new byte[size-SIZE];
+               buf.get(b);
+               data=new String(b);
+       }
+
+       public void writeBytes( ByteBuffer buf )
+       {
+               buf.putShort((short) getByteSize());
+               buf.putShort((short) IS_LINK_FILE);
+               hash.writeBytes(buf);
+
+               //fixme: character set encoding
+               buf.put(data.getBytes());
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/CSNSQuery.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/CSNSQuery.java        
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/CSNSQuery.java        
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,60 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+
+/**
+ * peer-to-peer message containing a namespace-query
+ */
+
+public class CSNSQuery extends CSMessage
+{
+       public static final int SIZE    =       HashCode512.SIZE*2+12;
+
+       /** how important is this request (network byte order) */
+       public int                      priority;
+
+       /** time to live in cronMILLIS (network byte order) */
+       public int                      ttl;
+
+       /** ID of the Namespace that we are searching in */
+       public HashCode512      namespace;
+
+       /** ID (in the namespace) that we're looking for */
+       public HashCode512      identifier;
+
+
+       public CSNSQuery()
+       {
+               super(IS_NSQUERY);
+       }
+
+       public String toString()
+       {
+               return "xxx";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public int getByteSize()
+       {
+               return SIZE;
+       }
+
+       public void readBytes( ByteBuffer buf, ErrorReporter err )
+       {
+               throw new IllegalStateException();
+       }
+
+       public void writeBytes( ByteBuffer buf )
+       {
+               throw new IllegalStateException();
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/CSQuery.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/CSQuery.java  2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/CSQuery.java  2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,151 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+import java.util.*;
+
+/**
+ * peer-to-peer message containing a set of queries.
+ */
+
+public class CSQuery extends CSMessage
+{
+       public static final int SIZE    =       12;
+
+       /** */
+       private int             size;
+
+       /** How important is this request ? */
+       private int             priority;
+
+       /** Time to live in milliseconds. */
+       private int             ttl;
+
+       /** Hashcodes of the files we're looking for.
+        If multiple queries are given, the first query is the super-query for 
the bloom filter. */
+       private List            queries;
+
+
+       public CSQuery()
+       {
+               super(IS_QUERY);
+               size=SIZE;
+               priority=0;
+               ttl=0;
+               queries=new ArrayList();
+       }
+
+       public String toString()
+       {
+               return "Client/server query [priority="+priority+", 
ttl="+ttl+", queries.count="+queries.size()+"]";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public int getPriority()
+       {
+               return priority;
+       }
+
+       public void setPriority( int pri )
+       {
+               priority=pri;
+       }
+
+       public int getTTL()
+       {
+               return ttl;
+       }
+
+       public void setTTL( int time )
+       {
+               ttl=time;
+       }
+
+       public void setPriorityAndTTL( int pri, int time )
+       {
+               priority=pri;
+               ttl=time;
+       }
+
+       public int getQueriesCount()
+       {
+               return queries.size();
+       }
+
+       public HashCode512 getQuery( int index )
+       {
+               return ((index>=0 && index<queries.size()) ? (HashCode512) 
queries.get(index) : null);
+       }
+
+       public void addQuery( HashCode512 h )
+       {
+               queries.add(PersistentHelper.copy(h));
+               size+=HashCode512.SIZE;
+       }
+
+       public HashCode512[] getQueries()
+       {
+               return (HashCode512[]) queries.toArray(new 
HashCode512[queries.size()]);
+       }
+
+       public void setQueries( HashCode512[] q )
+       {
+               queries.clear();
+               queries.addAll(Arrays.asList(q));
+               size=SIZE+q.length*HashCode512.SIZE;
+       }
+
+       public void clearQueries()
+       {
+               queries.clear();
+               size=SIZE;
+       }
+
+       public int getByteSize()
+       {
+               return SIZE+queries.size()*HashCode512.SIZE;
+       }
+
+       public void readBytes( ByteBuffer buf, ErrorReporter err )
+       {
+               HashCode512     h;
+               int                     type,i;
+
+               size=buf.getShort() & 0x0000ffff;
+               err.reportIf((size<SIZE || ((size-SIZE) % 
HashCode512.SIZE)!=0),"bad size");
+
+               type=buf.getShort() & 0x0000ffff;
+               err.reportIf(type!=IS_QUERY,"bad type !");
+
+               priority=buf.getInt();
+               ttl=buf.getInt();
+
+               queries.clear();
+               for (i=(size-SIZE)/HashCode512.SIZE; i>0; i--) {
+                       h=new HashCode512();
+                       h.readBytes(buf,err);
+                       queries.add(h);
+                       }
+       }
+
+       public void writeBytes( ByteBuffer buf )
+       {
+               int     i;
+
+               buf.putShort((short) size);
+               buf.putShort((short) IS_QUERY);
+               buf.putInt(priority);
+               buf.putInt(ttl);
+               for (i=0; i<queries.size(); i++) {
+                       ((HashCode512) queries.get(i)).writeBytes(buf);
+                       }
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/CSResult3Hash.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/CSResult3Hash.java    
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/CSResult3Hash.java    
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,104 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+
+/**
+ * TCP communication: search result content send back by gnunetd
+ */
+
+public class CSResult3Hash extends CSMessage implements AFSConstants
+{
+       public static final int SIZE    =       HashCode512.SIZE+CONTENT_SIZE+4;
+
+       /** The double-hash. */
+       private HashCode512     hash;
+
+       /** The search result. */
+       private byte[]          result;
+
+
+       public CSResult3Hash()
+       {
+               super(IS_RESULT_3HASH);
+               hash=new HashCode512();
+               result=new byte[CONTENT_SIZE];
+       }
+
+       public CSResult3Hash( HashCode512 h, ContentBlock block )
+       {
+               this();
+               hash=(HashCode512) PersistentHelper.copy(h);    //todo: copie 
utile ???
+               System.arraycopy(block.content,0,result,0,CONTENT_SIZE);
+       }
+
+       public String toString()
+       {
+               return "C/S 3-hash result [hash="+hash+"]";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public HashCode512 getDoubleHash()
+       {
+               return hash;
+       }
+
+       public HashCode512 getTripleHash()
+       {
+               return HashCode512.create(PersistentHelper.toBytes(hash));
+       }
+
+       /**
+        * Returns the content of the node in bytes.
+        * @param keyword
+        * @return
+        */
+
+       public byte[] decrypt( HashCode512 keyword )
+       {
+               byte[]          iv,buf;
+               SessionKey      skey;
+
+               // get key and init value from the hash code
+               iv=new byte[SessionKey.BLOWFISH_BLOCK_LENGTH];
+               skey=keyword.extractKey(iv);
+
+               buf=skey.decrypt(result,iv);
+               return buf;
+       }
+
+       public int getByteSize()
+       {
+               return SIZE;
+       }
+
+       public void readBytes( ByteBuffer buf, ErrorReporter err )
+       {
+               int     size,type;
+
+               size=buf.getShort() & 0x0000ffff;
+               err.reportIf(size!=SIZE,"bad size !");
+
+               type=buf.getShort() & 0x0000ffff;
+               err.reportIf(type!=IS_RESULT_3HASH,"bad type !");
+
+               hash.readBytes(buf,err);
+               buf.get(result);
+       }
+
+       public void writeBytes( ByteBuffer buf )
+       {
+               buf.putShort((short) SIZE);
+               buf.putShort((short) IS_RESULT_3HASH);
+               hash.writeBytes(buf);
+               buf.put(result);
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/CSResultChk.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/CSResultChk.java      
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/CSResultChk.java      
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,95 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+
+/**
+ * client-server message for search results
+ *
+ * Used in the CS-TCP communication: search result content send back
+ * by gnunetd
+ */
+
+public class CSResultChk extends CSMessage
+{
+       public static final int SIZE    =       ContentBlock.SIZE+4;
+
+       /** The search result. */
+       public ContentBlock     result;
+
+
+       public CSResultChk()
+       {
+               super(IS_RESULT_CHK);
+               result=null;
+       }
+
+       public String toString()
+       {
+               return "xxx";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       /**
+        * Decrypts inner data block.
+        *
+        * @param keyword       represents the key concatenated with the 
initial value used in the alg
+        * @return                      decrypted block on success, null on 
error
+        */
+
+       public byte[] decrypt( HashCode512 keyword )
+       {
+               byte[]          iv;
+               SessionKey      skey;
+
+               // get key and init value from the hash code
+               iv=new byte[SessionKey.BLOWFISH_BLOCK_LENGTH];
+               skey=keyword.extractKey(iv);
+
+               return skey.decrypt(result.content,iv);
+       }
+
+       public int getByteSize()
+       {
+               return SIZE;
+       }
+
+       public void readBytes( ByteBuffer buf, ErrorReporter err )
+       {
+               int     size,type;
+
+               size=buf.getShort() & 0x0000ffff;
+               err.reportIf(size!=SIZE,"bad size !");
+
+               type=buf.getShort() & 0x0000ffff;
+               err.reportIf(type!=IS_RESULT_CHK,"bad type !");
+
+               result=new ContentBlock();
+               result.readBytes(buf,err);
+       }
+
+       public void writeBytes( ByteBuffer buf )
+       {
+               int     i;
+
+               buf.putShort((short) SIZE);
+               buf.putShort((short) IS_RESULT_CHK);
+
+               if (result!=null) {
+                       result.writeBytes(buf);
+                       }
+               else {
+                       for (i=0; i<ContentBlock.SIZE; i++) {
+                               buf.put((byte) 0);
+                               }
+                       }
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/CSResultSBlock.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/CSResultSBlock.java   
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/CSResultSBlock.java   
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,75 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+
+/**
+ * client-server message for SBlock results
+ *
+ * Used in the CS-TCP communication: SBlock result content send back
+ * by gnunetd
+ */
+
+public class CSResultSBlock extends CSMessage
+{
+       public static final int SIZE    =       4+EncryptedSBlock.SIZE;
+
+       /** The search result. */
+       private EncryptedSBlock result;
+
+
+       public CSResultSBlock()
+       {
+               super(IS_RESULT_SBLOCK);
+               result=new EncryptedSBlock();
+       }
+
+       public CSResultSBlock( EncryptedSBlock esb )
+       {
+               this();
+               result=(EncryptedSBlock) PersistentHelper.copy(esb);    //todo: 
copie utile ???
+       }
+
+       public String toString()
+       {
+               return "C/S result SBlock [result="+result+"]";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public EncryptedSBlock getResult()
+       {
+               return result;
+       }
+
+       public int getByteSize()
+       {
+               return SIZE;
+       }
+
+       public void readBytes( ByteBuffer buf, ErrorReporter err )
+       {
+               int     size,type;
+
+               size=buf.getShort() & 0x0000ffff;
+               err.reportIf(size!=SIZE,"bad size");
+
+               type=buf.getShort() & 0x0000ffff;
+               err.reportIf(type!=IS_RESULT_SBLOCK,"bad type !");
+
+               result.readBytes(buf,err);
+       }
+
+       public void writeBytes( ByteBuffer buf )
+       {
+               buf.putShort((short) SIZE);
+               buf.putShort((short) IS_RESULT_SBLOCK);
+               result.writeBytes(buf);
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/CSUnindexBlock.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/CSUnindexBlock.java   
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/CSUnindexBlock.java   
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,51 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+
+/**
+ * Structure for an incoming request messages from the local TCP link
+ * to add content to the INDEX of the node.
+ */
+
+public class CSUnindexBlock extends CSMessage
+{
+       public static final int SIZE    =       ContentIndex.SIZE+4;
+
+       /** indexing information */
+       public ContentIndex     contentIndex;
+
+
+       public CSUnindexBlock()
+       {
+               super(IS_UNINDEX_BLOCK);
+       }
+
+       public String toString()
+       {
+               return "xxx";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public int getByteSize()
+       {
+               return SIZE;
+       }
+
+       public void readBytes( ByteBuffer buf, ErrorReporter err )
+       {
+               throw new IllegalStateException();
+       }
+
+       public void writeBytes( ByteBuffer buf )
+       {
+               throw new IllegalStateException();
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/CSUnindexFile.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/CSUnindexFile.java    
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/CSUnindexFile.java    
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,79 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.io.*;
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+
+/**
+ * Structure for an incoming request messages from the local TCP link
+ * to add a filename to the list of directly shared files
+ */
+
+public class CSUnindexFile extends CSMessage
+{
+       public static final int SIZE            =       8+HashCode512.SIZE;
+
+       /** Size of the file. */
+       public long                     filesize;
+
+       /** RIPE160 hash of the entire file (to avoid duplicates !). */
+       public HashCode512      hash;
+
+
+       public CSUnindexFile()
+       {
+               super(IS_UNINDEX_FILE);
+               filesize=0;
+               hash=new HashCode512();
+       }
+
+       public CSUnindexFile( FileLocation f )
+       {
+               this();
+               filesize=f.getSize();
+               hash=f.getHash();
+       }
+
+       public String toString()
+       {
+               return "Client/server unindex file [filesize="+filesize+", 
hash="+hash+"]";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public int getByteSize()
+       {
+               return SIZE;
+       }
+
+       public void readBytes( ByteBuffer buf, ErrorReporter err )
+       {
+               int     size,type;
+
+               size=buf.getShort() & 0x0000ffff;
+               err.reportIf(size!=SIZE,"bad size !");
+
+               type=buf.getShort() & 0x0000ffff;
+               err.reportIf(type!=IS_UNINDEX_FILE,"bad type !");
+
+               filesize=buf.getInt();  //fixme: convert to a long value
+
+               hash=new HashCode512();
+               hash.readBytes(buf,err);
+       }
+
+       public void writeBytes( ByteBuffer buf )
+       {
+               buf.putShort((short) SIZE);
+               buf.putShort((short) IS_UNINDEX_FILE);
+               buf.putInt((int) filesize);     //fixme: convert to a long value
+               hash.writeBytes(buf);
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/CSUnindexSuper.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/CSUnindexSuper.java   
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/CSUnindexSuper.java   
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,55 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+
+/**
+ * Structure for an incoming request messages from the local TCP link
+ * to add a super-query to the bloom filter.
+ */
+
+public class CSUnindexSuper extends CSMessage
+{
+       public static final int SIZE    =       HashCode512.SIZE+8;
+
+       /** The super-hash for the bloom-filter. */
+       public HashCode512      superHash;
+
+       /** The (initial) priority of the data  (network byte order) */
+       public int                      importance;
+
+
+       public CSUnindexSuper()
+       {
+               super(IS_UNINDEX_SUPER);
+       }
+
+       public String toString()
+       {
+               return "xxx";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public int getByteSize()
+       {
+               return SIZE;
+       }
+
+       public void readBytes( ByteBuffer buf, ErrorReporter err )
+       {
+               throw new IllegalStateException();
+       }
+
+       public void writeBytes( ByteBuffer buf )
+       {
+               throw new IllegalStateException();
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/CSUploadFile.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/CSUploadFile.java     
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/CSUploadFile.java     
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,125 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.io.*;
+import org.gnu.freeway.util.net.*;
+
+import java.io.*;
+import java.nio.*;
+import java.nio.channels.*;
+import java.util.logging.*;
+
+/**
+ * AFS message sent for uploading a file.
+ */
+
+public class CSUploadFile extends CSMessage
+{
+       public static final int SIZE            =       8+HashCode512.SIZE;
+
+       /** Position in the file. */
+       public long                     pos;
+
+       /** RIPE160MD hash of the entire file (to avoid duplicates !). */
+       public HashCode512      hash;
+
+       /** Chunk of binary content. */
+       public byte[]           data;
+
+
+       public CSUploadFile()
+       {
+               super(IS_UPLOAD_FILE);
+               pos=0;
+               hash=new HashCode512();
+               data=new byte[0];
+       }
+
+       public CSUploadFile( FileLocation f )
+       {
+               this();
+               hash=f.getHash();
+       }
+
+       public String toString()
+       {
+               return "Client/server upload file [pos="+pos+", hash="+hash+", 
data="+data+"]";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public byte[] getChunk()
+       {
+               return data;
+       }
+
+       public int copyChunk( FileChannel fc ) throws IOException       //todo: 
with MappedFile
+       {
+               Logger  logger;
+               int             copied,read;
+
+               pos=(int) fc.position();
+
+               copied=Math.min(65532-SIZE,(int) (fc.size()-fc.position()));
+
+               data=new byte[copied];
+               read=fc.read(ByteBuffer.wrap(data));
+               if (read!=copied) {
+                       logger=Logger.getLogger(getClass().getName());
+                       logger.log(Level.SEVERE,"Unable to read "+copied+" 
bytes from "+fc+" (read "+read+") !");
+                       return -1;
+                       }
+               return read;
+       }
+
+       public int copyChunk( byte[] b, int offset )
+       {
+               int     copied;
+
+               pos=offset;
+
+               copied=Math.min(65532-SIZE,b.length-offset);
+
+               data=new byte[copied];
+               System.arraycopy(b,offset,data,0,copied);
+               return copied;
+       }
+
+       public int getByteSize()
+       {
+               return SIZE+data.length;
+       }
+
+       public void readBytes( ByteBuffer buf, ErrorReporter err )
+       {
+               int     size,type;
+
+               size=buf.getShort() & 0x0000ffff;
+               err.reportIf(size<SIZE,"bad size !");
+
+               type=buf.getShort() & 0x0000ffff;
+               err.reportIf(type!=IS_UPLOAD_FILE,"bad type !");
+
+               pos=buf.getInt();       //fixme: convert to a long value
+
+               hash=new HashCode512();
+               hash.readBytes(buf,err);
+
+               data=new byte[size-SIZE];
+               buf.get(data);
+       }
+
+       public void writeBytes( ByteBuffer buf )
+       {
+               buf.putShort((short) getByteSize());
+               buf.putShort((short) IS_UPLOAD_FILE);
+               buf.putInt((int) pos);  //fixme: convert to a long value
+               hash.writeBytes(buf);
+               buf.put(data);
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/ChkHashes.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/ChkHashes.java        
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/ChkHashes.java        
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,114 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+
+/**
+ * Pair of Hashcodes for CHK encoded blocks.
+ *
+ * Every DBlock and IBlock is represented by two
+ * hashcodes, one is the key used to encrypt or
+ * decrypt the block; the other one is used to
+ * search for the block without reveiling the key.
+ * See also Freenet's CHK keys.<p>
+ *
+ * Note that GNUnet uses a different encoding for
+ * the RBlocks (root-nodes) in order to make searches
+ * possible.
+ */
+
+public class ChkHashes extends Object implements Persistent
+{
+       public static final int SIZE    =       HashCode512.SIZE*2;
+
+       /** The hash of the plaintext is the key to decrypt. */
+       public HashCode512      key;
+
+       /** The hash of the encrypted block is the query. */
+       public HashCode512      query;
+
+
+       public ChkHashes()
+       {
+               super();
+               key=new HashCode512();
+               query=new HashCode512();
+       }
+
+       public String toString()
+       {
+               return "Check hashes [key="+key+", query="+query+"]";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public HashCode512 getKeyHash()
+       {
+               return key;
+       }
+
+       public HashCode512 getQueryHash()
+       {
+               return query;
+       }
+
+       public int hashCode()
+       {
+               return (key.hashCode()<<1)^query.hashCode();
+       }
+
+       public boolean equals( Object obj )
+       {
+               ChkHashes       c;
+
+               if (!(obj instanceof ChkHashes)) {
+                       return false;
+                       }
+               c=(ChkHashes) obj;
+               return (c.key.equals(key) && c.query.equals(query));
+       }
+
+       public int getByteSize()
+       {
+               return SIZE;
+       }
+
+       public void readBytes( ByteBuffer buf, ErrorReporter err )
+       {
+               key=new HashCode512();
+               key.readBytes(buf,err);
+
+               query=new HashCode512();
+               query.readBytes(buf,err);
+       }
+
+       public void writeBytes( ByteBuffer buf )
+       {
+               int     i;
+
+               if (key!=null) {
+                       key.writeBytes(buf);
+                       }
+               else {
+                       for (i=0; i<HashCode512.SIZE; i++) {
+                               buf.put((byte) 0);
+                               }
+                       }
+
+               if (query!=null) {
+                       query.writeBytes(buf);
+                       }
+               else {
+                       for (i=0; i<HashCode512.SIZE; i++) {
+                               buf.put((byte) 0);
+                               }
+                       }
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/ContentBlock.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/ContentBlock.java     
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/ContentBlock.java     
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,63 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+import java.util.*;
+
+/**
+ * basic transmission unit for content in GNUnet
+ *
+ * A CONTENT_Block, representative of the structure
+ * of the leaf nodes (a simple chunk of 1 kb of data)
+ */
+
+public class ContentBlock extends Object implements Persistent, AFSConstants
+{
+       public static final int SIZE    =       1024;
+
+
+       public byte[]   content;
+
+//TODO: SUPPRIMER, NE SERT A RIEN !!!!!!!!!!!!
+       public ContentBlock()
+       {
+               super();
+               content=new byte[CONTENT_SIZE];
+               Arrays.fill(content,(byte) 0);  // not necessary, but...
+       }
+
+       public ContentBlock( byte[] b )
+       {
+               this();
+               assert(b.length==CONTENT_SIZE);
+               System.arraycopy(b,0,content,0,b.length);
+       }
+
+       public String toString()
+       {
+               return "Content block";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public int getByteSize()
+       {
+               return SIZE;
+       }
+
+       public void readBytes( ByteBuffer buf, ErrorReporter err )
+       {
+               buf.get(content);
+       }
+
+       public void writeBytes( ByteBuffer buf )
+       {
+               buf.put(content);
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/ContentEncoding.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/ContentEncoding.java  
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/ContentEncoding.java  
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,65 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.crypto.*;
+
+/**
+ * Encryption and decryption of blocks for deniability.
+ */
+
+public class ContentEncoding extends LoggedObject
+{
+       public ContentEncoding()
+       {
+               super(true);
+       }
+
+       public String toString()
+       {
+               return "xxx";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       /**
+        * Encrypts a given data block
+        *
+        * @param data represents the data block
+        * @param hashcode represents the key concatenated with the initial
+        *        value used in the alg
+        * @param result where to store the result (encrypted block)
+        * @return OK on success, false on error
+        */
+
+       public boolean encryptContent( ContentBlock data, HashCode512 hashcode, 
ContentBlock result )
+       {
+               return encryptContent(data.content,hashcode,result);
+       }
+
+       public boolean encryptContent( byte[] data, HashCode512 hashcode, 
ContentBlock result )
+       {
+               byte[]          iv,res;
+               SessionKey      skey;
+
+               if (data==null || hashcode==null || result==null) {
+                       trace("Aborting encryptContent: null in arguments.");
+                       return false;
+                       }
+
+               // get key and init value from the hash code
+               iv=new byte[SessionKey.BLOWFISH_BLOCK_LENGTH];
+               skey=hashcode.extractKey(iv);
+
+               res=skey.encrypt(data,0,ContentBlock.SIZE,iv);
+               if (res==null) {
+                       return false;
+                       }
+               System.arraycopy(res,0,result.content,0,res.length);
+               return true;
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/ContentIndex.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/ContentIndex.java     
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/ContentIndex.java     
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,136 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+
+/**
+ * Type of the content index file entries. The size of this
+ * struct dominates the database size, so keep it as small
+ * as possible. 32 byte should be enough!
+ *
+ * This structure is also used as a convenience struct to
+ * pass arguments around the db. Perhaps not a good idea.
+ */
+
+public class ContentIndex extends Object implements Persistent
+{
+       public static final int SIZE    =       HashCode512.SIZE+12;
+
+       /** Free entry. Historical. */
+       public static final int LOOKUP_TYPE_FREE        =       0;
+
+       /** Historical. */
+       public static final int LOOKUP_TYPE_DELETED     =       1;
+
+       /** (migrated) CHK content. */
+       public static final int LOOKUP_TYPE_CHK         =       2;
+
+       /** Search result, never indexed (always inserted). */
+       public static final int LOOKUP_TYPE_3HASH       =       3;
+
+       /** Super-query. Add to superBloomFilter, does not refer to any content 
in particular. */
+       public static final int LOOKUP_TYPE_SUPER       =       4;
+
+       /** CHK content covered by super-query (treat like CHK except do not 
add to singleBloomFilter). */
+       public static final int LOOKUP_TYPE_CHKS        =       5;
+
+       /** SBlock content. */
+       public static final int LOOKUP_TYPE_SBLOCK      =       6;
+
+       /** The double-hash (hash of the hash of the plaintext) of this entry 
for 3HASH entries, or the CHK query hash
+        (hash of the encrypted content) for CHK entries. Which is the case can 
be determined by looking at fileNameIndex and fileOffset. */
+       public HashCode512      hash;
+
+       /** The current rating of this content (in network byte order). */
+       public int                      importance;
+
+       /** The type of the entry. See LOOKUP_TYPE_XXX. The field is always in 
network byte order. */
+       public int                      type;
+
+       /** This field gives the index of the file into the file-index module 
if the value is >0. If the
+        value is 0, the file is in the contentdatabase. The field is always in 
network byte order. */
+       public int                      fileNameIndex;
+
+       /** The offset in the file for on-demand-encoded files where 
fileNameIndex is >0. */
+       public long                     fileOffset;
+
+
+       public ContentIndex()
+       {
+               super();
+               hash=null;
+               importance=0;
+               type=0;
+               fileNameIndex=0;
+               fileOffset=0;
+       }
+
+       public String toString()
+       {
+               return "Content index";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public int hashCode()
+       {
+               return type;
+       }
+
+       public boolean equals( Object obj )
+       {
+               ContentIndex    ce;
+
+               if (!(obj instanceof ContentIndex)) {
+                       return false;
+                       }
+
+               ce=(ContentIndex) obj;
+               return ((ce.hash!=null ? ce.hash.equals(hash) : hash==null) &&
+                       ce.importance==importance &&
+                       ce.type==type &&
+                       ce.fileNameIndex==fileNameIndex &&
+                       ce.fileOffset==fileOffset);
+       }
+
+       public int getByteSize()
+       {
+               return SIZE;
+       }
+
+       public void readBytes( ByteBuffer buf, ErrorReporter err )
+       {
+               hash=new HashCode512();
+               hash.readBytes(buf,err);
+
+               importance=buf.getInt();
+               type=buf.getShort() & 0x0000ffff;
+               fileNameIndex=buf.getShort() & 0x0000ffff;
+               fileOffset=buf.getInt();        //fixme: long value
+       }
+
+       public void writeBytes( ByteBuffer buf )
+       {
+               int     i;
+
+               if (hash!=null) {
+                       hash.writeBytes(buf);
+                       }
+               else {
+                       for (i=0; i<HashCode512.SIZE; i++) {
+                               buf.put((byte) 0);
+                               }
+                       }
+               buf.putInt(importance);
+               buf.putShort((short) type);
+               buf.putShort((short) fileNameIndex);
+               buf.putInt((int) fileOffset);   //fixme: long value
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/DBlock.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/DBlock.java   2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/DBlock.java   2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,284 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.net.*;
+
+import java.util.logging.*;
+
+/**
+ * Leaf (level-zero node) in the merkle-tree.
+ */
+
+public class DBlock extends Block
+{
+       /**
+        * Create a top-DBlock for files <= 1k where there is no parent IBlock.
+        *
+        * @param size the size of the file
+        */
+
+       public DBlock( int size )
+       {
+               super(size,size);
+       }
+
+       /**
+        * Create a DBlock. Note that this method can NOT be used for files <=
+        * 1k since parent may not be null (which it would be for the
+        * top-block). Use createTopDBlock for files <= 1k.
+        *
+        * @param parent        the parent block
+        * @param pos2          the offset of b block in the file
+        */
+
+       public DBlock( IBlock parent, int pos2 )
+       {
+               
super(parent,pos2,Math.min(ContentBlock.SIZE,parent.getFileSize()-pos2));
+       }
+
+       public String toString()
+       {
+               return "Data block [offset="+getFilePosition()+", 
size="+getFileSize()+"]";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public int getDepth()
+       {
+               return 0;
+       }
+
+       /**
+        * Insert the current block into the network. Implementations
+        * are also responsible for updating the corresponding fields
+        * of the parent node (of course, except if the parent is
+        * null in the case of the top-node in the tree).<p>
+        *
+        * Inner nodes first call the respective inserter methods for
+        * their children.<p>
+        *
+        * @param nc the context (gives us the priority)
+        * @param sock the socket to use to talk to the core
+        * @return OK on success, false on error
+        */
+
+       public boolean insert( NodeContext nc, CSSession sock )
+       {
+               boolean res;
+
+//debug("DI1 "+hasData());
+               if (hasData()) {
+                       return true;
+                       }
+
+//debug("will load");
+               if (!loadData(nc,sock!=null)) {
+                       return false;
+                       }
+
+               debug("Read "+getDataLength()+" bytes from IOC for insertion 
(2).");
+
+               nc.stats.progress += getDataLength();
+               if (nc.pmodel != null) {
+                       nc.pmodel.progress(nc.stats,nc.data);
+                       }
+
+               res = super.insert(nc,sock);
+               debug("Inserting dblock "+getFilePosition()+" of len 
"+getDataLength()+" under query "+getQuery().toHex()+".");
+               return res;
+       }
+
+       /**
+        * Delete the current block from the local peer. Works just
+        * like insert.
+        *
+        * @param nc the context (gives us the priority)
+        * @param sock the socket to use to talk to the core
+        * @return OK on success, false on error
+        */
+
+       public boolean delete( NodeContext nc, CSSession sock )
+       {
+               boolean res;
+
+               if (hasData()) {
+                       return true;
+                       }
+
+               if (!loadData(nc,sock!=null)) {
+                       return false;
+                       }
+               debug("Loaded "+getDataLength()+" bytes for deletion.");
+
+               nc.stats.progress += getDataLength();
+               if (nc.pmodel != null) {
+                       nc.pmodel.progress(nc.stats,nc.data);
+                       }
+               res = super.delete(nc,sock);
+               debug("Deleting dblock "+getFilePosition()+" of len 
"+getDataLength()+" under query "+getQuery().toHex());
+               return res;
+       }
+
+       /**
+        * Check if this dblock is already present on the drive.
+        * If the block is present, the parent and the
+        * ProgressModel are notified.
+        *
+        * @param nc the context (gives us the priority)
+        * @return YES if present, NO if not.
+        */
+
+       public boolean check( NodeContext nc )
+       {
+               HashCode512     hc;
+
+               debug("check: "+this);
+
+               /* first check if its already present */
+
+               if (loadData(nc,false)) {
+                       hc=HashCode512.create(getRawData(),0,getDataLength());
+                       if (hc.equals(getKey())) {
+                               setStatus(BLOCK_PRESENT);
+                               nc.stats.filesize = getFileSize();
+                               nc.stats.progress += getDataLength();
+                               nc.pmodel.progress(nc.stats,nc.data);
+                               return true;
+                               }
+                       }
+               clearData();
+               return false;
+       }
+
+       /**
+        * Download this node (and the children below). Note that the
+        * processing is asynchronous until the pmodel is called with position
+        * == total (and thus no more requests are pending) or the request
+        * manager is aborted by the user.
+        *
+        * @param nc the context (gives us the priority)
+        * @param rm the request manager
+        */
+
+       public void download( NodeContext nc, RequestManager rm )
+       {
+               debug("download");
+
+               if (check(nc)) {
+                       if (getParent() != null) {
+                               getParent().childDownloadCompleted(this,nc,rm);
+                               }
+                       /* leaf node, we're done when present */
+                       setStatus(BLOCK_DONE);
+                       destroy(rm);
+                       return;
+                       }
+               // not present, either request ourselves or let the parent do 
it automagically when we return...
+               setStatus(BLOCK_PENDING);
+               if (getParent() == null) {
+                       issueQuery(rm,new Listener() {
+                               public boolean listen( HashCode512 query, 
CSResultChk reply, RequestManager rmg, NodeContext data )
+                               {
+                                       return receive(query,reply,rmg,data);
+                               }
+                               },nc,getQuery());
+                       }
+       }
+
+       /**
+        * Function that is called when a message matching a
+        * request for a DBlock is received. Decrypts the received
+        * block and writes it to the file. Notifies the parent
+        * and the ProgressModel.
+        *
+        * @param query the query that was sent out
+        * @param reply the reply that was received
+        * @param rm the handle for the request manager
+        * @param nc the context (gives us the priority)
+        * @return false the request manager should abort the download,
+        *         OK if everything is fine
+        */
+
+       public boolean receive( HashCode512 query, CSResultChk reply, 
RequestManager rm, NodeContext nc )
+       {
+               ProgressStats   pstats;
+               int                             i;
+
+               debug("dblxock_download_receive : "+this);
+
+               if (!isStatus(BLOCK_PENDING)) {
+                       trace("dblxock_download_receive called, but no request 
was pending !");
+                       return false;
+                       }
+               if (!receivedChk(query,reply)) {
+                       pstats=new ProgressStats();
+                       nc.pmodel.progress(pstats,nc.data);
+                       return false;
+                       }
+               if (!writeData(nc)) {
+                       pstats=new ProgressStats();
+                       nc.pmodel.progress(pstats,nc.data);
+                       log(Level.SEVERE,"Writing to file failed !");
+                       return false;
+                       }
+
+               for (i=0; i<10; i++) {
+                       if (nc.stats.progress * 10000L > nc.stats.filesize * 
(10000L-(1024>>i)) &&
+                               (nc.stats.progress-getDataLength()) * 10000L <= 
nc.stats.filesize * (10000L - (1024 >> i))
+                               ) {
+                               // end-game boundary crossed, slaughter TTLs
+                               rm.endGame();
+                               }
+                       }
+
+               setStatus(BLOCK_PRESENT);
+               /* request satisfied, remove from RM */
+
+               nc.stats.progress += getDataLength();
+               if (getParent() != null) {
+                       /* child, must tell parent to adjust requests */
+                       getParent().childDownloadCompleted(this,nc,rm);
+                       getParent().doSuperRequest(nc,rm);
+                       }
+               else {
+                       /* top block, must cancel my own request */
+                       rm.update(this,null);
+                       }
+               // leaf, done when download complete
+               setStatus(BLOCK_DONE);
+               destroy(rm);
+               nc.pmodel.progress(nc.stats,nc.data);
+               return true;
+       }
+
+       /**
+        * Print a block to log.
+        * @param indent
+        */
+
+       public void print( int indent )
+       {
+               debug("print: "+this);
+
+               log(Level.FINEST,Utils.repeat('*',indent)+" DBLOCK (0) 
"+getFilePosition()+" "+getQuery().toHex());
+       }
+
+       /**
+        * Free the associated resources of this Block. DOES ALSO free the
+        * memory occupied by the Block struct itself!
+        *
+        * @param rm reference to the RequestManager for requests
+        */
+
+       public void destroy( RequestManager rm )
+       {
+               debug("destroy");
+               super.destroy(rm);
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/DeleteURI.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/DeleteURI.java        
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/DeleteURI.java        
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,29 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+/**
+ */
+
+public class DeleteURI extends GeneralURI
+{
+//     public int              action;
+       public String   filename;
+
+
+       public DeleteURI()
+       {
+               super();
+       }
+
+       public String toString()
+       {
+               return "xxx";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/DeleteUtil.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/DeleteUtil.java       
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/DeleteUtil.java       
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,116 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.io.*;
+import org.gnu.freeway.util.net.*;
+
+import java.util.logging.*;
+
+/**
+ * deleteutil, helper methods for file deletion.
+ * Break file that is deleted into blocks and encrypts
+ * them according to the CHK-triple-hash-tree scheme.
+ * Then sends delete-requests to gnunetd.
+ */
+
+public class DeleteUtil extends LoggedObject
+{
+       public DeleteUtil()
+       {
+               super(true);
+       }
+
+       public String toString()
+       {
+               return "xxx";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       /**
+        * Ask gnunetd for an index that matches the filename
+        * @param sock
+        * @param filename
+        * @return the index, -1 on error
+        */
+
+       protected int askDeleteFilename( CSSession sock, String filename )
+       {
+               CSUnindexFile   request;
+               CSResult        rv;
+               FileLocation            f;
+
+               f=new FileLocation(filename);
+               if (!f.exists()) {
+                       return -1;
+                       }
+
+               request=new CSUnindexFile(f);
+
+               if (!sock.send(request)) {
+                       log(Level.WARNING,"Could not send data to gnunetd. Is 
gnunetd running ?");
+                       return -1;
+                       }
+
+               rv=(CSResult) sock.receive(CSResult.class);
+               if (rv==null) {
+                       log(Level.WARNING,"Could not receive data from gnunetd. 
Is gnunetd running ?");
+                       return -1;
+                       }
+               return rv.getResult();
+       }
+
+       /**
+        * Deletes a file under the given name from the local GNUnet node.
+        *
+        * @param sock the socket to use to talk to gnunetd
+        * @param filename the name of the file to delete
+        * @param model the delete model used to
+        *        update status information; points to null if
+        *        no status updates shall be given, otherwise
+        *        to a method that takes two size_t arguments
+        *        (retrieved so far, total).
+        * @param model_data pointer that is passed to the model method
+        * @return OK on success, false on error
+        */
+
+       public boolean deleteFile( CSSession sock, String filename, 
ProgressModel model, Object model_data )
+       {
+               NodeContext     nc;
+               Block           top;
+               int                     filesize,ret;
+
+               filename = new FileLocation(filename).getPath();
+               filesize = (int) new FileLocation(filename).getSize();
+
+               nc=new NodeContext();
+               nc.pmodel = model;
+               nc.data = model_data;
+               nc.stats.filesize = filesize;
+               nc.priority = 0;
+
+               ret=askDeleteFilename(sock, filename);
+               if (ret <= 0) {
+                       return false;
+                       }
+               nc.index = ret;
+               if (!nc.ioc.init(filesize,filename,true)) {
+                       return false;
+                       }
+
+               top = Block.create(filesize);
+               if (!top.delete(nc,sock)) {
+                       top.destroy(null);
+                       nc.ioc.free(false);
+                       return false;
+                       }
+               nc.ioc.free(false);
+               top.destroy(null);
+               return true;
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/DownloadURI.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/DownloadURI.java      
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/DownloadURI.java      
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,31 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+/**
+ */
+
+public class DownloadURI extends GeneralURI
+{
+//     public int                              action;
+       public FileIdentifier           fid;
+       public String                   filename;
+
+
+       public DownloadURI()
+       {
+               super();
+       }
+
+       public String toString()
+       {
+               return "xxx";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/DownloadUtil.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/DownloadUtil.java     
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/DownloadUtil.java     
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,111 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.*;
+
+/**
+ * Helper functions for downloading.
+ * Download helper methods (which do the real work).
+ */
+
+public class DownloadUtil extends Object
+{
+
+       public DownloadUtil()
+       {
+               super();
+       }
+
+       public String toString()
+       {
+               return "xxx";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       protected void pModelWrap( ProgressStats stats, PMWrap wrap )
+       {
+               if (wrap.userModel!=null) {
+                       wrap.userModel.progress(stats,wrap.userData);
+                       }
+               if (stats.progress == stats.filesize) {
+                       wrap.nc.ioc.free(stats.progress!=0);
+                       }
+       }
+
+       /**
+        * Download a file.
+        *
+        * @param app
+        * @param p
+        * @param pppp
+        * @param fi the file identification (CHK, crc32, size) of the file
+        * @param fileName the name of the file
+        * @param model the download model used to
+        *        update status information; points to null if
+        *        no status updates shall be given, otherwise
+        *        to a method that takes two size_t arguments
+        *        (retrieved so far, total).
+        * @param data pointer that is passed to the model method
+        * @return a request manager that can be used to abort on success, null 
on error
+        */
+
+       public RequestManager downloadFile( AbstractClient app, Policy p, 
Priority pppp, FileIdentifier fi, String fileName, ProgressModel model, Object 
data )
+       {
+               NodeContext             nc;
+               Block                   top;
+               RequestManager  rm;
+               PMWrap                  wrap;
+
+               nc=new NodeContext();
+
+               rm=new RequestManager(app,p,pppp);
+               if (rm==null) {
+                       return null;
+                       }
+
+               if (!nc.ioc.init((int) fi.getFileLength(),fileName,false)) {    
//fixme: long value
+                       rm.destroy();
+                       return null;
+                       }
+
+               wrap=new PMWrap();
+               wrap.userModel = model;
+               wrap.userData = data;
+               wrap.nc = nc;
+
+               final PMWrap    _wrap = wrap;
+
+               nc.priority = 0; /* unused */
+               nc.index = 0; /* unused */
+               nc.pmodel = new ProgressModel() {
+                       public void progress( ProgressStats stats, Object o )
+                       {
+                               pModelWrap(stats,_wrap);
+                       }
+                       };
+               nc.data = wrap;
+               nc.stats=new ProgressStats();
+               nc.stats.filesize=(int) fi.getFileLength();     //fixme: long 
value
+
+               top=Block.create((int) fi.getFileLength());     //fixme: long 
value
+               top.setKeyAndQuery(fi);
+
+               rm.topCrc32=fi.getFileCRC();
+               rm.top=top;
+
+               top.download(nc,rm);
+               return rm;
+       }
+}
+
+class PMWrap extends Object
+{
+       protected ProgressModel userModel;
+       protected Object                userData;
+       protected NodeContext   nc;
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/EncryptedSBlock.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/EncryptedSBlock.java  
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/EncryptedSBlock.java  
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,130 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+
+/**
+ *
+ */
+
+public class EncryptedSBlock extends LoggedObject implements Persistent
+{
+       public static final int SIZE                            =       1024;
+       public static final int ENCRYPTED_SIZE  =       484;
+       public static final int SIGNED_SIZE             =       504;
+
+       /** */
+       private byte[]          encryptedData;
+
+       /** */
+       private HashCode512     identifier;
+
+       /** */
+       private Signature       signature;
+
+       /** */
+       private PublicKey       subspace;
+
+
+       public EncryptedSBlock()
+       {
+               super(true);
+               encryptedData=new byte[ENCRYPTED_SIZE];
+               identifier=new HashCode512();
+               signature=new Signature();
+               subspace=new PublicKey();
+       }
+
+       public EncryptedSBlock( byte[] b, HashCode512 id, Signature sig, 
PublicKey pub )
+       {
+               this();
+
+               assert(b.length==ENCRYPTED_SIZE);
+               System.arraycopy(b,0,encryptedData,0,ENCRYPTED_SIZE);
+               identifier=(HashCode512) PersistentHelper.copy(id);     //todo: 
copie utile ???
+               signature=(Signature) PersistentHelper.copy(sig);               
//todo: copie utile ???
+               subspace=(PublicKey) PersistentHelper.copy(pub);                
//todo: copie utile ???
+       }
+
+       public String toString()
+       {
+               return "xxx";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       /**
+        * Verify that a given encryted SBlock is well-formed.
+        * @return
+        */
+
+       public boolean verify()
+       {
+               byte[]  raw;
+
+               raw=PersistentHelper.toBytes(this);
+               return subspace.verify(signature,raw,0,SIGNED_SIZE);
+       }
+
+       /**
+        * @param key
+        * @return
+        */
+
+       public SBlock decrypt( HashCode512 key )
+       {
+               byte[]          raw,iv,tmp;
+               SessionKey      skey;
+
+               raw=PersistentHelper.toBytes(this);
+
+               iv=new byte[SessionKey.BLOWFISH_BLOCK_LENGTH];
+               skey=key.extractKey(iv);
+
+               tmp=skey.decrypt(raw,0,ENCRYPTED_SIZE,iv);
+               if (tmp==null || tmp.length!=ENCRYPTED_SIZE) {
+                       return null;
+                       }
+               System.arraycopy(tmp,0,raw,0,ENCRYPTED_SIZE);
+               return (SBlock) 
PersistentHelper.readFully(SBlock.class,ByteBuffer.wrap(raw));
+       }
+
+       public HashCode512 getIdentifier()
+       {
+               return identifier;
+       }
+
+       public HashCode512 getNameSpace()
+       {
+               return HashCode512.create(PersistentHelper.toBytes(subspace));
+       }
+
+       public int getByteSize()
+       {
+               return SIZE;
+       }
+
+       public void writeBytes( ByteBuffer buf )
+       {
+               buf.put(encryptedData);
+               identifier.writeBytes(buf);
+               signature.writeBytes(buf);
+               subspace.writeBytes(buf);
+       }
+
+       public void readBytes( ByteBuffer buf, ErrorReporter err )
+       {
+               buf.get(encryptedData);
+               identifier.readBytes(buf,err);
+               signature.readBytes(buf,err);
+               subspace.readBytes(buf,err);
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/Extractor.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/Extractor.java        
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/Extractor.java        
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,26 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+/**
+ *
+ */
+
+public class Extractor extends Object
+{
+       public Extractor()
+       {
+               super();
+       }
+
+       public String toString()
+       {
+               return "xxx";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/FileIdentifier.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/FileIdentifier.java   
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/FileIdentifier.java   
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,187 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+import java.util.logging.*;
+import java.util.regex.*;
+
+/**
+ * information required to download a file from GNUnet
+ *
+ * A FileIdentifier groups the information
+ * required to download (and check) a file.
+ */
+
+public class FileIdentifier extends Object implements Persistent, AFSConstants
+{
+       public static final int SIZE            =       48;
+
+       /** Total size of the file in bytes. */
+       private long                    fileLength;
+
+       /** Top CRC of the tree-encoding. */
+       private int                     fileCRC;
+
+       /** Query and key of the top IBlock. */
+       private ChkHashes       chk;
+
+
+       public FileIdentifier()
+       {
+               super();
+               fileLength=0;
+               fileCRC=0;
+               chk=new ChkHashes();
+       }
+
+       public FileIdentifier( Block b )
+       {
+               this();
+               chk=(ChkHashes) PersistentHelper.copy(b.getKeyAndQuery());      
//todo: copie utile ???
+               fileCRC=b.getDataCRC();
+               fileLength=b.getFileSize();
+       }
+
+       public FileIdentifier( long length, int crc, ChkHashes h )
+       {
+               this();
+               fileLength=length;
+               fileCRC=crc;
+               chk=(ChkHashes) PersistentHelper.copy(h);       //todo: copie 
utile ???
+       }
+
+       public String toString()
+       {
+               return "File identifier";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public long getFileLength()
+       {
+               return fileLength;
+       }
+
+       public int getFileCRC()
+       {
+               return fileCRC;
+       }
+
+       public HashCode512 getFileQuery()
+       {
+               return chk.query;
+       }
+
+       public HashCode512 getFileKey()
+       {
+               return chk.key;
+       }
+
+       /**
+        * Convert a fileIdentifier to an URI string
+        * (to display it to the user).
+        *
+        * @return string containing the url (must be freed by caller)
+        */
+
+       public String toURI()
+       {
+               StringBuffer    buf;
+               String          str;
+
+               buf=new StringBuffer();
+               buf.append(GNUNET_URI_PREFIX_AFS);
+               buf.append(chk.key.toHex());
+               buf.append(".");
+               buf.append(chk.query.toHex());
+               buf.append(".");
+
+               str="00000000000000000000000"+Utils.toHex(fileCRC);//todo: 
mieux faire !!!
+               buf.append(str.substring(str.length()-8));
+
+               buf.append(".");
+               buf.append(fileLength);
+               return buf.toString();
+       }
+
+       public int getByteSize()
+       {
+               return SIZE;
+       }
+
+       public void writeBytes( ByteBuffer buf )
+       {
+               int     i;
+
+               buf.putInt((int) fileLength);   //fixme: long value
+               buf.putInt(fileCRC);
+               if (chk!=null) {
+                       chk.writeBytes(buf);
+                       }
+               else {
+                       for (i=0; i<ChkHashes.SIZE; i++) {
+                               buf.put((byte) 0);
+                               }
+                       }
+       }
+
+       public void readBytes( ByteBuffer buf, ErrorReporter err )
+       {
+               fileLength=buf.getInt();
+               fileCRC=buf.getInt();
+               chk=new ChkHashes();
+               chk.readBytes(buf,err);
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       /**
+        * Converts an AFS uri to a FileIdentifier
+        * FIXME: Not much error handling. Doesn't recognize
+        * a namespace URL. We'd need something more general,
+        * or a mechanism that recognizes uri type and routes
+        * it to the right function.
+        *
+        * @param str the URI of a fileIdentifier
+        * @return the fileidentifier (caller must free)
+        */
+
+       public static FileIdentifier parseURI( String str )
+       {
+               FileIdentifier  fid;
+               Logger                  logger;
+               Matcher                 m;
+
+               assert(str!=null) : "null string";
+
+               if (!str.startsWith(GNUNET_URI_PREFIX_AFS)) {
+                       logger=Logger.getLogger(FileIdentifier.class.getName());
+                       logger.log(Level.SEVERE,"Malformed URI :"+str);
+                       return null;
+                       }
+
+               
m=Pattern.compile("([0-9A-Za-z]{40})\\.([0-9A-Za-z]{40})\\.([0-9A-Fa-f]+)\\.([0-9]+)").matcher("");
+               m.reset(str.substring(GNUNET_URI_PREFIX_AFS.length()));
+               if (!m.matches()) {
+                       logger=Logger.getLogger(FileIdentifier.class.getName());
+                       logger.log(Level.SEVERE,"Malformed URI :"+str);
+                       return null;
+                       }
+
+               fid=new FileIdentifier();
+               fid.chk.key=HashCode512.parse(m.group(1));
+               fid.chk.query=HashCode512.parse(m.group(2));
+               fid.fileCRC=(int) Long.parseLong(m.group(3),16);        //car 
parseInt ne prend pas en compte si >2^31 en non signe
+               fid.fileLength=Long.parseLong(m.group(4));
+               return fid;
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/GNDirectory.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/GNDirectory.java      
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/GNDirectory.java      
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,172 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.net.*;
+
+import java.io.*;
+import java.nio.*;
+import java.util.*;
+import java.util.logging.*;
+
+/**
+ * Format of a GNUnet directory (both in memory and on the drive).
+ * functions for building directories
+ *
+ * Helper functions for building directories.
+ *
+ * Directories are an add-on mechanism on top of the ESED II.  As
+ * such, gnunetd has no notion of directories.  Thus, this code is
+ * NEVER run inside of gnunetd but always by the various AFS tools.
+ * Since multiple AFS tools may concurrently access the directories
+ * from different processes, IPC is required to synchronize the
+ * access.
+ *
+ */
+
+public class GNDirectory extends Object implements Persistent, AFSConstants
+{
+       public static final int SIZE    =       RootNode.SIZE;  // 1024
+
+       /** */
+       public byte[]           MAGIC;
+
+       /** in network byte order */
+       public int                      version;
+
+       /** number of files in the directory */
+       public int                      number_of_files;
+
+       /** description/filename of the directory */
+       public String           description;
+
+       /** must be zero for now */
+       public byte[]           reserved;
+
+       /** number_of_files root-nodes */
+       public RootNode[]       contents;
+
+
+       public GNDirectory()
+       {
+               super();
+               MAGIC=new byte[8];
+               Arrays.fill(MAGIC,(byte) 0);
+               version=0;
+               number_of_files=0;
+               description="";
+               reserved=new byte[RootNode.SIZE - 256 - 16];
+               Arrays.fill(reserved,(byte) 0);
+               contents=new RootNode[] {};
+       }
+
+       /**
+        * Build a GNUnet directory in memory.
+        *
+        * @param numberOfEntries how many files are in the directory
+        * @param name what is the name of the directory
+        * @param entries the entries in the directory
+        */
+
+       public GNDirectory( int numberOfEntries, String name, RootNode[] 
entries )
+       {
+               this();
+
+               int     i;
+               System.arraycopy(GNUNET_DIRECTORY_MAGIC.getBytes(),0,MAGIC,0,8);
+               number_of_files = numberOfEntries;
+               if (!name.endsWith(File.separator)) {
+                       name+="/";
+                       }
+               description=name;
+
+               contents=new RootNode[numberOfEntries];
+               for (i=0; i<contents.length; i++) {
+                       contents[i]=entries[i];
+                       }
+       }
+
+       public String toString()
+       {
+               return "xxx";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public String getDescription()
+       {
+               return description;
+       }
+
+       public int getByteSize()
+       {
+               return SIZE+number_of_files*RootNode.SIZE;
+       }
+
+       public void writeBytes( ByteBuffer buf )
+       {
+               int     i;
+
+               buf.put(MAGIC);
+               buf.putInt(version);
+               buf.putInt(number_of_files);
+               AFSUtils.put(description,buf,256);
+               buf.put(reserved);
+
+               for (i=0; i<number_of_files; i++) {
+                       contents[i].writeBytes(buf);
+                       }
+       }
+
+       public void readBytes( ByteBuffer buf, ErrorReporter err )
+       {
+               int     i;
+
+               buf.get(MAGIC);
+               version=buf.getInt();
+               number_of_files=buf.getInt();
+               description=AFSUtils.get(buf,256);
+               buf.get(reserved);
+
+               err.reportIf(version!=0,"bad version");
+               
err.reportIf(!Arrays.equals(MAGIC,GNUNET_DIRECTORY_MAGIC.getBytes()),"bad 
magic");
+
+               contents=new RootNode[number_of_files];
+               for (i=0; i<number_of_files; i++) {
+                       contents[i]=new RootNode();
+                       contents[i].readBytes(buf,err);
+                       }
+
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       /**
+        * Appends a suffix ".gnd" to a given string if the suffix
+        * doesn't exist already. Existing suffix '/' is replaced if
+        * encountered.
+        *
+        * @param dn the directory name (string)
+        * @return the converted name on success
+        */
+
+       public static String expandDirectoryName( String dn )
+       {
+               if (dn==null) {
+                       
Logger.getLogger("").log(Level.SEVERE,"expandDirectoryName called with null 
argument");
+                       return null;
+                       }
+
+               if (dn.endsWith(File.separator)) {
+                       dn=dn.substring(0,dn.length()-File.separator.length());
+                       }
+               if (!dn.endsWith(GNUNET_DIRECTORY_EXT)) {
+                       dn+=GNUNET_DIRECTORY_EXT;
+                       }
+               return dn;
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/GNDirectoryDatabase.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/GNDirectoryDatabase.java      
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/GNDirectoryDatabase.java      
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,229 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.io.*;
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+import java.util.*;
+import java.util.logging.*;
+
+/**
+ * The "state" database (see include/util/state.h) is used
+ * to store the data.  Note that state does not do any locking,
+ * and that it in particular can not do any locking for us since
+ * it is IPC!
+ */
+
+public class GNDirectoryDatabase extends LoggedObject implements AFSConstants
+{
+       /** */
+       private Prefs   prefs;
+
+
+       public GNDirectoryDatabase( Prefs p )
+       {
+               super(true);
+               prefs=p;
+       }
+
+       public String toString()
+       {
+               return "GN directory database";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       /**
+        * Makes a root-node available for directory building.
+        *
+        * This function is called whenever a root-node is encountered.  This
+        * can either be because the user inserted a file locally; because we
+        * received a search result or because the user retrieved a directory
+        * with root-nodes.  From which context the root node was encountered
+        * is specified in the context parameters.<p>
+        *
+        * makeRootNodeAvailable adds the node to the list of files that
+        * we can build a directory from later.  The context is used to allow
+        * the user to filter on root-node sources.
+        *
+        * @param root the file identifier that was encountered
+        * @param context the context in which the identifier was encountered 
(may not be a bitmask)
+        */
+
+       public void makeRootNodeAvailable( Node root, int context )
+       {
+               byte[]          b;
+               RootNode        nd;
+               MappedFile      sem;
+               ByteBuffer      buf;
+               int                     ret;
+
+               if (!prefs.testString("AFS","COLLECT-FILE-IDENTIFIERS","YES") ) 
{
+                       log(Level.FINEST,"Collecting file identifiers 
disabled.");
+                       return;
+                       }
+
+               sem=createLock();
+               if (sem==null) {
+                       return;
+                       }
+
+               try {
+                       b=PersistentHelper.toBytes(root);
+
+                       buf=prefs.getContent("dir"+context);
+                       if (buf!=null) {
+                               // if size is not a multiple of the RootNode 
size, try to fix DB by truncating !
+                               ret=buf.limit();
+                               if (ret % RootNode.SIZE != 0) {
+                                       ret -= ret % RootNode.SIZE;
+
+                                       buf.position(ret);
+                                       buf.limit(ret);
+                                       prefs.putContent("dir"+context,buf);
+                                       buf.position(0);
+                                       }
+
+                               ret = ret / RootNode.SIZE;
+                               while (ret > 0) {
+                                       ret--;
+
+                                       nd=(RootNode) 
PersistentHelper.read(RootNode.class,buf);
+                                       if 
(Arrays.equals(b,PersistentHelper.toBytes(nd))) {//todo: 'equals' sur RootNode, 
NNode, SBlock
+                                               return; // already present
+                                               }
+                                       }
+                               }
+                       prefs.appendContent("dir"+context,b);
+                       }
+               finally {
+                       releaseLock(sem);
+                       }
+       }
+
+       /**
+        * Remove all of the root-nodes of a particular type
+        * from the directory database.
+        *
+        * @param contexts bitmask of the databases that should be emptied.
+        */
+
+       public void emptyDirectoryDatabase( int contexts )
+       {
+               MappedFile      sem;
+               int                     i;
+
+               sem=createLock();
+               if (sem==null) {
+                       return;
+                       }
+
+               try {
+                       i=1;
+                       while (contexts > 0) {
+                               if ((contexts & i) > 0) {
+                                       contexts -= i;
+                                       prefs.unlink("dir"+i);
+                                       }
+                               i*=2;
+                               }
+                       }
+               finally {
+                       releaseLock(sem);
+                       }
+       }
+
+       /**
+        * Iterate over all entries that match the given context
+        * mask.
+        *
+        * @param contexts context bitmask for the entries to iterate over
+        * @param callback function to call on each entry, may be null
+        * @param closure extra argument to the callback
+        * @return number of entries found
+        */
+
+       public int iterateDirectoryDatabase( int contexts, RootNodeCallback 
callback, Object closure )
+       {
+               RootNode        nd;
+               MappedFile      sem;
+               ByteBuffer      buf;
+               int                     ret,rval,i;
+
+               sem=createLock();
+               if (sem==null) {
+                       return 0;
+                       }
+
+               rval=0;
+               try {
+                       i = 1;
+                       while (contexts > 0) {
+                               if ((contexts & i) > 0) {
+                                       contexts -= i;
+
+                                       buf=prefs.getContent("dir"+i);
+                                       if (buf!=null) {
+                                               // if size is not a multiple of 
the RootNode size, try to fix DB by truncating !
+                                               ret=buf.limit();
+                                               if (ret % RootNode.SIZE != 0) {
+                                                       ret -= ret % 
RootNode.SIZE;
+
+                                                       buf.position(ret);
+                                                       buf.limit(ret);
+                                                       
prefs.putContent("dir"+i,buf);
+                                                       buf.position(0);
+                                                       }
+
+                                               ret = ret / RootNode.SIZE;
+                                               while (ret > 0) {
+                                                       ret--;
+                                                       nd=(RootNode) 
PersistentHelper.read(RootNode.class,buf);
+                                                       if (callback != null) {
+                                                               
callback.rootNode(nd,closure);
+                                                               }
+                                                       rval++;
+                                                       }
+                                               }
+                                       }
+                               i*=2;
+                               }
+                       }
+               finally {
+                       releaseLock(sem);
+                       }
+               return rval;
+       }
+
+       protected MappedFile createLock()
+       {
+               FileLocation            loc;
+               MappedFile              f;
+
+               
loc=prefs.getDirLocation("","GNUNET_HOME").getFile("directory_ipc_lock");
+
+               f=loc.openNew();
+               if (f==null) {
+                       return null;
+                       }
+               if (f.lock()) {
+                       return f;
+                       }
+               f.close();
+               return null;
+       }
+
+       protected void releaseLock( MappedFile f )
+       {
+               f.unlock();
+               f.close();
+
+               f.getLocation().delete();
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/GeneralURI.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/GeneralURI.java       
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/GeneralURI.java       
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,31 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+/**
+ * Header prefix for whatever is in an AFS URI.
+ * "action" is the identifier of the URI purpose/context.
+ */
+
+public class GeneralURI extends Object
+{
+       public int              action;
+//     public Object   data;
+
+
+       public GeneralURI()
+       {
+               super();
+       }
+
+       public String toString()
+       {
+               return "xxx";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/IBlock.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/IBlock.java   2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/IBlock.java   2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,757 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+import java.util.logging.*;
+
+/**
+ * An inner node in the merkle-tree.
+ */
+
+public class IBlock extends Block
+{
+       /** Number of CHK_Hashes per IBlock. The value must be 25 since 
25*40+20+4 is 1024.
+        The other values are 40=CHK_Hashes.SIZE, 20=HashCode512.SIZE for the 
super-hash
+        and 4=int.SIZE for the CRC32. */
+       public static final int CHK_PER_INODE   =       25;
+
+
+       /** The depth of this node in the file tree. At depth 0 we have the 
leaves, since this is an IBlock, depth is always > 0. */
+       private int             depth;
+
+       /** CRC (if (data != null): ((IBlockData)data).crc32). */
+       private int             crc32;
+
+       /** Number of children [1-CHK_PER_INODE] of this node. */
+       private int             childcount;
+
+       /** References to the children (IBlocks or DBlocks, depending on if 
depth > 1 or not). */
+       private Block[] children;
+
+       /** CRC of each of the children. */
+       private int[]   crcs;
+
+
+       /**
+        * Create a top-IBlock for the root of the file tree.
+        * Note that you must set the chk field before calling
+        * download.
+        * @param filesize2 the size of the file
+        */
+
+       public IBlock( int filesize2 )
+       {
+               
super(filesize2,computeDataLength(filesize2,0,IOContext.computeDepth(filesize2)));
+               depth=IOContext.computeDepth(filesize2);
+               childcount=0;
+               children=null;//new Block[CHK_PER_INODE];
+               crcs=new int[CHK_PER_INODE];
+
+               init();
+       }
+
+       /**
+        * Create an IBlock. Use createTopIBlxock for the
+        * node on top of the file-tree.
+        *
+        * @param pos2          the position of the IBlock in the file
+        * @param parent2       the parent block
+        */
+
+       public IBlock( IBlock parent2, int pos2 )
+       {
+               
super(parent2,pos2,computeDataLength(parent2.getFileSize(),pos2,parent2.depth - 
1));
+               depth=parent2.depth-1;
+               childcount=0;
+               children=null;//new Block[CHK_PER_INODE];
+               crcs=new int[CHK_PER_INODE];
+
+               init();
+       }
+
+       public IBlock( IBlock b )
+       {
+               super(b);
+               depth=b.depth;
+               childcount=b.childcount;
+               children=null;//new Block[CHK_PER_INODE];
+               System.arraycopy(b.children,0,children,0,b.children.length);
+               crcs=new int[CHK_PER_INODE];
+               System.arraycopy(b.crcs,0,crcs,0,b.crcs.length);
+
+               init();
+       }
+
+       public String toString()
+       {
+               return "Inner block [offset="+getFilePosition()+", 
size="+getFileSize()+", childcount="+childcount+"]";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       /**
+        * Initialize IBlock fields
+        */
+
+       protected void init()
+       {
+               
childcount=computeChildCount(getFileSize(),getFilePosition(),depth);
+               children=null;
+       }
+
+       /**
+        * Allocate space for children.
+        */
+
+       protected void allocateChildren()
+       {
+               int     cover,i;
+
+               if (children!=null)
+                       return;
+
+               children=new Block[childcount];
+
+               // create child nodes
+               cover=computeChildCover(depth);
+               for (i=0; i<childcount; i++) {
+                       if (depth>1) {
+                               children[i]=new 
IBlock(this,getFilePosition()+i*cover);
+                               }
+                       else {
+                               children[i]=new 
DBlock(this,getFilePosition()+i*cover);
+                               }
+                       }
+       }
+
+       public int getDepth()
+       {
+               return depth;
+       }
+
+       public boolean childrenDestroyed( Block b )
+       {
+               int     live,i;
+
+               live=0;
+               if (children!=null) {
+                       for (i=0; i<childcount;i++) {
+                               if (children[i] == b)
+                                       children[i] = null;
+                               if (children[i] != null)
+                                       live++;
+                               }
+                       }
+               return (live==0 && !isStatus(BLOCK_PERSISTENT));
+       }
+
+       /**
+        * A child has been completely downloaded. Perform the
+        * appropriate CRC checks in the parent node and free associated
+        * resources if possible. Since the only errors are either
+        * bugs or hash-crc-collisions (probability 1:2^160), we
+        * always die on errors (return values do not work well for
+        * async calls anyway).<p>
+        *
+        * Note that the leaves update the ProgressModel, so we do
+        * not have to worry about that.
+        * Note that the leaves update the ProgressModel, so we do
+        * not have to worry about that. If all children of a node
+        * are complete, this method calls itself recursively to
+        * notify the parent of the parent.
+        *
+        * @param child the completed child block
+        * @param nc the context (IO, priority, etc.)
+        * @param rm request manager to schedule queries
+        */
+
+       public void childDownloadCompleted( Block child, NodeContext nc, 
RequestManager rm )
+       {
+               int     i,pendingChildren;
+
+               debug("childDownloadxComplete "+this+" "+child);
+
+               assert(children!=null);
+
+               for (i=0;i<childcount;i++)
+                       if (children[i] == child)
+                               break;
+
+               assert (i!=childcount) : "childxDownloadCompleted called on 
node that does not know that child ! ("+child+", "+this+")";
+
+               crcs[i] = Crypto.crc32(child.getRawData(), 
0,child.getDataLength());
+
+               pendingChildren = 0;
+               for (i=0;i<childcount;i++)
+                       if ( (children[i] != null) && 
!children[i].isStatus(BLOCK_PRESENT))
+                               pendingChildren++;
+
+               // check if this IBlock is complete, if yes, go to our parent 
and notify that we are done !
+               if (getParent() != null) {
+                       if (pendingChildren == 0) {
+                               if (fuckedCRC32(crcs,childcount)!=crc32) {
+                                       log(Level.SEVERE,"File corrupted (or 
bug), crc mismatch in block "+depth+
+                                               " "+getFilePosition()+
+                                               ": 
"+fuckedCRC32(crcs,childcount)+
+                                               " != "+crc32);
+                                       }
+                               getParent().childDownloadCompleted(this,nc,rm);
+                               }
+                       }
+               else {  // parent == null
+                       if (pendingChildren == 0) {
+                               if (fuckedCRC32(crcs,childcount)!=crc32 ||
+                                       !checkTopCRC(rm.topCrc32)) {
+                                       log(Level.SEVERE,"File corrupted (or 
bug), top CRC mismatch in block "+depth+
+                                               " "+getFilePosition()+
+                                               ": 
"+Utils.toHex(fuckedCRC32(crcs,childcount))+
+                                               " != "+Utils.toHex(crc32)+
+                                               " or 
"+checkTopCRC(rm.topCrc32));
+
+                                       return;
+                                       }
+                               }
+                       }
+
+               // free memory as early as possible !
+               if (pendingChildren == 0) {
+                       clearData();
+                       }
+       }
+
+       public static int fuckedCRC32( int[] p, int count )
+       {
+               byte[]          b;
+               ByteBuffer      buf;
+               int                     i,crc;
+
+               b=new byte[count*4];
+               buf=ByteBuffer.wrap(b);
+               buf.order(ByteOrder.LITTLE_ENDIAN);//hum, hum...
+               for (i=0; i<count; i++) {
+                       buf.putInt(p[i]);
+                       }
+               crc=Crypto.crc32(b);
+               return ((crc & 0x000000ff)<<24) |
+                       ((crc & 0x0000ff00)<<8) |
+                       ((crc>>8) & 0x0000ff00) |
+                       ((crc>>24) & 0x000000ff)
+                       ;
+       }
+
+       /**
+        * Insert the current block into the network. Implementations
+        * are also responsible for updating the corresponding fields
+        * of the parent node (of course, except if the parent is
+        * null in the case of the top-node in the tree).<p>
+        *
+        * Inner nodes first call the respective inserter methods for
+        * their children.<p>
+        *
+        * @param nc the context (gives us the priority)
+        * @param sock the socket to use to talk to the core
+        * @return OK on success, false on error
+        */
+
+       public boolean insert( NodeContext nc, CSSession sock )
+       {
+               Block           child;
+               IBlockData      ibd;
+               CSIndexSuper    req;
+               ContentBlock    edata;
+               CSResult                rv;
+               int                     ui,childCover;
+
+               debug("insert");
+
+               setStatus(BLOCK_PERSISTENT);
+               ibd = new IBlockData();
+               setData(ibd);
+
+               childCover = ContentBlock.SIZE;
+               for (ui=0;ui<depth-1;ui++)
+                       childCover *= CHK_PER_INODE;
+
+               allocateChildren();
+
+               for (ui=0; ui<childcount; ui++) {
+                       child = children[ui];
+                       if (!child.insert(nc,sock)) {
+                               if (sock != null)
+                                       log(Level.WARNING,"Child insertion 
failed on level "+depth+", pos "+child.getFilePosition()+", aborting !");
+                               return false; /* abort! */
+                               }
+
+                       crcs[ui] = 
Crypto.crc32(child.getRawData(),0,child.getDataLength());
+                       ibd.chks[ui]=(ChkHashes) 
PersistentHelper.copy(child.getKeyAndQuery());
+                       child.destroy(null);
+                       children[ui] = null;
+                       }
+
+               
ibd.superHash=HashCode512.create(PersistentHelper.toBytes(ibd.chks),0,ChkHashes.SIZE
 * childcount);
+
+               if (nc.index!=0 && sock!=null) {
+                       req=new CSIndexSuper(ibd.superHash,nc.priority);
+
+                       if (sock.send(req)) {
+                               rv=(CSResult) sock.receive(CSResult.class);
+                               if (rv==null) {
+                                       log(Level.WARNING,"Server did not send 
confirmation of insertion.");
+                                       return false;
+                                       }
+                               if (!rv.isOkay()) {
+                                       log(Level.WARNING,"Server could not 
perform insertion.");
+                                       return false;
+                                       }
+                               }
+                       else {
+                               log(Level.WARNING,"Could not send super-index 
information to gnunetd. Is gnunetd running ?");
+                               return false;
+                               }
+                       }
+
+               ibd.crc32 = fuckedCRC32(crcs,childcount);
+               crc32 = ibd.crc32;
+               edata = encrypt();
+               return insertCHKBlock(sock,edata,nc.priority);
+       }
+
+       /**
+        * Remove the current block from the local AFS storage.
+        *
+        * @param nc the context (gives us the priority)
+        * @param sock the socket to use to talk to the core
+        * @return OK on success, false on error
+        */
+
+       public boolean delete( NodeContext nc, CSSession sock )
+       {
+               int                             ui,childCover;
+               Block                   child;
+               IBlockData              ibd;
+               CSUnindexSuper  req;
+               ContentBlock            edata;
+               CSResult                        rv;
+
+               setStatus(BLOCK_PERSISTENT);
+               ibd = new IBlockData();
+               setData(ibd);
+               childCover = ContentBlock.SIZE;
+               for (ui=0;ui<depth-1;ui++)
+                       childCover *= CHK_PER_INODE;
+
+               allocateChildren();
+
+               for (ui=0;ui<childcount;ui++) {
+                       child = children[ui];
+                       if (!child.delete(nc,sock)) {
+                               if (sock != null) {
+                                       log(Level.WARNING,"Failed to delete 
child on level "+depth+", pos "+child.getFilePosition()+". Will continue.");
+                                       }
+                               }
+                       crcs[ui] = 
Crypto.crc32(child.getRawData(),0,child.getDataLength());
+                       ibd.chks[ui]=(ChkHashes) 
PersistentHelper.copy(child.getKeyAndQuery());
+                       child.destroy(null);
+                       children[ui] = null;
+                       }
+
+               
ibd.superHash=HashCode512.create(PersistentHelper.toBytes(ibd.chks),0,ChkHashes.SIZE
 * childcount);
+               if (sock != null) {
+                       req=new CSUnindexSuper();
+                       req.importance = nc.priority;
+                       req.superHash=(HashCode512) 
PersistentHelper.copy(ibd.superHash);
+
+                       if (sock.send(req)) {
+                               rv=(CSResult) sock.receive(CSResult.class);
+                               if (rv==null) {
+                                       log(Level.WARNING,"Server did not send 
confirmation of deletion.");
+                                       return false;
+                                       }
+                               if (!rv.isOkay()) {
+                                       // super blocks don't matter !
+                                       }
+                               }
+                       else {
+                               log(Level.WARNING,"Could not send super-unindex 
information to gnunetd. Is gnunetd running ?");
+                               return false;
+                               }
+                       }
+
+               ibd.crc32 = fuckedCRC32(crcs,childcount);
+               edata = encrypt();
+               return deleteCHKBlock(sock,edata,nc.priority);
+       }
+
+       /**
+        * The request manager got a reply for one of the childs
+        * we were looking after. Update the RM query, call
+        * receive on the appropriate child, etc.
+        *
+        * @param query the query that was sent out
+        * @param reply the reply that was received
+        * @param rm the handle for the request manager
+        * @param nc the context (gives us the priority)
+        * @return false the request manager should abort the download
+        */
+
+       protected boolean receiveChild( HashCode512 query, CSResultChk reply, 
RequestManager rm, NodeContext nc )
+       {
+               int                     i;
+               IBlockData      ibd;
+
+               debug("iblock_download_receivec_child "+this);
+
+               if (!isStatus(BLOCK_SUPERQUERY_PENDING)) {
+                       trace("No superquery is pending !");
+                       return false;
+                       }
+
+               debug("iblock "+this+" receives message for child");
+
+               ibd=(IBlockData) getData(IBlockData.class);
+
+               allocateChildren();
+
+               for (i=0; i<childcount; i++) {
+                       if (query.equals(ibd.chks[i].query)) {
+                               if (children[i]!=null && 
children[i].isStatus(BLOCK_PENDING)) {
+                                       return 
children[i].receive(query,reply,rm,nc);
+                                       }
+                               }
+                       }
+               return true;    // we may receive replies twice, just ignore 
those
+       }
+
+       /**
+        * Call download on the children to test if they are present.
+        *
+        * @param nc the context (gives us the priority)
+        * @param rm the request manager
+        */
+
+       protected void downloadChildren( NodeContext nc, RequestManager rm )
+       {
+               int                     i;
+               IBlockData      ibd;
+               Block           child;
+
+               debug("iblock_download_childcren "+this);
+
+               assert(childcount<=CHK_PER_INODE) : "iblock "+this+" has 
"+childcount+" children !";
+
+               ibd=(IBlockData) getData(IBlockData.class);
+
+               allocateChildren();
+
+               for (i=0; i<childcount; i++) {
+                       child=children[i];
+                       if (child!=null) {
+                               child.setKeyAndQuery(ibd.chks[i]);
+                               child.download(nc,rm);
+                               }
+                       }
+       }
+
+       /**
+        * Send the super-request that groups the queries for all
+        * child-nodes in one large query. Note that recursion and
+        * updates are checked by the "superState" field of IBlock.
+        *
+        * @param rm reference to the RequestManager for requests
+        * @param nc the context (gives us the priority)
+        */
+
+       protected void doSuperRequest( NodeContext nc, RequestManager rm )
+       {
+               IBlockData              ibd;
+               CSQuery msg;
+               int                             liveChildren,i;
+
+               debug("ibclock_do_superrequest "+this);
+
+               liveChildren = 0;
+               allocateChildren();
+               for (i=0;i<childcount;i++)
+                       if (children[i] != null)
+                               if (children[i].isStatus(BLOCK_PENDING))
+                                       liveChildren++;
+
+               if (liveChildren == 0) {
+                       debug("iblock "+this+" cancels request, all children 
done ("+getStatus()+")");
+
+                       /* finally drop remaining requests, all satisfied! */
+                       if (isStatus(BLOCK_SUPERQUERY_PENDING)) {
+                               rm.update(this,null);
+                               }
+                       setStatus(BLOCK_CHILDREN_PRESENT);
+                       return; /* we are done here! */
+                       }
+
+               ibd=(IBlockData) getData(IBlockData.class);
+
+               msg=new CSQuery();
+               msg.setPriorityAndTTL(1,1);
+               msg.addQuery(ibd.superHash);
+//trace("CSQuery (1): "+msg);
+
+               liveChildren = 0;
+               allocateChildren();
+               for (i=0;i<childcount;i++) {
+                       if (children[i] != null) {
+                               if (children[i].isStatus(BLOCK_PENDING)) {
+                                       msg.addQuery(ibd.chks[i].query);
+                                       liveChildren++;
+                                       }
+                               }
+                       }
+
+               if (isStatus(BLOCK_SUPERQUERY_PENDING)) {
+                       debug("iblock "+this+" updates request, 
"+liveChildren+" children pending");
+
+                       rm.update(this,msg);
+                       }
+               else {
+                       debug("iblock "+this+" starts request, "+liveChildren+" 
children pending");
+
+                       setStatus(BLOCK_SUPERQUERY_PENDING);
+                       rm.request(this,new Listener() {
+                               public boolean listen( HashCode512 query, 
CSResultChk reply, RequestManager rmg, NodeContext data )
+                               {
+                                       return 
receiveChild(query,reply,rmg,data);
+                               }
+                               },nc,msg);
+                       }
+       }
+
+       /**
+        * Type of a method that is called by the RequestManager
+        * whenever a reply to a query has been received.
+        *
+        * @param query the query that was sent out
+        * @param reply the reply that was received
+        * @param rm the handle for the request manager
+        * @param nc the context (gives us the priority)
+        * @return false the request manager should abort the download
+        */
+
+       public boolean receive( HashCode512 query, CSResultChk reply, 
RequestManager rm, NodeContext nc )
+       {
+               ProgressStats   pstats;
+
+               debug("receive");
+
+               if (!isStatus(BLOCK_PENDING)) {
+                       /* As far as I can tell, this should never happen */
+                       log(Level.WARNING,"IBlock "+this+" receives reply, but 
we are already done !");
+                       return true;
+                       }
+
+               debug("iblock "+this+" receives reply");
+
+               if (!receivedChk(query,reply)) {
+                       pstats=new ProgressStats();
+                       nc.pmodel.progress(pstats,nc.data);
+                       return false;
+                       }
+
+               if (!writeData(nc)) {
+                       pstats=new ProgressStats();
+                       nc.pmodel.progress(pstats,nc.data);
+                       log(Level.SEVERE,"Write to temporary IBlock file 
failed, aborting.");
+                       return false;
+                       }
+
+               crc32=((IBlockData) getData()).crc32;
+
+               setStatus(BLOCK_PRESENT);
+               if (getParent() == null) {
+                       /* our request, stop doing it */
+                       rm.update(this,null);
+                       }
+               else {
+                       getParent().childDownloadCompleted(this,nc,rm);
+                       getParent().doSuperRequest(nc,rm);
+                       }
+               setStatus(BLOCK_PERSISTENT);
+               downloadChildren(nc,rm);
+               doSuperRequest(nc,rm);
+               return true;
+       }
+
+       /**
+        * Download this node (and the children below). Note that the
+        * processing is asynchronous until the pmodel is called with position
+        * == total (and thus no more requests are pending) or the request
+        * manager is aborted by the user.
+        *
+        * @param rm the request manager
+        * @param nc the context (gives us the priority)
+        */
+
+       public void download( NodeContext nc, RequestManager rm )
+       {
+               NodeContext     fakeContext;
+               IBlock          fakeThis;
+               boolean         isPresent;
+
+               debug("download");
+debug("S1");
+               isPresent=check(nc);
+debug("S11 "+isPresent);
+               if (!isPresent) {
+                       fakeContext=new NodeContext();
+                       fakeContext.ioc=new IOContext(nc.ioc);
+                       fakeContext.priority=0;
+                       fakeContext.index=-1;
+                       fakeContext.pmodel=ProgressModel.NO_MODEL;
+                       fakeContext.data = null;
+                       fakeContext.stats.progress = 0;
+
+                       fakeThis=new IBlock(this);
+
+                       fakeThis.detach();
+                       fakeThis.setStatus(BLOCK_PERSISTENT);
+
+                       if (fakeThis.insert(fakeContext,null)) {
+                               if 
(fakeThis.getKeyAndQuery().equals(getKeyAndQuery())) {
+                                       setStatus(BLOCK_PRESENT);
+                                       setData(fakeThis.getData());
+                                       crc32=fakeThis.crc32;
+                                       fakeThis.clearData();
+
+                                       isPresent = true;
+                                       }
+                               }
+                       fakeThis.destroy(null);
+                       }
+
+               if (isPresent) {
+                       if (getParent() != null) {
+                               getParent().childDownloadCompleted(this,nc,rm);
+                               getParent().doSuperRequest(nc,rm);
+                               }
+                       setStatus(BLOCK_PERSISTENT);
+
+                       downloadChildren(nc,rm);
+                       doSuperRequest(nc,rm);
+                       return;
+                       }
+               // not present, either request ourselves or let the parent do 
it automagically when we return...
+               setStatus(BLOCK_PENDING);
+               if (getParent() == null) {
+                       issueQuery(rm,new Listener() {
+                               public boolean listen( HashCode512 query, 
CSResultChk reply, RequestManager rmg, NodeContext data )
+                               {
+                                       return receive(query,reply,rmg,data);
+                               }
+                               },nc,getQuery());
+                       }
+       }
+
+       /**
+        * Check if an IBlock is already present.
+        *
+        * @param nc the context (gives us the priority)
+        * @return YES if it is present, NO if not.
+        */
+
+       public boolean check( NodeContext nc )
+       {
+               HashCode512 hc;
+
+               debug("check");
+
+               /* first check if its already present */
+               if (loadData(nc,false)) {
+debug("check 2");
+                       hc=HashCode512.create(getRawData(),0,getDataLength());
+                       if (hc.equals(getKey())) {
+                               crc32=((IBlockData) getData()).crc32;
+                               return true;
+                               }
+                       }
+debug("check 3");
+               clearData();
+               return false;
+       }
+
+       public void print( int indent )
+       {
+               int     i;
+
+               log(Level.FINEST,Utils.repeat('*',indent)+" IBLOCK ("+depth+") 
"+getFilePosition()+" "+getQuery().toHex()+" ("+childcount+" children)");
+
+               if (children!=null) {
+                       for (i=0; i<childcount; i++) {
+                               if (children[i]!=null) {
+                                       children[i].print(indent+2);
+                                       }
+                               }
+                       }
+       }
+
+       /**
+        * Free the associated resources of this Block. DOES ALSO free the
+        * memory occupied by the Block struct itself!
+        *
+        * @param rm reference to the RequestManager for requests
+        */
+
+       public void destroy( RequestManager rm )
+       {
+               int     i;
+
+               debug("destroy");
+               setStatus(BLOCK_PERSISTENT); /* last child would otherwise call 
destroy on us! */
+               if (children!=null) {
+                       for (i=0; i<childcount; i++) {
+                               if (children[i]!=null) {
+                                       children[i].destroy(rm);
+                                       }
+                               }
+                       children=null;
+                       }
+               super.destroy(rm);
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public static int computeChildCover( int depth )
+       {
+               int     cover,i;
+
+               cover=ContentBlock.SIZE;
+               for (i=0; i<depth-1; i++) {
+                       cover*=CHK_PER_INODE;
+                       }
+               return cover;
+       }
+
+       public static int computeChildCount( int size, int pos, int depth )
+       {
+               int     myCover,cover,count;
+
+               cover=computeChildCover(depth);
+
+               myCover=Math.min(size-pos,CHK_PER_INODE*cover);
+               for (count=0; myCover>0; count++) {
+                       myCover-=cover;
+                       }
+               return count;
+       }
+
+       public static int computeDataLength( int size, int pos, int depth )
+       {
+               // superhash + crc + chkhashes * children #
+               return 
computeChildCount(size,pos,depth)*ChkHashes.SIZE+HashCode512.SIZE+4;
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/IBlockData.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/IBlockData.java       
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/IBlockData.java       
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,88 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+
+/**
+ * format of an IBlock.
+ */
+
+public class IBlockData extends Object implements Persistent
+{
+       public static final int SIZE    =       
HashCode512.SIZE+4+ChkHashes.SIZE*IBlock.CHK_PER_INODE;
+
+       /** The super-Hashcode for retrieving all CHK_PER_INODE sub-nodes in 
one big lookup.
+        This hash is the hash of the concatenation of all encrypted 
CHK_PER_INODE children of this node. */
+       public HashCode512      superHash;
+
+       /** The CRC32 checksum of the sub-blocks (crc32N of the concatenation 
of the individual crc32N's over
+        the plaintext-data (without padding) of each block). */
+       public int                      crc32;
+
+       /** The keys and queries for the nodes one level below. This entry must 
be at the end since it is variable size ! */
+       public ChkHashes[]      chks;
+
+
+       public IBlockData()
+       {
+               super();
+               chks=new ChkHashes[IBlock.CHK_PER_INODE];
+               for (int i=0; i<chks.length; i++) {
+                       chks[i]=new ChkHashes();
+                       }
+       }
+
+       public String toString()
+       {
+               return "Inner block data";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public int getByteSize()
+       {
+               return SIZE;
+       }
+
+       public void readBytes( ByteBuffer buf, ErrorReporter err )
+       {
+               int     i;
+
+               superHash=new HashCode512();
+               superHash.readBytes(buf,err);
+
+               crc32=buf.getInt();
+
+               for (i=0; i<chks.length; i++) {
+                       chks[i]=new ChkHashes();
+                       chks[i].readBytes(buf,err);
+                       }
+       }
+
+       public void writeBytes( ByteBuffer buf )
+       {
+               int     i;
+
+               if (superHash!=null) {
+                       superHash.writeBytes(buf);
+                       }
+               else {
+                       for (i=0; i<HashCode512.SIZE; i++) {
+                               buf.put((byte) 0);
+                               }
+                       }
+
+               buf.putInt(crc32);
+
+               for (i=0; i<chks.length; i++) {
+                       chks[i].writeBytes(buf);
+                       }
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/IOContext.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/IOContext.java        
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/IOContext.java        
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,320 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.*;
+
+import EDU.oswego.cs.dl.util.concurrent.*;
+
+import java.io.*;
+import java.nio.*;
+import java.nio.channels.*;
+import java.util.*;
+import java.util.logging.*;
+
+/**
+ * IO context for reading-writing AFS file blocks.
+ *
+ * encapsulation of IO
+ *
+ * In GNUnet, files are stored in the form of a balanced tree, not
+ * unlike INodes in unix filesystems. When we download files, the
+ * inner nodes of the tree are stored under FILENAME.X (where X
+ * characterizes the level of the node in the tree). If the download
+ * is aborted and resumed later, these .X files can be used to avoid
+ * downloading the inner blocks again.  The successfully received leaf
+ * nodes in FILENAME (the target file) are of course also not
+ * downloaded again.<p>
+ *
+ * The IOContext struct presents an easy api to access the various
+ * dot-files. It uses function pointers to allow implementors to
+ * provide a different mechanism (other than files on the drive) to
+ * cache the IBlocks.
+ */
+
+public class IOContext extends LoggedObject implements AFSConstants
+{
+       /** The base-filename */
+       private String                  filename;
+
+       /** The depth of the file-tree. */
+       private int                             treeDepth;
+
+       /** A lock for each file-handle for synchronizing access. */
+       private Mutex[]                 locks;
+
+       /** The file handles for each level in the tree. */
+       private FileChannel[]           handles;
+
+
+       public IOContext()
+       {
+               super(true);
+               filename="";
+               treeDepth=0;
+               locks=null;
+               handles=null;
+       }
+
+       public IOContext( IOContext ioc )
+       {
+               this();
+               init(ioc);
+       }
+
+       public String toString()
+       {
+               return "xxx";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       protected void init( IOContext ioc )
+       {
+               filename=ioc.filename;
+               treeDepth=ioc.treeDepth;
+
+               locks=new Mutex[ioc.locks.length];
+               System.arraycopy(ioc.locks,0,locks,0,ioc.locks.length);
+
+               handles=new FileChannel[ioc.handles.length];
+               System.arraycopy(ioc.handles,0,handles,0,ioc.handles.length);
+       }
+
+       public String getFilename()
+       {
+               return filename;
+       }
+
+       /**
+        * Initialize an IOContext.
+        *
+        * @param filesize the size of the file
+        * @param str the name of the level-0 file
+        * @param rdOnly use YES for read-only IOC
+        * @return OK on success, false on failure
+        */
+
+       public boolean init( int filesize, String str, boolean rdOnly )
+       {
+               FileChannel     fc;
+               String          fn;
+               int                     i;
+
+               log(Level.FINEST,"Init on '"+str+"' ("+filesize+" bytes) 
\""+(rdOnly ? "r" : "r/w")+"\" mode.");
+
+               treeDepth = computeDepth(filesize);
+               locks = new Mutex[treeDepth+1];
+               handles = new FileChannel[treeDepth+1];
+               Arrays.fill(handles,null);
+               filename = str;
+
+               if (!rdOnly) {
+                       try {
+                               // if exists and oversized, truncate
+                               fc=new 
RandomAccessFile(filename,"rw").getChannel();
+                               try {
+                                       if (fc.size()>filesize) {
+                                               fc.truncate(filesize);
+                                               }
+                                       }
+                               catch( IOException x ) {
+                                       err("Unable to truncate "+filename+" 
!",x);
+                                       return false;
+                                       }
+                               }
+                       catch( FileNotFoundException x ) {
+                               }
+
+                       }
+
+               for (i=0; i<=treeDepth; i++) {
+                       locks[i]=new Mutex();
+
+                       fn=filename+(i>0 ? "."+(char) ('A'+i) : "");
+                       try {
+                               handles[i]=new RandomAccessFile(fn,(rdOnly ? 
"r" : "rw")).getChannel();
+                               }
+                       catch( IOException x ) {
+                               handles[i]=null;
+
+                               if (!rdOnly || i==0) {
+                                       err("Could not open file "+fn+" !",x);
+                                       free(false);
+                                       return false;
+                                       }
+                               }
+                       }
+               return true;
+       }
+
+       /**
+        * Read method.
+        *
+        * @param level level in the tree to read/write at
+        * @param pos position where to read or write
+        * @param buf where to read from or write to
+        * @param len how many bytes to read or write
+        * @return number of bytes read or written, -1 on error
+        */
+
+       public int read( int level, int pos, byte[] buf, int len )
+       {
+               return read(level,pos,ByteBuffer.wrap(buf,0,len));
+       }
+
+       public int read( int level, int pos, ByteBuffer buf )
+       {
+               int ret;
+               int lpos;
+
+
+//log(Level.FINEST,">>> read level="+level+", pos="+pos);
+
+               lpos=pos;
+               for (ret=0; ret<level; ret++) {
+                       lpos/=IBlock.CHK_PER_INODE;
+                       }
+
+               try {
+                       locks[level].acquire();
+                       try {
+                               handles[level].position(lpos);
+                               ret=handles[level].read(buf);
+                               }
+                       finally {
+                               locks[level].release();
+                               }
+                       }
+               catch( IOException x ) {
+                       err("Failed to read "+buf.remaining()+" bytes on 
"+handles[level]+" !",x);
+                       ret=-1;
+                       }
+               catch( InterruptedException x ) {
+                       err("Failed to read "+buf.remaining()+" bytes on 
"+handles[level]+" !",x);
+                       ret=-1;
+                       }
+               return ret;
+       }
+
+       /**
+        * Write method.
+        *
+        * @param level level in the tree to read/write at
+        * @param pos position where to read or write
+        * @param buf where to read from or write to
+        * @param len how many bytes to read or write
+        * @return number of bytes read or written, -1 on error
+        */
+
+       public int write( int level, int pos, byte[] buf, int len )
+       {
+               return write(level,pos,ByteBuffer.wrap(buf,0,len));
+       }
+
+       public int write( int level, int pos, ByteBuffer buf )
+       {
+               int ret,lpos,len;
+
+//log(Level.FINEST,">>> write level="+level+", pos="+pos);
+
+               lpos = pos;
+               for (ret=0; ret<level; ret++) {
+                       lpos /= IBlock.CHK_PER_INODE;
+                       }
+
+               try {
+                       locks[level].acquire();
+                       try {
+                               handles[level].position(lpos);
+                               len=buf.remaining();
+                               ret = handles[level].write(buf);
+                               if (ret != len) {
+                                       
log(Level.FINEST,"Write("+handles[level]+", "+buf+", "+len+") failed !");
+                                       }
+                               }
+                       finally {
+                               locks[level].release();
+                               }
+                       }
+               catch( IOException x ) {
+                       err("Failed to write "+buf.remaining()+" bytes on 
"+handles[level]+" !",x);
+                       ret=-1;
+                       }
+               catch( InterruptedException x ) {
+                       err("Interrupted !",x);
+                       ret=-1;
+                       }
+               return ret;
+       }
+
+       /**
+        * Close the files in the IOContext and free
+        * the associated resources. Does NOT free
+        * the memory occupied by the IOContext struct
+        * itself.
+        *
+        * @param unlinkTreeFiles if YES, the non-level 0 files
+        *     are unlinked (removed), set to NO if the download
+        *     is not complete and may be resumed later.
+        */
+
+       public void free( boolean unlinkTreeFiles )
+       {
+               int             i;
+               String  fn;
+
+               log(Level.FINEST,"Free (unlink: "+unlinkTreeFiles+").");
+               for (i=0;i<=treeDepth;i++) {
+                       if (handles[i] != null) {
+                               try {
+                                       handles[i].close();
+                                       }
+                               catch( IOException x ) {
+                                       err("Failed to close "+handles[i]+" 
!",x);
+                                       }
+                               handles[i] = null;
+                               }
+                       locks[i]=null;
+                       }
+
+               if (unlinkTreeFiles) {
+                       for (i=1; i<=treeDepth; i++) {
+                               fn=filename+"."+(char) ('A'+i);
+                               if (!new File(fn).delete()) {
+                                       log(Level.WARNING,"Could not unlink 
temporary file "+fn+".");
+                                       }
+                               }
+                       }
+
+               filename=null;
+               handles=null;
+               locks=null;
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       /**
+        * Compute the depth of the tree.
+        * @param size file length for which to compute the depth
+        * @return depth of the tree
+        */
+
+       public static int computeDepth( int size )
+       {
+               int     depth,n;
+
+               depth=0;
+
+               n=CONTENT_SIZE;
+               while (n<size) {
+                       depth++;
+                       n*=IBlock.CHK_PER_INODE;
+                       }
+               return depth;
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/InsertURI.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/InsertURI.java        
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/InsertURI.java        
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,33 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+/**
+ */
+
+public class InsertURI extends GeneralURI
+{
+//     public int              action;
+       public String   filename;
+       public String[] keywords;
+       public String   pseudonym;
+       public String   password;
+       public int              keycount;
+
+
+       public InsertURI()
+       {
+               super();
+       }
+
+       public String toString()
+       {
+               return "xxx";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/InsertUtil.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/InsertUtil.java       
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/InsertUtil.java       
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,660 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.io.*;
+import org.gnu.freeway.util.net.*;
+
+import java.io.*;
+import java.nio.channels.*;
+import java.util.*;
+import java.util.logging.*;
+
+/**
+ * Insertutil, helper methods for file insertion.
+ * Break file that is inserted into blocks and encrypts them according to the 
CHK-triple-hash-tree scheme (ESED II).
+ *
+ * @see "http://www.ovmj.org/GNUnet/encoding.php3";
+ */
+
+public class InsertUtil extends LoggedObject implements AFSConstants
+{
+       /** Default priority for locally indexed content ("infty") */
+       public static final int LOCAL_INDEXED_CONTENT_PRIO      =       0xFFFF;
+
+       private Prefs   prefs;
+
+
+       public InsertUtil( Prefs p )
+       {
+               super(true);
+               prefs=p;
+       }
+
+       public String toString()
+       {
+               return "xxx";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       /**
+        * Insert the SBlock
+        *
+        * @param esb
+        * @param sock
+        * @return OK on success, false on error
+        */
+
+       public boolean insert( EncryptedSBlock esb, CSSession sock )
+       {
+               CSInsertSBlock  msg;
+               CSResult        rv;
+
+               msg=new 
CSInsertSBlock(prefs.getInt("GNUNET-INSERT","CONTENT-PRIORITY",0),esb);
+
+               if (!sock.send(msg)) {
+                       log(Level.WARNING,"Server did not seem to be alive.");
+                       return false;
+                       }
+
+               rv=(CSResult) sock.receive(CSResult.class);
+               if (rv==null) {
+                       log(Level.WARNING,"Server did not send confirmation of 
insertion.");
+                       return false;
+                       }
+               if (!rv.isOkay()) {
+                       log(Level.WARNING,"Server could not perform 
insertion.");
+                       return false;
+                       }
+               return true;
+       }
+
+       /**
+        * Ask gnunetd to receive and store a file in
+        * on the server side.
+        *
+        * @param sock connection to gnunetd
+        * @param filename the name to add to fileindex.c
+        * @return the index, -1 on error
+        */
+
+       protected int transferFile( CSSession sock, String filename )
+       {
+               CSIndexFile             request;
+               CSUploadFile            upload;
+               CSResult                        rv;
+               int                             index,pos,fsize,delta;
+               FileChannel             handle;
+               FileLocation            f;
+
+               f=new FileLocation(filename);
+               if (!f.exists()) {
+                       return -1;
+                       }
+
+               /* first: request index */
+               request=new CSIndexFile(f);
+
+               if (!sock.send(request)) {
+                       log(Level.WARNING,"Could not request or receive data 
from gnunetd. Is gnunetd running ?");
+                       return -1;
+                       }
+
+               rv=(CSResult) sock.receive(CSResult.class);
+               if (rv==null) {
+                       log(Level.WARNING,"Could not request or receive data 
from gnunetd. Is gnunetd running ?");
+                       return -1;
+                       }
+
+               index=rv.getResult();
+               if (index == -1) {
+                       log(Level.WARNING,"gnunetd refused to index file (no 
space left ?)");
+                       return -1;
+                       }
+
+               assert(index!=0) : "gnunetd violated protocol (returned 0)";
+
+               if (prefs.testString("GNUNET-INSERT","LINK","YES")) {
+                       CSLinkFile      req;
+
+                       req=new CSLinkFile(f);
+
+                       if (sock.send(req)) {
+                               rv=(CSResult) sock.receive(CSResult.class);
+                               if (rv!=null && rv.isOkay()) {
+                                       /* link successful */
+                                       return index;
+                                       }
+                               }
+
+                       log(Level.WARNING,"WARNING: link request to gnunetd 
failed. Trying to, make copy instead.");
+                       }
+
+               // do not create link: transfer the file !
+               try {
+                       fsize=(int) f.getSize();
+
+                       upload=new CSUploadFile(f);
+
+                       handle=new 
RandomAccessFile(f.getPath(),"r").getChannel();
+                       try {
+                               pos     = 0;
+                               while (pos < fsize) {
+                                       delta=upload.copyChunk(handle);
+                                       if (delta<0) {
+                                               log(Level.SEVERE,"could not 
read file");
+                                               index=-1;
+                                               break;
+                                               }
+
+                                       if (!sock.send(upload)) {
+                                               log(Level.WARNING,"Could not 
send data to gnunetd. Is gnunetd running ?");
+                                               index=-1;
+                                               break;
+                                               }
+
+                                       rv=(CSResult) 
sock.receive(CSResult.class);
+                                       if (rv==null) {
+                                               log(Level.WARNING,"Could not 
receive data from gnunetd. Is gnunetd running ?");
+                                               index=-1;
+                                               break;
+                                               }
+                                       if (!rv.isOkay()) {
+                                               index=-1;
+                                               break;
+                                               }
+
+                                       pos+=delta;
+                                       }
+                               }
+                       finally {
+                               handle.close();
+                               }
+                       }
+               catch( IOException x ) {
+                       err("Could not upload file \""+f.getLabel()+"\" !",x);
+                       return -1;
+                       }
+               return index;
+       }
+
+       /**
+        * Inserts a file under the given name into the local GNUnet node.
+        * Insert (or index) a file under the given name into the local GNUnet
+        * node.
+        *
+        * @param sock the socket to use to talk to gnunetd/connection to 
gnunetd
+        * @param filename the name of the (incoming/source) file/the name of 
the file to insert
+        * @param model the insert model used to
+        *        update status information; points to null if
+        *        no status updates shall be given, otherwise
+        *        to a method that takes two integer arguments
+        *        (retrieved so far, total).
+        * @param model_data pointer that is passed to the model method
+        * @return top IBlock on success, null on error/null on error, 
otherwise the top block
+        */
+
+       public Block insertFile( CSSession sock, String filename, ProgressModel 
model, Object model_data )
+       {
+               NodeContext     nc;
+               int                     filesize;
+               Block           top;
+               FileLocation            f;
+               String  restore;
+               int                     ret;
+
+               f=new FileLocation(filename);
+               if (!f.exists()) {
+                       log(Level.WARNING,"Could not insert, file 
'"+f.getPath()+"' does not exist !");
+                       return null;
+                       }
+
+               filename=f.getPath();
+               filesize = (int) f.getSize();
+
+               restore = prefs.getString("GNUNET-INSERT","INDEX-CONTENT",null);
+               if (filesize <= ContentBlock.SIZE) {
+                       prefs.setString("GNUNET-INSERT","INDEX-CONTENT","NO");
+                       }
+
+               if (filesize==0) {
+                       log(Level.INFO,"File '"+filename+"' is empty, can't 
insert it.");
+                       
prefs.setString("GNUNET-INSERT","INDEX-CONTENT",restore);
+                       return null;
+                       }
+
+               nc=new NodeContext();
+               nc.pmodel = model;
+               nc.data = model_data;
+               nc.stats=new ProgressStats();
+               nc.stats.filesize = filesize;
+               nc.priority = 
prefs.getInt("GNUNET-INSERT","CONTENT-PRIORITY",0);
+               if (nc.priority == 0) {
+                       nc.priority = LOCAL_INDEXED_CONTENT_PRIO;
+                       }
+
+               if (prefs.testString("GNUNET-INSERT","INDEX-CONTENT","YES")) {
+                       ret = transferFile(sock, filename);
+                       assert(ret!=0);
+
+                       if (ret == -1) {
+                               log(Level.WARNING,"Adding to index list failed, 
trying insertion !");
+                               nc.index = 0;
+                               }
+                       else {
+                               nc.index = ret;
+                               }
+                       }
+               else {
+                       nc.index = 0; /* 0: no indexing */
+                       }
+
+               if (!nc.ioc.init(filesize,filename,true)) {
+                       
prefs.setString("GNUNET-INSERT","INDEX-CONTENT",restore);
+                       return null;
+                       }
+
+               top = Block.create(filesize);
+               if (!top.insert(nc,sock)) {
+                       top.destroy(null);
+                       nc.ioc.free(false);
+                       
prefs.setString("GNUNET-INSERT","INDEX-CONTENT",restore);//todo: le mettre en 
finally
+                       return null;
+                       }
+               nc.ioc.free(false);
+               prefs.setString("GNUNET-INSERT","INDEX-CONTENT",restore);
+               return top;
+       }
+
+       /**
+        * Insert a root-block into GNUnet.
+        *
+        * @param sock connection to gnunetd
+        * @param top the top block of the file
+        * @param description description to use
+        * @param filenameRoot filename to use
+        * @param mimetype mimetype to use
+        * @param keywords the keywords that shall be used to retrieve the 
file/to be associated with the file
+        * @return the root node on success, null on error
+        */
+
+       public RootNode insertRoot( CSSession sock, Block top, String 
description, String filenameRoot, String mimetype, String[] keywords )
+       {
+               int                             i,priority;
+               RootNode                rn;
+               boolean                 res;
+               FileIdentifier  fid;
+
+               priority = prefs.getInt("GNUNET-INSERT","CONTENT-PRIORITY",0);
+
+               fid=new FileIdentifier(top);
+
+               rn=new RootNode(fid);
+               rn.setDescription(description);
+               rn.setFileName(filenameRoot);
+               rn.setMimeType(mimetype);
+
+               res = true;
+               for (i=0; i<keywords.length; i++) {
+                       if 
(!insertRootWithKeyword(sock,rn,keywords[i],priority)) {
+                               res = false;
+                               }
+                       }
+
+               // directory support...
+               new 
GNDirectoryDatabase(prefs).makeRootNodeAvailable(rn,DIR_CONTEXT_INSERT);
+               return (res ? (RootNode) PersistentHelper.copy(rn) : null);     
        //todo: copie utile ???
+       }
+
+       /**
+        * Creates root node for the tree and writes the top-level tree node.
+        *
+        * @param sock connection to gnunetd
+        * @param rn the RootNode to insert
+        * @param keyword the keyword under which the rn is inserted
+        * @param contentPriority priority of the inserted content
+        * @return
+        */
+
+       public boolean insertRootWithKeyword( CSSession sock, RootNode rn, 
String keyword, int contentPriority )
+       {
+               HashCode512             hc;
+               CSInsert3Hash   msg;
+               CSResult        rv;
+               ContentBlock    block;
+
+               hc=HashCode512.create(keyword);
+
+               block=new ContentBlock();
+               if (!new 
ContentEncoding().encryptContent(PersistentHelper.toBytes(rn),hc,block)) {
+                       log(Level.SEVERE,"Encryption failed.");
+                       return false;
+                       }
+
+               msg = new CSInsert3Hash(contentPriority,hc,block);
+               if (!sock.send(msg)) {
+                       log(Level.WARNING,"WARNING: could not send data to 
gnunetd. Is gnunetd running ?");
+                       return false;
+                       }
+
+               rv=(CSResult) sock.receive(CSResult.class);
+               if (rv==null) {
+                       log(Level.WARNING,"WARNING: server did not send 
confirmation of insertion");
+                       return false;
+                       }
+               if (!rv.isOkay()) {
+                       log(Level.WARNING,"WARNING: server could not perform 
insertion");
+                       return false;
+                       }
+               return true;
+       }
+
+       /**
+        * Inserts a directory. Sets the file-identifier that can afterwards be 
used to retrieve the directory.
+        * Does <em>not</em> insert any RBlocks or SBlocks.
+        *
+        * @param sock
+        * @param nodeCount how many rootNodes in the directory
+        * @param rootNodes the actual nodes
+        * @param dirName name of this directory
+        * @param model
+        * @param modelArg
+        * @return null on failure, resulting file identifier for the directory 
on success
+        */
+
+       public FileIdentifier insertDirectory( CSSession sock, int nodeCount, 
RootNode[] rootNodes, String dirName, ProgressModel model, Object modelArg )
+       {
+               GNDirectory     dir;
+               FileLocation    temp;
+               Block           top;
+               String          oldval;
+               FileIdentifier  fid;
+               MappedFile      f;
+               boolean         res;
+
+               dir=new GNDirectory(nodeCount,dirName,rootNodes);
+
+               temp=FileLocation.temporary();
+
+               f=temp.openNew();
+               res=f.writePersistent(dir);
+               f.close();
+
+               if (!res) {
+                       log(Level.WARNING,"Could not write directory to 
temporary file \""+temp.getLabel()+"\".");
+                       temp.delete();
+                       return null;
+                       }
+
+               /* ok, insert the directory */
+               oldval = prefs.getString("GNUNET-INSERT",       
"INDEX-CONTENT",null);
+               prefs.setString("GNUNET-INSERT",        "INDEX-CONTENT","NO");
+               top = insertFile(sock,temp.getPath(),model,modelArg);
+
+               temp.delete();
+
+               prefs.setString("GNUNET-INSERT","INDEX-CONTENT",        oldval);
+               if (top == null) {
+                       log(Level.SEVERE,"Error inserting directory 
"+temp.getPath()+".\nYou may want to check whether or not you are out of 
space.\nRun gnunet-stats | grep \"AFS storage left\" to check.\n");
+                       return null;
+                       }
+
+               fid=new FileIdentifier(top);
+               top.destroy(null);
+               return fid;
+       }
+
+       /**
+        * Build an RBlock for the given file and insert
+        * it into GNUnet under all applicable keywords.
+        *
+        * @param sock
+        * @param fid the identifier for the file
+        * @param filename the full filename (complete path)
+        * @param gloKeywords
+        * @param gloKeywordCnt
+        * @param extractors_
+        * @return the RootNode
+        */
+
+       protected RootNode buildFileRBlock( CSSession sock, FileIdentifier fid, 
String filename, String[] gloKeywords, int gloKeywordCnt, List extractors_ )
+       {
+               RootNode        result;
+               int index,i;
+               String  description;
+               String  mimetype;
+               String  shortFN;
+               boolean nodirectindex;
+               String[]        keywords;
+               Extractor[]             extractors = (Extractor[]) 
extractors_.toArray(new Extractor[extractors_.size()]);
+
+               mimetype = prefs.getString("GNUNET-INSERT","MIMETYPE",null);
+               description = 
prefs.getString("GNUNET-INSERT","DESCRIPTION",null);
+               nodirectindex = 
prefs.testString("GNUNET-INSERT","ADDITIONAL-RBLOCKS","NO");
+
+               shortFN = prefs.getString("GNUNET-INSERT","FILENAME",null);
+               if (shortFN == null) {
+                       index=filename.replace('\\','/').lastIndexOf('/');
+                       shortFN=filename.substring(index+1);
+                       }
+
+               keywords=new String[] {};
+               if (!prefs.testString("GNUNET-INSERT","EXTRACT-KEYWORDS","NO")) 
{
+                       keywords=new 
KeyWords().extractKeywordsMulti(filename,description,mimetype,extractors);
+                       }
+
+               if (mimetype == null)
+                       mimetype = "unknown";
+               if (description == null)
+                       description = shortFN;
+
+               result=new RootNode(fid);
+               result.setDescription(description);
+               result.setFileName(shortFN);
+               result.setMimeType(mimetype);
+
+               for (i=0;i<gloKeywordCnt;i++)
+                       if (!insertRootWithKeyword(sock,
+                                       result,
+                                       gloKeywords[i],
+                                       
prefs.getInt("GNUNET-INSERT","CONTENT-PRIORITY",0))) {
+                               log(Level.SEVERE,"Failed to insert RBlock. Is 
gnunetd running and space available ?");
+                               break;
+                       }
+
+               for (i=0; i<keywords.length; i++) {
+                       if (!nodirectindex) {
+                               if 
(!insertRootWithKeyword(sock,result,keywords[i],prefs.getInt("GNUNET-INSERT","CONTENT-PRIORITY",0)))
 {
+                                       log(Level.SEVERE,"Failed to insert 
RBlock. Is gnunetd running and space available ?");
+                                       }
+                               }
+                       }
+               return result;
+       }
+
+       /**
+        * Build an RBlock for a directory (and insert the RBlock
+        * into GNUnet under all applicable keywords).
+        * @param sock
+        *
+        * @param fid the identifier for the file
+        * @param dirName the name of the last component of the path to the 
directory
+        * @param description the description for the file
+        * @param gloKeywords
+        * @param gloKeywordCnt
+        * @return the RBlock
+        */
+
+       public RootNode buildDirectoryRBlock( CSSession sock, FileIdentifier 
fid, String dirName, String description, String[] gloKeywords, int 
gloKeywordCnt )
+       {
+               RootNode        result;
+               int i;
+               String  dn;
+
+               dn=dirName;
+               if (!dn.endsWith(GNUNET_DIRECTORY_EXT)) {
+                       dn+=GNUNET_DIRECTORY_EXT;
+                       }
+
+               result=new RootNode(fid);
+               result.setDescription(description);
+               result.setFileName(dn);
+               result.setMimeType(GNUNET_DIRECTORY_MIME);
+
+               for (i=0;i<gloKeywordCnt;i++) {
+                       if 
(!insertRootWithKeyword(sock,result,gloKeywords[i],prefs.getInt("GNUNET-INSERT","CONTENT-PRIORITY",0)))
 {
+                               log(Level.SEVERE,"Failed to insert RBlock. Is 
gnunetd running and space available ?");
+                               }
+                       }
+               return result;
+       }
+
+       /**
+        * Index or insert a file or directory.  Creates and inserts RootNodes
+        * for the file if applicable.  Recursively processes directory if
+        * applicable.  If directories are build or if filename refers to a
+        * single file, a plaintext RootNode that identifies the inserted
+        * object is returned and the FileIdentifier fid is set.  If we do not
+        * create directories and a directory is given or if there was an
+        * error, null is returned.  Every file encountered is inserted with
+        * all specified global keywords and (if applicable) additional keywords
+        * are extracted with the extractors.
+        *
+        * @param sock
+        * @param filename the name of the file or directory
+        * @param gloKeywords
+        * @param gloKeywordCnt
+        * @param extractors_
+        * @param model
+        * @param modelArg
+        * @param insert callback used to insert individual (leaf) files
+        * @param insertArg
+        * @return RootNode that identifies the single file or directory or
+        *      null on error or null if filename is a directory and we don't
+        *      create directories.
+        */
+
+       public RootNode insertRecursively( CSSession sock, String filename, 
String[] gloKeywords, int gloKeywordCnt, List extractors_, ProgressModel model, 
Object modelArg, InsertWrapper insert, Object insertArg )
+       {
+               DECData                         dec;
+               DirLocation             dir;
+               FileIdentifier  fid;
+               String                  dirName;
+               int                             index;
+               boolean                 builddir,processRecursive;
+
+               dir=new DirLocation(filename);
+               if (!dir.exists()) {
+                       fid=insert.insert(sock,filename,insertArg);
+                       if (fid==null) {
+                               return null;
+                               }
+                       return 
buildFileRBlock(sock,fid,filename,gloKeywords,gloKeywordCnt,extractors_);
+                       }
+
+               processRecursive = 
prefs.testString("GNUNET-INSERT","RECURSIVE","YES");
+               if (processRecursive) {
+                       builddir = 
prefs.testString("GNUNET-INSERT","BUILDDIR","YES");
+
+                       dec=new DECData();
+                       dec.identifiers.clear();
+                       dec.nodes.clear();
+                       dec.sock = sock;
+                       dec.gloKeywords = gloKeywords;
+                       dec.gloKeywordCnt = gloKeywordCnt;
+                       dec.extractors_ = extractors_;
+                       dec.model = model;
+                       dec.model_arg = modelArg;
+                       dec.insert = insert;
+                       dec.insert_arg = insertArg;
+
+                       final DECData   _dec = dec;
+
+                       dir.traverse(new Traverser() {
+                               public boolean examine( Location node, int 
depth )
+                               {
+                                       dirEntryCallback(node,_dec);
+                                       return true;
+                               }
+                               });
+
+                       assert(dec.nodes.size() == dec.identifiers.size()) : 
"ERROR: assertion violated";
+
+                       if (builddir) {
+                               
index=filename.replace('\\','/').lastIndexOf('/');
+                               dirName=filename.substring(index+1);
+
+                               
fid=insertDirectory(sock,dec.nodes.size(),dec.getNodes(),dirName,model,modelArg);
+                               dec.identifiers.clear();
+                               dec.nodes.clear();
+                               return 
buildDirectoryRBlock(sock,fid,dirName,dirName,gloKeywords,gloKeywordCnt);
+                               }
+                       dec.identifiers.clear();
+                       dec.nodes.clear();
+                       return null;
+                       }
+               return null;
+       }
+
+       protected void dirEntryCallback( Location node, DECData data )
+       {
+               String  fn;
+               RootNode        rb;
+
+               data.identifiers.add(null);
+               data.nodes.add(null);
+
+               fn = node.getName();
+               rb = insertRecursively(data.sock,
+                               fn,
+                               data.gloKeywords,
+                               data.gloKeywordCnt,
+                               data.extractors_,
+                               data.model,
+                               data.model_arg,
+                               data.insert,
+                               data.insert_arg);
+
+               if (rb != null) {
+                       
data.identifiers.set(data.identifiers.size()-1,rb.getFileIdentifier());
+                       data.nodes.set(data.nodes.size()-1,rb);
+                       }
+               else {
+                       data.identifiers.remove(data.identifiers.size()-1);
+                       data.nodes.remove(data.nodes.size()-1);
+                       }
+       }
+}
+
+class DECData extends Object
+{
+       public List                     identifiers;
+       public List                     nodes;
+       public CSSession        sock;
+       public String[]         gloKeywords;
+       public int                      gloKeywordCnt;
+       public List                     extractors_;
+       public ProgressModel    model;
+       public Object           model_arg;
+       public InsertWrapper    insert;
+       public Object           insert_arg;
+
+
+       public DECData()
+       {
+               super();
+               identifiers=new ArrayList();
+               nodes=new ArrayList();
+       }
+
+       public RootNode[] getNodes()
+       {
+               return (RootNode[]) nodes.toArray(new RootNode[nodes.size()]);
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/InsertWrapper.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/InsertWrapper.java    
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/InsertWrapper.java    
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,27 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.net.*;
+
+/**
+ *
+ */
+
+public interface InsertWrapper
+{
+       /**
+        * Wrapper around insertFile that gives the user the appropriate
+        * feedback.  The insertWrapper is expected to return fid at the
+        * end of the insertion.  See "gnunet-insert.c::doFile()" for
+        * a possible implementation.
+        * @param sock
+        * @param filename
+        * @param closure
+        * @return
+        */
+
+       public FileIdentifier insert( CSSession sock, String filename, Object 
closure );
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/KeyWords.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/KeyWords.java 2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/KeyWords.java 2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,90 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import java.io.*;
+
+/**
+ * Layer to encapsulate the keyword extraction API and
+ * make it accessible to gnunet-insert.
+ * Layer to encapsulate the keyword extraction API and make it
+ * accessible to gnunet-insert.
+ *
+ * TODO: extractors à implémenter
+ */
+
+public class KeyWords extends Object
+{
+       public KeyWords()
+       {
+               super();
+       }
+
+       public String toString()
+       {
+               return "xxx";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       /**
+        * Extract keywords, mime-type and description from a file
+        *
+        * @param filename the name of the file
+        * @param description the description (the user may have
+        *        supplied a description already (*description != null),
+        *        in that case, append, mind the maximum size!
+        * @param mimetype the mimetype, again, the user may
+        *        have supplied one
+        * @return the list of keywords
+        */
+
+       public String[] extractKeywords( File filename, StringBuffer 
description, StringBuffer mimetype )
+       {
+               //todo: à implémenter
+               return new String[] {};
+       }
+
+       public String[] extractKeywords( File filename, StringBuffer 
description, StringBuffer mimetype, Extractor[] xxx )
+       {
+               //todo: à implémenter
+               return new String[] {};
+       }
+
+       /**
+        * Load the extractors as specified by the configuration.
+        *
+        * @return linked list of extractrs
+        */
+
+       public Extractor[] getExtractors()
+       {
+               //todo: à implémenter
+               return new Extractor[] {};
+       }
+
+       /**
+        * Extract keywords, mime-type and description from a file
+        *
+        * @param filename the name of the file
+        * @param description the description (the user may have
+        *        supplied a description already (*description != null),
+        *        in that case, append, mind the maximum size!
+        * @param mimetype the mimetype, again, the user may
+        *        have supplied one
+        * @param exList the list of extractors/list of libextractor plugins, 
null if
+        *        libextractor is not used.  Of type EXTRACTOR_ExtractorList*
+        * @return keywords the list of keywords, allocate space at
+        *        another location if required, copy existing keywords
+        *        over to that space! Do NEVER free *keywords!
+        */
+
+       public String[] extractKeywordsMulti( String filename, String 
description, String mimetype, Extractor[] exList )
+       {
+               //todo: à implémenter
+               return new String[] {};
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/Listener.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/Listener.java 2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/Listener.java 2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,29 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.crypto.*;
+
+/**
+ * type of callback used by nodes in the merkle tree to receive
+ * content-arrived notifications from the RequestManager
+ */
+
+public interface Listener
+{
+       /**
+        * Type of a method that is called by the RequestManager
+        * whenever a reply to a query has been received.
+        *
+        * @param query the query that was sent out
+        * @param reply the reply that was received
+        * @param rm the handle for the request manager
+        * @param data an opaque handle that is passed along,
+        *        typically used to pass the NodeContext
+        * @return false the request manager should abort the download
+        */
+
+       public boolean listen( HashCode512 query, CSResultChk reply, 
RequestManager rm, NodeContext data );
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/NSSearchResultCallback.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/NSSearchResultCallback.java   
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/NSSearchResultCallback.java   
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,21 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+/**
+ */
+
+public interface NSSearchResultCallback
+{
+       /**
+        * Type of a callback method for results that have
+        * been received.
+        *
+        * @param sb the plaintext of the SBlock that has been received
+        * @param data the opaque handle (context for the callee)
+        */
+
+       public void searchResult( SBlock sb, Object data );
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/Node.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/Node.java     2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/Node.java     2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,116 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.net.*;
+
+/**
+ *
+ */
+
+public abstract class Node extends LoggedObject implements Persistent, 
AFSConstants
+{
+       /** Major format version. */
+       protected int                   majorVersion;
+
+       /** Minor format version. */
+       protected int                   minorVersion;
+
+       /** Information required for the download. */
+       protected FileIdentifier        fileIdentifier;
+
+       /** Description of the content. */
+       protected String                        description;
+
+       /** Suggested filename. */
+       protected String                        fileName;
+
+       /** Mime-type (as claimed by insertion !). */
+       protected String                        mimeType;
+
+
+       protected Node( int major, int minor )
+       {
+               super(true);
+               majorVersion=major;
+               minorVersion=minor;
+               fileIdentifier=new FileIdentifier();
+               description="";
+               fileName="";
+               mimeType="";
+       }
+
+       public String toString()
+       {
+               return "Node [majorVersion="+majorVersion+", 
minorVersion="+minorVersion+"]";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public int getMajorVersion()
+       {
+               return majorVersion;
+       }
+
+       public int getMinorVersion()
+       {
+               return minorVersion;
+       }
+
+       public FileIdentifier getFileIdentifier()
+       {
+               return fileIdentifier;
+       }
+
+       public String getDescription()
+       {
+               return description;
+       }
+
+       public void setDescription( String str )
+       {
+               description=str;
+       }
+
+       public String getFileName()
+       {
+               return fileName;
+       }
+
+       public void setFileName( String str )
+       {
+               fileName=str;
+       }
+
+       public String getMimeType()
+       {
+               return mimeType;
+       }
+
+       public void setMimeType( String str )
+       {
+               mimeType=str;
+       }
+
+       public abstract String toLabel();
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public static int extractMajorVersion( byte[] b )
+       {
+               return (b.length>=2 ? ((b[0] & 0x000000ff)<<8) | (b[1] & 
0x000000ff) : -1);
+       }
+
+       public static int extractMinorVersion( byte[] b )
+       {
+               if (b.length<4) {
+                       return -1;
+                       }
+               return (b.length>=4 ? ((b[2] & 0x000000ff)<<8) | (b[3] & 
0x000000ff) : -1);
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/NodeContext.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/NodeContext.java      
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/NodeContext.java      
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,55 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+/**
+ * Context information for the merkle-tree objects
+ *
+ * The NodeContext groups the IOC and the progress model
+ * into a single struct.
+ */
+
+public class NodeContext extends Object
+{
+       /** The IO context for IO operations. */
+       public IOContext                ioc;
+
+       /** Priority */
+       public int                              priority;
+
+       /** Index of the file that we are indexing, 0 for insertion. */
+       public int                              index;
+
+       /** The ProgressModel to communicate status updates. */
+       public ProgressModel    pmodel;
+
+       /** Data argument to the ProgressModel. */
+       public Object                   data;
+
+       /** Current progress so far. */
+       public ProgressStats    stats;
+
+
+       public NodeContext()
+       {
+               super();
+               ioc=new IOContext();
+               priority=0;
+               index=0;
+               pmodel=null;
+               data=null;
+               stats=new ProgressStats();
+       }
+
+       public String toString()
+       {
+               return "xxx";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/P2P3HashResult.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/P2P3HashResult.java   
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/P2P3HashResult.java   
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,91 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+
+/**
+ * A return message for search query with double-hash proof.
+ */
+
+public class P2P3HashResult extends P2PMessage implements AFSConstants
+{
+       public static final int SIZE    =       HashCode512.SIZE+CONTENT_SIZE+4;
+
+       /** The double-hash. */
+       private HashCode512     hash;
+
+       /** The search result. */
+       private byte[]          result;
+
+
+       public P2P3HashResult()
+       {
+               super(IS_3HASH_RESULT);
+               hash=new HashCode512();
+               result=new byte[CONTENT_SIZE];
+       }
+
+       public P2P3HashResult( HashCode512 h, ContentBlock block )
+       {
+               this();
+               hash=h;
+               System.arraycopy(block.content,0,result,0,CONTENT_SIZE);
+       }
+
+       public String toString()
+       {
+               return "P2P 3-hash result [hash="+hash+"]";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public HashCode512 getDoubleHash()
+       {
+               return hash;
+       }
+
+       public HashCode512 getTripleHash()
+       {
+               return HashCode512.create(PersistentHelper.toBytes(hash));
+       }
+
+       public byte[] getResult()
+       {
+               return result;
+       }
+
+       public int getByteSize()
+       {
+               return SIZE;
+       }
+
+       public void readBytes( ByteBuffer buf, ErrorReporter err )
+       {
+               int     size,type;
+
+               size=buf.getShort() & 0x0000ffff;
+               err.reportIf(size!=SIZE,"bad size !");
+
+               type=buf.getShort() & 0x0000ffff;
+               err.reportIf(type!=IS_3HASH_RESULT,"bad type !");
+
+               hash.readBytes(buf,err);
+
+               buf.get(result);
+       }
+
+       public void writeBytes( ByteBuffer buf )
+       {
+               buf.putShort((short) SIZE);
+               buf.putShort((short) IS_3HASH_RESULT);
+               hash.writeBytes(buf);
+               buf.put(result);
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/P2PChkResult.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/P2PChkResult.java     
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/P2PChkResult.java     
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,71 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+
+/**
+ * Return message for content download (CHK style)
+ */
+
+public class P2PChkResult extends P2PMessage
+{
+       public static final int SIZE    =       4+ContentBlock.SIZE;
+
+       /** The search result. */
+       public ContentBlock     result;
+
+
+       public P2PChkResult()
+       {
+               super(IS_CHK_RESULT);
+               result=null;
+       }
+
+       public String toString()
+       {
+               return "P2P chk result [result="+result+"]";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public int getByteSize()
+       {
+               return SIZE;
+       }
+
+       public void readBytes( ByteBuffer buf, ErrorReporter err )
+       {
+               int     size,type;
+
+               size=buf.getShort() & 0x0000ffff;
+               err.reportIf(size!=SIZE,"bad size !");
+
+               type=buf.getShort() & 0x0000ffff;
+               err.reportIf(type!=IS_CHK_RESULT,"bad type !");
+
+               result=new ContentBlock();
+               result.readBytes(buf,err);
+       }
+
+       public void writeBytes( ByteBuffer buf )
+       {
+               int     i;
+
+               buf.putShort((short) SIZE);
+               buf.putShort((short) IS_CHK_RESULT);
+               if (result!=null) {
+                       result.writeBytes(buf);
+                       }
+               else {
+                       for (i=0; i<ContentBlock.SIZE; i++) {
+                               buf.put((byte) 0);
+                               }
+                       }
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/P2PNSQuery.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/P2PNSQuery.java       
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/P2PNSQuery.java       
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,61 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+
+/**
+ * Request for content from a namespace.
+ */
+
+public class P2PNSQuery extends P2PQuery
+{
+       public static final int SIZEX   =       
P2PQuery.SIZE+HashCode512.SIZE*2;
+
+       /** Namespace that we are restricted to */
+       public HashCode512      namespace;
+
+       /** Identifier that we are looking for. */
+       public HashCode512      identifier;
+
+
+       public P2PNSQuery( HostIdentity hi )
+       {
+               this();
+               //todo: copie de 'hi' utile ???
+               throw new IllegalStateException("A CORRIGER");
+       }
+
+       public P2PNSQuery()
+       {
+               super(IS_NSQUERY);
+       }
+
+       public String toString()
+       {
+               return "xxx";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public int getByteSize()
+       {
+               return SIZEX;
+       }
+
+       public void readBytes( ByteBuffer buf, ErrorReporter err )
+       {
+               throw new IllegalStateException();
+       }
+
+       public void writeBytes( ByteBuffer buf )
+       {
+               throw new IllegalStateException();
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/P2PQuery.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/P2PQuery.java 2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/P2PQuery.java 2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,180 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+import java.util.*;
+
+/**
+ * Request for content. The number of queries can
+ * be determined from the header size.
+ */
+
+public class P2PQuery extends P2PMessage
+{
+       public static final int SIZE    =       12+HostIdentity.SIZE;
+
+       /** How important is this request ? */
+       private int                             priority;
+
+       /** Time to live in milliseconds. */
+       private int                             ttl;
+
+       /** To whom to return results ? */
+       private HostIdentity    returnTo;
+
+       /** Hashcodes of the file(s) we're looking for. If multiple queries are 
given, the first query
+        is the super-query for the bloom filter. If only one query is given, 
the bloom filter should NOT
+        be used since it does not contain summaries for simple 1k blocks. It 
is not possible to group
+        multiple queries with this message type if they are not dominated by 
the same super-query. */
+       private List                    queries;
+
+
+       public P2PQuery()
+       {
+               this(IS_QUERY);
+       }
+
+       public P2PQuery( HostIdentity hi )
+       {
+               this();
+               returnTo=(HostIdentity) PersistentHelper.copy(hi);      //todo: 
copie utile ?
+       }
+
+       protected P2PQuery( int t )
+       {
+               super(t);
+               priority=0;
+               ttl=0;
+               returnTo=null;
+               queries=new ArrayList();
+       }
+
+       public String toString()
+       {
+               return "AFS p2p query";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public int getPriority()
+       {
+               return priority;
+       }
+
+       public int getTTL()
+       {
+               return ttl;
+       }
+
+       public void setPriorityAndTTL( int p, int t )
+       {
+               priority=p;
+               ttl=t;
+       }
+
+       public HostIdentity getReturnTo()
+       {
+               return returnTo;
+       }
+
+       public void setReturnTo( HostIdentity hi )
+       {
+               returnTo=(HostIdentity) PersistentHelper.copy(hi);
+       }
+
+       public boolean sameQueries( P2PQuery q )
+       {
+               return queries.equals(q.queries);
+       }
+
+       public HashCode512 getQuery( int index )
+       {
+               return ((index>=0 && index<queries.size()) ? (HashCode512) 
queries.get(index) : null);
+       }
+
+       public void addQuery( HashCode512 h )
+       {
+               queries.add(PersistentHelper.copy(h));
+       }
+
+       public void setQuery( int index, HashCode512 h )
+       {
+               queries.set(index,PersistentHelper.copy(h));    //todo: copie 
utile ???
+       }
+
+       public HashCode512[] getQueries()
+       {
+               return (HashCode512[]) queries.toArray(new 
HashCode512[queries.size()]);
+       }
+
+       public void setQueries( HashCode512[] q )
+       {
+               queries.clear();
+               queries.addAll(Arrays.asList(q));
+       }
+
+       public void clearQueries()
+       {
+               queries.clear();
+       }
+
+       public int getByteSize()
+       {
+               return SIZE+queries.size()*HashCode512.SIZE;
+       }
+
+       public void readBytes( ByteBuffer buf, ErrorReporter err )
+       {
+               HashCode512     h;
+               int                     size,type,i;
+
+               size=buf.getShort() & 0x0000ffff;
+               err.reportIf(size<SIZE,"bad size");
+
+               type=buf.getShort() & 0x0000ffff;
+               err.reportIf(type!=IS_QUERY,"bad type !");
+
+               priority=buf.getInt();
+               ttl=buf.getInt();
+
+               returnTo=new HostIdentity();
+               returnTo.readBytes(buf,err);
+
+               queries.clear();
+               for (i=(size-SIZE)/HashCode512.SIZE; i>0; i--) {
+                       h=new HashCode512();
+                       h.readBytes(buf,err);
+                       queries.add(h);
+                       }
+       }
+
+       public void writeBytes( ByteBuffer buf )
+       {
+               HashCode512     h;
+               int                     i;
+
+               buf.putShort((short) getByteSize());
+               buf.putShort((short) IS_QUERY);
+               buf.putInt(priority);
+               buf.putInt(ttl);
+               if (returnTo!=null) {
+                       returnTo.writeBytes(buf);
+                       }
+               else {
+                       for (i=0; i<HostIdentity.SIZE; i++) {
+                               buf.put((byte) 0);
+                               }
+                       }
+               for (i=0; i<queries.size(); i++) {
+                       h=(HashCode512) queries.get(i);
+                       h.writeBytes(buf);
+                       }
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/P2PSBlockResult.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/P2PSBlockResult.java  
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/P2PSBlockResult.java  
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,72 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+
+/**
+ * Return message for SBlock download
+ */
+
+public class P2PSBlockResult extends P2PMessage
+{
+       public static final int SIZE    =       4+EncryptedSBlock.SIZE;
+
+       /** The search result. */
+       private EncryptedSBlock result;
+
+
+       public P2PSBlockResult()
+       {
+               super(IS_SBLOCK_RESULT);
+               result=new EncryptedSBlock();
+       }
+
+       public P2PSBlockResult( EncryptedSBlock esb )
+       {
+               this();
+               result=(EncryptedSBlock) PersistentHelper.copy(esb);    //todo: 
copie utile ???
+       }
+
+       public String toString()
+       {
+               return "P2P result SBlock [result="+result+"]";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public EncryptedSBlock getResult()
+       {
+               return result;
+       }
+
+       public int getByteSize()
+       {
+               return SIZE;
+       }
+
+       public void readBytes( ByteBuffer buf, ErrorReporter err )
+       {
+               int     size,type;
+
+               size=buf.getShort() & 0x0000ffff;
+               err.reportIf(size!=SIZE,"bad size");
+
+               type=buf.getShort() & 0x0000ffff;
+               err.reportIf(type!=IS_SBLOCK_RESULT,"bad type !");
+
+               result.readBytes(buf,err);
+       }
+
+       public void writeBytes( ByteBuffer buf )
+       {
+               buf.putShort((short) SIZE);
+               buf.putShort((short) IS_SBLOCK_RESULT);
+               result.writeBytes(buf);
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/Policy.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/Policy.java   2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/Policy.java   2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,373 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.server.*;
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.net.*;
+
+import java.util.logging.*;
+
+/**
+ * The code in this module is responsible for enforcing
+ * the anonymity policy set by the user.
+ */
+
+public class Policy extends LoggedObject implements AFSConstants
+{
+       /** Socket to communicate with gnunetd. */
+       private CSSession               sock;
+
+       /** CoreAPI, null if we are using the socket ! */
+       private CoreForProtocol coreAPI;
+
+       /** Which value did the user specify for the sendPolicy ? */
+       private int                             sendPolicy;
+
+       /** Which value did the user specify for the receivePolicy ? */
+       private int                             receivePolicy;
+
+       /** Last time traffic information was obtained. */
+       private long                            lastPoll;
+
+       /** Mutex for synchronizing access. */
+       private Object                  lock;
+
+       /** Number of peers that were active at the last poll (since this is 
always type sensitive, not totals). */
+       private int                             chkPeers;
+
+       /** */
+       private int                             hashPeers;
+
+       /** */
+       private int                             queryPeers;
+
+       /** Number of bytes of unmatched bytes of traffic at the last poll 
(totals and separate for the 3 message types). */
+       private int                             totalReceiveBytes;
+
+       /** */
+       private int                             totalCHKBytes;
+
+       /** */
+       private int                             total3HASHBytes;
+
+       /** */
+       private int                             totalQueryBytes;
+
+
+       protected Policy( Prefs prefs )
+       {
+               super(true);
+               sock=null;
+               coreAPI=null;
+               sendPolicy=prefs.getInt("AFS","ANONYMITY-SEND",0);
+               receivePolicy=prefs.getInt("AFS","ANONYMITY-RECEIVE",0);
+               lastPoll=0;
+               lock=new Object();
+               chkPeers=0;
+               hashPeers=0;
+               queryPeers=0;
+               totalReceiveBytes=0;
+               totalCHKBytes=0;
+               total3HASHBytes=0;
+               totalQueryBytes=0;
+       }
+
+       /**
+        * Initialize the module / from server.
+        *
+        * @param prefs
+        * @param capi the GNUnet core API
+        */
+
+       public Policy( Prefs prefs, CoreForProtocol capi )
+       {
+               this(prefs);
+               coreAPI=capi;
+       }
+
+       /**
+        * Initialize the module / we are a client.
+        *
+        * @param prefs
+        * @param session
+        */
+
+       public Policy( Prefs prefs, CSSession session )
+       {
+               this(prefs);
+
+               assert(session!=null);
+               sock=session;
+       }
+
+       public String toString()
+       {
+               return "xxx";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       /**
+        * Shutdown the module.
+        */
+
+       public void doneAnonymityPolicy()
+       {
+               if (sock!=null) {
+                       sock.disconnect();
+                       sock=null;
+                       }
+               coreAPI=null;
+       }
+
+       /**
+        * Check if the anonymity policy will be violated if
+        * a message of the given type will be send.
+        * @param type the request type of the message that will be
+        *        transmitted
+        * @param size the size of the message that will be
+        *        transmitted
+        * @return true if this is ok for the policy, false if not
+        */
+
+       public boolean checkAnonymityPolicy( int type, int size )
+       {
+               if (sock==null && coreAPI==null) {
+                       return true;
+                       }
+
+               if (sock == null)
+                       pollCAPI();
+               else
+                       pollSocket();
+
+               switch (type) {
+                       case CSMessage.IS_QUERY:
+                               return checkPolicy(receivePolicy, type, size);
+                       case CSMessage.IS_RESULT_3HASH:
+                       case CSMessage.IS_RESULT_CHK:
+                               return checkPolicy(sendPolicy, type, size);
+                       default:
+                               return true;
+                       }
+       }
+
+       /**
+        * Poll gnunetd via TCP about traffic information.
+        * Note that we only send the request if we would
+        * otherwise potentially have to refuse messages.
+        */
+
+       protected void pollSocket()
+       {
+               long                            now;
+               CSTrafficInfo           info;
+               CSTrafficRequest        req;
+               TrafficCounter          tc;
+               int                                     i;
+
+               now=Scheduler.now();
+
+               synchronized(lock) {
+                       if (now - lastPoll < TTL_DECREMENT) {
+                               return; // poll at most every ttl-decrement 
time units
+                               }
+
+                       lastPoll = now;
+
+                       req=new CSTrafficRequest();
+                       req.timePeriod=(int) TTL_DECREMENT;
+
+                       if (!sock.send(req)) {
+                               log(Level.WARNING,"WARNING: could not query 
gnunetd about traffic conditions");
+                               return;
+                               }
+
+                       info = (CSTrafficInfo) 
sock.receive(CSTrafficInfo.class);
+                       if (info==null) {
+                               log(Level.WARNING,"WARNING: did not receive 
reply from gnunetd about traffic conditions");
+                               return;
+                               }
+
+                       for (i=info.count-1; i>=0; i--) {
+                               tc=info.counters[i];
+                               if ((tc.flags & TrafficCounter.TC_TYPE_MASK) == 
TrafficCounter.TC_RECEIVED) {
+                                       totalReceiveBytes += tc.count * 
tc.avrg_size;
+                                       switch (tc.type) {
+                                               case P2PMessage.IS_QUERY:
+                                                       totalQueryBytes += 
tc.count * tc.avrg_size;
+                                                       queryPeers += (tc.flags 
& TrafficCounter.TC_DIVERSITY_MASK);
+                                                       break;
+                                               case P2PMessage.IS_3HASH_RESULT:
+                                                       total3HASHBytes += 
tc.count * tc.avrg_size;
+                                                       hashPeers += (tc.flags 
& TrafficCounter.TC_DIVERSITY_MASK);
+                                                       break;
+                                               case P2PMessage.IS_CHK_RESULT:
+                                                       totalCHKBytes += 
tc.count * tc.avrg_size;
+                                                       chkPeers += (tc.flags & 
TrafficCounter.TC_DIVERSITY_MASK);
+                                                       break;
+                                               default:
+                                                       break;
+                                               }
+                                       }
+                               }
+                       }
+       }
+
+       /**
+        * Poll gnunet core via coreapi about traffic information.
+        */
+
+       protected void pollCAPI()
+       {
+               long    now;
+               int             
avgMessageSize,messageCount,peerCount,messageType;
+               int[]   counts;
+
+               now=Scheduler.now();
+               synchronized(lock) {
+                       if (now - lastPoll < TTL_DECREMENT) {
+                               return; // don't bother
+                               }
+                       lastPoll = now;
+
+                       for 
(messageType=0;messageType<P2PMessage.IS_MAX;messageType++) {
+                               
counts=coreAPI.getTrafficStats(messageType,TrafficCounter.TC_RECEIVED,(int) 
TTL_DECREMENT);
+                               avgMessageSize=counts[0];
+                               messageCount=counts[1];
+                               peerCount=counts[2];
+
+                               totalReceiveBytes += messageCount * 
avgMessageSize;
+                               switch (messageType) {
+                                       case P2PMessage.IS_QUERY:
+                                               totalQueryBytes += messageCount 
* avgMessageSize;
+                                               queryPeers += peerCount;
+                                               break;
+
+                                       case P2PMessage.IS_3HASH_RESULT:
+                                               total3HASHBytes += messageCount 
* avgMessageSize;
+                                               hashPeers += peerCount;
+                                               break;
+
+                                       case P2PMessage.IS_CHK_RESULT:
+                                               totalCHKBytes += messageCount * 
avgMessageSize;
+                                               chkPeers += peerCount;
+                                               break;
+
+                                       default:
+                                               break;
+                                       }
+                               }
+                       }
+       }
+
+       /**
+        * Test if the required number of peers were active.
+        *
+        * @param port which protocol are we interested in
+        * @param peerCount how many peers are required
+        * @return true if we had sufficient amounts of traffic, false if not
+        */
+
+       protected boolean checkPeerPolicy( int port, int peerCount )
+       {
+               switch (port) {
+                       case P2PMessage.IS_QUERY:
+                               if (queryPeers >= peerCount)
+                                       return true;
+                               return false;
+                       case P2PMessage.IS_CHK_RESULT:
+                               if (chkPeers >= peerCount)
+                                       return true;
+                               return false;
+                       case P2PMessage.IS_3HASH_RESULT:
+                               if (hashPeers >= peerCount)
+                                       return true;
+                               return false;
+                       default:
+                               return false;
+                       }
+       }
+
+       /**
+        * Test if the required amount of traffic is available.
+        *
+        * @param port what type of traffic are we interested in?
+        * @param size how much traffic do we intend to produce?
+        * @param byteRatio how much cover traffic do we need (byteRatio*size)
+        * @param strictMatch does only traffic of exactly the same
+        *        type (port) count?
+        * @return true if enough cover traffic was be found, false if not.
+        */
+
+       protected boolean checkRatioPolicy( int port, int size, int byteRatio, 
boolean strictMatch )
+       {
+               int     cost;
+
+               cost = byteRatio * size;
+               if (strictMatch) {
+                       switch (port) {
+                       case P2PMessage.IS_QUERY:
+                               if (totalQueryBytes < cost)
+                                       return false;
+                               totalQueryBytes -= cost;
+                               return true;
+                       case P2PMessage.IS_CHK_RESULT:
+                               if (totalCHKBytes < cost)
+                                       return false;
+                               totalCHKBytes -= cost;
+                               return true;
+                       case P2PMessage.IS_3HASH_RESULT:
+                               if (total3HASHBytes < cost)
+                                       return false;
+                               total3HASHBytes -= cost;
+                               return true;
+                       default:
+                               return false;
+                       }
+                       }
+               if (totalReceiveBytes < cost)
+                       return false;
+               totalReceiveBytes -= cost;
+               return true;
+       }
+
+       /**
+        * Test if the policy requirements are fullfilled.
+        *
+        * @param policyValue the anonymity degree required by the user
+        * @param type the message type
+        * @param size the size of the message
+        * @return true if this isok for the policy, false if not.
+        */
+
+       protected boolean checkPolicy( int policyValue, int type, int size )
+       {
+               int     peerCount,byteRatio;
+
+               if (policyValue <= 0)
+                       return true; /* no policy */
+               if (policyValue >= 1000) {
+                       byteRatio = policyValue / 1000;
+                       peerCount = policyValue % 1000;
+               } else {
+                       byteRatio = policyValue;
+                       peerCount = 0;
+               }
+               if (peerCount > 0)
+                       if (false == checkPeerPolicy(type,
+                                       peerCount))
+                               return false;
+               if (byteRatio > 0)
+                       if (false == checkRatioPolicy(type,
+                                       size,
+                                       byteRatio,
+                                       policyValue >= 1000))
+                               return false;
+               return true;
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/Priority.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/Priority.java 2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/Priority.java 2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,99 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.*;
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.net.*;
+
+/**
+ * Keep track of the maximum priority that we are currently using.
+ */
+
+public class Priority extends LoggedObject implements AFSConstants
+{
+       private int                             maxPriority_;
+       private Scheduler               scheduler;
+       private AbstractClient  client;
+       private ScheduledTask           trackJob;
+
+
+       public Priority( Application app )
+       {
+               super(true);
+               maxPriority_=0;
+               client=(AbstractClient) app;
+               scheduler=app.getScheduler();
+               trackJob=new ScheduledTask("TRACK-PRIORITY",new 
AbstractAction() {
+                       public void perform()
+                       {
+                               trackPriority();
+                       }
+                       },TTL_DECREMENT);
+       }
+
+       /**
+        * This method must be called to start the priority
+        * tracker.
+        */
+
+       public void startAFSPriorityTracker()
+       {
+               trackPriority();
+               scheduler.addJob(trackJob,TTL_DECREMENT);
+       }
+
+       /**
+        * This method must be called to stop the priority
+        * tracker.  Call after cron has been stopped.
+        */
+
+       public void stopAFSPriorityTracker()
+       {
+               scheduler.deleteJob(trackJob);
+       }
+
+       /**
+        * What is the highest priority that AFS clients should use for 
requests at this point in time ?
+        * @return
+        */
+
+       public int getMaxPriority()
+       {
+               return maxPriority_;
+       }
+
+       protected void trackPriority()
+       {
+               CSSession               sock;
+               CSGetAvgPriority        req;
+               CSResult        rv;
+               int                             res;
+
+               sock=client.connect();
+               if (sock == null) {
+                       maxPriority_ = 0;
+                       return;
+                       }
+
+               req=new CSGetAvgPriority();
+               if (sock.send(req)) {
+                       rv=(CSResult) sock.receive(CSResult.class);
+                       if (rv!=null) {
+                               res=rv.getResult();
+                               maxPriority_ = 2*res+1;
+
+                               debug("Current maximum priority : 
"+maxPriority_);
+                               }
+                       else {
+                               maxPriority_ = 0;
+                               }
+                       }
+               else {
+                       maxPriority_ = 0;
+                       }
+               sock.disconnect();
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/ProgressModel.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/ProgressModel.java    
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/ProgressModel.java    
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,33 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+/**
+ * callback for updates on the progress of an operation
+ */
+
+public interface ProgressModel
+{
+       public static final ProgressModel       NO_MODEL        =       new 
ProgressModel() {
+               public void progress( ProgressStats stats, Object data )
+               {
+               }
+               };
+
+       /**
+        * Called whenever we make progress. Callback methods
+        * of this type are used during insertion and download to notify the
+        * user interface of the progress we're making. If the model is called
+        * with position == total, the download is complete. If the model
+        * is called with position == total == 0, then there was a fatal error
+        * and the download was aborted.
+        *
+        * @param stats progress statistics
+        * @param data a context passed around for use by the PM
+        *        implementation
+        */
+
+       public void progress( ProgressStats stats, Object data );
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/ProgressStats.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/ProgressStats.java    
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/ProgressStats.java    
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,54 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+/**
+ * statistics about the progress
+ *
+ * Progress of the current operation. Used for passing
+ * data to callbacks. Some of these make sense only for
+ * downloading.
+ */
+
+public class ProgressStats extends Object
+{
+       public int      progress;                       /* bytes processed */
+       public int      filesize;                       /* total file size */
+       public int      requestsSent;
+       public int      requestsPending;
+       public int      currentRetries;
+       public int      totalRetries;
+       public int      currentTTL;
+       public int      duplicationEstimate;
+
+
+       public ProgressStats()
+       {
+               super();
+       }
+
+       public ProgressStats( ProgressStats p )
+       {
+               super();
+               progress=p.progress;
+               filesize=p.filesize;
+               requestsSent=p.requestsSent;
+               requestsPending=p.requestsPending;
+               currentRetries=p.currentRetries;
+               totalRetries=p.totalRetries;
+               currentTTL=p.currentTTL;
+               duplicationEstimate=p.duplicationEstimate;
+       }
+
+       public String toString()
+       {
+               return "xxx";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/Pseudonym.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/Pseudonym.java        
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/Pseudonym.java        
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,282 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.io.*;
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+import java.util.*;
+import java.util.logging.*;
+
+/**
+ * functions for handling pseudonyms
+ */
+
+public class Pseudonym extends LoggedObject
+{
+       private static final String     PSEUDODIR       =       
"data/pseudonyms";
+       private static final String     NS_HANDLE       =       
"known_namespaces";
+
+       private Prefs           prefs;
+       private DirLocation     base;
+
+
+       public Pseudonym( Prefs p )
+       {
+               super(true);
+               prefs=p;
+
+               base=prefs.getDirLocation("","GNUNET_HOME");
+               if (base==null) {
+                       trace("Configuration file must specify a directory for 
GNUnet to store per-peer data under GNUNET_HOME !");
+                       throw new RuntimeException();
+                       }
+               base=base.getDirectory(PSEUDODIR);
+               base.create();
+       }
+
+       public String toString()
+       {
+               return "xxx";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       /**
+        * Create a new pseudonym.
+        *
+        * @param name the name of the pseudonym
+        * @param password passphrase to encrypt the pseudonym on disk (may be 
null)
+        * @return null on error (e.g. pseudonym exists), otherwise the secret 
key
+        */
+
+       public PrivateKey createPseudonym( String name, String password )
+       {
+               FileLocation            fileName;
+               PrivateKey              hk;
+               byte[]                  dst;
+               SessionKey              key;
+               HashCode512             hc;
+               byte[]                  iv;
+               MappedFile              f;
+
+               fileName=base.getFile(name);
+               if (fileName.exists()) {
+                       log(Level.WARNING,"Can't create pseudonym "+name+", 
file \""+fileName.getLabel()+"\" exists.");
+                       return null;
+                       }
+
+               hk=PrivateKey.create();
+               dst=PersistentHelper.toBytes(hk);
+
+               if (password!=null) {
+                       iv=new byte[SessionKey.BLOWFISH_BLOCK_LENGTH];
+                       
System.arraycopy(SessionKey.INITVALUE,0,iv,0,SessionKey.BLOWFISH_BLOCK_LENGTH);
+
+                       hc=HashCode512.create(password);
+                       key=hc.extractKey(null);
+
+                       dst=key.encrypt(dst,iv);
+                       if (dst==null) {
+                               return null;
+                               }
+                       }
+
+               f=fileName.openNew();
+               if (!f.writeBytes(dst)) {
+                       log(Level.WARNING,"Failed to write "+dst.length+" 
bytes.");
+                       f.close();
+
+                       fileName.delete();
+                       return null;
+                       }
+               f.close();
+               return hk;
+       }
+
+       /**
+        * Delete a pseudonym.
+        *
+        * @param name the name of the pseudonym
+        * @return OK on success, false on error
+        */
+
+       public boolean deletePseudonym( String name )
+       {
+               FileLocation            f;
+
+               f=base.getFile(name);
+               if (!f.exists()) {
+                       log(Level.WARNING,"File does not exist : "+f+".");
+                       return false;
+                       }
+               if (!f.delete()) {
+                       log(Level.WARNING,"Could not unlink "+f+".");
+                       return false;
+                       }
+               return true;
+       }
+
+       /**
+        * Read pseudonym.
+        *
+        * @param name the name of the pseudonym
+        * @param password passphrase to encrypt the pseudonym on disk (may be 
null)
+        * @return null on error (e.g. password invalid, pseudonym does not 
exist), otherwise the secret key
+        */
+
+       public PrivateKey readPseudonym( String name, String password )
+       {
+               PrivateKey              hk;
+               ByteBuffer              buf;
+               SessionKey              key;
+               HashCode512             hc;
+               byte[]                  iv,tmp;
+               MappedFile              f;
+               FileLocation            loc;
+
+               loc=base.getFile(name);
+               if (!loc.exists()) {
+                       log(Level.WARNING,"File \""+loc.getLabel()+"\" does not 
exist.");
+                       return null;
+                       }
+
+               f=loc.open();
+               buf=f.readBuffer();
+               if (buf==null) {
+                       log(Level.WARNING,"Failed to read "+loc.getLabel()+".");
+                       f.close();
+                       return null;
+                       }
+
+               if (password != null) {
+                       iv=new byte[SessionKey.BLOWFISH_BLOCK_LENGTH];
+                       
System.arraycopy(SessionKey.INITVALUE,0,iv,0,SessionKey.BLOWFISH_BLOCK_LENGTH);
+
+                       hc=HashCode512.create(password);
+                       key=hc.extractKey(null);
+
+                       tmp=new byte[buf.capacity()];
+                       buf.get(tmp);
+                       tmp=key.decrypt(tmp,iv);
+                       if (tmp==null) {
+                               log(Level.WARNING,"Decrypting pseudonym 
failed.");
+                               return null;
+                               }
+                       buf=ByteBuffer.wrap(tmp);
+                       }
+
+               hk=(PrivateKey) PersistentHelper.readFully(PrivateKey.class, 
buf);
+               if (hk==null) {
+                       // wrong PW happens A LOT, thus don't always print this 
warning !
+                       log(Level.FINEST,"Pseudonym format for "+name+" 
invalid. Wrong password ?");
+                       f.close();
+                       return null;
+                       }
+               f.close();
+               return hk;
+       }
+
+       /**
+        * Test if we have any pseudonyms.
+        *
+        * @return YES if we do have pseudonyms, otherwise NO.
+        */
+
+       public boolean havePseudonyms()
+       {
+               return base.count()>0;
+       }
+
+       /**
+        * Build a list of all available pseudonyms.
+        *
+        * @return null on error, otherwise the list of pseudonyms
+        */
+
+       public String[] listPseudonyms()
+       {
+               TraverserContext        ctx;
+               final List      _myList=new ArrayList();
+
+               ctx=new TraverserContext();
+               ctx.setFilesOnly(true);
+
+               base.traverse(new Traverser() {
+                       public boolean examine( Location node, int depth )
+                       {
+                               _myList.add(node.getName());
+                               return true;
+                       }
+                       },ctx);
+               return (String[]) _myList.toArray(new String[_myList.size()]);
+       }
+
+       /**
+        * Build a list of all known namespaces.
+        *
+        * @param list where to store the names of the namespaces
+        * @return false on error, otherwise the number of known namespaces
+        */
+
+       public int listNamespaces( HashCode512[][] list )
+       {
+               list[0]=listNamespaces();
+               return (list[0]!=null ? list[0].length : -1);
+       }
+
+       public HashCode512[] listNamespaces()
+       {
+               HashCode512[]   list;
+               ByteBuffer              buf;
+               int                             i;
+
+               buf=prefs.getContent(NS_HANDLE);
+               if (buf==null) {
+                       return null;
+                       }
+               if ((buf.limit() % HashCode512.SIZE)!=0) {
+                       log(Level.WARNING,"State database "+NS_HANDLE+" 
corrupted, deleting contents.");
+                       prefs.unlink(NS_HANDLE);
+                       return null;
+                       }
+
+               list=new HashCode512[buf.limit()/HashCode512.SIZE];
+               for (i=0; i<list.length; i++) {
+                       list[i]=(HashCode512) 
PersistentHelper.read(HashCode512.class,buf);
+                       if (list[i]==null) {
+                               return null;
+                               }
+                       }
+               return list;
+       }
+
+       /**
+        * Add a namespace to the set of known namespaces.
+        *
+        * @param ns the namespace identifier
+        */
+
+       public void addNamespace( HashCode512 ns )
+       {
+               HashCode512[]   list;
+               int                             i;
+
+               list=listNamespaces();
+               if (list!=null) {
+                       for (i=0; i<list.length; i++) {
+                               if (ns.equals(list[i])) {
+                                       return; // seen before
+                                       }
+                               }
+                       }
+
+               prefs.appendContent(NS_HANDLE,PersistentHelper.toBytes(ns));
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/RequestContinuation.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/RequestContinuation.java      
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/RequestContinuation.java      
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,37 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+/**
+ */
+
+public class RequestContinuation extends Object
+{
+       public NodeContext                      nc;
+       public RequestEntry                     entry;
+       /* in HOST byte order! */
+       public int                                      ttl;
+       /* in HOST byte order! */
+       public int                                      prevttl;
+       public int                                      prevpri;
+       public long                                     prevlt;
+       public RequestContinuation      next;
+
+
+       public RequestContinuation()
+       {
+               super();
+       }
+
+       public String toString()
+       {
+               return "xxx";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/RequestEntry.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/RequestEntry.java     
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/RequestEntry.java     
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,50 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+/**
+ * Format of a request as tracked by the RequestManager.
+ */
+
+public class RequestEntry extends Object
+{
+       /** The message that is send to gnunetd. */
+       public CSQuery  message;
+
+       /** Last time the query was send. */
+       public long                     lasttime;
+
+       /** Whom to call once we get a reply? */
+       public Listener         receiver;
+
+       /** The node to pass to the receiver method. */
+       public Block            receiverNode;
+
+       /** Opaque data handle to pass to the Listener. */
+       public NodeContext      data;
+
+       /** How long have we been actively trying this one? */
+       public int                      tries;
+
+       /** How many replies have we received for this entry? (for 
super-queries, thus always in [0,25]).
+        [reset for each retransmission; used to NOT increment the TTL if we 
got a reply] */
+       public int                      successful_replies;
+
+
+       public RequestEntry()
+       {
+               super();
+       }
+
+       public String toString()
+       {
+               return "xxx";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/RequestManager.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/RequestManager.java   
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/RequestManager.java   
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,855 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.*;
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.net.*;
+
+import java.util.logging.*;
+
+/**
+ * The RequestManager keeps track of and re-issues queries
+ * The RequestManager keeps track of queries and re-issues
+ *        requests if no reply is received.
+ * structure that keeps track of currently pending requests for a download
+ *
+ * Handle to the state of a request manager.  Here we keep track of
+ * which queries went out with which priorities and which nodes in
+ * the merkle-tree are waiting for the replies.
+ */
+
+public class RequestManager extends LoggedObject implements AFSConstants
+{
+       private static int      lpri;
+
+       static {
+               lpri=0;
+               }
+
+       /** Mutex for synchronizing access to this struct */
+       private Object                          lock;
+
+       /** Current list of all pending requests */
+       private RequestEntry[]          requestList;
+
+       /** Number of pending requests (highest used index) */
+       private int                                     requestListIndex;
+
+       /** Number of entries allocated for requestList */
+       private int                                     requestListSize;
+
+       /** Current "good" TTL (initial) [64s].  In HOST byte order. */
+       private int                                     initialTTL;
+
+       /** Congestion window.  How many messages should be pending 
concurrently? */
+       private int                                     congestionWindow;
+
+       /** Slow-start threshold (see RFC 2001) */
+       private int                                     ssthresh;
+
+       /** Current estimate of "duplication" rate (amount of duplicate replies 
we get). */
+       private int                                     duplicationEstimate;
+
+       /** Socket used to talk to gnunetd. */
+       private CSSession                       sock;
+
+       /** The thread that receives results from gnunetd. */
+       private Task                                    receiveThread_;
+
+       /** */
+       private int                                     lastDET;
+
+       /** */
+       private RequestContinuation     start;
+
+       /** CRC of the top-IBlock, see downloadutil.c and 
block.c::childDownloadCompleted. */
+       public int                                      topCrc32;       
//fixme: private
+
+       /** The top block. */
+       public Block                                    top;            //todo: 
remettre en private
+
+       private Scheduler                       scheduler;
+       private Policy                          policy;
+
+       private ScheduledTask           rrjob = new 
ScheduledTask("REQUEST-JOB",new AbstractAction() {
+               public void perform()
+               {
+                       requestJob();
+               }
+               });
+       private Priority                priority;
+
+
+       private RequestManager()
+       {
+               super(true);
+       }
+
+       /**
+        * Create a request manager. Will create the request manager
+        * datastructures and also connect to gnunetd. Creates thread that
+        * listens to gnunetd replies and another thread that periodically
+        * re-issues the queries. Use destroyRequestManager to abort and/or to
+        * free resources after the download is complete. The callback method
+        * in nc will be invoked to notify the caller of the download progress
+        * such that it is possible to tell when we are done.
+        * @param app
+        * @param p
+        * @param pppp
+        */
+
+       public RequestManager( AbstractClient app, Policy p, Priority pppp )
+       {
+               this();
+               scheduler=app.getScheduler();
+               policy=p;
+               priority=pppp;
+
+               start=null;
+               lastDET=0;
+               lock=new Object();
+               requestListIndex=0;
+               requestListSize=256;
+               requestList=new RequestEntry[256];
+               initialTTL= (int) Scheduler.SECS_5;
+               /* RFC 2001 suggests to use 1 segment size initially;
+                Given 1500 octets per message in GNUnet, we would
+                have 2-3 queries of maximum size (552); but since
+                we are multi-casting to many peers at the same
+                time AND since queries can be much smaller,
+                we do WHAT??? */
+               congestionWindow=1; /* RSS is 1 */
+               ssthresh=65535;
+               duplicationEstimate=0;
+               sock=app.connect();
+               if (sock==null) {
+                       log(Level.WARNING,"Could not create socket to connect 
to gnunetd.");
+                       requestList=null;
+                       requestListSize=0;
+                       return;
+                       }
+
+               receiveThread_=new Task("REQUEST-MANAGER-RECEIVE",new 
AbstractAction() {
+                       public void perform()
+                       {
+                               receiveThread();
+                       }
+                       });
+               receiveThread_.launch();
+       }
+
+       public String toString()
+       {
+               return "xxx";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       /**
+        * Destroy the resources associated with a request manager.
+        * Invoke this method to abort the download or to clean up
+        * after the download is complete.
+        *
+        */
+
+       public void destroy()
+       {
+               CSSession       sockx;
+               int                     i;
+
+               scheduler.suspend();
+
+               synchronized(lock) {
+                       sockx=sock;
+                       sock = null;
+                       scheduler.deleteJob(rrjob);
+                       }
+
+               if (sockx != null) {
+                       sockx.disconnect(); /* unblock RM thread */
+                       }
+
+               receiveThread_.join();
+
+               synchronized(lock) {
+                       for (i=0; i<requestListIndex; i++) {
+                               freeInContinuations(requestList[i]);
+                               requestList[i]=null;
+                               }
+                       requestListIndex = 0;
+                       requestList=null;
+                       requestListSize=0;
+                       }
+
+               if (top != null) {
+                       top.destroy(this);
+                       }
+               scheduler.resume();
+       }
+
+       /**
+        * Print the contents of the request manager. For debugging.
+        * For debugging.
+        */
+
+       public void print()
+       {
+               int     i;
+
+               synchronized(lock) {
+                       log(Level.FINEST,"RM TTL "+initialTTL+" 
duplicates"+duplicationEstimate);
+
+                       for (i=0; i<requestListIndex; i++) {
+                               log(Level.FINEST,i+": 
"+requestList[i].message.getQuery(0).toHex()+" for node 
"+requestList[i].receiverNode+" ("+requestList[i].tries+" tries)");
+                               }
+                       }
+       }
+
+       /**
+        * Assert that there are no pending requests for this node.
+        * @param node
+        */
+
+       public void assertDead( Block node )
+       {
+               int i;
+
+               synchronized(lock) {
+                       for (i=0; i<requestListIndex; i++) {
+                               if (requestList[i].receiverNode == node) {
+                                       assert(false) : "Node "+node+" is being 
destroyed while request is pending.";
+                                       }
+                               }
+                       }
+       }
+
+       /**
+        * We are approaching the end of the download. Cut all TTLs in half.
+        */
+
+       public void endGame()
+       {
+               int i;
+
+               synchronized(lock) {
+                       for (i=0;i<requestListIndex;i++) {
+                               RequestEntry  entry = requestList[i];
+                               int ttl = entry.message.getTTL();
+                               entry.message.setTTL(ttl / 2);
+                               }
+                       }
+       }
+
+       /**
+        * Queue a request for execution.
+        *
+        * @param node the node to call once a reply is received
+        * @param callback the method to invoke
+        * @param data the data argument to the Listener
+        * @param message the query to send to gnunetd, freed by callee!
+        */
+
+       public void request( Block node, Listener callback, NodeContext data, 
CSQuery message )
+       {
+               RequestEntry[]  old;
+               RequestEntry    entry;
+
+               debug("Request request for "+node+" with callback "+callback);
+
+               entry = new RequestEntry();
+               entry.message= message;
+               entry.successful_replies= 0;
+               entry.lasttime = 0; /* never sent */
+               entry.receiver = callback;
+               entry.receiverNode= node;
+               entry.data = data;
+               entry.tries= 0; /* not tried so far */
+
+               synchronized(lock) {
+                       /* can we add to current list & issue instantly? */
+                       if (requestListSize == requestListIndex)  {
+                               old=requestList;
+
+                               requestListSize*=2;
+                               requestList=new RequestEntry[requestListSize];
+                               
System.arraycopy(old,0,requestList,0,old.length);
+                               }
+                       requestList[requestListIndex++] = entry;
+
+                       debug("Scheduling next run for now !");
+                       scheduler.advanceJob(rrjob);
+                       }
+               return;
+       }
+
+       /**
+        * Update a request. This method is used to selectively
+        * change a query or drop it entirely.
+        *
+        * @param node the block for which the request is updated
+        * @param msg the new query message for that node, null for
+        *        none (then the request is dropped)
+        */
+
+       public void update( Block node, CSQuery msg )
+       {
+               int i;
+
+               debug("DEBUG: updating request for "+node+" to "+msg);
+
+               synchronized(lock) {
+                       for (i=0;i<requestListIndex;i++) {
+                               if (requestList[i].receiverNode == node) {
+                                       if (msg != null) {
+                                               // update
+                                               // keep priority & ttl
+                                               
msg.setPriorityAndTTL(requestList[i].message.getPriority(),requestList[i].message.getTTL());
+                                               
requestList[i].successful_replies++;
+                                               requestList[i].message = msg;
+
+                                               // also wait a bit longer 
before re-issueing the request, after all, we got at least one of the replies !
+                                               // add 2*TTL grace time if we 
got a reply to a multi-query; this dramatically reduces the amount of "useless" 
(duplicate) replies we get !
+                                               
requestList[i].lasttime=Scheduler.now()+2*TTL_DECREMENT;
+                                               }
+                                       else {
+                                               // delete
+
+                                               // update stats
+                                               if(requestList[i].tries > 1)
+                                                       
requestList[i].data.stats.currentRetries-= (requestList[i].tries - 1);
+
+                                               requestList[i].message=null;
+                                               
freeInContinuations(requestList[i]);
+                                               requestList[i]=null;
+                                               requestList[i]= 
requestList[--requestListIndex];
+                                               
requestList[requestListIndex]=null;
+                                               }
+                                       return; // found !
+                                       }
+                               }
+                       }
+       }
+
+       /**
+        * Test that the given entry does not occur
+        * in the continuations list (and if it does,
+        * null it out).
+        * @param entry
+        */
+
+       protected void freeInContinuations( RequestEntry entry )
+       {
+               RequestContinuation     cur;
+
+               cur = start;
+               while (cur != null) {
+                       if (cur.entry == entry)
+                               cur.entry = null;
+                       cur = cur.next;
+                       }
+       }
+
+       /**
+        * We have determined success or failure for
+        * sending the query.  Now update the state of
+        * the RM adequately.  start contains the
+        * continuation that holds the state required
+        * to do this update.
+        * @param ok
+        */
+
+       protected void runContinuation( boolean ok )
+       {
+               RequestContinuation     cur;
+
+               cur = start;
+               if (cur.entry != null) {
+                       if (!ok) {
+                               /* we did not send this entry, revert! */
+                               log(Level.FINEST,"Sending canceled (would 
block).");
+                               cur.entry.message.setPriorityAndTTL( 
cur.prevpri, cur.prevttl);
+                               cur.entry.lasttime = cur.prevlt;
+                               cur.entry.tries--;
+                               }
+                       else {
+                               if (cur.entry.tries > 1) {
+                                       int     nowTT;
+
+                                       nowTT=(int) 
Scheduler.toSeconds(Scheduler.now());
+                                       if ( (nowTT - initialTTL) > lastDET) {
+                                               /* only consider congestion 
control every
+                                                "average" TTL seconds, 
otherwise the system
+                                                reacts to events that are far 
too old! */
+                                               /* we performed retransmission, 
treat as congestion (RFC 2001) */
+
+                                               debug("Received duplicate data, 
changing CW ("+congestionWindow+
+                                                       " to 
"+((congestionWindow/2)+1)+
+                                                       ") and SST ("+ssthresh+
+                                                       
"."+(congestionWindow/2)+")");
+
+                                               ssthresh= congestionWindow / 2;
+                                               if (ssthresh < 2)
+                                                       ssthresh = 2;
+                                               congestionWindow= ssthresh + 1;
+                                               lastDET = nowTT;
+                                               }
+                                       cur.nc.stats.totalRetries++;
+                                       cur.nc.stats.currentRetries++;
+                                       }
+                               }
+                       }
+               start = cur.next;
+       }
+
+       /**
+        * Send the request from the requestList[requestIndex] out onto
+        * the network.
+        *
+        * @param requestIndex the index of the Request to issue
+        */
+
+       protected void issueRequest( int requestIndex )
+       {
+               RequestContinuation     con;
+               RequestContinuation     pos;
+               RequestEntry                    entry;
+               NodeContext                     nc;
+               long                                    now;
+               CSMessage                       msg;
+               CSSession                       sockx;
+               boolean                         ok;
+
+               now=Scheduler.now();
+
+               con = new RequestContinuation();
+               con.next = null;
+               con.entry= entry= requestList[requestIndex];
+
+               if ((entry.lasttime+entry.message.getTTL()) > now - 
TTL_DECREMENT) {
+                       log(Level.WARNING,"Assertion failed: "+entry.lasttime+" 
+ "+entry.message.getTTL()+" > "+now+" + "+TTL_DECREMENT);
+                       assert(false);
+                       }
+
+               if (entry.lasttime == 0) {
+                       entry.message.setPriorityAndTTL( 
entry.message.getPriority(),  0); /* to avoid assert failure */
+                       con.ttl = initialTTL;
+                       con.prevttl = con.ttl;
+                       }
+               else {
+                       con.ttl = entry.message.getTTL();
+                       con.prevttl = con.ttl;
+                       if (con.ttl > MAX_TTL) {
+                               con.ttl = (int) (MAX_TTL + Crypto.nextInt((int) 
(2*TTL_DECREMENT)));
+                               entry.message.setPriorityAndTTL( 
entry.message.getPriority(),  (int) MAX_TTL); /* to avoid assert failure */
+                               }
+                       else if (con.ttl > initialTTL) {
+                               /* switch to slow back-off */
+                               int rd;
+                               if (initialTTL == 0)
+                                       rd = con.ttl;
+                               else
+                                       rd = con.ttl / initialTTL;
+                               if (rd == 0)
+                                       rd = 1; /* how? */
+                               rd = (int) (TTL_DECREMENT / rd);
+                               if (rd == 0)
+                                       rd = 1;
+                               con.ttl += Crypto.nextInt((int) 
(Scheduler.MILLIS_50+rd));
+                               /* rd == TTL_DECREMENT / (con.ttl / initialTTL) 
+ saveguards
+                                50ms: minimum increment */
+                               }
+                       else {
+                               con.ttl += Crypto.nextInt((int) (con.ttl + 2 * 
TTL_DECREMENT)); /* exponential backoff with random factor */
+                               }
+                       }
+
+               con.prevlt = entry.lasttime;
+               entry.lasttime = now + 2 * TTL_DECREMENT;
+               if (Crypto.nextInt(1+entry.tries) > 1) {
+                       /* do linear (in tries) extra back-off (in addition to 
ttl)
+                        to avoid repeatedly tie-ing with other peers; this is 
somewhat
+                        equivalent to what ethernet is doing, only that 
'tries' is our
+                        (rough) indicator for collisions.  For ethernet 
back-off, see:
+                        
http://www.industrialethernetuniversity.com/courses/101_4.htm
+                        */
+                       entry.lasttime += Crypto.nextInt((int) (TTL_DECREMENT * 
(1+entry.tries)));
+                       }
+
+               if 
(!policy.checkAnonymityPolicy(CSMessage.IS_QUERY,entry.message.getByteSize()+HostIdentity.SIZE))
 {
+                       debug("DEBUG: not sending query due to anonymity policy 
!");
+                       return;
+                       }
+
+               if (con.ttl < entry.message.getTTL()) {
+                       log(Level.WARNING,"WARNING: assertion failed; 
decrementing TTL from "+entry.message.getTTL()+" to "+con.ttl+" !");
+                       assert(false);
+                       }
+
+               con.prevpri = entry.message.getPriority();
+               if (con.prevpri > 0x0FFFFFF)
+                       con.prevpri = Crypto.nextInt(0xFFFFFF); /* bound! */
+               entry.tries++;
+               if (entry.successful_replies > 0) {
+                       /* do NOT change priority / ttl for n iterations
+                        where n is the number of successful replies!*/
+                       con.ttl = entry.message.getTTL();
+                       entry.successful_replies /= 2; /* better than --? 
better than = 0? */
+                       }
+               else {
+                       int tpriority;
+                       int mpriority;
+                       int count;
+
+                       if (con.ttl > (con.prevpri+8)* TTL_DECREMENT)  {
+                               con.ttl =(int) ((con.prevpri+8) * 
TTL_DECREMENT);
+                               }
+
+                       entry.message.setTTL(con.ttl);
+                       tpriority = con.prevpri + Crypto.nextInt(entry.tries);
+                       mpriority = priority.getMaxPriority();
+                       /* adjust mpriority according to the number of queries 
*/
+
+                       count=entry.message.getQueriesCount();
+                       if (count >= 2)
+                               count--; /* discount super-query */
+                       mpriority *= count;
+
+                       if (tpriority > mpriority) {
+                               /* mpriority is (2 * (current average priority 
+ 2)) and
+                                is used as the maximum priority that we use; 
if the
+                                calculated tpriority is above it, we reduce 
tpriority
+                                to random value between the average 
(mpriority/2) but
+                                bounded by mpriority */
+                               tpriority = mpriority / 2 + 
(Crypto.nextInt(1+mpriority/2));
+                               }
+                       entry.message.setPriority(tpriority);
+                       }
+
+               if (isDebug()) {
+                       int i;
+                       int count;
+                       String  hex;
+
+                       count=entry.message.getQueriesCount();
+                       for (i=0; i<count; i++) {
+                               hex=entry.message.getQuery(i).toHex();
+
+                               if (con.prevlt == 0) {
+                                       log(Level.FINEST,i+"sending 
"+entry.tries+"st time "+
+                                               "(last: NEVER; ttl 
"+con.prevttl+")"+
+                                               " "+hex+"; ttl "+con.ttl+", 
priority "+entry.message.getPriority()+" ("+initialTTL+")");
+                                       }
+                               else {
+                                       log(Level.FINEST,i+" sending 
"+entry.tries+"-th time "+
+                                               "(last: "+(now - con.prevlt)+" 
ms ago; ttl "+con.prevttl+")"+
+                                               " "+hex+"; ttl "+con.ttl+", 
priority "+entry.message.getPriority()+" ("+initialTTL+")");
+                                       }
+                               }
+                       }
+
+               con.nc= nc= entry.data;
+               nc.stats.requestsPending= requestListIndex;
+               nc.stats.requestsSent = requestListIndex;
+               nc.stats.currentTTL = con.ttl;
+               nc.stats.duplicationEstimate= duplicationEstimate;
+               nc.pmodel.progress(nc.stats, nc.data);
+
+               if (0 == (entry.tries % (MAX_TRIES * 50))) {
+                       log(Level.WARNING,"WARNING: 
"+entry.message.getQuery(0).toHex()+" seems to be not available on the 
network");
+                       entry.receiverNode.print(0);
+                       }
+
+               msg = (CSMessage) PersistentHelper.copy(entry.message);
+               synchronized(lock) {
+                       sockx = sock;
+                       }
+
+               ok = false;
+               if (sockx != null) {
+                       /* add con to the end of the (very short)
+                        linked list */
+                       pos = start;
+                       if (pos == null) {
+                               start = con;
+                               }
+                       else {
+                               while (pos.next != null)
+                                       pos = pos.next;
+                               pos.next = con;
+                               }
+
+                       /* destroyRM may set sock to null at ANY point! */
+                       sockx.setBlocking(false);
+                       ok = sockx.flushAndSend(msg);
+                       sockx.setBlocking(true);
+                       if (!ok) {
+                               log(Level.WARNING,"WARNING: could not send 
request to gnunetd, is it running ?");
+                               runContinuation(false);
+                               }
+                       else {
+                               /* receiverThread will call runContinuation */
+                               }
+                       }
+       }
+
+       /**
+        * Cron job that re-issues requests. Should compute how long to sleep
+        * (min ttl until next job is ready) and re-schedule itself
+        * accordingly!
+        */
+
+       protected void requestJob()
+       {
+               long minSleep;
+               long now;
+               long delta;
+               int i;
+               int pending;
+               int[] perm;
+
+               debug("CRON: requestJob running");
+
+               synchronized(lock) {
+                       if (requestListIndex == 0) {
+                               return;
+                               }
+                       now=Scheduler.now();
+                       pending = 0;
+
+                       for (i=0;i<requestListIndex;i++) {
+                               if (requestList[i].lasttime 
+requestList[i].message.getTTL() >= now) {
+                                       pending++;
+                                       }
+                               }
+
+                       minSleep = Scheduler.SECS_5; /* max-sleep! */
+                       perm = Crypto.permute(requestListIndex);
+                       for (i=0;i<requestListIndex;i++) {
+                               int j = perm[i];
+                               if ( (requestList[j].lasttime +
+                                               
requestList[j].message.getTTL()) <= now - TTL_DECREMENT) {
+                                       int pOCWCubed;
+                                       int pendingOverCWin = pending - 
congestionWindow;
+                                       if (pendingOverCWin <= 0)
+                                               pendingOverCWin = -1; /* avoid 
0! */
+                                       pOCWCubed = pendingOverCWin *
+                                       pendingOverCWin *
+                                       pendingOverCWin;
+
+                                       if ( (pOCWCubed <= 0) ||
+                                                       (pOCWCubed * 
requestListIndex <= 0) /* see #642 */ ||
+                                                       /* avoid no-start: 
override congestionWindow occasionally... */
+                                                       (0 == 
Crypto.nextInt(requestListIndex *pOCWCubed)) ) {
+                                               delta = 
requestList[j].message.getTTL() + Scheduler.MILLIS_10;
+                                               issueRequest(j);
+                                               pending++;
+                                               }
+                                       else {
+                                               if (isDebug()) {
+                                                       lpri++;
+                                                       /* do not print ALL the 
time, just once per iteration */
+                                                       if ( (lpri % 
(requestListIndex+1)) == 0)
+                                                               
log(Level.FINEST,"Congestion control: "+pending+" pending, "+congestionWindow+" 
window; "+initialTTL+" initial TTL.");
+                                                       }
+                                               delta = 0;
+                                               }
+                                       }
+                               else {
+                                       delta = (requestList[j].lasttime + 
TTL_DECREMENT +requestList[j].message.getTTL()) - now;
+
+                                       debug("Request "+i+":"+
+                                               
Utils.toHex(requestList[j].message.getQuery(0).getInt(0))+" "+
+                                               "(TTL: 
"+requestList[j].message.getTTL()+") "+
+                                               "is still pending for 
"+Scheduler.toSeconds(delta)+"s.");
+                                       }
+
+                               if ( delta < minSleep ) {
+                                       minSleep = delta;
+                                       }
+                               }
+
+                       if (minSleep < Scheduler.MILLIS_100) {
+                               minSleep = Scheduler.MILLIS_100; /* maximum 
resolution: 100ms */
+                               }
+
+                       if (requestListIndex > 0) {
+                               debug("Scheduling next run for in 
"+Scheduler.toMillis(minSleep)+"ms.");
+
+                               scheduler.addJob(rrjob,minSleep);
+                               }
+                       else {
+                               debug("No more jobs pending, cron not renewed 
!");
+                               }
+                       }
+       }
+
+       /**
+        * This method receives data corresponding to the indicated filename
+        * (hashcode). Finds the Listener that scheduled this request and drop
+        * it from the list of pending requests.<p>
+        *
+        * @param msg the message received from gnunetd
+        */
+
+       protected void receive( CSResultChk msg )
+       {
+               int                             pos,i,j;
+               HashCode512             query;
+               RequestEntry    entry;
+
+               /* check type of reply msg, fill in query */
+               query=HashCode512.create(PersistentHelper.toBytes(msg.result));
+               pos = -1;
+               /* find which query matches the reply, call the callback
+                and recycle the slot */
+               for (i=0;i<requestListIndex;i++) {
+                       CSQuery acq;
+
+                       acq = requestList[i].message;
+                       j = (acq.getByteSize()-CSQuery.SIZE)/HashCode512.SIZE;
+                       while ( j > 0 ) {
+                               j--;
+                               if (query.equals(acq.getQuery(j)))
+                                       pos = i;
+                                       }
+                               }
+               if (pos == -1) {
+                       int     nowTT;
+
+                       nowTT=(int) Scheduler.toSeconds(Scheduler.now());
+                       duplicationEstimate++;
+                       if ( (nowTT - initialTTL) > lastDET) {
+                               /* only consider congestion control every
+                                "average" TTL seconds, otherwise the system
+                                reacts to events that are far too old! */
+
+                               /* duplicate reply, treat as congestion (RFC 
2001) */
+                               debug("Received duplicate data, changing CW 
("+congestionWindow+
+                                       " to "+((congestionWindow/2)+1)+
+                                       ") and SST ("+ssthresh+
+                                       "."+(congestionWindow/2)+")");
+
+                               ssthresh = congestionWindow / 2;
+                               if (ssthresh < 2)
+                                       ssthresh = 2;
+                               congestionWindow
+                               = ssthresh + 1;
+                               lastDET = nowTT;
+                       }
+
+                       debug("Received useless data matching query 
"+query.toHex()+" ("+duplicationEstimate+", 
"+Scheduler.toSeconds(initialTTL)+"s) !");
+                       return;
+                       }
+
+               debug("Received reply for request "+pos+" 
("+requestList[pos].message.getQuery(0).toHex()+")");
+
+               entry = requestList[pos];
+
+               if ( (entry.lasttime < Scheduler.now()) &&
+                               (entry.lasttime != 0) ) {
+                       int weight = 15;
+                       int ettl = entry.message.getTTL();
+                       if (ettl > TTL_DECREMENT)
+                               ettl -= TTL_DECREMENT;
+                       else
+                               ettl = 0;
+                       if ( (ettl > 4 * initialTTL) &&
+                                       ( (Scheduler.now() - entry.lasttime) < 
initialTTL) ) {
+                               weight = 127; /* eTTL is MUCH bigger than what 
we currently expect AND the time
+                               between the last query and the reply was in the 
range of the
+                               expected TTL => don't take ettl too much into 
account! */
+                       }
+                       initialTTL = ((initialTTL) * weight + ettl) / 
(weight+1);
+
+                       /* RFC 2001: increase cwnd; note that we can't really 
discriminate between
+                        slow-start and cong. control mode since our RSS is too 
small... */
+                       if (congestionWindow < ssthresh)
+                               congestionWindow += 2; /* slow start */
+                       else
+                               congestionWindow += 1; /* slower start :-) */
+               }
+               /* and finally use the entry to notify the node
+                that we got a reply! */
+
+               debug("DEBUG: request manager receives data for 
"+entry.receiverNode);
+
+               if (!entry.receiver.listen(query,
+                               msg,
+                               this,
+                               entry.data)) {
+
+                       /* ABORT download, receiver method has
+                        already notified the controller via
+                        the pmodel callback, we need to stop
+                        requesting... */
+
+                       debug("DEBUG: entry.receiver aborted download !");
+
+                       for (i=0;i<requestListIndex;i++) {
+                               freeInContinuations(requestList[i]);
+                               requestList[i]=null;
+                               }
+                       requestListIndex = 0;
+                       }
+       }
+
+       /**
+        * Listen on socket and receive messages. Call requestManagerReceive
+        * on every reply. Never returns, if the RM dies, it will cancel us.
+        */
+
+       protected void receiveThread()
+       {
+               CSMessage                       buffer;
+               CSSession       sockx;
+               PersistentDecoder       decoder;
+
+               while (sock != null) {
+                       synchronized(lock) {
+                               sockx = sock;
+                               }
+
+                       if (sockx == null)
+                               break;
+
+                       //todo: a ne faire qu'une fois
+                       decoder=new PersistentDecoder();
+                       decoder.add(CSMessage.IS_RESULT,CSResult.class);
+                       decoder.add(CSMessage.IS_RESULT_CHK,CSResultChk.class);
+
+                       buffer = (CSMessage) sockx.receive(decoder);
+                       if (buffer==null) {
+                               if (sock == null)
+                                       break;
+                               log(Level.WARNING,"Receive thread could not 
read data from gnunetd, is the server running ?");
+                               Scheduler.sleep(Scheduler.SECS_15);
+                               continue;
+                               }
+
+                       if (buffer instanceof CSResult) {
+                               int value;
+
+                               value = ((CSResult) buffer).getResult();
+
+                               synchronized(lock) {
+                                       if (start == null) {
+                                               log(Level.SEVERE,"Received 
return value from gnunetd but I have no continuation! (bug!)");
+                                               }
+                                       else {
+                                               
runContinuation(value!=0);//todo: value!=0 est le bon test ???
+                                               }
+                                       }
+                               }
+                       else if (buffer instanceof CSResultChk) {
+                               synchronized(lock) {
+                                       receive((CSResultChk) buffer);
+                                       }
+                               }
+                       }
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/RootNode.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/RootNode.java 2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/RootNode.java 2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,120 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+
+/**
+ * full CONTENT_SIZE'ed root node
+ * header of the RootNode (search result with meta-data)
+ *
+ * The structure of the root node - contains pertinent information for
+ * the file (file length, checksum, hashcode of main indirection node,
+ * description length, and description.
+ *
+ * The structure of the root node, including padding to make it to 1k.
+ */
+
+public class RootNode extends Node
+{
+       public static final int SIZE                            =       1024;
+       public static final int NOT_PADDED_SIZE =       564;
+
+
+       public RootNode()
+       {
+               super(ROOT_MAJOR_VERSION,ROOT_MINOR_VERSION);
+       }
+
+       public RootNode( FileIdentifier fid )
+       {
+               this();
+               fileIdentifier=(FileIdentifier) PersistentHelper.copy(fid);     
        //todo: copie utile ?
+       }
+
+       public String toString()
+       {
+               return "Root node [description="+description+", 
filename="+fileName+", mimetype="+mimeType+"]";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       /**
+        * Convert this node to a human readable string.
+        * @return
+        */
+
+       public String toLabel()
+       {
+               StringBuffer    buf;
+
+               buf=new StringBuffer();
+               if (mimeType.equals(GNUNET_DIRECTORY_MIME)) {
+                       buf.append(GNDirectory.expandDirectoryName(fileName));
+                       }
+               else {
+                       buf.append(fileName);
+                       }
+               buf.append(": ");
+               buf.append(description);
+               buf.append(" of type '");
+               buf.append(mimeType);
+               buf.append("' (size ");
+               buf.append(fileIdentifier.getFileLength());
+               buf.append(")\n");
+               buf.append(fileIdentifier.toURI());
+               return buf.toString();
+       }
+
+       public int getByteSize()
+       {
+               return SIZE;
+       }
+
+       public void writeBytes( ByteBuffer buf )
+       {
+               int     i;
+
+               buf.putShort((short) ROOT_MAJOR_VERSION);
+               buf.putShort((short) ROOT_MINOR_VERSION);
+
+               fileIdentifier.writeBytes(buf);
+
+               AFSUtils.put(description,buf,256);
+               AFSUtils.put(fileName,buf,128);
+               AFSUtils.put(mimeType,buf,128);
+
+               for (i=NOT_PADDED_SIZE; i<SIZE; i++) {
+                       buf.put((byte) 0);
+                       }
+       }
+
+       public void readBytes( ByteBuffer buf, ErrorReporter err )
+       {
+               int             major,minor,i;
+               byte            b;
+
+               major=buf.getShort() & 0x0000ffff;
+               minor=buf.getShort() & 0x0000ffff;
+
+               err.reportIf(major!=ROOT_MAJOR_VERSION,"bad major version");
+               err.reportIf(minor!=ROOT_MINOR_VERSION,"bad minor version");
+
+               fileIdentifier=new FileIdentifier();
+               fileIdentifier.readBytes(buf,err);
+
+               description=AFSUtils.get(buf,256);
+               fileName=AFSUtils.get(buf,128);
+               mimeType=AFSUtils.get(buf,128);
+
+               for (i=NOT_PADDED_SIZE; i<SIZE; i++) {
+                       b=buf.get();
+                       err.reportIf(b!=0,"bad zero-padding");
+                       }
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/RootNodeCallback.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/RootNodeCallback.java 
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/RootNodeCallback.java 
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,19 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+/**
+ */
+
+public interface RootNodeCallback
+{
+       /**
+        * Callback function.
+        * @param root a root-node
+        * @param closure a closure
+        */
+
+       public void rootNode( RootNode root, Object closure );
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/SBlock.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/SBlock.java   2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/SBlock.java   2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,438 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.net.*;
+
+import java.io.*;
+import java.nio.*;
+import java.util.logging.*;
+
+/**
+ * data structure SBlock
+ * data structure SBlock
+ * total: 1024 bytes
+ */
+
+public class SBlock extends Node
+{
+       public static final int ENCRYPTED_SIZE  =       484;
+       public static final int SIGNED_SIZE             =       504;
+       public static final int SIZE                            =       1024;
+
+       /** */
+       private int                     creationTime;
+
+       /** */
+       private int                     updateInterval;
+
+       /** N */
+       private HashCode512     nextIdentifier;
+
+       /** I */
+       private HashCode512     identifierIncrement;
+
+       /** R = H(N-I)^S where S= H(subspace) */
+       private HashCode512     identifier;
+
+       /** */
+       private Signature       signature;
+
+       /** */
+       private PublicKey       subspace;
+
+
+       public SBlock()
+       {
+               super(SBLOCK_MAJOR_VERSION,SBLOCK_MINOR_VERSION);
+               creationTime=0;
+               updateInterval=SBLOCK_UPDATE_NONE;
+               nextIdentifier=null;
+               identifierIncrement=null;
+               identifier=null;
+               signature=null;
+               subspace=null;
+       }
+
+       public SBlock( FileIdentifier fid )
+       {
+               this();
+               fileIdentifier=(FileIdentifier) PersistentHelper.copy(fid);     
        //todo: copie utile ?
+       }
+
+       public String toString()
+       {
+               return "S(igned)Block [description="+description+", 
filename="+fileName+", mimetype="+mimeType+"]";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       /**
+        * Convert a root-node to a string (to display it to the user).
+        * @return
+        */
+
+       public String toLabel()
+       {
+               HashCode512     ns;
+               String          str;
+
+               //todo: add creation time & update frequency
+
+               if (mimeType.equals(GNUNET_DIRECTORY_MIME)) {
+                       str=GNDirectory.expandDirectoryName(fileName);
+                       }
+               else {
+                       str=fileName;
+                       }
+               
ns=HashCode512.create(PersistentHelper.toBytes(subspace),0,PublicKey.SIZE);
+               return str+": "+description+" of type '"+mimeType+"' (size 
"+fileIdentifier.getFileLength()+", namespace 
"+ns.toHex()+")\n"+fileIdentifier.toURI();
+       }
+
+       public int getCreationTime()
+       {
+               return creationTime;
+       }
+
+       public void setCreationTime( int ctime )
+       {
+               creationTime=ctime;
+       }
+
+       public int getUpdateInterval()
+       {
+               return updateInterval;
+       }
+
+       /**
+        * @param interval the update frequency (0: never, -1: sporadic)
+        */
+
+       public void setUpdateInterval( int interval )
+       {
+               updateInterval=interval;
+       }
+
+       /**
+        * Returns K=N-I
+        * @return
+        */
+
+       public HashCode512 getKey()
+       {
+               HashCode512     h;
+
+               h=new HashCode512(nextIdentifier);
+               h.sub(identifierIncrement);
+               return h;
+       }
+
+       public HashCode512 getIdentifierIncrement()
+       {
+               return identifierIncrement;
+       }
+
+       public HashCode512 getIdentifier()
+       {
+               return identifier;
+       }
+
+       /**
+        * Compute the "current" ID of an updateable SBlock. Will set the ID of 
the sblock itself for non-updatable content,
+        * the ID of the next identifier for sporadically updated SBlocks and 
the ID computed from the timing function
+        * for periodically updated SBlocks.
+        *
+        * @param now the time for which the ID should be computed
+        * @return the resulting current ID (set)
+        */
+
+       public HashCode512 computeIdAtTime( int now )
+       {
+               int                     pos;
+               HashCode512     tmp,c;
+
+               if (updateInterval == SBLOCK_UPDATE_SPORADIC) {
+                       return (HashCode512) 
PersistentHelper.copy(nextIdentifier);
+                       }
+
+               if (updateInterval == SBLOCK_UPDATE_NONE) {
+                       /* H(N-I)^S is the current routing key, so N-I = k */
+                       return getKey();
+                       }
+
+               pos = creationTime;
+
+               c=getKey();
+
+               while (pos + updateInterval < now) {
+                       pos += updateInterval;
+
+                       tmp=new HashCode512(c);
+                       tmp.add(identifierIncrement);
+
+                       c=(HashCode512) PersistentHelper.copy(tmp);
+
+                       debug("Update at 
"+Utils.formatMoment(Scheduler.seconds(pos))+" should have key "+c.toHex());
+                       }
+               return c;
+       }
+
+       public PublicKey getPublisherKey()
+       {
+               return subspace;
+       }
+
+       public HashCode512 getPublisherNameSpace()
+       {
+               return HashCode512.create(PersistentHelper.toBytes(subspace));
+       }
+
+       /**
+        * Build an (encrypted) SBlock.
+        *
+        * @param priv
+        * @param key           the key for this SBlock
+        * @param nextKey       the key for the next SBlock (if updateable)
+        * @return
+        */
+
+       public EncryptedSBlock encryptAndSign( PrivateKey priv, HashCode512 
key, HashCode512 nextKey )
+       {
+               byte[]          tmp;
+               HashCode512     hk;
+               byte[]          iv,raw;
+               SessionKey      skey;
+
+               // next identifier N
+               nextIdentifier=(HashCode512) PersistentHelper.copy(nextKey);
+
+               // identifier increment I=N-K
+               identifierIncrement=new HashCode512(nextKey);
+               identifierIncrement.sub(key);
+
+               // routing identifier R=H(K)^S   =H(N-I)^S
+               hk=HashCode512.create(PersistentHelper.toBytes(key));
+               
identifier=HashCode512.create(PersistentHelper.toBytes(priv.toPublicKey()));
+               identifier.xor(hk);
+
+               raw=new byte[SIZE];
+               writeBytes(ByteBuffer.wrap(raw));
+
+               iv=new byte[SessionKey.BLOWFISH_BLOCK_LENGTH];
+               skey=key.extractKey(iv);
+
+               tmp=skey.encrypt(raw,0,ENCRYPTED_SIZE,iv);
+               if (tmp==null || tmp.length!=ENCRYPTED_SIZE) {
+                       log(Level.SEVERE,"Failed to encrypt SBlock !");
+                       return null;
+                       }
+               System.arraycopy(tmp,0,raw,0,tmp.length);
+
+               signature=priv.sign(raw,0,SIGNED_SIZE);
+               if (signature==null) {
+                       log(Level.SEVERE,"Failed to sign SBlock !");
+                       return null;
+                       }
+
+               subspace=priv.toPublicKey();
+               return new EncryptedSBlock(tmp,identifier,signature,subspace);
+       }
+
+       /**
+        * Verify that a given SBlock is well-formed.
+        * @return
+        */
+
+       public boolean verify()
+       {
+               byte[]          iv,raw,tmp;
+               SessionKey      skey;
+
+               raw=new byte[SIZE];
+               writeBytes(ByteBuffer.wrap(raw));
+
+               iv=new byte[SessionKey.BLOWFISH_BLOCK_LENGTH];
+               skey=getKey().extractKey(iv);
+
+               tmp=skey.encrypt(raw,0,ENCRYPTED_SIZE,iv);
+               if (tmp==null || tmp.length!=ENCRYPTED_SIZE) {
+                       log(Level.SEVERE,"Failed to decrypt SBlock !");
+                       return false;
+                       }
+               System.arraycopy(tmp,0,raw,0,tmp.length);
+
+               return subspace.verify(signature,raw,0,SIGNED_SIZE);
+       }
+
+       /**
+        * Print the information contained in an SBlock.
+        *
+        * @param stream where to print the information to
+        */
+
+       public void print( PrintWriter stream )
+       {
+               HashCode512     hc;
+               int                     now,pos;
+               String          fstring;
+               String          fn;
+
+               /* if it is a GNUnet directory, replace suffix '/' with ".gnd" 
*/
+               if (mimeType.equals(GNUNET_DIRECTORY_MIME)) {
+                       fn = GNDirectory.expandDirectoryName(fileName);
+                       }
+               else {
+                       fn = fileName;
+                       }
+
+               hc=HashCode512.create(PersistentHelper.toBytes(subspace));
+               stream.println(description+" ("+mimeType+") published by 
"+hc.toHex());
+
+               fstring = fileIdentifier.toURI();
+               stream.println("gnunet-download -o \""+fn+"\" "+fstring);
+
+               switch (updateInterval) {
+                       case SBLOCK_UPDATE_SPORADIC:
+                               stream.println("Next update will be 
"+nextIdentifier.toHex()+".");
+                               break;
+
+                       case SBLOCK_UPDATE_NONE:
+                               stream.println("SBlock indicates no updates.");
+                               break;
+
+                       default:
+                               pos = creationTime;
+
+                               hc=new HashCode512(nextIdentifier);
+                               hc.sub(identifierIncrement);
+
+                               now=(int) Scheduler.toSeconds(Scheduler.now());
+                               while (pos + updateInterval < now) {
+                                       HashCode512 tmp;
+
+                                       pos += updateInterval;
+
+                                       tmp=new HashCode512(hc);
+                                       tmp.add(identifierIncrement);
+
+                                       stream.println("Update due at 
"+Utils.formatMoment(Scheduler.seconds(pos))+" has key "+tmp.toHex());
+                                       }
+                               break;
+                       }
+       }
+
+       public int getByteSize()
+       {
+               return SIZE;
+       }
+
+       public void writeBytes( ByteBuffer buf )
+       {
+               int     i;
+
+               buf.putShort((short) SBLOCK_MAJOR_VERSION);
+               buf.putShort((short) SBLOCK_MINOR_VERSION);
+
+               if (fileIdentifier!=null) {
+                       fileIdentifier.writeBytes(buf);
+                       }
+               else {
+                       for (i=0; i<FileIdentifier.SIZE; i++) {
+                               buf.put((byte) 0);
+                               }
+                       }
+
+               AFSUtils.put(description,buf,256);
+               AFSUtils.put(fileName,buf,64);
+               AFSUtils.put(mimeType,buf,64);
+
+               buf.putInt(creationTime);
+               buf.putInt(updateInterval);
+
+               if (nextIdentifier!=null) {
+                       nextIdentifier.writeBytes(buf);
+                       }
+               else {
+                       for (i=0; i<HashCode512.SIZE; i++) {
+                               buf.put((byte) 0);
+                               }
+                       }
+
+               if (identifierIncrement!=null) {
+                       identifierIncrement.writeBytes(buf);
+                       }
+               else {
+                       for (i=0; i<HashCode512.SIZE; i++) {
+                               buf.put((byte) 0);
+                               }
+                       }
+
+               if (identifier!=null) {
+                       identifier.writeBytes(buf);
+                       }
+               else {
+                       for (i=0; i<HashCode512.SIZE; i++) {
+                               buf.put((byte) 0);
+                               }
+                       }
+
+               if (signature!=null) {
+                       signature.writeBytes(buf);
+                       }
+               else {
+                       for (i=0; i<Signature.SIZE; i++) {
+                               buf.put((byte) 0);
+                               }
+                       }
+
+               if (subspace!=null) {
+                       subspace.writeBytes(buf);
+                       }
+               else {
+                       for (i=0; i<PublicKey.SIZE; i++) {
+                               buf.put((byte) 0);
+                               }
+                       }
+       }
+
+       public void readBytes( ByteBuffer buf, ErrorReporter err )
+       {
+               int     major,minor;
+
+               major=buf.getShort() & 0x0000ffff;
+               minor=buf.getShort() & 0x0000ffff;
+
+               err.reportIf(major!=SBLOCK_MAJOR_VERSION,"bad major version");
+               err.reportIf(minor!=SBLOCK_MINOR_VERSION,"bad minor version");
+
+               fileIdentifier=new FileIdentifier();
+               fileIdentifier.readBytes(buf,err);
+
+               description=AFSUtils.get(buf,256);
+               fileName=AFSUtils.get(buf,64);
+               mimeType=AFSUtils.get(buf,64);
+
+               creationTime=buf.getInt();
+               updateInterval=buf.getInt();
+
+               nextIdentifier=new HashCode512();
+               nextIdentifier.readBytes(buf,err);
+
+               identifierIncrement=new HashCode512();
+               identifierIncrement.readBytes(buf,err);
+
+               identifier=new HashCode512();
+               identifier.readBytes(buf,err);
+
+               signature=new Signature();
+               signature.readBytes(buf,err);
+
+               subspace=new PublicKey();
+               subspace.readBytes(buf,err);
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/SearchResultCallback.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/SearchResultCallback.java     
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/SearchResultCallback.java     
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,29 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+/**
+ *
+ */
+
+public interface SearchResultCallback
+{
+       /**
+        * Type of a callback method for results that have
+        * been received.
+        *
+        * @param root the RootNode of the result that has been received
+        */
+
+       public void searchResult( RootNode root );
+
+       /**
+        * @param root
+        * @param str
+        *
+        */
+
+       public void newNameFor( RootNode root, String str );
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/SearchURI.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/SearchURI.java        
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/SearchURI.java        
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,34 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.crypto.*;
+
+/**
+ */
+
+public class SearchURI extends GeneralURI
+{
+//     public int                      action;
+       public HashCode512      namespace;
+       public HashCode512      keyhash;
+       public String[]         keywords;
+       public int                      keycount;
+
+
+       public SearchURI()
+       {
+               super();
+       }
+
+       public String toString()
+       {
+               return "xxx";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/SendNSQueryContext.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/SendNSQueryContext.java       
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/SendNSQueryContext.java       
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,249 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.net.*;
+
+import java.util.logging.*;
+
+/**
+ */
+
+public class SendNSQueryContext extends LoggedObject implements AFSConstants
+{
+       /** Time when the cron-job was first started. */
+       public long                                     start;
+
+       /** How many cron-units may we run (total) ? */
+       public long                                     timeout;
+
+       /** */
+       public CSSession        sock;
+
+       /** */
+       public CSNSQuery                query;
+
+
+       public SendNSQueryContext()
+       {
+               super(true);
+       }
+
+       public String toString()
+       {
+               return "xxx";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       /**
+        * Retrieve an SBlock.
+        *
+        * @param policy
+        * @param scheduler
+        * @param prefs
+        * @param sockx socket to use to contact gnunetd
+        * @param s namespace which namespace to search
+        * @param k key to decrypt the SBlock in the namespace (query
+        *        to identify the block is derived from k)
+        * @param testTerminate function to poll for abort
+        * @param resultCallback function to call for results
+        * @param closure argument to pass to resultCallback
+        * @return OK on success, false on error (= no result found)
+        */
+
+       public boolean searchSBlock( Policy policy, Scheduler scheduler, Prefs 
prefs, CSSession sockx, HashCode512 s, HashCode512 k, TestTerminateThread 
testTerminate, NSSearchResultCallback resultCallback, Object closure )
+       {
+               CSResultSBlock          reply;
+               boolean                         ret;
+               HashCode512                     hc,hk;
+               HashCode512                     r; /* r = H(k) ^ s */
+               SendNSQueryContext      sqc;
+               SBlock                          result;
+               ScheduledTask           sendTask;
+
+               hk=HashCode512.create(PersistentHelper.toBytes(k));
+               r=new HashCode512(s);
+               r.xor(hk);  /* compute routing key R */
+
+               sqc=new SendNSQueryContext();
+               sqc.sock = sockx;
+               sqc.query = new CSNSQuery();
+               sqc.query.priority = 1;
+               sqc.query.ttl = (int) (1+Crypto.nextLong(TTL_DECREMENT));
+               sqc.query.namespace=(HashCode512) PersistentHelper.copy(s);
+               sqc.query.identifier=(HashCode512) PersistentHelper.copy(r);
+
+               // add cron job to send search query
+               final SendNSQueryContext        _sqc = sqc;
+               final Policy                            _policy = policy;
+               final Scheduler                 _scheduler = scheduler;
+
+               sendTask=new ScheduledTask("SEND-NSQUERY2",new AbstractAction() 
{
+                       public void perform()
+                       {
+                               sendNSQuery(_sqc,_policy,_scheduler);
+                       }
+                       });
+               scheduler.addJob(sendTask,0);
+
+               ret = false;
+               while (!testTerminate.test()) {
+                       reply=(CSResultSBlock) 
sock.receive(CSResultSBlock.class);
+                       if (reply==null) {
+                               log(Level.WARNING,"Unable to read *correct* 
message.");
+                               if (testTerminate.test())
+                                       break;
+                               Scheduler.sleep(Scheduler.SECS_1);
+                               continue;
+                               }
+
+                       debug("Received message from gnunetd");
+
+                       if (!reply.getResult().verify()) {
+                               log(Level.WARNING,"WARNING: SBlock received 
from gnunetd failed verification.");
+                               break;
+                               }
+                       new 
Pseudonym(prefs).addNamespace(reply.getResult().getNameSpace());
+
+                       // internal identifier (for routing HT, etc.) is "xor" 
of the user-identifier with the namespace ID
+                       // to avoid keyword collisions with realnames in the 
global, 3HASH namespace
+
+                       hc=reply.getResult().getNameSpace();
+                       if (!hc.equals(s)) {
+                               log(Level.WARNING,"WARNING: SBlock received 
from gnunetd belongs to wrong namespace.");
+                               break;
+                               }
+                       if (!r.equals(reply.getResult().getIdentifier()) ) {
+                               log(Level.WARNING,"WARNING: SBlock received 
from gnunetd has wrong identifier.");
+                               break;
+                               }
+
+                       result=reply.getResult().decrypt(k);
+                       resultCallback.searchResult(result,closure);
+                       ret = true;
+                       }
+
+               // delete cron job
+               scheduler.deleteJob(sendTask);
+               return ret;
+       }
+
+       public void sendNSQuery( SendNSQueryContext sqc, Policy policy, 
Scheduler scheduler )
+       {
+               long    now,remTime,new_ttl,new_priority;
+
+               debug("enter sendnsquery");
+
+               now=Scheduler.now();
+
+               if (sqc.timeout != 0) {
+                       remTime = (int) (sqc.start - now + sqc.timeout);
+                       if (remTime <= 0) {
+                               log(Level.FINEST,"Exiting sendnsquery without 
making a query.");
+                               return;
+                               }
+                       }
+               else {
+                       remTime = 0x7FFFFFFF; /* max signed int */
+                       }
+
+               if 
(policy.checkAnonymityPolicy(CSMessage.IS_NSQUERY,P2PNSQuery.SIZEX)) {
+                       if (sqc.sock.send(sqc.query)) {
+                               /* successful transmission to GNUnet,
+                                increase ttl/priority for the next time */
+                               new_ttl = sqc.query.ttl;
+                               if (new_ttl > 0x00FFFFFF) {
+                                       new_ttl = Crypto.nextLong(0x00FFFFFF); 
/* if we get too large, reduce! */
+                                       }
+                               sqc.query.ttl= (int) 
Crypto.nextLong(1+4*new_ttl);
+                               new_priority = sqc.query.priority;
+                               if (new_priority > 0xFFFFFF) {
+                                       new_priority = 
Crypto.nextLong(0xFFFFFF); /* if we get too large, reduce! */
+                                       }
+                               sqc.query.priority= (int) 
Crypto.nextLong(1+4*new_priority);
+                               }
+                       else {
+                               new_ttl = Scheduler.SECS_5; /* wait at least 5s 
for gnunetd */
+                               }
+                       }
+               else {
+                       new_ttl = TTL_DECREMENT;
+                       }
+
+               /* Don't repeat a search faster than TTL_DEC seconds */
+               if (new_ttl < TTL_DECREMENT)
+                       new_ttl = TTL_DECREMENT;
+
+               debug("Will wait for min("+new_ttl+", "+remTime+") ms");
+
+               /* Do not sleep longer than the amount of time we have until
+                we shut down */
+               if (new_ttl >= remTime)
+                       new_ttl = remTime;
+
+               if (remTime > 0) {
+                       debug("Reinstating sendnsquery in "+new_ttl);
+
+                       final SendNSQueryContext        _sqc = sqc;
+                       final Policy                            _policy = 
policy;
+                       final Scheduler                 _scheduler = scheduler;
+
+                       scheduler.addJob(new ScheduledTask("SEND-NSQUERY",new 
AbstractAction() {
+                               public void perform()
+                               {
+                                       sendNSQuery(_sqc,_policy,_scheduler);
+                               }
+                               }),new_ttl);
+                       }
+       }
+
+       /**
+        * Perform a namespace search.
+        *
+        * @param prefs
+        * @param scheduler
+        * @param policy
+        * @param sock
+        * @param keyStrings
+        * @param keywordCount
+        * @param handler
+        * @param handlerArgs
+        * @param testTerminate
+        * @param ttContext
+        * @return
+        */
+
+       public static boolean searchRBlock( Prefs prefs, final Scheduler 
scheduler, final Policy policy, CSSession sock, String[] keyStrings, int 
keywordCount, SearchResultCallback handler, Object handlerArgs, 
TestTerminateThread testTerminate, Object ttContext )
+       {
+               SendQueriesContext      sqc;
+               HashCode512[]           keywords;
+               ScheduledTask           sendJob;
+
+               keywords=AFSUtils.parseKeywords(keyStrings);
+
+               sqc=new SendQueriesContext(keywords,sock);
+               
sqc.setTimeOut(Scheduler.seconds(prefs.getInt("AFS","SEARCHTIMEOUT",0)));
+
+               final SendQueriesContext        _sqc = sqc;
+
+               sendJob=new ScheduledTask("SEND-RBLOCK",new AbstractAction() {
+                       public void perform()
+                       {
+                               _sqc.repeatedlySend(scheduler,policy);
+                       }
+                       });
+               scheduler.addJob(sendJob,0);
+
+//             receiveResults(sock,keywordCount,keywords,      
messages,handler,handlerArgs,testTerminate,ttContext);
+               sqc.receive(prefs,handler,testTerminate);
+
+               scheduler.deleteJob(sendJob);
+               return true;
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/SendQueriesContext.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/SendQueriesContext.java       
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/SendQueriesContext.java       
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,356 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.net.*;
+
+import java.nio.*;
+import java.util.*;
+import java.util.logging.*;
+
+/**
+ * Context of the send queries cron job.
+ */
+
+public class SendQueriesContext extends LoggedObject implements AFSConstants
+{
+       private static Comparator       fComparator;
+
+       static {
+               fComparator=createFilenameComparator();
+               }
+
+       /** Time when the cron job was first started. */
+       private long                    start;
+
+       /** How many cron units may we run (total) ? */
+       private long                    timeout;
+
+       /** Socket for communication with gnunetd. */
+       private CSSession       sock;
+
+       /** */
+       private HashCode512[]   keywords;
+
+       /** Query messages. */
+       private CSQuery[]               messages;
+
+       private PersistentDecoder               decoder;
+
+       /**
+        * Construct a new query context.
+        *
+        * @param kwords        the keywords (for decryption)
+        * @param s                     socket used to receive results from
+        */
+
+       public SendQueriesContext( HashCode512[] kwords, CSSession s )
+       {
+               super(true);
+               start=Scheduler.now();
+               timeout=0;
+
+               sock=s;
+
+               decoder=new PersistentDecoder();
+               decoder.add(CSMessage.IS_RESULT,CSResult.class);
+               decoder.add(CSMessage.IS_RESULT_3HASH,CSResult3Hash.class);
+
+               keywords=kwords;
+               messages=AFSUtils.buildMessages(keywords);
+       }
+
+       public String toString()
+       {
+               return "Send queries context";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public CSSession getSocket()
+       {
+               return sock;
+       }
+
+       public CSQuery[] getQueries()
+       {
+               return messages;
+       }
+
+       public void setTimeOut( long t )
+       {
+               timeout=t;
+       }
+
+       /**
+        * Repeatedly send out the queries to GNUnet.
+        * This cron-job self-terminates after sqc.timout cron-units have 
passed.
+        * @param scheduler
+        * @param policy
+        */
+
+       public void repeatedlySend( final Scheduler scheduler, final Policy 
policy )
+       {
+               long            ttl;
+
+               ttl=send(policy);
+               if (ttl>0) {
+                       scheduler.addJob(new ScheduledTask("REPEAT-SEND",new 
AbstractAction() {
+                               public void perform()
+                               {
+                                       repeatedlySend(scheduler,policy);
+                               }
+                               }),ttl);
+                       }
+       }
+
+       public int send( Policy policy )
+       {
+               long    now;
+               int             remTime,ttl,newTTL,newPriority,i;
+
+               now=Scheduler.now();
+
+               if (timeout != 0) {
+                       remTime = (int) (start - now + timeout);
+                       if (remTime <= 0)
+                               return 0;
+                       }
+               else {
+                       remTime = 0x7FFFFFFF;   // max signed int
+                       }
+
+               ttl=0;
+               for (i=0; i<messages.length; i++) {
+                       log(Level.FINEST,"Sending query with ttl 
"+messages[i].getTTL()+".");
+
+                       ttl=Crypto.nextInt((int) TTL_DECREMENT)+1;
+
+                       if 
(policy.checkAnonymityPolicy(CSMessage.IS_QUERY,messages[i].getByteSize()+HostIdentity.SIZE))
 {
+                               if (sock.send(messages[i])) {
+                                       // successful transmission, increase 
ttl/priority for the next time
+                                       newTTL=messages[i].getTTL();
+                                       ttl=Math.max(newTTL,ttl);
+
+                                       if (newTTL>0xFFFFFF) {
+                                               newTTL=Crypto.nextInt(0xFFFFFF);
+                                               }
+
+                                       newPriority=messages[i].getPriority();
+                                       if (newPriority>0xFFFFFF) {
+                                               
newPriority=Crypto.nextInt(0xFFFFFF);
+                                               }
+
+                                       messages[i].setPriorityAndTTL( 
Crypto.nextInt(1+4*newPriority),Crypto.nextInt(1+4*newTTL));
+                                       }
+                               }
+                       }
+
+               // sleep approximately the time the longest ttl will take
+               ttl=ttl+Crypto.nextInt(ttl+1);
+
+               // don't repeat a search faster than TTL_DECREMENT seconds
+               if (ttl<TTL_DECREMENT) {
+                       ttl=(int) TTL_DECREMENT;
+                       }
+
+               log(Level.FINEST,"Will wait for min("+ttl+","+remTime+") ms.");
+
+               // do not sleep longer than the amount of time we have until we 
shut down
+               if (ttl>=remTime) {
+                       ttl=remTime;
+                       }
+               return ttl;
+       }
+
+       /**
+        * Start retrieving results from GNUnet. This method terminates only
+        * if the testTerminate-method returns YES after a result was received
+        * or there is an error with reading from the socket.
+        *
+        * @param prefs
+        * @param handler the method to call on each result matching all 
keywords
+        * @param testTerminate method used to check if we should termiante
+        */
+
+       public void receive( Prefs prefs, SearchResultCallback handler, 
TestTerminateThread testTerminate )
+       {
+               ResultContext   rc;
+               Persistent              buffer;
+               CSResult3Hash   reply;
+               byte[]                  rootNodeBytes;
+               int                             i;
+               HashCode512             tripleHash;
+
+               rc=new ResultContext(keywords.length);
+
+               while (!testTerminate.test()) {
+                       buffer=sock.receive(decoder);
+                       if (buffer==null) {
+                               if (testTerminate.test())
+                                       break;
+                               Scheduler.sleep(Scheduler.SECS_1);
+                               continue;
+                               }
+
+                       if (buffer instanceof CSResult) {
+                               log(Level.FINEST,"Daemon has acknowledeged.");
+                               // ignore: confirmation of gnunetd that it 
received a search request from the other thread
+                               }
+                       else {
+                               reply=(CSResult3Hash) buffer;
+
+                               // now decrypt the reply & call a method to use 
it
+                               tripleHash=reply.getTripleHash();
+                               for (i=0; i<keywords.length; i++) {
+                                       if 
(tripleHash.equals(messages[i].getQuery(0))) {
+                                               
rootNodeBytes=reply.decrypt(keywords[i]);
+                                               if (rootNodeBytes==null) {
+                                                       
log(Level.SEVERE,"Failed to decrypt content !?");
+                                                       continue;
+                                                       }
+                                               if 
(Node.extractMajorVersion(rootNodeBytes)!=ROOT_MAJOR_VERSION || 
Node.extractMinorVersion(rootNodeBytes)!=ROOT_MINOR_VERSION) {
+                                                       log(Level.INFO,"Content 
has unsupported version: 
"+Node.extractMajorVersion(rootNodeBytes)+"."+Node.extractMinorVersion(rootNodeBytes));
+                                                       continue; /* bah! 
broken! */
+                                                       }
+
+                                               filterResult(prefs,
+                                                       (RootNode) 
PersistentHelper.readFully(RootNode.class,ByteBuffer.wrap(rootNodeBytes)),
+                                                       i,rc,handler);
+                                               }
+                                       else {
+                                               log(Level.FINEST,"Reply 
"+reply.getDoubleHash().toHex()+" does not match expected hash 
"+messages[i].getQuery(0).toHex());
+                                               }
+                                       }
+                               }
+                       }
+       }
+
+       /**
+        * Filter results that do not match ALL keywords.
+        * Display the result, but make sure that
+        * every file is only displayed once.
+        *
+        * @param prefs
+        * @param rootNode      the new reply
+        * @param keyIndex      for which key this result matches
+        * @param rc            the context to keep track of which replies we 
got so far
+        * @param handler
+        */
+
+       protected void filterResult( Prefs prefs, RootNode rootNode, int 
keyIndex, ResultContext rc, SearchResultCallback handler )
+       {
+               Map             tmp;
+               List    tmp2;
+               String  name;
+               int             i,j;
+
+               tmp=rc.key2hash[keyIndex];
+               if 
(tmp.containsKey(rootNode.getFileIdentifier().getFileQuery())) {
+                       name=(String) 
tmp.get(rootNode.getFileIdentifier().getFileQuery());
+                       if (fComparator.compare(rootNode.getFileName(),name)>0) 
{
+                               log(Level.FINEST,"Daemon has returned *a 
better* filename for already seen result.");
+                               
tmp.put(PersistentHelper.copy(rootNode.getFileIdentifier().getFileQuery()),rootNode.getFileName());
+
+                               
handler.newNameFor(rootNode,rootNode.getFileName());
+                               return;
+                               }
+
+                       log(Level.FINEST,"Daemon has returned *already seen* 
results to filter.");
+                       return;
+                       }
+
+               log(Level.FINEST,"Daemon has returned new results to filter.");
+               // add to the matching files for this key
+               
tmp.put(PersistentHelper.copy(rootNode.getFileIdentifier().getFileQuery()),rootNode.getFileName());
+
+               // check if the file now matches all keys
+               for (i=0; i<keywords.length; i++) {
+                       tmp2=new ArrayList(rc.key2hash[i].keySet());
+
+                       for (j=0; j<tmp2.size(); j++) {
+                               if 
(tmp2.get(j).equals(rootNode.getFileIdentifier().getFileQuery())) {
+                                       break;
+                                       }
+                               }
+
+                       if (j == tmp2.size()) {
+                               log(Level.FINEST,"Not enough results for the 
AND query.");
+                               return; // not found, exit !
+                               }
+                       }
+
+               // ok, rootNode matches all the AND criteria, display
+
+               // this check should be redundant...
+               if 
(rc.results.contains(rootNode.getFileIdentifier().getFileQuery())) {
+                       log(Level.FINEST,"We have seen this result before : 
"+rootNode.getFileIdentifier().getFileQuery().toHex());
+                       return;
+                       }
+
+               // directory support...
+               new 
GNDirectoryDatabase(prefs).makeRootNodeAvailable(rootNode,DIR_CONTEXT_SEARCH);
+
+               
rc.results.add(PersistentHelper.copy(rootNode.getFileIdentifier().getFileQuery()));
+               handler.searchResult(rootNode);
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       protected static Comparator createFilenameComparator()
+       {
+               return new Comparator() {
+                       public int compare( Object o1, Object o2 )
+                       {
+                               if (o1==null || o2==null) {
+                                       return (o1!=null ? 1 : (o2!=null ? -1 : 
0));
+                                       }
+                               return 
evalCorrectness(o1.toString())-evalCorrectness(o2.toString());
+                       }
+                       };
+       }
+
+       protected static int evalCorrectness( String str )
+       {
+               int             count,i;
+               char    c;
+
+               for (i=count=0; i<str.length(); i++) {
+                       c=str.charAt(i);
+                       if (c<' ' || c>127) {
+                               count+=2;
+                               }
+                       else if (c==' ') {
+                               count++;
+                               }
+               }
+               return -count;
+       }
+}
+
+class ResultContext extends Object
+{
+       /** the results we've got so far (hash of root-node) */
+       public List             results;
+
+       /** unmatched ("AND") results so far, list of root-node hashes that 
were received for each keyword */
+       public Map[]    key2hash;
+
+
+       public ResultContext( int count )
+       {
+               super();
+               results=new ArrayList();
+
+               key2hash=new Map[count];
+               while (count>0) {
+                       key2hash[--count]=new HashMap();
+                       }
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/TestTerminateThread.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/TestTerminateThread.java      
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/TestTerminateThread.java      
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,19 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+/**
+ *
+ */
+
+public interface TestTerminateThread
+{
+       /**
+        * Method to test if the receive-thread should terminate.
+        * @return
+        */
+
+       public boolean test();
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/esed2/URI.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/esed2/URI.java      2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/esed2/URI.java      2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,480 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.esed2;
+
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.crypto.*;
+
+import java.util.*;
+import java.util.logging.*;
+
+/**
+ * URI handling
+ * GNUnet AFS uri format
+ *
+ * gnunet://afs/action/tag1=value1?tag2=value2...
+ *
+ * where action is one of {download,search,insert,delete}
+ * and supported tags are
+ *
+ * tag         description
+ * -------------------------------------------------------
+ * ns          Namespace identifier
+ * kh          Keyhash
+ * qh          Queryhash
+ * crc         File CRC32
+ * fn          File name (suggested)
+ * size        File length
+ * keyword     Search keyword (can be specified multiple times)
+ * pseudonym   Pseudonym for namespace insertion
+ * password    Password for a pseudonym
+ *
+ * Not all tags make sense in all contexts.
+ *
+ * Parses and produces uri strings.
+ *
+ * What spaghetti ...
+ *
+ * How it works: first parse all tag/value pairs into a table. Take
+ * note of the "action" type. Then call a specific parser to create
+ * the actual data structure.
+ *
+ * Bugs: leaks mem if some tags except keyword or numeric tags
+ * are specified more than once.
+ */
+
+public class URI extends LoggedObject
+{
+       public static final String      AFS_URI_PREFIX  =       "gnunet://afs/";
+
+       public static final int URI_ACTION_DOWNLOAD     =       1;
+       public static final int URI_ACTION_SEARCH       =       2;
+       public static final int URI_ACTION_INSERT       =       3;
+       public static final int URI_ACTION_DELETE       =       4;
+
+       public static final int GOT_FILENAME            =       (1<<0);
+       public static final int GOT_NS                  =       (1<<1);
+       public static final int GOT_QH                  =       (1<<2);
+       public static final int GOT_KH                  =       (1<<3);
+       public static final int GOT_KEYWORD             =       (1<<4);
+       public static final int GOT_SIZE                        =       (1<<5);
+       public static final int GOT_CRC                 =       (1<<6);
+       public static final int GOT_PSEUDONYM   =       (1<<7);
+       public static final int GOT_PASSWORD            =       (1<<8);
+
+
+       public URI()
+       {
+               super(true);
+       }
+
+       public String toString()
+       {
+               return "xxx";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       /**
+        * Parses an AFS URI string to an internal representation
+        *
+        * Usage:
+        *   GeneralURI * block;
+        *   parseURI(string, &block);
+        *   if(block.action == URI_ACTION_DOWNLOAD) {
+        *     downloadURI * bl;
+        *     bl = (downloadURI *)block;
+        *   ...
+        *
+        * @param uri an uri string
+        * @param block output, the parsed values
+        * @return false on failure
+        */
+
+       public boolean parseURI( String uri, GeneralURI[] block )
+       {
+               String  scratch;
+               String  uptr;
+               String  sptr;
+               String  nptr;
+               int action;
+               TagTable        tags = new TagTable();
+               int tagcount = 0;
+               boolean ret = false;
+               int     index;
+
+               if (uri==null || !uri.startsWith(AFS_URI_PREFIX)) {
+                       return false;
+                       }
+
+               /* parse action */
+               uptr = uri.substring(AFS_URI_PREFIX.length());
+
+               index=uptr.indexOf('/');
+               scratch=(index>=0 ? uptr.substring(0,index) : "");
+               uptr=uptr.substring(index+1);
+
+               if (uptr.length()==0) {
+                       log(Level.SEVERE,"Premature end of URI");
+                       return false;
+                       }
+
+               if (scratch.equals("download")) {
+                       action = URI_ACTION_DOWNLOAD;
+                       }
+               else if (scratch.equals("search")) {
+                       action = URI_ACTION_SEARCH;
+                       }
+               else if (scratch.equals("insert")) {
+                       action = URI_ACTION_INSERT;
+                       }
+               else if (scratch.equals("delete")) {
+                       action = URI_ACTION_DELETE;
+                       }
+               else {
+                       log(Level.SEVERE,"Unknown action in "+scratch);
+                       return false;
+                       }
+
+               /* parse all tags to a tagTable */
+               while (uptr.length()>0) {
+                       index=uptr.indexOf('=');
+                       nptr = (index>=0 ? uptr.substring(0,index) : "");
+                       uptr=uptr.substring(index+1);
+
+                       if (uptr.length()==0) {
+                               log(Level.SEVERE,"Premature end of tag/name 
pair (1)");
+                               return false;
+                               }
+
+                       index=uptr.indexOf('?');
+                       sptr = (index>=0 ? uptr.substring(0,index) : "");
+                       uptr=uptr.substring(index+1);
+
+                       /* get the value */
+
+                       if (sptr.length()==0) {
+                               log(Level.SEVERE,"Missing value for tag "+nptr);
+                               return false;
+                               }
+
+                       tags.add(nptr,sptr);
+                       }
+
+               switch (action) {
+                       case URI_ACTION_DOWNLOAD:
+                               ret = parseDownloadURI(tags,tagcount,block);
+                               break;
+
+                       case URI_ACTION_SEARCH:
+                               ret = parseSearchURI(tags,tagcount,block);
+                               break;
+
+                       case URI_ACTION_INSERT:
+                               ret = parseInsertURI(tags,tagcount,block);
+                               break;
+
+                       case URI_ACTION_DELETE:
+                               ret = parseDeleteURI(tags,tagcount,block);
+                               break;
+
+                       default:
+                               break;
+                       }
+               return ret;
+       }
+
+       protected boolean parseDownloadURI( TagTable tags, int tagcount, 
GeneralURI[] block )
+       {
+               int                     i;
+               int                     gotmask=0;
+               String          tag,value;
+               DownloadURI     ret;
+
+               long            fileLength=0;
+               int             fileCRC=0;
+               ChkHashes       fileHashes=new ChkHashes();
+
+               ret = new DownloadURI();
+               ret.action = URI_ACTION_DOWNLOAD;
+
+               for(i=0; i<tagcount; i++) {
+                       tag=tags.getTag(i);
+                       value=tags.getValue(i);
+
+                       if (tag.equals("filename")) {
+                               ret.filename = value;
+                               gotmask |= GOT_FILENAME;
+                               }
+                       else if (tag.equals("kh")) {
+                               fileHashes.key=HashCode512.parse(value);
+                               gotmask |= GOT_KH;
+                               }
+                       else if (tag.equals("qh")) {
+                               fileHashes.query=HashCode512.parse(value);
+                               gotmask |= GOT_QH;
+                               }
+                       else if (tag.equals("size")) {
+                               fileLength=Long.parseLong(value);
+                               gotmask |= GOT_SIZE;
+                               }
+                       else if (tag.equals("crc")) {
+                               fileCRC=Integer.parseInt(value,16);
+                               gotmask |= GOT_CRC;
+                               }
+                       else {
+                               log(Level.WARNING,"Unknown tag "+tag+" in 
download context");
+                               }
+                       }
+
+               ret.fid=new FileIdentifier(fileLength,fileCRC,fileHashes);
+
+               if((gotmask & GOT_CRC)==0 || (gotmask & GOT_KH)==0 || (gotmask 
& GOT_QH)==0 || (gotmask & GOT_SIZE)==0) {
+                       log(Level.SEVERE,"Insufficient tags for download");
+                       return false;
+                       }
+
+               block[0]=ret;
+               return true;
+       }
+
+       protected boolean parseSearchURI( TagTable tags, int tagcount, 
GeneralURI[] block )
+       {
+               int i;
+               int gotmask=0;
+               String  tag,value;
+               SearchURI       ret;
+
+               ret = new SearchURI();
+               ret.action = URI_ACTION_SEARCH;
+
+               for(i=0;i<tagcount;i++) {
+                       tag = tags.getTag(i);
+                       value = tags.getValue(i);
+
+                       if (tag.equals("namespace")) {
+                               ret.namespace = HashCode512.parse(value);
+                               if (ret.namespace==null) {
+                                       log(Level.SEVERE,"Namespace is not in 
HEX format");
+                                       return false;
+                                       }
+                               gotmask |= GOT_NS;
+                               }
+                       /* namespace keyhash identifier */
+                       /* FIXME: either keywords or kh is redundant */
+                       else if (tag.equals("kh")) {
+                               ret.keyhash = HashCode512.parse(value);
+                               if (ret.keyhash==null) {
+                                       log(Level.FINEST,"Namespace ID is not 
in HEX format, using hash of ASCII text ("+value+").");
+                                       ret.keyhash=HashCode512.create(value);
+                                       }
+                               gotmask |= GOT_KH;
+                               }
+                       else if (tag.equals("keyword")) {
+                               String[]        old;
+
+                               old=ret.keywords;
+                               ret.keycount++;
+                               ret.keywords=new String[ret.keycount];
+                               
System.arraycopy(old,0,ret.keywords,0,old.length);
+
+                               ret.keywords[ret.keycount-1] = value;
+                               gotmask |= GOT_KEYWORD;
+                               }
+                       else {
+                               log(Level.WARNING,"Unknown tag name "+tag+" in 
search context");
+                               }
+                       }
+
+               if ((gotmask & GOT_KEYWORD)==0) {
+                       log(Level.SEVERE,"Insufficient tags for search");
+                       return false;
+                       }
+
+               block[0]=ret;
+               return true;
+       }
+
+       protected boolean parseInsertURI( TagTable tags, int tagcount, 
GeneralURI[] block )
+       {
+               int i;
+               int gotmask=0;
+               String  tag,value;
+               InsertURI       ret;
+
+               ret = new InsertURI();
+               ret.action = URI_ACTION_INSERT;
+
+               for(i=0;i<tags.getSize();i++) {
+                       tag = tags.getTag(i);
+                       value = tags.getValue(i);
+
+                       if(tag.equals("filename")) {
+                               ret.filename = value;
+                               gotmask |= GOT_FILENAME;
+                               }
+                       else if(tag.equals("pseudonym")) {
+                               ret.pseudonym = value;
+                               gotmask |= GOT_PSEUDONYM;
+                               }
+                       else if(tag.equals("password")) {
+                               ret.password = value;
+                               gotmask |= GOT_PASSWORD;
+                               }
+                       else {
+                               log(Level.WARNING,"Unknown tag name "+tag+" in 
search context");
+                               }
+               }
+
+               if ((gotmask & GOT_FILENAME)==0) {
+                       log(Level.SEVERE,"Insufficient tags for insert");
+                       return false;
+                       }
+
+               block[0]=ret;
+               return true;
+       }
+
+       protected boolean parseDeleteURI( TagTable tags, int tagcount, 
GeneralURI[] block )
+       {
+               int i;
+               int gotmask=0;
+               DeleteURI       ret;
+               String  tag,value;
+
+               ret = new DeleteURI();
+               ret.action = URI_ACTION_DELETE;
+
+               for(i=0; i<tags.getSize(); i++) {
+                       tag = tags.getTag(i);
+                       value = tags.getValue(i);
+
+                       if (tag.equals("filename")) {
+                               ret.filename=value;
+                               gotmask |= GOT_FILENAME;
+                               }
+                       else {
+                               log(Level.WARNING,"Unknown tag name "+tag+" in 
search context");
+                               }
+                       }
+
+               if ((gotmask & GOT_FILENAME)==0) {
+                       log(Level.SEVERE,"Insufficient tags for delete");
+                       return false;
+                       }
+               block[0]=ret;
+               return true;
+       }
+
+       /**
+        * Turns an internal representation into a AFS uri string
+        *
+        * @param block the values to print
+        * @param url   output
+        * @return false on failure
+        */
+
+       public boolean produceURI( GeneralURI block, String[] url )
+       {
+               int     i;
+
+               if (block==null) {
+                       log(Level.SEVERE,"null block passed to produceURI()");
+                       return false;
+                       }
+
+               url[0]=AFS_URI_PREFIX;
+
+               switch(block.action) {
+                       case URI_ACTION_DOWNLOAD:
+                               DownloadURI     du;
+
+                               du = (DownloadURI) block;
+                               url[0]+="download/";
+                               url[0]+="kh="+du.fid.getFileKey().toHex()+"?";
+                               url[0]+="qh="+du.fid.getFileQuery().toHex()+"?";
+                               url[0]+="size="+du.fid.getFileLength()+"?";
+                               
url[0]+="crc="+Utils.toHex(du.fid.getFileCRC())+"?";
+                               if (du.filename != null) {
+                                       url[0]+=du.filename+"?";
+                                       }
+                               break;
+
+                       case URI_ACTION_SEARCH:
+                               SearchURI       su;
+
+                               su=(SearchURI) block;
+                               url[0]+="search/";
+                               if (su.namespace!=null) {
+                                       url[0]+="ns="+su.namespace.toHex()+"?";
+                                       }
+                               if (su.keyhash!=null) {
+                                       url[0]+="kh="+su.keyhash.toHex()+"?";
+                                       }
+                               for (i=0; i<su.keycount; i++) {
+                                       url[0]+="keyword="+su.keywords[i]+"?";
+                                       }
+                               break;
+
+                       case URI_ACTION_INSERT:
+                               InsertURI       iu;
+
+                               iu=(InsertURI) block;
+                               url[0]+="insert/";
+                               if (iu.filename!=null) {
+                                       url[0]+=iu.filename+"?";
+                                       }
+                               break;
+
+                       case URI_ACTION_DELETE:
+                               DeleteURI       dd;
+
+                               dd=(DeleteURI) block;
+                               url[0]+="delete/";
+                               if (dd.filename!=null) {
+                                       url[0]+=dd.filename+"?";
+                                       }
+                               break;
+
+                       default:
+                               url[0]=null;
+                               log(Level.SEVERE,"Unknown action 
"+block.action);
+                               return false;
+                       }
+
+               if (url[0].endsWith("?")) {
+                       url[0]=url[0].substring(0,url[0].length()-1);
+                       }
+               return true;
+       }
+}
+
+/* internal struct for tag/value pairs */
+
+class TagTable extends Object
+{
+       private List    all=new ArrayList();
+
+       public void add( String t, String v )
+       {
+               all.add(t);
+               all.add(v);
+       }
+       public int getSize()
+       {
+               return all.size()/2;
+       }
+
+       public String getTag( int i )
+       {
+               return (String) all.get(i*2);
+       }
+
+       public String getValue( int i )
+       {
+               return (String) all.get(i*2+1);
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/CreatePseudonymDialog.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/CreatePseudonymDialog.java    
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/CreatePseudonymDialog.java    
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,161 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.swing;
+
+import org.gnu.freeway.protocol.fs.esed2.*;
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.ui.*;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+
+/**
+ *
+ */
+
+public class CreatePseudonymDialog extends GDialog implements ActionListener
+{
+       private SController             controller;
+
+       private JTextField              pseudoField;
+       private JPasswordField  passwordField;
+       private JButton                 cancelButton;
+       private JButton                 createButton;
+
+
+       public CreatePseudonymDialog( SController ctr, GFrame f )
+       {
+               super(f,"pseudo-create");
+               setTitle("Create Pseudonym");
+               controller=ctr;
+               addWindowListener(new WindowAdapter() {
+                       public void windowClosing(WindowEvent we)
+                       {
+                               onClose();
+                       }
+                       });
+       }
+
+       public String toString()
+       {
+               return "Create pseudonym dialog";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       /**
+        * Setup a window to allow the user to create a pseudonym
+        * @return
+        */
+
+       public JComponent createContent()
+       {
+               JPanel          panel,buttonPane;
+               GForm   form;
+               JLabel          iconLabel;
+
+               pseudoField=new JTextField(10);
+               passwordField=new JPasswordField(10);
+
+               form=new GForm();
+               form.addWidget("Pseudonym :",pseudoField);
+               form.addWidget("Password :",passwordField);
+
+               cancelButton=new JButton("Cancel");
+               cancelButton.addActionListener(this);
+
+               createButton=new JButton("Create");
+               createButton.addActionListener(this);
+
+               buttonPane=new JPanel();
+               buttonPane.setLayout(new BoxLayout(buttonPane, 
BoxLayout.LINE_AXIS));
+               buttonPane.add(Box.createHorizontalGlue());
+               buttonPane.add(cancelButton);
+               buttonPane.add(Box.createRigidArea(new Dimension(3,0)));
+               buttonPane.add(createButton);
+
+               iconLabel=new JLabel(new 
ImageIcon(controller.getResources().getImageURL("app.minilogo")));
+               iconLabel.setVerticalAlignment(SwingConstants.TOP);
+
+               panel=new JPanel(new BorderLayout(3,1));
+               panel.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+               panel.add(iconLabel,BorderLayout.WEST);
+               panel.add(form,BorderLayout.CENTER);
+               panel.add(buttonPane,BorderLayout.SOUTH);
+
+               getRootPane().setDefaultButton(createButton);
+
+               return panel;
+       }
+
+       public void updateContent()
+       {
+               pseudoField.setText("");
+               passwordField.setText("");
+       }
+
+       public void leaveContent()
+       {
+               pseudoField.setText("");
+               passwordField.setText("");
+       }
+
+       public void actionPerformed( ActionEvent evt )
+       {
+               if (evt.getSource()==createButton) {
+                       onCreate();
+                       }
+               else if (evt.getSource()==cancelButton) {
+                       onClose();
+                       }
+       }
+
+       protected void onClose()
+       {
+               close();
+       }
+
+       protected void onCreate()
+       {
+               Task    task;
+
+               final String _name = pseudoField.getText();
+               if (_name.length()==0) {
+                       controller.guiMessage("Cowardly refusing to create 
pseudonym without name.");
+                       return;
+                       }
+
+               final String _pass = new String(passwordField.getPassword());
+
+               pseudoField.setEnabled(false);
+               passwordField.setEnabled(false);
+               createButton.setEnabled(false);
+               cancelButton.setEnabled(false);
+
+               task=new Task("NEW-PSEUDO",new 
org.gnu.freeway.util.AbstractAction() {
+                       public void perform()
+                       {
+                               PrivateKey      priv;
+                               // we may want to do this in another thread to 
keep the event manager running (and potentially even
+                               // give feedback in the form of a popup 
window). After all, this can take a while...
+                               priv=new 
Pseudonym(controller.getPreferences()).createPseudonym(_name,_pass);
+                               if (priv==null) {
+                                       controller.guiMessage("Failed to create 
pseudonym (see logs).");
+                                       }
+                               controller.refreshMenus();
+
+                               pseudoField.setEnabled(true);
+                               passwordField.setEnabled(true);
+                               createButton.setEnabled(true);
+                               cancelButton.setEnabled(true);
+                               close();
+                       }
+                       });
+               task.launch();
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/DaemonWindow.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/DaemonWindow.java     
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/DaemonWindow.java     
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,207 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.swing;
+
+import org.gnu.freeway.*;
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.io.*;
+import org.gnu.freeway.util.ui.*;
+
+import java.awt.event.*;
+import javax.swing.*;
+
+/**
+ *
+ */
+
+public class DaemonWindow extends GFrame
+{
+       private SController             controller;
+       private UIResources             resources;
+       private DaemonLauncher  launcher;
+
+       private GStatus                 status;
+       private GConsole                text;
+       private GBar            bar;
+       private JTextField              argsField;
+
+
+       public DaemonWindow( UIResources res, SController ctr )
+       {
+               super(ctr,"daemon-window");
+               setTitle("GNUnet Swing : Daemon");
+               controller=ctr;
+               resources=new UIResources(res,"daemon-window.xml");
+               resources.setGlobalTarget(this);
+               launcher=new DaemonLauncher(ctr.getApplication());
+
+               addWindowListener(new WindowAdapter() {
+                       public void windowClosing( WindowEvent evt )
+                       {
+                               onClose();
+                       }
+                       });
+       }
+
+       public String toString()
+       {
+               return "Daemon window";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public JComponent createContent()
+       {
+               JPanel  panel;
+               Box             box;
+
+               status=new GStatus();
+
+               text=new GConsole();
+
+               argsField=new JTextField(10);
+               argsField.setText("-L INFO");
+
+               bar=new GBar();
+               bar.add("Arguments :",argsField);
+               bar.add(resources.getAction("start.action"));
+               bar.add(resources.getAction("stop.action"));
+               bar.addSeparator();
+               bar.add(resources.getAction("close.action"));
+
+               resources.getAction("close.action").setEnabled(true);
+
+               box=new Box(BoxLayout.X_AXIS);
+               box.add(status);
+               box.add(Box.createHorizontalGlue());
+               box.add(text.createControls());
+
+               panel=new JPanel(new TileLayout());
+               panel.add(box/*status*/,TileLayout.NORTH);
+               panel.add(bar,TileLayout.SOUTH);
+               panel.add(text);
+
+               setJMenuBar(resources.getMenuBar("main.menu"));
+               return panel;
+       }
+
+       public void updateContent()
+       {
+       }
+
+       /**
+        * Launch gnunetd w/ checks
+        */
+
+       public void onLaunchDaemon()
+       {
+               GProgress       dialog;
+               final boolean[] _beurk = new boolean[1];
+
+               if (launcher.checkDaemonRunning() ) {
+                       showMessage("Daemon is already running !");
+                       return;
+                       }
+
+               _beurk[0]=false;
+               dialog=new GProgress(this,"launch-progress") {
+                       public void perform()
+                       {
+                               String  argLine;
+
+                               argLine=(argsField!=null ? argsField.getText() 
: null);
+                               
_beurk[0]=launcher.launchWithExec(this,argLine,new ConnectorEndPoint() {
+                                       public void gotMessage( int type, 
String str )
+                                       {
+                                               text.println(str);
+                                       }
+                                       });
+                               Scheduler.sleep(Scheduler.MILLIS_200);
+                       }
+                       };
+               dialog.setTitle("Launch daemon");
+               dialog.launch("Please wait while daemon is being 
launched...\n");
+
+               if (!_beurk[0]) {
+                       return;
+                       }
+
+               resources.getAction("start.action").setEnabled(false);
+               resources.getAction("stop.action").setEnabled(false);
+               status.setText("Checking...");
+               argsField.setEnabled(false);
+       }
+
+       /**
+        * Kill gnunetd
+        */
+
+       public void onKillDaemon()
+       {
+               resources.getAction("start.action").setEnabled(false);
+               resources.getAction("stop.action").setEnabled(false);
+               status.setText("Waiting for daemon's response...");
+               argsField.setEnabled(false);
+
+               if (!launcher.checkDaemonRunning() ) {
+                       showMessage("Daemon is not running...");
+                       return;
+                       }
+
+               if (launcher.killDaemon()) {
+                       showMessage("Daemon agreed to shut down.");
+                       }
+               else {
+                       showMessage("Daemon refuses to shut down.");
+                       }
+       }
+
+       public void cronCheckDaemon()
+       {
+               UIAction        killEntry;
+               UIAction        launchEntry;
+               UIAction        statsEntry;
+               String          host;
+               boolean         isLocal;
+
+               killEntry=resources.getAction("advanced.kill.gnunetd");
+               launchEntry=resources.getAction("advanced.launch.gnunetd");
+               statsEntry=resources.getAction("file.show.stats");
+
+               host = 
controller.getPreferences().getString("NETWORK","HOST",null);
+               isLocal=(host==null || host.equalsIgnoreCase("localhost"));
+
+               if (!launcher.checkDaemonRunning()) {
+                       statsEntry.setEnabled(false);
+                       killEntry.setEnabled(false);
+                       launchEntry.setEnabled(isLocal);
+
+                       resources.getAction("start.action").setEnabled(true);
+                       resources.getAction("stop.action").setEnabled(false);
+                       if (status!=null) {
+                               status.setText("No daemon found.");
+                               argsField.setEnabled(true);
+                               }
+                       }
+               else {
+                       statsEntry.setEnabled(true);
+                       killEntry.setEnabled(true);
+                       launchEntry.setEnabled(false);
+
+                       resources.getAction("start.action").setEnabled(false);
+                       resources.getAction("stop.action").setEnabled(true);
+                       if (status!=null) {
+                               status.setText("Daemon is now up and running.");
+                               argsField.setEnabled(false);
+                               }
+                       }
+       }
+
+       public void onClose()
+       {
+               close();
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/DeletePseudonymDialog.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/DeletePseudonymDialog.java    
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/DeletePseudonymDialog.java    
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,151 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.swing;
+
+import org.gnu.freeway.protocol.fs.esed2.*;
+import org.gnu.freeway.util.ui.*;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.event.*;
+
+/**
+ *
+ */
+
+public class DeletePseudonymDialog extends GDialog implements ActionListener, 
ListSelectionListener
+{
+       private SController             controller;
+       private Pseudonym               pseudonym;
+
+       private JList                   pseudoList;
+       private JButton                 cancelButton;
+       private JButton                 deleteButton;
+
+
+       public DeletePseudonymDialog( SController ctr, GFrame f )
+       {
+               super(f,"pseudo-delete");
+               setTitle("Delete Pseudonym");
+               controller=ctr;
+               pseudonym=new Pseudonym(controller.getPreferences());
+               addWindowListener(new WindowAdapter() {
+                       public void windowClosing(WindowEvent we)
+                       {
+                               onClose();
+                       }
+                       });
+       }
+
+       public String toString()
+       {
+               return "Delete pseudonym dialog";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       /**
+        * Open a window to allow the user to delete a pseudonym
+        * @return
+        */
+
+       public JComponent createContent()
+       {
+               JScrollPane     scrollPane;
+               JPanel          panel,buttonPane,listPane;
+               JLabel          iconLabel,titleLabel;
+
+               pseudoList=new JList(pseudonym.listPseudonyms());
+               
pseudoList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+               pseudoList.setLayoutOrientation(JList.VERTICAL);
+               pseudoList.setVisibleRowCount(5);
+               pseudoList.addListSelectionListener(this);
+
+               scrollPane=new JScrollPane(pseudoList);
+               scrollPane.setAlignmentX(LEFT_ALIGNMENT);
+
+               titleLabel=new JLabel("Select a pseudonym to delete :");
+
+               listPane = new JPanel();
+               listPane.setLayout(new BoxLayout(listPane, 
BoxLayout.PAGE_AXIS));
+               listPane.add(titleLabel);
+               listPane.add(Box.createRigidArea(new Dimension(0,1)));
+               listPane.add(scrollPane);
+
+               cancelButton=new JButton("Cancel");
+               cancelButton.setEnabled(true);
+               cancelButton.addActionListener(this);
+
+               deleteButton=new JButton("Delete");
+               deleteButton.setEnabled(false);
+               deleteButton.addActionListener(this);
+
+               buttonPane=new JPanel();
+               buttonPane.setLayout(new BoxLayout(buttonPane, 
BoxLayout.LINE_AXIS));
+               buttonPane.add(Box.createHorizontalGlue());
+               buttonPane.add(cancelButton);
+               buttonPane.add(Box.createRigidArea(new Dimension(3,0)));
+               buttonPane.add(deleteButton);
+
+               iconLabel=new JLabel(new 
ImageIcon(controller.getResources().getImageURL("app.minilogo")));
+               iconLabel.setVerticalAlignment(SwingConstants.TOP);
+
+               panel=new JPanel(new BorderLayout(3,1));
+               panel.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+               panel.add(iconLabel,BorderLayout.WEST);
+               panel.add(listPane,BorderLayout.CENTER);
+               panel.add(buttonPane,BorderLayout.SOUTH);
+
+               getRootPane().setDefaultButton(deleteButton);
+               return panel;
+       }
+
+       public void actionPerformed( ActionEvent evt )
+       {
+               if (evt.getSource()==deleteButton) {
+                       onDelete();
+                       }
+               else if (evt.getSource()==cancelButton) {
+                       onClose();
+                       }
+       }
+
+       public void valueChanged( ListSelectionEvent evt )
+       {
+               if (!evt.getValueIsAdjusting()) {
+                       
deleteButton.setEnabled(pseudoList.getSelectedIndex()>=0);
+                       }
+       }
+
+       protected void onClose()
+       {
+               close();
+       }
+
+       protected void onDelete()
+       {
+//             int     index;
+
+//             index=pseudoList.getSelectedIndex();
+/*
+               listModel.remove(index);
+
+               int size = listModel.getSize();
+
+               if (size == 0) { //Nobody's left, disable firing.
+                       fireButton.setEnabled(false);
+                       }
+               else { //Select an index.
+                       if (index == listModel.getSize()) {
+                               //removed item in last position
+                               index--;
+                               }
+                       list.setSelectedIndex(index);
+                       list.ensureIndexIsVisible(index);
+                       }*/
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/DownloadAdapter.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/DownloadAdapter.java  
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/DownloadAdapter.java  
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,64 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.swing;
+
+import org.gnu.freeway.protocol.fs.esed2.*;
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.ui.*;
+
+import java.io.*;
+
+/**
+ *
+ */
+
+public class DownloadAdapter extends AbstractAdapter implements AFSConstants
+{
+       public DownloadAdapter()
+       {
+               super(8);
+       }
+
+       protected void fillFacets( Object obj, Object[] facets )
+       {
+               DownloadModel   dlm;
+               double                  currentRetryAvg, averageBps, percentage;
+               long                    now;
+
+               dlm=(DownloadModel) obj;
+
+               currentRetryAvg=0;
+               averageBps=0;
+               percentage=0;
+
+               now=Scheduler.toSeconds(Scheduler.now());
+               if (dlm.pstats!=null) {
+                       if (dlm.pstats.requestsSent>0) {
+                               currentRetryAvg=(double) 
dlm.pstats.currentRetries/(double) dlm.pstats.requestsSent;
+                               }
+                       if (now-dlm.downloadStartTime>0) {
+                               averageBps=(double) 
dlm.pstats.progress/(double) (now-dlm.downloadStartTime);
+                               }
+                       if (dlm.pstats.filesize>0) {
+                               percentage=100.0*((double) 
dlm.pstats.progress/(double) dlm.pstats.filesize);
+                               }
+                       }
+
+               
facets[0]=dlm.fileName.substring(dlm.fileName.lastIndexOf(File.separator)+1);
+               facets[1]=(percentage!=0 ? (Object) new Double(percentage) : 
(Object) "0%");
+               facets[2]=(dlm.pstats!=null ? (Object) new 
Integer(dlm.pstats.progress) : (Object) "-");
+               
facets[3]=String.valueOf(dlm.root.getFileIdentifier().getFileLength());
+               facets[4]=(dlm.pstats!=null ? (Object) new 
Integer(dlm.pstats.requestsSent) : (Object) "-");
+               facets[5]=(dlm.pstats!=null ? (Object) new 
Double(currentRetryAvg) : (Object) "-");
+               facets[6]=(dlm.pstats!=null ? (Object) new 
Integer(dlm.pstats.totalRetries) : (Object) "-");
+               facets[7]=(dlm.pstats!=null ? (Object) new Double(averageBps) : 
(Object) "-");
+
+               if (dlm.pstats!=null && dlm.pstats.filesize == 
dlm.pstats.progress) {
+                       // reset the request counters (just cosmetic)
+                       facets[4]="0";
+                       facets[5]="0.0";
+                       }
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/DownloadModel.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/DownloadModel.java    
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/DownloadModel.java    
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,209 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.swing;
+
+import org.gnu.freeway.*;
+import org.gnu.freeway.protocol.fs.esed2.*;
+import org.gnu.freeway.util.*;
+
+import EDU.oswego.cs.dl.util.concurrent.*;
+
+import java.util.logging.*;
+import javax.swing.*;
+
+/**
+* State associated with a download window.
+ */
+
+public class DownloadModel extends Object implements AFSConstants
+{
+       /* Suitable values of DownloadModel.downloadStatus */
+       public static final int DOWNLOAD_COMPLETE       =       0;
+       public static final int DOWNLOAD_FAILED         =       1;
+       public static final int DOWNLOAD_ABORTED        =       2;
+       public static final int DOWNLOAD_PENDING        =       3;
+
+       public Node                     root;
+       public String                   fileName;
+       public Task                             downloadThread;
+       public RequestManager   rm;
+       public Semaphore                doneSem;
+       public long                             downloadStartTime;
+       public boolean                  successfulStart;
+       public int                              downloadStatus;
+       public long                             lastDisplayTime;
+       public JTable                   dlList;
+
+       public ProgressStats    pstats;
+
+       private SController             controller;
+
+
+       public String[] cols;
+
+
+       public DownloadModel( SController ctr )
+       {
+               super();
+               root=null;
+               fileName="";
+               downloadThread=null;
+               controller=ctr;
+               successfulStart=false;
+               pstats=null;
+       }
+
+       /**
+        * Main function of the download thread. This function terminates
+        * automagically once the download is complete or if the download
+        * entry is removed while dl.  It is responsible for stopping the
+        * requestmanager.
+        * @throws InterruptedException
+        */
+
+       public void downloadFile_() throws InterruptedException
+       {
+               String  mime;
+
+               controller.log(Level.FINEST,"Entering downloadFile_ for 
"+fileName);
+
+               /* initiate download */
+               downloadStartTime=Scheduler.toSeconds(Scheduler.now());
+
+               /* this starts the "real" download thread in the background,
+                with "modelCallback" called back to tell us about the progress 
*/
+               rm = new DownloadUtil().downloadFile((AbstractClient) 
controller.getApplication(),
+                       controller.getPolicy(),
+                       null,   //todo: va planter
+                       root.getFileIdentifier(),fileName,new ProgressModel() {
+                       public void progress( ProgressStats stats, Object data )
+                       {
+                               modelCallback(stats);
+                       }
+                       },this);
+               if (rm == null) {
+                       controller.guiMessage("Could not download 
"+fileName+".\nConsult logs.\n");
+                       doneSem=null;
+                       return;
+                       }
+
+               /* Wait here until download is complete or the window is closed 
or gnunet-gtk is terminated */
+               controller.log(Level.FINEST,"Waiting for DL completion.");
+
+               doneSem.acquire();
+
+               controller.log(Level.FINEST,"Download complete 
("+downloadStatus+") enter destroyRequestManager.");
+
+               /* stop the RequestManager */
+               if (rm != null) {
+                       rm.destroy();
+                       }
+               else {
+                       /* this can happen if the requestmanager initialization
+                        * failed for reason or another (e.g. write permission 
denied) */
+                       controller.log(Level.WARNING,"rm was null !");
+                       }
+
+               /*
+                ok, now why are we here? 4 possibilities:
+                a) download aborted (user closed window)
+                b) gnunet-gtk terminated (same as download aborted)
+                c) download failed (gnunetd exit, out-of-space)
+                d) download completed
+
+                In case "d" we show the "YAY" window
+                and wait for another signal.
+                In case "c" we show the "BAH" window
+                and wait for another signal.
+                */
+
+               switch (downloadStatus) {
+                       case DOWNLOAD_COMPLETE:
+//                             gdk_threads_enter();
+                               /* color successful dl green */
+//                             gtk_clist_freeze(GTK_CLIST(dlList));
+//                             row = 
gtk_clist_find_row_from_data(GTK_CLIST(dlList),dlm);
+//                             gtk_clist_set_foreground(GTK_CLIST(dlList), 
row,textColors[3]);
+//                             
gtk_clist_set_text(GTK_CLIST(dlList),row,1,"DONE");
+//                             gtk_clist_thaw(GTK_CLIST(dlList));
+
+                               mime=root.getMimeType();
+                               if (mime.equals(GNUNET_DIRECTORY_MIME)) {
+                                       
controller.displayDirectory(fileName,root);
+                                       }
+
+//                             gdk_threads_leave();
+                               doneSem.acquire(); /* wait for window closing */
+                               break;
+
+                       case DOWNLOAD_FAILED:
+//                             gdk_threads_enter();
+                               /* color failed dl red */
+//                             gtk_clist_freeze(GTK_CLIST(dlList));
+//                             row = 
gtk_clist_find_row_from_data(GTK_CLIST(dlList),dlm);
+//                             
gtk_clist_set_foreground(GTK_CLIST(dlList),row,textColors[4]);
+//                             
gtk_clist_set_text(GTK_CLIST(dlList),row,1,"FAIL");
+//                             gtk_clist_thaw(GTK_CLIST(dlList));
+//                             gdk_threads_leave();
+                               doneSem.acquire(); /* wait for window closing */
+                               break;
+
+                       default:
+                               /* do nothing */
+                               break;
+                       }
+
+               /* finally, disentangle from the clist and free dlm resources */
+//             gdk_threads_enter();
+//             gtk_clist_freeze(GTK_CLIST(dlList));
+//             row = gtk_clist_find_row_from_data(GTK_CLIST(dlList),dlm);
+//             gtk_clist_set_row_data(GTK_CLIST(dlList),row,null);
+//             gtk_clist_thaw(GTK_CLIST(dlList));
+//             gdk_threads_leave();
+               doneSem=null;
+               fileName=null;
+       }
+
+       /**
+        * This method is called by the download code to notify the user
+        * interface of the download progress.
+        *
+        * @param stats the new statistical values
+        */
+
+       protected void modelCallback( ProgressStats stats )
+       {
+               long    now;
+
+               if (downloadStatus != DOWNLOAD_PENDING)
+                       return;
+
+               /* don't display more often than once/sec */
+               now=Scheduler.toSeconds(Scheduler.now());
+               if ((now-lastDisplayTime) < 1 && (stats.filesize != 
stats.progress))
+                       return;
+
+               lastDisplayTime = now;
+
+               pstats=new ProgressStats(stats);
+
+
+               if (!successfulStart && stats.progress > 0) {
+//                     
gtk_clist_set_foreground(GTK_CLIST(dlList),row,textColors[1]);
+                       successfulStart=true;
+                       }
+
+               if (stats.filesize == stats.progress) {
+                       /* reset the request counters (just cosmetic) */
+                       controller.refreshMenus();
+
+                       if (stats.filesize == 0)
+                               downloadStatus = DOWNLOAD_FAILED;
+                       else
+                               downloadStatus = DOWNLOAD_COMPLETE;
+                       doneSem.release(); /* signal: we're done with download 
*/
+                       }
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/DownloadWindow.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/DownloadWindow.java   
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/DownloadWindow.java   
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,125 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.swing;
+
+import org.gnu.freeway.protocol.fs.esed2.*;
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.net.*;
+import org.gnu.freeway.util.ui.*;
+
+import EDU.oswego.cs.dl.util.concurrent.*;
+
+import java.awt.event.*;
+import javax.swing.*;
+
+/**
+ *
+ */
+
+public class DownloadWindow extends GFrame implements AFSConstants, 
SwingConstants
+{
+       private SController                     controller;
+       private UIResources                     resources;
+
+       private DefaultListModel        dModel;
+       private ListSelectionModel      dSelection;
+       private GTable                          dTable;
+
+
+       public DownloadWindow( SController ctr )
+       {
+               super(ctr,"download-window");
+               setTitle("GNUnet Swing : Downloads");
+               addWindowListener(new WindowAdapter() {
+                       public void windowClosing(WindowEvent we)
+                       {
+                               onClose();
+                       }
+                       });
+
+               controller=ctr;
+
+               resources=new UIResources("download.xml",controller.getCache());
+               resources.setGlobalTarget(this);
+       }
+
+       public String toString()
+       {
+               return "Download window";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public JComponent createContent()
+       {
+               JPanel  panel;
+               GStatus status;
+               GBar    bar;
+
+               resources.getAction("abort.id").setEnabled(false);
+               resources.getAction("close.id").setEnabled(true);
+
+               dModel=new DefaultListModel();
+               dSelection=new DefaultListSelectionModel();
+
+               dTable=new GTable(new DownloadAdapter(),dModel,dSelection);
+               dTable.setColumns(new GColumn[] {
+                       new GColumn("Filename", 290, LEFT,   true),
+                       new GColumn("%",        50,  CENTER, true),     /* 
Completion percentage */
+                       new GColumn("Position", 70,  RIGHT,  true),     /* 
Bytes dl'ed so far */
+                       new GColumn("Size",     70,  RIGHT,  true),
+                       new GColumn("AReq",     40,  LEFT,   true),     /* 
Active block requests */
+                       new GColumn("CR/A",     30,  LEFT,   true),     /* 
Current Retries per Active requests */
+                       new GColumn("totR",     50,  LEFT,   true),     /* 
Total Retries */
+                       new GColumn("BPS",      50,  LEFT,   true),     /* 
Current bytes per second -estimate */
+                       });
+
+               status=new GStatus();
+
+               bar=new GBar();
+               bar.add(resources.getAction("abort.id"));
+               bar.add(resources.getAction("close.id"));
+
+               panel=new JPanel(new TileLayout());
+               panel.add(status,TileLayout.NORTH);
+               panel.add(bar,TileLayout.SOUTH);
+               panel.add(new JScrollPane(dTable));
+               return panel;
+       }
+
+       public void addDownload( String str, Node node )
+       {
+               DownloadModel   dlm;
+
+               dlm=new DownloadModel(controller);
+               dlm.fileName=str;
+               dlm.root=(Node) PersistentHelper.copy(node);
+               dlm.lastDisplayTime=Scheduler.toSeconds(Scheduler.now());
+               dlm.dlList = dTable;
+               dlm.downloadStatus=DownloadModel.DOWNLOAD_PENDING;
+               dlm.doneSem=new Semaphore(0);
+
+//             gtk_clist_freeze(GTK_CLIST(dTable));
+
+               dModel.addElement(dlm);
+
+//             gtk_clist_set_foreground(GTK_CLIST(dTable),row,textColors[11]);
+//             gtk_clist_thaw(GTK_CLIST(dTable));
+
+               /* create thread that runs the download */
+               dlm.downloadThread=new Task("DOWNLOAD-"+str,new 
EvalAction(dlm,"downloadFile_"));
+               dlm.downloadThread.launch();
+       }
+
+       public void onClose()
+       {
+               close();
+       }
+
+       public void onAbort()
+       {
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/InsertDialog.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/InsertDialog.java     
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/InsertDialog.java     
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,515 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.swing;
+
+import org.gnu.freeway.protocol.fs.esed2.*;
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.io.*;
+import org.gnu.freeway.util.net.*;
+import org.gnu.freeway.util.ui.*;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.io.*;
+import java.util.logging.*;
+import javax.swing.*;
+import javax.swing.event.*;
+import javax.swing.text.*;
+
+/**
+ *
+ */
+
+public class InsertDialog extends GWizard
+{
+       private SController     controller;
+       private UIResources     resources;
+
+       private DirLocation     chosenBase;
+       private FileLocation    chosenFile;
+       private String          chosenFilename;
+       private String          chosenMime;
+       private String          chosenDescription;
+       private boolean         choosedToIndex;
+       private String[]                chosenKeywords;
+
+
+       public InsertDialog( GFrame frame )
+       {
+               super(frame,"insert");
+               setTitle("GNUNet Swing : Insert");
+
+               controller=(SController) frame.getController();
+               resources=new 
UIResources("insert.xml",frame.getController().getApplication().getPreferences().getSystemCache());
+               resources.setGlobalTarget(this);
+
+               init();
+       }
+
+       public String toString()
+       {
+               return "Insert dialog";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public UIResources getResources()
+       {
+               return resources;
+       }
+
+       public GWizardPage[] getSteps()
+       {
+               return new GWizardPage[] {
+                       new ChooseFileStep(),
+                       new EditAttributesStep(),
+                       new ReviewStep()
+                       };
+       }
+
+       protected void init()
+       {
+               Prefs   prefs;
+
+               prefs=controller.getApplication().getPreferences();
+
+               chosenBase=new DirLocation("~");
+               chosenFile=null;
+               chosenFilename="";
+               chosenMime="";
+               chosenDescription="";
+               choosedToIndex=true;
+               chosenKeywords=new String[] {};
+       }
+
+       public void close()
+       {
+               Prefs   prefs;
+
+               prefs=controller.getApplication().getPreferences();
+               //prefs.setGlobalString("insert","directory",(chosenBase!=null 
? chosenBase.getLabel() : ""));
+
+               super.close();
+       }
+
+       public void doTask()
+       {
+               GProgress       progress;
+
+               final boolean[] _beurk = new boolean[1];
+
+               _beurk[0]=false;
+
+               progress=new GProgress(this,"insert-progress") {
+                       public void perform()
+                       {
+                               doInsert(this,false);
+                               _beurk[0]=true;
+                       }
+                       };
+               progress.setTitle(chosenFile.getLabel());
+               progress.launch("Please wait while file is being 
inserted...\n");
+               if (_beurk[0]) {
+                       close();
+                       }
+       }
+
+       protected void doInsert( final GProgress _progress, boolean 
deleteAfterInsert )
+       {
+               Prefs   prefs;
+               RootNode                                res;
+               CSSession                       sock;
+               Block                                   top;
+               InsertUtil                              insertUtil;
+               FileIdentifier                  fid;
+               String                                  fstring;
+
+               prefs=controller.getPreferences();
+               prefs.setString("GNUNET-INSERT","INDEX-CONTENT",(choosedToIndex 
? "YES" : "NO"));
+
+               insertUtil=new InsertUtil(prefs);
+
+               controller.enterCritical();
+               try {
+                       sock=controller.connect();
+
+                       top=insertUtil.insertFile(sock,chosenFile.getPath(),new 
ProgressModel() {
+                               public void progress( ProgressStats stats, 
Object data )
+                               {
+                                       insertModelCallback(stats,_progress);
+                               }
+                               },null);
+                       if (top!=null) {
+                               //todo: erreur ? getName() ou getPath() ???
+                               
res=insertUtil.insertRoot(sock,top,chosenDescription,chosenFile.getName(),chosenMime,chosenKeywords);
+                               }
+                       else {
+                               res=null;
+                               }
+
+                       controller.refreshMenus();
+
+                       if (res!=null) {
+                               fid=new FileIdentifier(top);
+
+                               fstring = fid.toURI();
+
+                               controller.infoMessage(false,"Successfully 
processed "+chosenFile.getLabel()+"\n  => "+fstring+"\n");
+                               controller.log(Level.FINEST,"Successfully 
processed "+chosenFile.getLabel()+"\n  => "+fstring);
+                               }
+                       else {
+                               controller.guiMessage("Insertion of 
"+chosenFile.getLabel()+" FAILED !");
+                               }
+
+                       if (top!=null) {
+                               top.destroy(null);
+                               }
+                       sock.disconnect();
+                       }
+               finally {
+                       /* insert complete */
+                       controller.leaveCritical();
+                       }
+
+               if (deleteAfterInsert) {
+                       chosenFile.delete();
+                       }
+       }
+
+       public void onQuit()
+       {
+               if (JOptionPane.showConfirmDialog(
+                               this,
+                               "Do you really want to close insertion dialog 
?",
+                               "Closing...",
+                               JOptionPane.YES_NO_OPTION,
+                               JOptionPane.QUESTION_MESSAGE,
+                               new 
ImageIcon(resources.getImageURL("application.medium.icon"))
+                               )==JOptionPane.YES_OPTION) {
+
+                       close();
+                       }
+       }
+
+       protected void insertModelCallback( ProgressStats stats, GProgress 
progress )
+       {
+               progress.progress(
+                       stats.progress,
+                       stats.filesize,
+                       stats.progress+" bytes of "+stats.filesize+" 
"+(choosedToIndex ? "indexed" : "inserted")
+                       );
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public class ChooseFileStep extends GWizardPage
+       {
+               private GStack  stack;
+               private GText   text;
+               private GFile   file;
+
+
+               public ChooseFileStep()
+               {
+                       super("Choose file");
+               }
+
+               public JComponent createView()
+               {
+                       text=new GText();
+                       text.setText("Choose file to be inserted.");
+
+                       file=new GFile();
+
+                       stack=new GStack();
+                       stack.addWidget(text);
+                       stack.addWidget(file);
+                       return stack;
+               }
+
+               public void updateView()
+               {
+                       file.selectFile("File to insert",chosenBase);
+                       file.setSelectedLocation(chosenFile);
+               }
+
+               public String canLeaveView()
+               {
+                       FileLocation            f;
+
+                       f=(FileLocation) file.getSelectedLocation();
+                       if (f==null) {
+                               return "You must choose a file to insert !";
+                               }
+                       if (!f.exists()) {
+                               return "Please select a valid file !";
+                               }
+                       return null;
+               }
+
+               public void leaveView()
+               {
+                       chosenFile=(FileLocation) file.getSelectedLocation();
+                       chosenBase=file.getSelectedBase();
+               }
+       }
+
+       public class EditAttributesStep extends GWizardPage
+       {
+               private GStyledText                     text;
+               private JTextField                      filenameField;
+               private JTextField                      mimeField;
+               private JTextField                      descriptionField;
+               private JComboBox                       methodCombo;
+               private DefaultListModel        keywordModel;
+               private JList                           keywordList;
+               private JTextField                      keywordField;
+               private JButton                         addButton;
+               private JButton                         deleteButton;
+
+
+               public EditAttributesStep()
+               {
+                       super("Edit attributes");
+               }
+
+               public JComponent createView()
+               {
+                       GStack  stack;
+                       GForm   form;
+                       GBar    bar;
+
+                       filenameField=new JTextField(10);
+                       mimeField=new JTextField(10);
+                       descriptionField=new JTextField(10);
+                       methodCombo=new JComboBox(new String[] { "Index only", 
"Full insertion" });
+
+                       keywordModel=new DefaultListModel();
+
+                       keywordList=new JList(keywordModel);
+                       
keywordList.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
+                       keywordList.setLayoutOrientation(JList.VERTICAL);
+                       keywordList.setVisibleRowCount(5);
+                       keywordList.addListSelectionListener(new 
ListSelectionListener() {
+                               public void valueChanged( ListSelectionEvent 
evt )
+                               {
+                                       
deleteButton.setEnabled(!keywordList.isSelectionEmpty());
+                               }
+                               });
+
+                       keywordField=new JTextField(10);
+                       keywordField.addActionListener(new ActionListener() {
+                               public void actionPerformed( ActionEvent evt )
+                               {
+                                       addKeyword();
+                               }
+                               });
+
+                       addButton=new JButton("Add keyword");
+                       addButton.addActionListener(new ActionListener() {
+                               public void actionPerformed( ActionEvent evt )
+                               {
+                                       addKeyword();
+                               }
+                               });
+
+                       deleteButton=new JButton("Delete keyword");
+                       deleteButton.addActionListener(new ActionListener() {
+                               public void actionPerformed( ActionEvent evt )
+                               {
+                                       deleteKeyword();
+                               }
+                               });
+                       deleteButton.setEnabled(false);
+
+                       bar=new GBar();
+                       bar.add(addButton);
+                       bar.add(deleteButton);
+
+                       text=new GStyledText();
+                       
StyleConstants.setFontFamily(text.style("fi"),"SansSerif");
+                       StyleConstants.setBold(text.style("fi"),true);
+                       StyleConstants.setItalic(text.style("fi"),true);
+                       StyleConstants.setForeground(text.style("fi"),new 
Color(33,87,136));
+
+                       form=new GForm();
+                       form.addWidget("Published filename :",filenameField);
+                       form.addWidget("MIME type :",mimeField);
+                       form.addWidget("Description :",descriptionField);
+                       form.addWidget("Insertion method :",methodCombo);
+                       form.addWidget("Keyword(s) used :",new 
JScrollPane(keywordList),0);
+                       form.addWidget("",keywordField);
+                       form.addWidget("",bar);
+
+                       stack=new GStack();
+                       stack.addWidget(text);
+                       stack.addWidget(form);
+                       return stack;
+               }
+
+               public void updateView()
+               {
+                       String[]                                keywords;
+                       Prefs   prefs;
+                       StringBuffer                    description,mimetype;
+                       int                                             i;
+
+                       text.clear();
+                       text.print("Now, edit attributes file 
<fi>"+chosenFile.getLabel()+"</fi> will be inserted with.");
+
+                       // try to extract keywords
+                       description=new StringBuffer();
+                       mimetype=new StringBuffer();
+                       keywords=new KeyWords().extractKeywords(new 
File(chosenFile.getPath()),description,mimetype);
+                       if (description.length()==0) {
+                               description.append("No description supplied");
+                               }
+                       if (mimetype.length()==0) {
+                               mimetype.append("unknown");
+                               }
+
+                       filenameField.setText(chosenFile.getName());
+                       mimeField.setText(mimetype.toString());
+                       descriptionField.setText(description.toString());
+
+                       prefs=controller.getPreferences();
+                       if 
(prefs.testString("GNUNET-INSERT","INDEX-CONTENT","YES")) {
+                               methodCombo.setSelectedIndex(0);
+                               }
+                       else {
+                               methodCombo.setSelectedIndex(1);
+                               }
+
+                       keywordModel.clear();
+                       for (i=0; i<keywords.length; i++) {
+                               keywordModel.addElement(keywords[i]);
+                               }
+                       keywordField.setText("");
+               }
+
+               /**
+                * The keyword add button was clicked. Add whatever is in the 
keyword box to the list of keywords.
+                */
+
+               protected void addKeyword()
+               {
+                       String  key;
+
+                       key=keywordField.getText();
+                       if (key==null || key.trim().length()==0) {
+                               return;
+                               }
+                       key=key.trim();
+
+                       keywordModel.addElement(key);
+
+                       keywordField.setText("");
+               }
+
+               /**
+                * The keyword delete button was clicked. Delete the currently 
selected keyword.
+                */
+
+               protected void deleteKeyword()
+               {
+                       int[]   indices;
+                       int             i;
+
+                       indices=keywordList.getSelectedIndices();
+                       for (i=indices.length-1; i>=0; i--) {
+                               keywordModel.remove(indices[i]);
+                               }
+               }
+
+               public void leaveView()
+               {
+                       int     i;
+
+                       chosenFilename=filenameField.getText().trim();
+                       if (chosenFilename.length()==0) {
+                               chosenFilename="none specified";
+                               }
+
+                       chosenMime=mimeField.getText().trim();
+                       if (chosenMime.length()==0) {
+                               chosenMime="unknown";
+                               }
+
+                       chosenDescription=descriptionField.getText().trim();
+                       if (chosenDescription.length()==0) {
+                               chosenDescription="no description specified";
+                               }
+
+                       choosedToIndex=methodCombo.getSelectedIndex()==0;
+
+                       chosenKeywords=new String[keywordModel.getSize()];
+                       for (i=0; i<chosenKeywords.length; i++) {
+                               chosenKeywords[i]=(String) keywordModel.get(i);
+                               }
+               }
+       }
+
+       public class ReviewStep extends GWizardPage
+       {
+               private GStyledText     text;
+               private Icon                    dot;
+
+
+               public ReviewStep()
+               {
+                       super("Review");
+               }
+
+               public JComponent createView()
+               {
+                       dot=new ImageIcon(resources.getImageURL("dot.icon"));
+
+                       text=new GStyledText();
+                       StyleConstants.setLeftIndent(text.style("ind"),10);
+                       StyleConstants.setSpaceAbove(text.style("ind"),3);
+                       StyleConstants.setSpaceBelow(text.style("ind"),1);
+
+                       StyleConstants.setForeground(text.style("v"),new 
Color(33,87,136));
+                       
StyleConstants.setFontFamily(text.style("v"),"Monospaced");
+
+                       
StyleConstants.setFontFamily(text.style("fi"),"SansSerif");
+                       StyleConstants.setBold(text.style("fi"),true);
+                       StyleConstants.setItalic(text.style("fi"),true);
+                       StyleConstants.setForeground(text.style("fi"),new 
Color(33,87,136));
+                       return text;
+               }
+
+               public void updateView()
+               {
+                       StringBuffer    buf;
+                       int                             i;
+
+                       buf=new StringBuffer();
+                       if (chosenKeywords.length>0) {
+                               for (i=0; i<chosenKeywords.length; i++) {
+                                       buf.append(chosenKeywords[i]);
+                                       if (i<chosenKeywords.length-1) {
+                                               buf.append(", ");
+                                               }
+                                       }
+                               }
+                       else {
+                               buf.append("(none)");
+                               }
+
+                       text.clear();
+                       text.println("You'd like to insert file 
<fi>"+chosenFile.getLabel()+"</fi>.");
+                       text.print("Please check information you've entered are 
correct. ");
+                       text.println("Then click on Go button to perform 
insertion.");
+                       text.print("<ind>").print(dot).print(" Filename : 
<v>"+chosenFilename).println("</v></ind>");
+                       text.print("<ind>").print(dot).print(" Mime type : 
<v>"+chosenMime).println("</v></ind>");
+                       text.print("<ind>").print(dot).print(" Description : 
<v>"+chosenDescription).println("</v></ind>");
+                       text.print("<ind>").print(dot).print(" Insertion method 
: <v>"+(choosedToIndex ? "Index only" : "Full 
insertion")).println("</v></ind>");
+                       text.print("<ind>").print(dot).print(" Keywords : 
<v>"+buf).println("</v></ind>");
+               }
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/SController.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/SController.java      
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/SController.java      
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,53 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.swing;
+
+import org.gnu.freeway.protocol.fs.esed2.*;
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.net.*;
+import org.gnu.freeway.util.ui.*;
+
+import java.beans.*;
+import java.io.*;
+import java.util.logging.*;
+
+/**
+ *
+ */
+
+public interface SController extends Controller
+{
+       public static final String      QUIT_EVENT      =       "*quit*";
+
+       public Service service( Class c );
+       public void log( Level level, String msg );
+       public void err( String msg, Throwable x );
+
+       public void refreshMenus();
+
+       public void download( Node node );
+
+       public Policy getPolicy();
+
+       public void guiMessage( String str );
+       public void infoMessage( boolean doPopup, String str );
+
+       public void displayDirectory( String fileName, Node root );
+       public void showStats();
+       public CSSession connect();
+
+       public void addPropertyChangeListener( PropertyChangeListener listener 
);
+       public void addPropertyChangeListener( String name, 
PropertyChangeListener listener );
+       public void removePropertyChangeListener( PropertyChangeListener 
listener );
+       public void removePropertyChangeListener( String name, 
PropertyChangeListener listener );
+
+       public UIResources getResources();
+
+       public void enterCritical();
+       public void leaveCritical();
+
+       public File getCache();
+       public Prefs getPreferences();
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/SearchAdapter.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/SearchAdapter.java    
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/SearchAdapter.java    
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,44 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.swing;
+
+import org.gnu.freeway.protocol.fs.esed2.*;
+import org.gnu.freeway.util.ui.*;
+
+import java.io.*;
+
+/**
+ *
+ */
+
+public class SearchAdapter extends AbstractAdapter implements AFSConstants
+{
+       public SearchAdapter()
+       {
+               super(7);
+       }
+
+       protected void fillFacets( Object obj, Object[] facets )
+       {
+               Node    node;
+               String  str;
+
+               node=(Node) obj;
+
+               facets[0]=node.getDescription();
+               facets[1]=new Long(node.getFileIdentifier().getFileLength());
+
+               str=node.getFileName();
+               if (node.getMimeType().equals(GNUNET_DIRECTORY_MIME) && 
!str.endsWith(File.separator) ) {
+                       str+="/";
+                       }
+
+               facets[2]=str;
+               facets[3]=new Long(node.getFileIdentifier().getFileCRC());
+               facets[4]=node.getFileIdentifier().getFileQuery().toHex();
+               facets[5]=node.getFileIdentifier().getFileKey().toHex();
+               facets[6]=node.getMimeType();
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/SearchOverviewPanel.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/SearchOverviewPanel.java      
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/SearchOverviewPanel.java      
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,122 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.swing;
+
+import org.gnu.freeway.util.ui.*;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.beans.*;
+import javax.swing.*;
+
+/**
+ * Box displaying search results for the swing client.
+ */
+
+public class SearchOverviewPanel extends JPanel implements ActionListener, 
PropertyChangeListener
+{
+       private SController     controller;
+
+       private JTabbedPane     resultTab;
+       private JTextField      inputField;
+       private JButton         searchButton;
+
+
+       public SearchOverviewPanel( SController ctr )
+       {
+               super(new BorderLayout());
+               controller=ctr;
+
+               setup();
+       }
+
+       public String toString()
+       {
+               return "Search overview panel";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       protected void setup()
+       {
+               GBar    bar;
+
+               resultTab=new JTabbedPane();
+               resultTab.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
+
+               inputField=new JTextField(15);
+               inputField.addActionListener(this);
+
+               searchButton=new JButton("Search");
+               searchButton.addActionListener(this);
+
+               bar=new GBar();
+               bar.add("Keyword(s) :",inputField);
+               bar.add(searchButton);
+
+               add(bar,BorderLayout.SOUTH);
+               add(resultTab,BorderLayout.CENTER);
+       }
+
+       /**
+        * This method is called whenever the user clicks the search button of 
the main window.
+        * @param evt
+        */
+
+       public void actionPerformed( ActionEvent evt )
+       {
+               SearchPanel     panel;
+               String          str;
+               int                     index;
+
+               str=inputField.getText();
+               if (str.length()==0) {
+                       JOptionPane.showMessageDialog(this,"searchString == 
null");
+                       return;
+                       }
+
+               // remove heading spaces (parsing bugs if not)
+               str=str.trim();
+               if (str.length()==0) {
+                       JOptionPane.showMessageDialog(this,"No search key 
given!");
+                       return;
+                       }
+
+               // add a new page in the notebook with the search results. 
getSearchWindow returns the page
+               panel=new SearchPanel(controller,str);
+               if (panel.launch()) {
+                       index=resultTab.getTabCount();
+
+                       
panel.addPropertyChangeListener(SearchPanel.RESULTS_COUNT,this);
+                       
panel.addPropertyChangeListener(SearchPanel.CLOSE_ME,this);
+                       resultTab.addTab(panel.getKeyword(),panel);
+                       resultTab.setSelectedIndex(index);
+                       }
+
+               // reset search line to empty
+               inputField.setText("");
+       }
+
+       public void propertyChange( PropertyChangeEvent evt )
+       {
+               if (evt.getPropertyName().equals(SearchPanel.RESULTS_COUNT)) {
+                       updateTitleFor((SearchPanel) evt.getSource());
+                       }
+               else if (evt.getPropertyName().equals(SearchPanel.CLOSE_ME)) {
+                       close((SearchPanel) evt.getSource());
+                       }
+       }
+
+       protected void updateTitleFor( SearchPanel p )
+       {
+               
resultTab.setTitleAt(resultTab.indexOfComponent(p),p.getKeyword()+" 
("+p.getResultCount()+")");
+       }
+
+       protected void close( SearchPanel p )
+       {
+               resultTab.remove(p);
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/SearchPanel.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/SearchPanel.java      
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/SearchPanel.java      
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,472 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.swing;
+
+import org.gnu.freeway.*;
+import org.gnu.freeway.protocol.fs.esed2.*;
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.crypto.*;
+import org.gnu.freeway.util.net.*;
+import org.gnu.freeway.util.ui.*;
+
+import EDU.oswego.cs.dl.util.concurrent.*;
+
+import java.awt.event.*;
+import java.util.*;
+import java.util.logging.*;
+import javax.swing.*;
+import javax.swing.event.*;
+
+/**
+ * box displaying search results for the gtk+ client.
+ */
+
+public class SearchPanel extends JPanel implements AFSConstants, SwingConstants
+{
+       public static final String      RESULTS_COUNT   =       
"#results#count#";
+       public static final String      CLOSE_ME                =       
"#close#me#";
+
+       public static final int LM_TYPE_SEARCH          =       1;
+       public static final int LM_TYPE_DIRECTORY       =       2;
+       public static final int LM_TYPE_NSSEARCH        =       3;
+
+       private SController                                     controller;
+       private String                                          keyword;
+       private ListModel2                                      model;
+       private ScheduledTask           sendJob;
+
+       private GTable                          searchTable;
+       private DefaultListModel        searchModel;
+       private ListSelectionModel      searchSelection;
+
+       private UIResources                     resources;
+       private SelectDialog            selectDialog;
+
+
+       public SearchPanel( SController ctr, String str )
+       {
+               super(new TileLayout());
+               keyword=str;
+               controller=ctr;
+               resources=new 
UIResources("search-panel.xml",controller.getCache());
+               resources.setGlobalTarget(this);
+               setup();
+       }
+
+       public String toString()
+       {
+               return "Search panel";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       protected void setup()
+       {
+               JPopupMenu      popup;
+               GBar            bar;
+
+               resources.getAction("download.id").setEnabled(false);
+
+               searchModel=new DefaultListModel();
+
+               searchSelection=new DefaultListSelectionModel();
+               searchSelection.addListSelectionListener(new 
ListSelectionListener() {
+                       public void valueChanged( ListSelectionEvent evt )
+                       {
+                               
resources.getAction("download.id").setEnabled(!searchSelection.isSelectionEmpty());
+                       }
+                       });
+
+               bar=new GBar();
+               bar.add(resources.getAction("download.id"));
+               bar.add(resources.getAction("popup.searchClose"));
+               add(bar,TileLayout.SOUTH);
+
+               searchTable=new GTable(new 
SearchAdapter(),searchModel,searchSelection);
+               searchTable.setColumns(new GColumn[] {
+                       new GColumn("Description", 470, LEFT,   true),
+                       new GColumn("Size",        70,  RIGHT,  true),
+                       new GColumn("Filename",    200, LEFT,   true),
+                       new GColumn("CRC",         40,  LEFT,   false),
+                       new GColumn("HASH1",       40,  LEFT,   false),
+                       new GColumn("HASH2",       40,  LEFT,   false),
+                       new GColumn("Mimetype",    200, CENTER, true)
+                       });
+               add(new JScrollPane(searchTable));
+
+               // when we are destroyed (e.g. search aborted), stop the search 
thread
+               addHierarchyListener(new HierarchyListener() {
+                       public void hierarchyChanged( HierarchyEvent e )
+                       {
+                               if (getParent()==null && (e.getChangeFlags() & 
HierarchyEvent.PARENT_CHANGED)!=0) {
+                                       stopSearch();
+                                       }
+                       }
+                       });
+
+               // add a right button popup menu
+               resources.getAction("popup.selectall").setEnabled(true);
+               resources.getAction("popup.unselectall").setEnabled(true);
+               
resources.getAction("popup.searchSelectByName").setEnabled(true);
+               
resources.getAction("popup.searchSelectByDesc").setEnabled(true);
+               
resources.getAction("popup.searchSelectByMime").setEnabled(true);
+               
resources.getAction("popup.searchDownloadSelected").setEnabled(true);
+               resources.getAction("popup.searchClose").setEnabled(true);
+
+               popup=resources.getPopupMenu("popup.menu");
+
+               UIHelper.addPopupToComponent(popup,this);
+               UIHelper.addPopupToComponent(popup,bar);
+               UIHelper.addPopupToComponent(popup,searchTable);
+       }
+
+       public String getKeyword()
+       {
+               return keyword;
+       }
+
+       public int getResultCount()
+       {
+               return searchTable.getRowCount();
+       }
+
+       protected void addRow( Node node )
+       {
+               int     size;
+
+               size=searchTable.getRowCount();
+
+               searchModel.addElement(node);
+
+               firePropertyChange(RESULTS_COUNT,size,size+1);
+       }
+
+       /**
+        * Returns a box containing the search results list.
+        * @return
+        */
+
+       public boolean launch()
+       {
+               boolean ok;
+
+               model=new ListModel2();
+               model.type = LM_TYPE_SEARCH;
+               model.doTerminate = false;
+               model.skipMenuRefresh = false;
+               model.sem = new Semaphore(0);
+               model.searchTable= searchTable;
+               model.SEARCH_socket_= ((AbstractClient) 
controller.getApplication()).connect();
+               if (model.SEARCH_socket_ == null) {
+                       return false;
+                       }
+
+               /* start searching */
+               ok=startSearch();
+               if (!ok) {
+                       model.SEARCH_socket_.disconnect();
+                       return false;
+                       }
+               return ok;
+       }
+
+       /**
+        * The main method of the search-thread.
+        *
+        * @return OK on success, false on error
+        */
+
+       protected boolean startSearch()
+       {
+               HashCode512[]           keys;
+               final SendQueriesContext        sqc;
+               Task                            receiveThread;
+               String[]                        keywords;
+               ArrayList                       list;
+               String[]                        p;
+               int                                     i;
+               final Scheduler 
scheduler=controller.getApplication().getScheduler();
+
+               list=new ArrayList();
+
+               p=keyword.split("\\s+");
+               for (i=0; i<p.length; i++) {
+                       if (p[i].length()>0) {
+                               list.add(p[i]);
+                               }
+                       }
+
+               keywords=(String[]) list.toArray(new String[list.size()]);
+
+               keys=AFSUtils.parseKeywords(keywords);
+               if (keys.length==0) {
+                       return false;
+                       }
+
+               sqc=new SendQueriesContext(keys,model.SEARCH_socket_);
+               sqc.setTimeOut(0x00FFFFFF);     // "forever" is 6 months...
+
+               model.sqc = sqc;
+
+               sendJob=new ScheduledTask("SEND-JOB",new 
org.gnu.freeway.util.AbstractAction() {
+                       public void perform()
+                       {
+                               
sqc.repeatedlySend(scheduler,controller.getPolicy());
+                       }
+                       });
+               scheduler.addJob(sendJob,0);
+
+               receiveThread=new Task("RECEIVE-RESULTS",new 
org.gnu.freeway.util.AbstractAction() {
+                       public void perform()
+                       {
+                               sqc.receive(
+                                       controller.getPreferences(),
+                                       new SearchResultCallback() {
+                                               public void searchResult( 
RootNode root )
+                                               {
+                                                       displayResultGTK(root);
+                                               }
+
+                                               public void newNameFor( 
RootNode root, String str )
+                                               {
+                                                       //fixme: 'd better 
replace row in data model !
+                                                       searchResult(root);
+                                               }
+                                               },
+                                       new TestTerminateThread() {
+                                               public boolean test()
+                                               {
+                                                       return 
model.doTerminate;
+                                               }
+                                               }
+                                       );
+
+                               model.sem.release();    // signal: thread 
terminated
+                       }
+                       });
+               receiveThread.launch();
+               return true;
+       }
+
+       /**
+        * Stop the search thread and free the model.  This method MUST always
+        * be called when the search ends, either because the search was
+        * aborted or because gnunet-gtk exists as a whole.
+        */
+
+       protected void stopSearch()
+       {
+               Scheduler       scheduler;
+
+               //todo: verifier que c'est appelé
+
+               controller.log(Level.FINEST,"stopSearch called");
+               /* this must be done as a cron-job, since otherwise
+                it may deadlock (this is called from the
+                gtk event thread, and cron may be waiting for
+                the gtk event lock, so we can't delete a cron
+                job in this thread */
+               model.doTerminate = true;
+
+               scheduler=controller.getApplication().getScheduler();
+               scheduler.addJob(new ScheduledTask("STOP-SEARCH",new 
EvalAction(this,"stopSearch_")),0);
+       }
+
+       /**
+        * Cron job that stops the search.
+        * @throws InterruptedException
+        */
+
+       public void stopSearch_() throws InterruptedException
+       {
+               switch (model.type) {
+                       case LM_TYPE_DIRECTORY:
+                               break;
+
+                       case LM_TYPE_SEARCH:
+                               /* the terminated search thread ups this 
semaphore
+                                once it is done and we can free data 
structures */
+                               model.doTerminate = true;
+
+                               /* this signals the download thread to 
terminate */
+                               model.SEARCH_socket_.disconnect();
+
+                               /* stop the cron-job that does the requests */
+                               
controller.getApplication().getScheduler().deleteJob(sendJob);
+
+                               /* wait for download thread signal */
+                               model.sem.acquire();
+
+                               /* Now we can finally free the shared data 
structures.
+                                Note that the terminated search thread freed
+                                some of the memory that was allocated in
+                                x (see receive_Results_)
+                                we free the rest. */
+                               model.sem=null;
+                               break;
+
+                       case LM_TYPE_NSSEARCH:
+                               model.doTerminate = true;
+                               /* this signals the download thread to 
terminate */
+                               model.SEARCH_socket_.disconnect();
+                               /* wait for download thread signal */
+                               model.sem.acquire();
+
+                               /* Now we can finally free the shared data 
structures.
+                                Note that the terminated search thread freed
+                                some of the memory that was allocated in
+                                x (see receive_Results_)
+                                we free the rest. */
+                               model.sem = null;
+                               break;
+
+                       default:
+                               controller.log(Level.SEVERE,"Unknown model.type 
: "+model.type);
+                               break;
+                       }
+       }
+
+       /**
+        * Display results.  This is a callback from receive_Results that is
+        * called on every new result.
+        *
+        * @param node  Data about a file
+        */
+
+       public void displayResultGTK( Node node )
+       {
+               final   Node    _copy = (Node) PersistentHelper.copy(node);
+
+               if (model.doTerminate)
+                       return;
+
+               SwingUtilities.invokeLater(new Runnable() {
+                       public void run()
+                       {
+                               addRow(_copy);
+
+                               if (!model.skipMenuRefresh) {
+                                       controller.refreshMenus();
+                                       }
+                       }
+                       });
+       }
+
+       /**
+        * Selects all search results from the current search page.
+        */
+
+       public void onSelectAll()
+       {
+               searchTable.selectAll();
+       }
+
+       /**
+        * Unselects all search results from the current search page.
+        */
+
+       public void onClearSelection()
+       {
+               searchSelection.clearSelection();
+       }
+
+       public void onSelectByName()
+       {
+               if (selectDialog==null) {
+                       selectDialog=new SelectDialog(controller,(GFrame) 
SwingUtilities.windowForComponent(this));
+                       }
+               selectDialog.setTitle("Select By Filename");
+               selectDialog.display();
+               if (selectDialog.getTypedText()!=null) {
+                       if 
(UIHelper.selectMatching(searchTable,2,selectDialog.getTypedText())==0) {
+                               controller.guiMessage("No matches...");
+                               }
+                       }
+       }
+
+       public void onSelectByDescription()
+       {
+               if (selectDialog==null) {
+                       selectDialog=new SelectDialog(controller,(GFrame) 
SwingUtilities.windowForComponent(this));
+                       }
+               selectDialog.setTitle("Select By Description");
+               selectDialog.display();
+               if (selectDialog.getTypedText()!=null) {
+                       if 
(UIHelper.selectMatching(searchTable,0,selectDialog.getTypedText())==0) {
+                               controller.guiMessage("No matches...");
+                               }
+                       }
+       }
+
+       public void onSelectByMime()
+       {
+               if (selectDialog==null) {
+                       selectDialog=new SelectDialog(controller,(GFrame) 
SwingUtilities.windowForComponent(this));
+                       }
+               selectDialog.setTitle("Select By Mimetype");
+               selectDialog.display();
+               if (selectDialog.getTypedText()!=null) {
+                       if 
(UIHelper.selectMatching(searchTable,6,selectDialog.getTypedText())==0) {
+                               controller.guiMessage("No matches...");
+                               }
+                       }
+       }
+
+       /**
+        * This method is called whenever the user clicks the download button. 
It opens the "save-as" dialog.
+        */
+
+       public void onDownload()
+       {
+               Node    node;
+               int             i;
+
+               // download all selected entries
+               for (i=searchSelection.getMaxSelectionIndex(); 
i>=searchSelection.getMinSelectionIndex(); i--) {
+                       if (searchSelection.isSelectedIndex(i)) {
+                               node=(Node) searchModel.get(i);
+                               controller.download(node);
+
+                               // Remove entry from search results.
+                               // Yes, if the user cancel's the download, the 
entry does not re-appear.
+                               // That's intended, after all, if you cancel, 
it's probably because it took too long to download anyway...
+                               // If you really need it back, just search 
again!
+
+                               // already on swing thread
+                               searchModel.remove(i);
+                               
firePropertyChange(RESULTS_COUNT,searchModel.getSize()+1,searchModel.getSize());
+                               }
+                       }
+       }
+
+       /**
+        * Remove the active page from the search results notebook.
+        * The respective search will be stopped as well
+        * (by a callback assigned to the page earlier on).
+        */
+
+       public void onAbortSearch()
+       {
+               firePropertyChange(CLOSE_ME,null,new Object());
+       }
+}
+
+/**
+ * Data for a search process
+ */
+
+class ListModel2 extends Object
+{
+       public int                      type;
+       public JTable           searchTable;
+       public boolean          doTerminate;
+       public Semaphore        sem;
+       public CSSession        SEARCH_socket_;
+       /** contents determined by type! */
+       public Object           sqc;
+       /** don't refresh gtk menus (its slow)? (YES/NO) */
+       public boolean          skipMenuRefresh;
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/SearchWindow.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/SearchWindow.java     
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/SearchWindow.java     
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,280 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.swing;
+
+import org.gnu.freeway.*;
+import org.gnu.freeway.protocol.fs.esed2.*;
+import org.gnu.freeway.server.*;
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.net.*;
+import org.gnu.freeway.util.ui.*;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+
+/**
+ *
+ */
+
+public class SearchWindow extends GFrame implements AFSConstants
+{
+       private UIResources             resources;
+
+       private SController                     controller;
+       private JMenuBar                        menuBar;
+       private SearchOverviewPanel     searchPanel;
+       private GStatus                 statusBar;
+       private DaemonWindow    daemonWin;
+
+
+       public SearchWindow( SController ctr )
+       {
+               super(ctr,"search-window");
+               setTitle("GNUnet Swing : Search");
+               controller=ctr;
+
+
+               resources=new 
UIResources("search-window.xml",controller.getCache());
+               resources.setGlobalTarget(this);
+
+               daemonWin=new DaemonWindow(resources,controller);
+
+               init();
+       }
+
+       public String toString()
+       {
+               return "Main window";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       protected void init()
+       {
+               JPanel          panel;
+               UIAction        entry;
+
+//resources.getImage("app.logo");
+
+               resources.getAction("download.show.id").setEnabled(true);
+               
resources.getAction("download.show.id").setTarget(controller.getApplication());
+
+               getAction("file.insert").setEnabled(true);
+               getAction("file.download.uri").setEnabled(true);
+               getAction("file.import.directory").setEnabled(true);
+               getAction("file.unindex.file").setEnabled(true);
+               getAction("file.show.downloads").setEnabled(true);
+               getAction("file.show.messages").setEnabled(true);
+               getAction("file.show.stats").setEnabled(false);
+               getAction("file.quit").setEnabled(true);
+               getAction("advanced.manage.pseudonyms.create").setEnabled(true);
+               getAction("advanced.launch.gnunetd").setEnabled(false);
+               getAction("advanced.kill.gnunetd").setEnabled(false);
+               getAction("advanced.daemon.window").setEnabled(true);
+               getAction("help.about").setEnabled(true);
+
+               menuBar=resources.getMenuBar("window-menu");
+               
menuBar.add(UIHelper.createLAFMenu(controller.getApplication()));
+
+               searchPanel=new SearchOverviewPanel(controller);
+
+               statusBar=new GStatus();
+
+               panel=new JPanel(new BorderLayout());
+               panel.add(searchPanel,BorderLayout.CENTER);
+               panel.add(statusBar,BorderLayout.NORTH);
+
+               addWindowListener(new WindowAdapter() {
+                       public void windowClosing( WindowEvent evt )
+                       {
+                               close();
+                       }
+                       });
+
+               setContent(panel);
+               setJMenuBar(menuBar);
+
+//             getRootPane().setWindowDecorationStyle(JRootPane.FRAME);
+//             setIconImage(new 
ImageIcon(getClass().getClassLoader().getResource("images/gnunetd.png")).getImage());
+
+               entry=getAction("file.show.downloads");
+               entry.setEnabled(false);
+
+               refreshMenus();
+
+               Scheduler       scheduler;
+
+               scheduler=controller.getApplication().getScheduler();
+               scheduler.addJob(new 
ScheduledTask("REFRESH-CONNECTED-PEERS",new 
EvalAction(this,"onRefreshConnectedPeers"),Scheduler.SECS_5),Scheduler.SECS_5);
+       }
+
+       protected UIAction getAction( String str )
+       {
+               return resources.getAction(str);
+       }
+
+       public void refreshMenus()
+       {
+               GNDirectoryDatabase     db;
+               UIAction        entry;
+               boolean         
havePseudo,haveSearch,haveInsert,haveDirect,haveNamesp,haveAny;
+
+               db=new GNDirectoryDatabase(controller.getPreferences());
+
+               havePseudo = new 
Pseudonym(controller.getPreferences()).havePseudonyms();
+               haveSearch = 
db.iterateDirectoryDatabase(DIR_CONTEXT_SEARCH,null,null)>0;
+               haveInsert = 
db.iterateDirectoryDatabase(DIR_CONTEXT_INSERT,null,null)>0;
+               haveDirect = 
db.iterateDirectoryDatabase(DIR_CONTEXT_DIRECTORY,null,null)>0;
+               haveNamesp = 
db.iterateDirectoryDatabase(DIR_CONTEXT_INSERT_SB,null,null)>0;
+               haveAny = 
db.iterateDirectoryDatabase(DIR_CONTEXT_ALL,null,null)>0;
+
+               entry=getAction("advanced.assemble.directory.from.sr");
+               entry.setEnabled(haveSearch);
+
+               entry=getAction("advanced.assemble.directory.from.if");
+               entry.setEnabled(haveInsert);
+
+               entry=getAction("advanced.assemble.directory.from.lns");
+               entry.setEnabled(haveNamesp);
+
+               entry=getAction("advanced.assemble.directory.from.fid");
+               entry.setEnabled(haveDirect);
+
+               entry=getAction("advanced.assemble.directory.from.all");
+               entry.setEnabled(haveAny);
+
+               entry=getAction("advanced.manage.pseudonyms.delete");
+               entry.setEnabled(havePseudo);
+
+               entry=getAction("advanced.insert_into_namespace.select_sr");
+               entry.setEnabled(havePseudo && haveSearch);
+
+               entry=getAction("advanced.insert_into_namespace.select_if");
+               entry.setEnabled(havePseudo && haveInsert);
+
+               entry=getAction("advanced.insert_into_namespace.select_dd");
+               entry.setEnabled(havePseudo && haveDirect);
+
+               entry=getAction("advanced.insert_into_namespace.select_lns");
+               entry.setEnabled(havePseudo && haveNamesp);
+
+               entry=getAction("advanced.insert_into_namespace.select_all");
+               entry.setEnabled(havePseudo && haveAny);
+
+               entry=getAction("advanced.search.namespace");
+               entry.setEnabled(haveNamesp);
+
+               entry=getAction("advanced.reset_fid.list_sr");
+               entry.setEnabled(haveSearch);
+
+               entry=getAction("advanced.reset_fid.list_if");
+               entry.setEnabled(haveInsert);
+
+               entry=getAction("advanced.reset_fid.list_lns");
+               entry.setEnabled(haveNamesp);
+
+               entry=getAction("advanced.reset_fid.list_dd");
+               entry.setEnabled(haveDirect);
+
+               entry=getAction("advanced.reset_fid.all");
+               entry.setEnabled(haveAny);
+       }
+
+       public void cronCheckDaemon()
+       {
+               daemonWin.cronCheckDaemon();
+       }
+
+       /**
+        * This displays an about dialog
+        */
+
+       public void about()
+       {
+               String  about;
+
+               about="\nGNUnet "+GNUNetDaemon.VERSION+
+                       ", gnunet-swing 
"+controller.getApplication().getVersion()+
+                       "\n\n\n"+
+                       "GNUnet is free software, released under GNU General 
Public License version 2."+
+                       "\n\n\n"+
+                       "For more information, visit the GNUnet homepage at 
\n\n"+
+                       "http://www.ovmj.org/GNUnet/\n";;
+
+               JOptionPane.showOptionDialog(this,about,"About 
gnunet-swing",JOptionPane.YES_NO_OPTION,JOptionPane.INFORMATION_MESSAGE,null,new
 Object[] { "Right" },null);
+       }
+
+       public void showStats()
+       {
+               controller.showStats();
+       }
+
+       public void destroy_stub()
+       {
+       }
+
+       public void onCreatePseudonym()
+       {
+               CreatePseudonymDialog   dialog;
+
+               dialog=new CreatePseudonymDialog(controller,this);
+               dialog.display();
+       }
+
+       public void onDeletePseudonym()
+       {
+               DeletePseudonymDialog   dialog;
+
+               dialog=new DeletePseudonymDialog(controller,this);
+               dialog.display();
+       }
+
+       public void openSelectFile()
+       {
+               new InsertDialog(this).display();
+       }
+
+       public void onDaemonWindow()
+       {
+               daemonWin.display();
+       }
+
+       public void onLaunchDaemon()
+       {
+               daemonWin.display();
+               daemonWin.onLaunchDaemon();
+       }
+
+       public void onKillDaemon()
+       {
+               daemonWin.display();
+               daemonWin.onKillDaemon();
+       }
+
+       public void onRefreshConnectedPeers()
+       {
+               CSSession       sock;
+               CSResult        rv;
+
+               sock=controller.connect();
+               try {
+                       if (!sock.send(new CSGetClientCount())) {
+                               return;
+                               }
+
+                       rv=(CSResult) sock.receive(CSResult.class);
+                       if (rv==null) {
+                               return;
+                               }
+
+                       statusBar.setText("Connected hosts : "+rv.getResult());
+                       }
+               finally {
+                       sock.disconnect();
+                       }
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/SelectDialog.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/SelectDialog.java     
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/SelectDialog.java     
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,165 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.swing;
+
+import org.gnu.freeway.util.ui.*;
+
+import java.awt.event.*;
+import java.beans.*;
+import javax.swing.*;
+
+/**
+ *
+ */
+
+public class SelectDialog extends GDialog implements PropertyChangeListener
+{
+       private SController     controller;
+
+       private JTextField      textField;
+       private JOptionPane     optionPane;
+       private String          typedText;
+
+
+       /**
+        * Creates the reusable dialog.
+        * @param ctr
+        * @param parent
+        */
+
+       public SelectDialog( SController ctr, GFrame parent )
+       {
+               super(parent,"select-dialog");
+               controller=ctr;
+               typedText=null;
+
+               addWindowListener(new WindowAdapter() {
+                       public void windowClosing(WindowEvent we)
+                       {
+                               onClose();
+                       }
+                       });
+
+               // ensure the text field always gets the first focus
+               addComponentListener(new ComponentAdapter() {
+                       public void componentShown( ComponentEvent evt )
+                       {
+                               textField.requestFocusInWindow();
+                       }
+                       });
+       }
+
+       public String toString()
+       {
+               return "Select dialog";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       /**
+        * Returns null if the typed string was invalid;
+        * otherwise, returns the string as the user entered it.
+        * @return
+        */
+
+       public String getTypedText()
+       {
+               return typedText;
+       }
+
+       public JComponent createContent()
+       {
+               Object[]        options;
+
+               textField=new JTextField(10);
+               textField.addActionListener(new ActionListener() {
+                       public void actionPerformed( ActionEvent evt )
+                       {
+                               onEnter();
+                       }
+                       });
+
+               options=new Object[] { "Enter", "Cancel" };
+
+               optionPane=new JOptionPane(
+                               new Object[] { "Pattern ?", textField },
+                               JOptionPane.QUESTION_MESSAGE,
+                               JOptionPane.YES_NO_OPTION,
+                               new 
ImageIcon(controller.getResources().getImageURL("app.minilogo")),
+                               options,
+                               options[0]
+                               );
+               optionPane.addPropertyChangeListener(this);
+               return optionPane;
+       }
+
+       public void updateContent()
+       {
+               textField.setText("");
+       }
+
+       public void leaveContent()
+       {
+               textField.setText("");
+       }
+
+       /**
+        * This method reacts to state changes in the option pane.
+        * @param e
+        */
+
+       public void propertyChange( PropertyChangeEvent e )
+       {
+               String  str;
+               Object  value;
+
+               if (!isVisible() || e.getSource()!=optionPane) {
+                       return;
+                       }
+
+               str=e.getPropertyName();
+               if (str.equals(JOptionPane.VALUE_PROPERTY) || 
str.equals(JOptionPane.INPUT_VALUE_PROPERTY)) {
+                       value=optionPane.getValue();
+
+                       if (value==JOptionPane.UNINITIALIZED_VALUE) {
+                               //ignore reset
+                               return;
+                               }
+
+                       // if you don't do this, then if the user presses the 
same button next time, no property change event will be fired.
+                       optionPane.setValue(JOptionPane.UNINITIALIZED_VALUE);
+
+                       if (value.equals(optionPane.getOptions()[0])) {
+                               onEnter();
+                               }
+                       else {
+                               onClose();
+                               }
+                       }
+       }
+
+       protected void onEnter()
+       {
+               typedText=textField.getText().trim();
+               if (typedText.length()==0) {
+                       textField.selectAll();
+                       
JOptionPane.showMessageDialog(SelectDialog.this,"Please, enter a non empty 
response.","Try again",JOptionPane.ERROR_MESSAGE);
+                       textField.requestFocusInWindow();
+
+                       typedText=null;
+                       }
+               else {
+                       // we're done; clear and dismiss the dialog
+                       close();
+                       }
+       }
+
+       protected void onClose()
+       {
+               typedText=null;
+               close();
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/StatsWindow.java
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/StatsWindow.java      
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/StatsWindow.java      
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,156 @@
+/**
+ * @PROJECT_INFO@
+ */
+
+package org.gnu.freeway.protocol.fs.swing;
+
+import org.gnu.freeway.protocol.fs.esed2.*;
+import org.gnu.freeway.server.*;
+import org.gnu.freeway.util.*;
+import org.gnu.freeway.util.net.*;
+import org.gnu.freeway.util.ui.*;
+
+import java.util.logging.*;
+import javax.swing.*;
+
+/**
+ *
+ */
+
+public class StatsWindow extends GFrame implements AFSConstants, SwingConstants
+{
+       private SController     controller;
+       private UIResources     resources;
+
+       private GTable          statTable;
+       private GStatus         statusBar;
+
+
+       public StatsWindow( SController ctr )
+       {
+               super(ctr,"stats-window");
+               setTitle("GNUnet Swing : Statistics");
+               controller=ctr;
+
+               resources=new 
UIResources("stats-window.xml",controller.getCache());
+               resources.setGlobalTarget(this);
+       }
+
+       public String toString()
+       {
+               return "Statistics window";
+       }
+
+
+       
////////////////////////////////////////////////////////////////////////////////////////////////
+
+       public JComponent createContent()
+       {
+               JPanel  panel;
+               GBar    bar;
+
+               resources.getAction("main.refresh").setEnabled(true);
+               resources.getAction("main.close").setEnabled(true);
+
+               setJMenuBar(resources.getMenuBar("window-menu"));
+
+               bar=new GBar();
+               bar.add(resources.getAction("main.refresh"));
+               bar.add(resources.getAction("main.close"));
+
+               statTable=new GTable(new AbstractAdapter(2) {
+                       protected void fillFacets( Object obj, Object[] facets )
+                       {
+                               Stat    stat;
+
+                               stat=(Stat) obj;
+                               facets[0]=stat.getName();
+                               facets[1]=new Long(stat.get());
+                       }
+                       });
+               statTable.setColumns(new GColumn[] {
+                       new GColumn("Statistic", 400, RIGHT, true),
+                       new GColumn("Value",     270, LEFT,  true)
+                       });
+
+               statusBar=new GStatus();
+               statusBar.setText("");
+
+               panel=new JPanel(new TileLayout());
+               panel.add(bar,TileLayout.SOUTH);
+               panel.add(statusBar,TileLayout.NORTH);
+               panel.add(new JScrollPane(statTable));
+               return panel;
+       }
+
+       public void onRefresh()
+       {
+               if (!refresh()) {
+                       statusBar.setText("Failed to connect to daemon.");
+                       }
+       }
+
+       public void onClose()
+       {
+               close();
+       }
+
+       protected boolean refresh()
+       {
+               Stat[]                  stats;
+               int[]                           sel;
+               CSSession               sock;
+               DefaultListModel        sModel;
+               //CSStatistics          statMsg;
+               long                            t;
+               int                                     totalCounters,count,i;
+
+               sel=statTable.getSelectedRows();
+
+               sModel=(DefaultListModel) statTable.getUnsortedModel();
+               sModel.clear();
+
+               /*sock=controller.connect();
+               try {
+                       if (!sock.send(new CSStatisticsRequest())) {
+                               controller.log(Level.WARNING,"Error sending 
request for statistics to gnunetd.");
+                               return false;
+                               }
+
+                       count=0;
+                       totalCounters=1;
+
+                       while (count<totalCounters) {
+                               //statMsg=(CSStatistics) 
sock.receive(CSStatistics.class);
+                               //if (statMsg==null) {
+                               //      controller.log(Level.WARNING,"Error 
receiving reply for statistics from gnunetd.");
+                               //      return false;
+                               //      }
+
+                               if (count==0) {
+                                       t=Scheduler.toSeconds(Scheduler.now());
+                                       t-=statMsg.getStartTime();
+
+                                       statusBar.setText("Uptime : 
"+Utils.formatDuration(Scheduler.seconds(t))+" (refreshed at 
"+Utils.formatMoment(Scheduler.now())+")");
+                                       
totalCounters=statMsg.getTotalCounters();
+                                       }
+
+                               if (statMsg.getTotalCounters()!=totalCounters) {
+                                       controller.log(Level.WARNING,"Corrupted 
data ?");
+                                       return false;
+                                       }
+
+                               stats=statMsg.getStatistics();
+                               for (i=0; i<stats.length; i++) {
+                                       sModel.addElement(stats[i]);
+                                       }
+                               count+=stats.length;
+                               }
+                       }
+               finally {
+                       sock.disconnect();
+                       }*/
+               statTable.setSelectedRows(sel);
+               return true;
+       }
+}

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/about.c
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/about.c       2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/about.c       2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,128 @@
+/*
+     This file is part of GNUnet
+
+     GNUnet 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, or (at your
+     option) any later version.
+
+     GNUnet 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 GNUnet; see the file COPYING.  If not, write to the
+     Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+     Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file src/applications/afs/gtkui/about.c
+ * @author Christian Grothoff
+ * @author Igor Wronsky
+ *
+ * This file contains the about dialog.
+ **/
+
+#include "gnunet_afs_esed2.h"
+
+#include "helper.h"
+#include "about.h"
+
+#define ABOUT_STRING "\nGNUnet "\
+  VERSION\
+  ", gnunet-gtk "\
+  AFS_VERSION\
+  "\n\n\n"\
+  "GNUnet is free software, released under GNU General Public License version 
2."\
+  "\n\n\n"\
+  "For more information, visit the GNUnet homepage at \n\n"\
+  "http://www.ovmj.org/GNUnet/\n";
+
+
+/**
+ * This displays an about window
+ **/
+void about(GtkWidget *dummy,
+          gpointer data) {
+  GtkWidget * window;
+  GtkWidget * box1;
+  GtkWidget * table;
+  GtkWidget * text;
+  GtkWidget * button;
+
+  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+  gtk_signal_connect(GTK_OBJECT(window), 
+                     "delete_event",
+                     GTK_SIGNAL_FUNC(deleteEvent), 
+                    NULL);
+ 
+  gtk_window_set_title(GTK_WINDOW(window), 
+                      "About gnunet-gtk");
+  gtk_widget_set_usize(GTK_WIDGET(window), 
+                      600, 
+                      300);
+
+  box1 = gtk_vbox_new(FALSE, 0);
+  gtk_container_add(GTK_CONTAINER (window), 
+                   box1);
+  gtk_widget_show(box1);
+
+  table = gtk_table_new(2, 2, FALSE);
+  gtk_table_set_row_spacing(GTK_TABLE (table), 
+                           0,
+                           2);
+  gtk_table_set_col_spacing(GTK_TABLE (table), 
+                           0, 
+                           2);
+  gtk_box_pack_start(GTK_BOX (box1), 
+                    table, 
+                    TRUE,
+                    TRUE,
+                    0);
+  gtk_widget_show(table);
+
+  /* create a text widget */
+  text = gtk_text_new(NULL, NULL);
+  gtk_text_set_editable(GTK_TEXT (text), 
+                       FALSE);
+  gtk_table_attach(GTK_TABLE (table), 
+                  text,
+                  0, 
+                  1, 
+                  0, 
+                  1,
+                  GTK_EXPAND | GTK_SHRINK | GTK_FILL,
+                  GTK_EXPAND | GTK_SHRINK | GTK_FILL,
+                  0, 0);
+  gtk_widget_show(text);
+  gtk_widget_realize(text);
+
+  /* write some about text */
+  gtk_text_freeze(GTK_TEXT (text));
+
+  gtk_text_insert(GTK_TEXT(text), 
+                 NULL, 
+                 &text->style->black, 
+                 NULL,
+                 ABOUT_STRING, -1); 
+  
+  gtk_text_thaw(GTK_TEXT(text));
+
+  /* finish with a close button */
+  button = gtk_button_new_with_label("Right");
+  gtk_box_pack_start(GTK_BOX (box1), 
+                    button, 
+                    FALSE, 
+                    FALSE, 
+                    0);
+  gtk_signal_connect(GTK_OBJECT(button), 
+                    "clicked",
+                    GTK_SIGNAL_FUNC(destroyWidget), 
+                    window);
+  gtk_widget_show(button);
+  gtk_widget_show(window);
+}
+
+/* end of about.c */

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/about.h
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/about.h       2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/about.h       2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,35 @@
+/*
+     This file is part of GNUnet
+
+     GNUnet 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, or (at your
+     option) any later version.
+
+     GNUnet 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 GNUnet; see the file COPYING.  If not, write to the
+     Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+     Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file applications/afs/gtkui/about.h
+ * @author Christian Grothoff
+ **/
+
+#ifndef GTKUI_ABOUT_H
+#define GTKUI_ABOUT_H
+
+/**
+ * This displays an about window
+ **/
+void about(GtkWidget *dummy,
+          gpointer data);
+
+ 
+#endif

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/delete.c
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/delete.c      2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/delete.c      2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,175 @@
+/*
+     This file is part of GNUnet
+
+     GNUnet 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, or (at your
+     option) any later version.
+
+     GNUnet 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 GNUnet; see the file COPYING.  If not, write to the
+     Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+     Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file src/applications/afs/gtkui/delete.c
+ * @brief handles file deletions
+ * @author Igor Wronsky
+ **/
+
+#include "gnunet_afs_esed2.h"
+#include "helper.h"
+#include "main.h"
+#include "insertprogress.h"
+#include "delete.h"
+
+static gint setProgressValue(SaveCall *call) {
+  gtk_progress_set_value(GTK_PROGRESS(((SetProgress *)call->args)->bar),
+                        ((SetProgress *)call->args)->val);
+  gtkSaveCallDone(call->sem);
+  
+  return FALSE;
+}
+
+static void deleteModelCallback(ProgressStats * stats,
+                               InsertModel * ilm) {
+  SetProgress progress;
+       
+  progress.val = stats->progress;
+  progress.bar = ilm->progressBar;     
+  gtkSaveCall((GtkFunction) setProgressValue, &progress);
+}
+
+static gint destroyProgressBar(SaveCall *call) {
+  gtk_widget_destroy((GtkWidget *) call->args);
+  gtkSaveCallDone(call->sem);
+  
+  return FALSE;
+}
+
+static void deleteFileGtkThread(InsertModel * ilm) {
+  int res;
+  GNUNET_TCP_SOCKET * sock;
+  
+  SEMAPHORE_DOWN(refuseToDie);
+  sock = getClientSocket();
+  if (sock == NULL) {
+    guiMessage("Failed to connect to gnunetd.  Consult logs.");
+    SEMAPHORE_UP(refuseToDie);
+    return;
+  }
+  
+  LOG(LOG_DEBUG, 
+      "DEBUG: attempt to delete %s\n",
+      ilm->fileName);  
+
+  res = deleteFile(sock,
+                  ilm->fileName,
+                  (ProgressModel)&deleteModelCallback,
+                  ilm);
+
+  gtkSaveCall((GtkFunction) destroyProgressBar, ilm->progressBarWindow);
+  refreshMenuSensitivity();
+  
+  if(res != OK) 
+    guiMessage("Failed to delete\n\n%s", 
+              ilm->fileName);
+  else
+    guiMessage("File deleted.");
+
+  releaseClientSocket(sock);
+
+  SEMAPHORE_UP(refuseToDie);
+  
+  FREE(ilm->fileName);
+  FREE(ilm);
+}
+
+/**
+ * Callback for the file selection window. Launches the
+ * thread to delete the selected file.
+ *
+ * @param okButton not used
+ * @param window the file selection window
+ */
+static gint file_selected(GtkWidget * okButton, 
+                         GtkWidget * window) {
+  gchar * filename;
+  InsertModel * ilm;
+  PTHREAD_T deleteThread;
+
+  filename 
+    = gtk_file_selection_get_filename(GTK_FILE_SELECTION(window));
+  if ( (filename == NULL) ||
+       (0 == assertIsFile(filename)) ) {
+    guiMessage("Please select a file!\n");
+    gtk_widget_destroy(window);
+    return FALSE;
+  }
+
+  if (filename[0] != '/')
+    errexit("FATAL: ASSERTION failed: path name does not start with a '/'");
+
+  ilm = MALLOC(sizeof(InsertModel));
+  ilm->fileName = expandFileName(filename);
+
+  strcpy(ilm->opDescription, "deleted");
+  createInsertProgressBar(ilm);
+  /* start the delete thread */
+  if (0 != PTHREAD_CREATE(&deleteThread,
+                         (PThreadMain) deleteFileGtkThread,
+                         ilm,
+                         16 * 1024))
+    errexit("FATAL: could not create delete thread (%s)!\n",
+           STRERROR(errno));
+  PTHREAD_DETACH(&deleteThread);
+  
+  /* destroy the file selector */
+  gtk_widget_destroy(window);
+ 
+  return FALSE;
+}
+
+
+/**
+ * Close the open-file window.
+ **/
+static gint destroyOpenFile(GtkWidget * widget,
+                           GtkWidget * window) {
+  LOG(LOG_DEBUG, 
+      "DEBUG: destroying open-file window (%x)\n", 
+      window);
+  return TRUE;
+}
+
+/**
+ * Pops up a file selector for the user. Callback starts
+ * the file deletion thread.
+ *
+ **/
+void openDeleteFile(void) {
+  GtkWidget * window;
+
+  window = gtk_file_selection_new("Choose file to be unindexed");
+  gtk_signal_connect(GTK_OBJECT(window), 
+                    "destroy",
+                    GTK_SIGNAL_FUNC(destroyOpenFile),
+                    window);
+  gtk_signal_connect(GTK_OBJECT(GTK_FILE_SELECTION(window)->ok_button),
+                    "clicked", 
+                    GTK_SIGNAL_FUNC(file_selected),
+                    window);
+  gtk_signal_connect(GTK_OBJECT(GTK_FILE_SELECTION(window)->cancel_button),
+                    "clicked", 
+                    GTK_SIGNAL_FUNC(destroyWidget),
+                    window);
+  gtk_widget_show(window);
+}
+
+/* end of delete.c */

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/delete.h
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/delete.h      2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/delete.h      2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,35 @@
+/*
+     This file is part of GNUnet
+
+     GNUnet 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, or (at your
+     option) any later version.
+
+     GNUnet 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 GNUnet; see the file COPYING.  If not, write to the
+     Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+     Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file applications/afs/gtkui/delete.h
+ * @author Igor Wronsky 
+ **/
+
+#ifndef GTKUI_DELETE_H
+#define GTKUI_DELETE_H
+
+typedef struct {
+  size_t val;
+  GtkWidget *bar;
+} SetProgress;
+
+void openDeleteFile(void);
+
+#endif

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/directory.c
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/directory.c   2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/directory.c   2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,731 @@
+/*
+     This file is part of GNUnet
+
+     GNUnet 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, or (at your
+     option) any later version.
+
+     GNUnet 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 GNUnet; see the file COPYING.  If not, write to the
+     Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+     Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file src/applications/afs/gtkui/directory.c
+ * @brief Directory dialog for the AFS interface
+ * @author Christian Grothoff
+ * @author Igor Wronsky
+ **/
+#include "gnunet_afs_esed2.h"
+#include "helper.h"
+#include "insertprogress.h"
+#include "directory.h"
+#include "directorydisplay.h"
+
+
+/**
+ * @brief state of the create Directory window
+ **/
+typedef struct {
+  char * fileName;
+  GtkWidget * editAttributesWindow;
+  GtkWidget * fileNameLine;
+  GtkWidget * descriptionLine;
+  GtkWidget * keywordLine;
+  GtkWidget * keywordList;
+  GtkWidget * availableList;
+  GtkWidget * selectedList;
+  RootNode ** availableEntries;
+  RootNode ** selectedEntries;
+  int availableCount;
+  int selectedCount;
+} AssembleWindowModel;
+
+/**
+ * Collects the results of the assembly dialog, creates an insertion 
+ * progressbar and launches the insertion thread.
+ *
+ * @param dummy not used
+ * @param ewm the state of the edit window
+ **/
+static void startAssemble(GtkWidget * dummy, 
+                         AssembleWindowModel * ewm) {
+  int i;
+  RootNode * coll;
+  GNUnetDirectory * dir;
+  char * name;
+  InsertModel * ilm;
+  char * fileName;
+  gchar * txt;
+  PTHREAD_T insertThread;
+  
+  if (ewm->selectedCount == 0) {
+    guiMessage("WARNING: cowardly refusing to build empty directory.\n");
+    LOG(LOG_WARNING,
+       "WARNING: cowardly refusing to build empty directory.\n");
+    return;
+  }
+  ilm = MALLOC(sizeof(InsertModel));
+
+  name = gtk_entry_get_text(GTK_ENTRY(ewm->descriptionLine));
+  if (name == NULL)
+    name = "no description specified";
+
+  coll = MALLOC(ewm->selectedCount * sizeof(RootNode));
+  for (i=0;i<ewm->selectedCount;i++)
+    memcpy(&coll[i],
+          ewm->selectedEntries[i],
+          sizeof(RootNode));
+  dir = buildDirectory(ewm->selectedCount,
+                      name,
+                      coll);
+  FREE(coll);
+
+  /* get the published filename */
+  txt = gtk_entry_get_text(GTK_ENTRY(ewm->fileNameLine));
+  if (txt == NULL)
+    ilm->fileNameRoot = STRDUP("directory");
+  else
+    ilm->fileNameRoot = STRDUP(txt);
+ 
+  fileName = MALLOC(strlen("/tmp/gnunetdir_") + strlen(ilm->fileNameRoot) + 
strlen(".XXXXXX") + 1);
+  strcpy(fileName, "/tmp/gnunetdir_");
+  strcat(fileName, ilm->fileNameRoot);
+  strcat(fileName, ".XXXXXX");
+  mkstemp(fileName);
+  
+  if (SYSERR == writeGNUnetDirectory(dir, fileName)) {
+    LOG(LOG_WARNING,
+       "WARNING: could not write directory to temporary file.\n");
+    FREE(fileName);
+    FREE(dir);
+    FREE(ilm);
+    return;
+  }
+  ilm->fileName = fileName;
+  ilm->indexContent = NO;
+  ilm->deleteAfterInsert = YES;
+  /* get the new description, if any */
+  txt = gtk_entry_get_text(GTK_ENTRY(ewm->descriptionLine));
+  if (txt == NULL)
+    ilm->description = STRDUP("no description specified");
+  else
+    ilm->description = STRDUP(txt);
+  ilm->mimetype = STRDUP(GNUNET_DIRECTORY_MIME);
+
+  /* get list of keywords */
+  ilm->num_keywords = GTK_CLIST(ewm->keywordList)->rows;
+  if (ilm->num_keywords > 0) {
+    ilm->keywords = (char**) MALLOC(ilm->num_keywords * sizeof(char*));
+    for(i=0;i<ilm->num_keywords;i++) {     
+      gtk_clist_get_text(GTK_CLIST(ewm->keywordList),
+                        i,
+                        0,
+                        &txt);
+      ilm->keywords[i] = STRDUP(txt);
+    } 
+  } else
+    ilm->keywords = NULL;
+ 
+  strcpy(ilm->opDescription, "processed");
+  createInsertProgressBar(ilm);
+  /* start the insert thread */
+  if (0 != PTHREAD_CREATE(&insertThread,
+                         (PThreadMain) insertFileGtkThread,
+                         ilm,
+                         16 * 1024))
+    errexit("FATAL: could not create insert thread (%s)!\n",
+           STRERROR(errno));
+  PTHREAD_DETACH(&insertThread);
+
+  /* destroy the "assemble directory" window */
+  gtk_widget_destroy(ewm->editAttributesWindow);
+}
+
+/**
+ * Exit the application (called when the main window
+ * is closed or the user selects File-Quit).
+ **/
+static void destroyAssembleWindow(GtkWidget * widget,
+                                 AssembleWindowModel * ewm) {
+  int i;
+
+  for (i=0;i<ewm->availableCount;i++)
+    FREE(ewm->availableEntries[i]);
+  for (i=0;i<ewm->selectedCount;i++)
+    FREE(ewm->selectedEntries[i]);
+  GROW(ewm->availableEntries,
+       ewm->availableCount,
+       0);
+  GROW(ewm->selectedEntries,
+       ewm->selectedCount,
+       0);
+  FREE(ewm);
+}
+
+/**
+ * The keyword add button was clicked. Add whatever 
+ * is in the keyword box to the list of keywords.
+ *
+ * @param w not used
+ * @param ewm the state of the edit window
+ **/
+static void button_add_clicked(GtkWidget * w, 
+                              AssembleWindowModel * ewm) {
+  gchar * key;
+  gchar * newKeyword;
+  int i;
+
+  key = gtk_entry_get_text(GTK_ENTRY(ewm->keywordLine));
+  if (key == NULL) {
+    /* message to enter a string? */
+    return;
+  }    
+
+  newKeyword = STRDUP(key);
+  key = newKeyword;
+
+  /* remove trailing & heading spaces */
+  i = strlen(key)-1;
+  while ( (newKeyword[i] == ' ') && 
+         (i >= 0) ) {
+    newKeyword[i--] = '\0';
+  }
+  while (*newKeyword == ' ')
+    newKeyword++;
+
+  if ( *newKeyword == '\0' ) {
+    /* message to enter more than spaces? */    
+  } else {
+    gtk_clist_append(GTK_CLIST(ewm->keywordList),
+                    &newKeyword);
+  } 
+  FREE(key);
+  gtk_entry_set_text(GTK_ENTRY(ewm->keywordLine),
+                    "");
+}
+
+
+/**
+ * The keyword delete button was clicked. Delete the 
+ * currently selected keyword.
+ *
+ * @param w not used
+ * @param ewm state of the edit window
+ **/
+static void button_del_clicked(GtkWidget * w, 
+                              AssembleWindowModel * ewm) {
+  GList * tmp;
+
+  tmp = GTK_CLIST(ewm->keywordList)->selection;
+  if (NULL == tmp) {
+    /* message that keyword must be selected to delete one? */
+    return;
+  }  
+  gtk_clist_remove(GTK_CLIST(ewm->keywordList),
+                  (int)tmp->data);
+}
+
+/**
+ * The keyword add button was clicked. Add whatever 
+ * is in the keyword box to the list of keywords.
+ *
+ * @param w not used
+ * @param ewm the state of the edit window
+ **/
+static void button_select_clicked(GtkWidget * w, 
+                                 AssembleWindowModel * ewm) {
+  gchar * key[1];
+  GList * tmp;
+  int row;
+  int i;
+
+  tmp = GTK_CLIST(ewm->availableList)->selection;
+  if (NULL == tmp) 
+    return;  
+  row = (int) tmp->data;
+  if ( (row < 0) ||
+       (row >= ewm->availableCount) )
+    return; /* should never happen... */
+  key[0] = NULL;
+  gtk_clist_get_text(GTK_CLIST(ewm->availableList),
+                    row,
+                    0,
+                    &key[0]);
+  gtk_clist_append(GTK_CLIST(ewm->selectedList),
+                  &key[0]); 
+  gtk_clist_remove(GTK_CLIST(ewm->availableList),
+                  row);
+  if (row > 0)
+    gtk_clist_select_row(GTK_CLIST(ewm->availableList),
+                        row-1,
+                        0);
+  else
+    gtk_clist_select_row(GTK_CLIST(ewm->availableList),
+                        0,
+                        0);
+  GROW(ewm->selectedEntries,
+       ewm->selectedCount,
+       ewm->selectedCount+1);
+  ewm->selectedEntries[ewm->selectedCount-1] 
+    = ewm->availableEntries[row];
+  for (i=row;i<ewm->availableCount-1;i++)
+    ewm->availableEntries[i] 
+    = ewm->availableEntries[i+1];
+  GROW(ewm->availableEntries,
+       ewm->availableCount,
+       ewm->availableCount-1);
+}
+
+/**
+ * The keyword add button was clicked. Add whatever 
+ * is in the keyword box to the list of keywords.
+ *
+ * @param w not used
+ * @param ewm the state of the edit window
+ **/
+static void button_deselect_clicked(GtkWidget * w, 
+                                   AssembleWindowModel * ewm) {
+  gchar * key[1];
+  GList * tmp;
+  int row;
+  int i;
+
+  tmp = GTK_CLIST(ewm->selectedList)->selection;
+  if (NULL == tmp) 
+    return;  
+  row = (int) tmp->data;
+  if ( (row < 0) ||
+       (row >= ewm->selectedCount) )
+    return; /* should never happen... */
+  key[0] = NULL;
+  gtk_clist_get_text(GTK_CLIST(ewm->selectedList),
+                    row,
+                    0,
+                    &key[0]);
+  gtk_clist_append(GTK_CLIST(ewm->availableList),
+                  &key[0]); 
+  gtk_clist_remove(GTK_CLIST(ewm->selectedList),
+                  row);
+  if (row > 0)
+    gtk_clist_select_row(GTK_CLIST(ewm->selectedList),
+                        row-1,
+                        0);
+  else
+    gtk_clist_select_row(GTK_CLIST(ewm->selectedList),
+                        0,
+                        0);
+  GROW(ewm->availableEntries,
+       ewm->availableCount,
+       ewm->availableCount+1);
+  ewm->availableEntries[ewm->availableCount-1] 
+    = ewm->selectedEntries[row];
+  for (i=row;i<ewm->selectedCount-1;i++)
+    ewm->selectedEntries[i] 
+    = ewm->selectedEntries[i+1];
+  GROW(ewm->selectedEntries,
+       ewm->selectedCount,
+       ewm->selectedCount-1);
+}
+
+
+static void appendToCList(RootNode * root,
+                         AssembleWindowModel * ewm) {
+  gchar * entry[1];
+
+  entry[0] = STRDUP(root->header.description);
+  gtk_clist_append(GTK_CLIST(ewm->availableList), 
+                  entry);
+  FREE(entry[0]);
+  GROW(ewm->availableEntries,
+       ewm->availableCount,
+       ewm->availableCount+1);
+  ewm->availableEntries[ewm->availableCount-1]
+    = MALLOC(sizeof(RootNode));
+  memcpy(ewm->availableEntries[ewm->availableCount-1],
+        root,
+        sizeof(RootNode));
+}
+
+
+/**
+ * Open a window to allow the user to build a directory.
+ *
+ * @param unused GTK handle that is not used
+ * @param context selector for a subset of the known RootNodes
+ **/
+void openAssembleDirectoryDialog(GtkWidget * unused,
+                                unsigned int context) {
+  AssembleWindowModel * ewm;
+  GtkWidget * window;
+  GtkWidget * vbox, * hbox;
+  GtkWidget * clist;
+  GtkWidget * scrolled_window;
+  GtkWidget * label;
+  GtkWidget * separator; 
+  GtkWidget * button_add;
+  GtkWidget * button_delete;
+  GtkWidget * button_ok;
+  GtkWidget * button_cancel;
+  GtkWidget * keyword_line;
+  gchar * titles[1] = { "Keyword(s) used" };
+  gchar * titlesAvailable[1] = { "Files available" };
+  gchar * titlesSelected[1] = { "Files selected"};
+  gchar * directoryMimetype[1] = { GNUNET_DIRECTORY_MIME };
+
+  ewm = MALLOC(sizeof(AssembleWindowModel));
+  /* create new window for editing */
+  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+  ewm->editAttributesWindow = window;
+  gtk_widget_set_usize(GTK_WIDGET(window),
+                      620,
+                      480);
+  gtk_window_set_title(GTK_WINDOW(window), 
+                      "Assemble directory");
+
+  /* add container for window elements */
+  vbox = gtk_vbox_new(FALSE, 0);
+  gtk_container_add(GTK_CONTAINER(window),
+                   vbox);
+  gtk_widget_show(vbox);
+
+  /* when user clicks on close box, always "destroy" */
+  gtk_signal_connect(GTK_OBJECT(window),
+                    "delete_event",
+                    GTK_SIGNAL_FUNC(deleteEvent),
+                    ewm);
+  /* whenever edit window gets destroyed, 
+     free *ALL* ewm data */
+  gtk_signal_connect(GTK_OBJECT(window),
+                    "destroy",
+                    GTK_SIGNAL_FUNC(destroyAssembleWindow),
+                    ewm);
+
+  gtk_container_set_border_width(GTK_CONTAINER(window), 
+                                10);
+
+  /* Create a line to change the published filename */
+  hbox = gtk_hbox_new(FALSE, 0);
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    hbox,
+                    FALSE,
+                    TRUE,
+                    0);
+  gtk_widget_show(hbox);
+  label = gtk_label_new("Published directory name:");
+  gtk_box_pack_start(GTK_BOX(hbox),
+                    label, 
+                    FALSE, 
+                    FALSE, 
+                    0);
+  gtk_widget_show(label); 
+  ewm->fileNameLine = gtk_entry_new();
+  gtk_box_pack_start(GTK_BOX(hbox),
+                    ewm->fileNameLine,
+                    TRUE,
+                    TRUE,
+                    0);
+  gtk_entry_set_text(GTK_ENTRY(ewm->fileNameLine), 
+                    "");
+  gtk_widget_show(ewm->fileNameLine);
+  
+  /* Create a line to change the description */
+  hbox = gtk_hbox_new(FALSE, 0);
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    hbox,
+                    FALSE,
+                    TRUE,
+                    0);
+  gtk_widget_show(hbox);
+  label = gtk_label_new("Description:");
+  gtk_box_pack_start(GTK_BOX(hbox),
+                    label, 
+                    FALSE, 
+                    FALSE, 
+                    0);
+  gtk_widget_show(label);  
+  ewm->descriptionLine = gtk_entry_new();
+  gtk_box_pack_start(GTK_BOX(hbox),
+                    ewm->descriptionLine, 
+                    TRUE, 
+                    TRUE,
+                    0);
+  gtk_entry_set_text(GTK_ENTRY(ewm->descriptionLine), 
+                    "A GNUnet directory");
+  gtk_widget_show(ewm->descriptionLine);
+  
+  separator = gtk_hseparator_new();
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    separator,
+                    TRUE, 
+                    TRUE,
+                    0);
+  gtk_widget_show(separator);
+
+  /* add a list of keywords */
+  scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
+                                GTK_POLICY_AUTOMATIC, 
+                                GTK_POLICY_ALWAYS);
+  gtk_box_pack_start(GTK_BOX(vbox), 
+                    scrolled_window, 
+                    TRUE, 
+                    TRUE, 
+                    0);
+  gtk_widget_show(scrolled_window);  
+  clist = gtk_clist_new_with_titles(1, titles); 
+  /* add mimetype as a keyword */
+  gtk_clist_append(GTK_CLIST(clist), directoryMimetype);
+  ewm->keywordList = clist;
+  gtk_container_add(GTK_CONTAINER(scrolled_window), 
+                   clist);
+  gtk_widget_show(clist);
+
+  /* add a line to input new keywords */
+  keyword_line = gtk_entry_new();
+  ewm->keywordLine = keyword_line;
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    keyword_line, 
+                    FALSE,
+                    FALSE,
+                    0);
+  gtk_entry_set_text(GTK_ENTRY(keyword_line), 
+                    "");
+  gtk_signal_connect(GTK_OBJECT(keyword_line),
+                    "activate",
+                     GTK_SIGNAL_FUNC(button_add_clicked),
+                     ewm);
+  gtk_widget_show(keyword_line);
+
+  /* add the buttons to add and delete keywords */
+  hbox = gtk_hbox_new(FALSE, 0);
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    hbox,
+                    FALSE,
+                    TRUE,
+                    0);
+  gtk_widget_show(hbox);
+  button_add = gtk_button_new_with_label("Add keyword");
+  button_delete = gtk_button_new_with_label("Delete keyword");
+  gtk_box_pack_start(GTK_BOX(hbox), 
+                    button_add, 
+                    TRUE, 
+                    TRUE, 
+                    0);
+  gtk_box_pack_start(GTK_BOX(hbox), 
+                    button_delete, 
+                    TRUE, 
+                    TRUE, 
+                    0);
+  gtk_signal_connect(GTK_OBJECT(button_add),
+                    "clicked",
+                    GTK_SIGNAL_FUNC(button_add_clicked),
+                    ewm);
+  gtk_signal_connect(GTK_OBJECT(button_delete), 
+                    "clicked",
+                    GTK_SIGNAL_FUNC(button_del_clicked),
+                    ewm);
+  gtk_widget_show(button_add);
+  gtk_widget_show(button_delete);
+
+  separator = gtk_hseparator_new();
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    separator,
+                    TRUE, 
+                    TRUE,
+                    0);
+  gtk_widget_show(separator);
+
+  /* add the box for the two lists */
+  hbox = gtk_hbox_new(FALSE, 0);
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    hbox,
+                    TRUE,
+                    TRUE,
+                    0);
+  gtk_widget_show(hbox);
+
+  /* add a list of available entries */
+  scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
+                                GTK_POLICY_AUTOMATIC, 
+                                GTK_POLICY_ALWAYS);
+  gtk_box_pack_start(GTK_BOX(hbox), 
+                    scrolled_window, 
+                    TRUE, 
+                    TRUE, 
+                    0);
+  gtk_widget_show(scrolled_window);  
+  clist = gtk_clist_new_with_titles(1, titlesAvailable); 
+  ewm->availableList = clist;
+  gtk_container_add(GTK_CONTAINER(scrolled_window), 
+                   clist);
+  /* add the known RootNodes to the list */
+  gtk_clist_freeze(GTK_CLIST(clist));
+  iterateDirectoryDatabase(context,
+                          (RootNodeCallback)&appendToCList,
+                          ewm);
+  gtk_clist_thaw(GTK_CLIST(clist));
+  gtk_widget_show(clist);
+
+
+  /* add a list of selected entries */
+  scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
+                                GTK_POLICY_AUTOMATIC, 
+                                GTK_POLICY_ALWAYS);
+  gtk_box_pack_start(GTK_BOX(hbox), 
+                    scrolled_window, 
+                    TRUE, 
+                    TRUE, 
+                    0);
+  gtk_widget_show(scrolled_window);  
+  clist = gtk_clist_new_with_titles(1, titlesSelected); 
+  ewm->selectedList = clist;
+  gtk_container_add(GTK_CONTAINER(scrolled_window), 
+                   clist);
+  gtk_widget_show(clist);
+
+
+  /* add the box for the buttons to move between the
+     two lists */
+  hbox = gtk_hbox_new(FALSE, 0);
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    hbox,
+                    FALSE,
+                    TRUE,
+                    0);
+  gtk_widget_show(hbox);
+
+  button_add = gtk_button_new_with_label("=>");
+  button_delete = gtk_button_new_with_label("<=");
+  gtk_box_pack_start(GTK_BOX(hbox), 
+                    button_add, 
+                    TRUE, 
+                    TRUE, 
+                    0);
+  gtk_box_pack_start(GTK_BOX(hbox), 
+                    button_delete, 
+                    TRUE, 
+                    TRUE, 
+                    0);
+  gtk_signal_connect(GTK_OBJECT(button_add),
+                    "clicked",
+                    GTK_SIGNAL_FUNC(button_select_clicked),
+                    ewm);
+  gtk_signal_connect(GTK_OBJECT(button_delete), 
+                    "clicked",
+                    GTK_SIGNAL_FUNC(button_deselect_clicked),
+                    ewm);
+  gtk_widget_show(button_add);
+  gtk_widget_show(button_delete);
+
+
+
+  /* add the insertion ok/cancel buttons */
+  separator = gtk_hseparator_new();
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    separator,
+                    TRUE, 
+                    TRUE,
+                    0);
+  gtk_widget_show(separator);
+
+  hbox = gtk_hbox_new(FALSE, 0);
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    hbox, 
+                    FALSE, 
+                    TRUE, 
+                    0);
+  gtk_widget_show(hbox);
+  button_ok = gtk_button_new_with_label("Ok");
+  button_cancel = gtk_button_new_with_label("Cancel");
+  gtk_box_pack_start(GTK_BOX(hbox),
+                    button_ok,
+                    TRUE,
+                    TRUE,
+                    0);
+  gtk_box_pack_start(GTK_BOX(hbox), 
+                    button_cancel, 
+                    TRUE,
+                    TRUE, 
+                    0);
+  gtk_signal_connect(GTK_OBJECT(button_ok), 
+                    "clicked",
+                    GTK_SIGNAL_FUNC(startAssemble),
+                    ewm);
+  gtk_signal_connect(GTK_OBJECT(button_cancel),
+                    "clicked",
+                    GTK_SIGNAL_FUNC(destroyWidget),
+                    window);
+  gtk_widget_show(button_ok);
+  gtk_widget_show(button_cancel);
+
+  /* all clear, show the window */
+  gtk_widget_show(window);
+}
+
+/**
+ * Callback for displaying user-selected directory
+ **/ 
+static gint importDirectoryCallback(GtkWidget * okButton,
+                                   GtkWidget * window) 
+{
+  gchar * filename;
+
+  filename 
+    = gtk_file_selection_get_filename(GTK_FILE_SELECTION(window));
+  if ( (filename == NULL) ||
+       (0 == assertIsFile(filename)) ) {
+    guiMessage("Please select a file!\n");
+    gtk_widget_destroy(window);
+    return FALSE;
+  }
+ 
+  displayDirectory(filename,
+                  NULL);
+
+  gtk_widget_destroy(window); 
+
+  return FALSE;
+}
+
+/**
+ * Asks user to select a .gnd directory (from disk) to be displayed
+ **/
+void importDirectory(void) 
+{
+  GtkWidget * window;
+  char pattern[16];
+  
+  window = gtk_file_selection_new("Choose directory to be imported");
+
+  sprintf(pattern, "*%s", GNUNET_DIRECTORY_EXT);
+  gtk_file_selection_complete(GTK_FILE_SELECTION(window),
+                             pattern);
+  
+  gtk_signal_connect(GTK_OBJECT(window),
+                     "destroy",
+                     GTK_SIGNAL_FUNC(destroyWidget),
+                     window);
+  gtk_signal_connect(GTK_OBJECT(GTK_FILE_SELECTION(window)->ok_button),
+                     "clicked",
+                     GTK_SIGNAL_FUNC(importDirectoryCallback),
+                     window);
+  gtk_signal_connect(GTK_OBJECT(GTK_FILE_SELECTION(window)->cancel_button),
+                     "clicked",
+                     GTK_SIGNAL_FUNC(destroyWidget),
+                     window);
+  gtk_widget_show(window);
+
+}
+
+
+/* end of directory.c */

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/directory.h
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/directory.h   2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/directory.h   2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,44 @@
+/*
+     This file is part of GNUnet
+
+     GNUnet 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, or (at your
+     option) any later version.
+
+     GNUnet 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 GNUnet; see the file COPYING.  If not, write to the
+     Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+     Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file applications/afs/gtkui/directory.h
+ * @author Christian Grothoff
+ **/
+
+#ifndef GTKUI_DIRECTORY_H
+#define GTKUI_DIRECTORY_H
+
+
+
+/**
+ * Open a window to allow the user to build a directory.
+ *
+ * @param context selector for a subset of the known RootNodes
+ **/
+void openAssembleDirectoryDialog(GtkWidget * unused,
+                                unsigned int context);
+
+/**
+ * Asks user to select a .gnd directory (from disk) to be displayed
+ **/
+void importDirectory(void);
+
+
+#endif

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/directorydisplay.c
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/directorydisplay.c    
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/directorydisplay.c    
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,91 @@
+/*
+     This file is part of GNUnet
+
+     GNUnet 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, or (at your
+     option) any later version.
+
+     GNUnet 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 GNUnet; see the file COPYING.  If not, write to the
+     Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+     Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file src/applications/afs/gtkui/directorydisplay.c
+ * @brief code that displays the contents of a directory
+ * @author Christian Grothoff
+ **/
+
+
+#include "gnunet_afs_esed2.h"
+#include "helper.h"
+#include "directory.h"
+#include "directorydisplay.h"
+#include "search.h"
+
+void displayDirectory(char * filename,
+                     RootNode * rn) {
+  GtkWidget * box;
+  ListModel * model;  
+  GNUnetDirectory * dir;
+  int i;
+
+  dir = readGNUnetDirectory(filename);
+  if (dir == NULL) {
+    LOG(LOG_WARNING,
+        "WARNING: directory downloaded (%s) has invalid format.\n",
+        filename);
+    guiMessage("WARNING: directory downloaded (%s) has invalid format.\n",
+               filename);
+    return;
+  }
+  model = (ListModel*) MALLOC(sizeof(ListModel));
+  model->type = LM_TYPE_DIRECTORY;
+  box = initializeSearchResultList(model);
+  
+  /* do a nested freeze on the clist, for efficiency */
+  gtk_clist_freeze(GTK_CLIST(model->search_result_list));
+    
+  for (i=0;i<ntohl(dir->number_of_files);i++) {
+    /* sneaky side-effect: add to state DB!
+     (note that if you download a directory
+     with gnunet-download, this does not happen
+     since we don't know the mime-type in
+     gnunet-download.) */
+    makeRootNodeAvailable(&((GNUnetDirectory_GENERIC*)dir)->contents[i], 
+                         DIR_CONTEXT_DIRECTORY);
+    model->skipMenuRefresh = (i==ntohl(dir->number_of_files)-1 ? NO : YES );
+    displayResultGTK(&((GNUnetDirectory_GENERIC*)dir)->contents[i],
+                    model);
+  }
+  FREE(dir);
+ 
+  gtk_clist_thaw(GTK_CLIST(model->search_result_list));
+ 
+  if (rn != NULL ) {
+    rn->header.description[MAX_DESC_LEN-1] = '\0';
+    addToNotebook(rn->header.description,
+                 box);
+  } else {
+    char * fileNameRoot = filename;
+    int i;
+
+    for (i=strlen(filename)-1;i>=0;i--) {
+      if (filename[i] == DIR_SEPARATOR) {
+        fileNameRoot = &filename[i+1];
+        break;
+      }
+    }
+    addToNotebook(fileNameRoot,
+                 box);
+  }
+}
+
+/* end of directorydisplay.c */

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/directorydisplay.h
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/directorydisplay.h    
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/directorydisplay.h    
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,36 @@
+/*
+     This file is part of GNUnet
+
+     GNUnet 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, or (at your
+     option) any later version.
+
+     GNUnet 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 GNUnet; see the file COPYING.  If not, write to the
+     Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+     Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file applications/afs/gtkui/directorydisplay.h
+ * @author Christian Grothoff
+ **/
+
+#ifndef GTKUI_DIRECTORY_DISPLAY_H
+#define GTKUI_DIRECTORY_DISPLAY_H
+
+#include "gnunet_afs_esed2.h"
+#include <gtk/gtk.h>
+#include "directorydisplay.h"
+#include "download.h"
+
+void displayDirectory(char * filename,
+                     RootNode * rn);
+ 
+#endif

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/download.c
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/download.c    2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/download.c    2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,887 @@
+/*
+     This file is part of GNUnet
+
+     GNUnet 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, or (at your
+     option) any later version.
+
+     GNUnet 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 GNUnet; see the file COPYING.  If not, write to the
+     Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+     Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file src/applications/afs/gtkui/download.c
+ * @brief code that handles the download window
+ * @author Christian Grothoff
+ * @author Igor Wronsky
+ *
+ * FIXME:
+ * - shutdown of gnunet-gtk does NOT terminate
+ *   each of the pending downloads.  There
+ *   should be a handler function that is invoked
+ *   whenever gnunet-gtk shuts down and that
+ *   stops all pending downloads.  This used to
+ *   be implemented but got lost when Igor added
+ *   the download window.
+ **/
+
+#include "gnunet_afs_esed2.h"
+#include "helper.h"
+#include "directorydisplay.h"
+#include "download.h"
+#include "main.h"
+
+GtkWidget * dlWindow = NULL;
+
+#define DEBUG_WRITE_CPSDATA NO
+
+/* Suitable values of DownloadModel.downloadStatus */
+#define DOWNLOAD_COMPLETE 0
+#define DOWNLOAD_FAILED   1
+#define DOWNLOAD_ABORTED  2
+#define DOWNLOAD_PENDING  3
+
+/* colors taken from x-chat source, regards */
+GdkColor textColors[] = {
+        {0, 0xcf3c, 0xcf3c, 0xcf3c}, /* 0  white */
+        {0, 0x0000, 0x0000, 0x0000}, /* 1  black */
+        {0, 0x0000, 0x0000, 0xcccc}, /* 2  blue */
+        {0, 0x0000, 0xcccc, 0x0000}, /* 3  green */
+        {0, 0xdddd, 0x0000, 0x0000}, /* 4  red */
+        {0, 0xaaaa, 0x0000, 0x0000}, /* 5  light red */
+        {0, 0xbbbb, 0x0000, 0xbbbb}, /* 6  purple */
+        {0, 0xffff, 0xaaaa, 0x0000}, /* 7  orange */
+        {0, 0xeeee, 0xdddd, 0x2222}, /* 8  yellow */
+        {0, 0x3333, 0xdede, 0x5555}, /* 9  green */
+        {0, 0x0000, 0xcccc, 0xcccc}, /* 10 aqua */
+        {0, 0x3333, 0xeeee, 0xffff}, /* 11 light aqua */
+        {0, 0x0000, 0x0000, 0xffff}, /* 12 blue */
+        {0, 0xeeee, 0x2222, 0xeeee}, /* 13 light purple */
+        {0, 0x7777, 0x7777, 0x7777}, /* 14 grey */
+        {0, 0x9999, 0x9999, 0x9999}, /* 15 light grey */
+        {0, 0xa4a4, 0xdfdf, 0xffff}, /* 16 marktext Back (blue) */
+        {0, 0x0000, 0x0000, 0x0000}, /* 17 marktext Fore (black) */
+        {0, 0xdf3c, 0xdf3c, 0xdf3c}, /* 18 foreground (white) */
+        {0, 0x0000, 0x0000, 0x0000}, /* 19 background (black) */
+        {0, 0x8c8c, 0x1010, 0x1010}, /* 20 tab New Data (dark red) */
+        {0, 0x0000, 0x0000, 0xffff}, /* 21 tab Nick Mentioned (blue) */
+        {0, 0xf5f5, 0x0000, 0x0000}, /* 22 tab New Message (red) */
+};
+
+static void selectAll(void);
+static void unSelectAll(void);
+static void removeFinished(void);
+static void abortHelper(void);
+static void hideHelper(void);
+static gint abortSelectedDownloads(GtkWidget * widget,
+                           GtkWidget * clist);
+
+static GtkItemFactoryEntry dlWindowMenu[] = {
+ { "/Select all",       NULL,    selectAll,           0, "<Item>" },
+ { "/Unselect all",     NULL,    unSelectAll,         0, "<Item>" },
+ { "/sep1",             NULL,    NULL,                0, "<Separator>" },
+ { "/Remove selected",  NULL,    abortHelper,         0, "<Item>" },
+ { "/Remove finished",  NULL,    removeFinished,      0, "<Item>" },
+ { "/sep2",             NULL,    NULL,                0, "<Separator>" },
+ { "/Hide dl window",   NULL,    hideHelper,          0, "<Item>" }
+};
+
+static gint dlWindowMenuItems 
+  = sizeof (dlWindowMenu) / sizeof (dlWindowMenu[0]);
+
+static void selectAll(void) 
+{
+    GtkWidget * clist;
+    
+    clist = gtk_object_get_data(GTK_OBJECT(dlWindow),
+                                "LIST");
+    gtk_clist_select_all(GTK_CLIST(clist));                                
+}
+
+static void unSelectAll(void) 
+{
+    GtkWidget * clist;
+    
+    clist = gtk_object_get_data(GTK_OBJECT(dlWindow),
+                                "LIST");
+    gtk_clist_unselect_all(GTK_CLIST(clist));                              
+}
+
+static void removeFinished(void)
+{
+    GtkCList * clist;
+    int i;
+    gchar * string;
+
+    unSelectAll();
+
+    clist = GTK_CLIST(gtk_object_get_data(GTK_OBJECT(dlWindow),
+                                           "LIST"));
+    gtk_clist_freeze(clist);
+
+    for(i=0;i<clist->rows;i++) {
+      gtk_clist_get_text(clist, 
+                         i,
+                        1,
+                        &string);
+      if(strcmp(string, "DONE")==0)
+        gtk_clist_select_row(clist,
+                            i,
+                            1);
+      else
+       gtk_clist_unselect_row(clist,
+                            i,
+                            1);
+    }
+      
+    gtk_clist_thaw(clist);
+    
+    abortSelectedDownloads(NULL, GTK_WIDGET(clist));
+}
+
+static void hideHelper(void)
+{
+  if(dlWindow)
+    gtk_widget_hide(dlWindow);
+}
+  
+static void abortHelper(void)
+{
+    GtkWidget * clist;
+    
+    clist = gtk_object_get_data(GTK_OBJECT(dlWindow),
+                                "LIST");
+    abortSelectedDownloads(NULL, clist);
+}
+
+/**
+ * Changes the current sort column and sorts the list.
+ **/
+static void sort_column_callback(GtkCList * clist,
+                                 gint column,
+                                 gpointer data) {
+  static int sortOrder[8]={0,0,0,0,0,0,0,0};
+
+  sortOrder[column]^=1;
+
+  if(sortOrder[column]==1)
+    gtk_clist_set_sort_type(clist,
+                           GTK_SORT_ASCENDING);
+  else
+    gtk_clist_set_sort_type(clist,
+                           GTK_SORT_DESCENDING);
+  
+  /* Sort column 0 as string, 1 as percent and rest as numbers */
+  switch(column) {
+    case 0: 
+      gtk_clist_set_compare_func(clist,
+                                 (GtkCListCompareFunc)alphaComp);
+      break; 
+    case 1:
+      gtk_clist_set_compare_func(clist,
+                                 (GtkCListCompareFunc)percentComp);
+      break;
+    default:
+      gtk_clist_set_compare_func(clist,
+                                 (GtkCListCompareFunc)numericComp);
+      break;
+  }
+  gtk_clist_set_sort_column(clist, column);
+  gtk_clist_freeze(clist);
+  gtk_clist_sort(clist);
+  gtk_clist_thaw(clist);
+}
+
+/**
+ * "delete_event" handler for a download. Sets the 
+ * download state as aborted, and releases the wait semaphore for 
+ * the thread awaiting download completion. Returns TRUE so that 
+ * gtk doesn't destroy the widget (its never destroyed).
+ *
+ * @param widget not used
+ * @param clist the list of downloads
+ **/
+static gint abortSelectedDownloads(GtkWidget * widget,
+                                  GtkWidget * clist) {
+  DownloadModel * dlm;
+  int row;
+  GList * tmp;
+
+  LOG(LOG_DEBUG,
+      "DEBUG: abortSelectedDownloads %x (%x)\n",
+      clist, widget);
+  if(!GTK_CLIST(clist))
+    return TRUE;
+  
+  gtk_clist_freeze(GTK_CLIST(clist));
+
+  tmp = GTK_CLIST(clist)->selection;
+  while (tmp) {
+    row = (int)tmp->data;
+    tmp = tmp->next;
+
+    /* if dlm is not NULL, abort the download */
+    dlm = gtk_clist_get_row_data(GTK_CLIST(clist),
+                                row);
+    if(dlm) {
+      dlm->downloadStatus = DOWNLOAD_ABORTED;
+      SEMAPHORE_UP(dlm->doneSem);
+    } 
+    
+    /* remove list entry */
+    gtk_clist_remove(GTK_CLIST(clist),
+                    row);
+  }
+
+  gtk_clist_thaw(GTK_CLIST(clist));
+  
+  return TRUE;
+}
+
+static gint displayStats(SaveCall *call) {
+  DLStats *dlStats = (DLStats *) call->args;
+  DownloadModel *dlm = dlStats->dlm;
+  gint row;
+
+  gtk_clist_freeze(GTK_CLIST(dlm->dlList));
+  row = gtk_clist_find_row_from_data(GTK_CLIST(dlm->dlList),
+                                    dlStats->dlm);
+  gtk_clist_set_text(GTK_CLIST(dlm->dlList),
+                    row,
+                    1,
+                    dlStats->perc);
+  gtk_clist_set_text(GTK_CLIST(dlm->dlList),
+                    row,
+                    2,
+                    dlStats->pos);
+  gtk_clist_set_text(GTK_CLIST(dlm->dlList),
+                    row,
+                    4,
+                    dlStats->areq);
+  gtk_clist_set_text(GTK_CLIST(dlm->dlList),
+                    row,
+                    5,
+                    dlStats->cra);
+  gtk_clist_set_text(GTK_CLIST(dlm->dlList),
+                    row,
+                    6,
+                    dlStats->tr);
+  gtk_clist_set_text(GTK_CLIST(dlm->dlList),
+                    row,
+                    7,
+                    dlStats->kbs);
+  if(dlm->successfulStart == NO && dlStats->stats->progress > 0) {
+      gtk_clist_set_foreground(GTK_CLIST(dlm->dlList), 
+                               row,
+                               &textColors[1]);
+      dlm->successfulStart = YES;
+  } 
+  gtk_clist_thaw(GTK_CLIST(dlm->dlList));
+
+  if (dlStats->stats->filesize == dlStats->stats->progress) {
+    /* reset the request counters (just cosmetic) */
+    snprintf(dlStats->areq, sizeof(dlStats->areq), "0");
+    snprintf(dlStats->cra, sizeof(dlStats->cra), "0.0");
+    
+    gtk_clist_freeze(GTK_CLIST(dlm->dlList));
+    gtk_clist_set_text(GTK_CLIST(dlm->dlList),
+                      row,
+                        4,
+                        dlStats->areq);
+    gtk_clist_set_text(GTK_CLIST(dlm->dlList),
+                      row,
+                          5,
+                          dlStats->cra);
+    gtk_clist_thaw(GTK_CLIST(dlm->dlList));
+    refreshMenuSensitivity();
+
+    if (dlStats->stats->filesize == 0)       
+      dlm->downloadStatus = DOWNLOAD_FAILED;
+    else
+      dlm->downloadStatus = DOWNLOAD_COMPLETE;
+    SEMAPHORE_UP(dlm->doneSem); /* signal: we're done with download */
+    
+    gtkSaveCallDone(call->sem);
+    
+    return FALSE;
+  }
+  gtkSaveCallDone(call->sem);
+    
+  return FALSE;  
+}
+
+/**
+ * This method is called by the download code to notify the user
+ * interface of the download progress.
+ *
+ * @param stats the new statistical values
+ * @param dlm the accessor to the GTK window
+ **/
+static void modelCallback(ProgressStats * stats,
+                         DownloadModel * dlm) {
+  double currentRetryAvg, averageBps, percentage;
+  DLStats dlStats;
+  TIME_T now;
+#if DEBUG_WRITE_CPSDATA
+  char scratch[128];
+  FILE * fp;
+#endif
+  
+  if (dlm->downloadStatus != DOWNLOAD_PENDING) 
+    return;
+
+  /* don't display more often than once/sec */
+  TIME(&now);
+  if ((now-(dlm->lastDisplayTime)) < 1 &&
+      (stats->filesize != stats->progress))
+    return;
+  else
+    dlm->lastDisplayTime = now;
+ 
+  if(stats->requestsSent > 0)
+    currentRetryAvg = 
+      (double)stats->currentRetries / 
+      (double)stats->requestsSent;
+  else
+    currentRetryAvg = 0;
+  
+  if (now - dlm->downloadStartTime > 0)
+    averageBps =
+      (double)(stats->progress) / 
+      ((double)(now - dlm->downloadStartTime));
+  else
+    averageBps = 0;
+
+#if DEBUG_WRITE_CPSDATA
+  sprintf(scratch, "/tmp/cps-%x.txt", dlm->root.header.fileIdentifier.crc);
+  fp = FOPEN(scratch, "a+");
+  fprintf(fp, "%d %d %d %f\n", (int)(now-(dlm->downloadStartTime)),
+         stats->progress,
+         stats->totalRetries,
+          averageBps);
+  fclose(fp);
+#endif
+  
+  if(stats->filesize>0)
+    percentage = 100.0*((double)stats->progress/(double)stats->filesize);
+  else
+    percentage = 0;
+
+  snprintf(dlStats.pos, sizeof(dlStats.pos), "%d", stats->progress);
+  snprintf(dlStats.kbs, sizeof(dlStats.kbs), "%.1f", averageBps);
+  snprintf(dlStats.perc, sizeof(dlStats.perc), "%3.1f%%", percentage);
+  snprintf(dlStats.areq, sizeof(dlStats.areq), "%d", stats->requestsSent);
+  snprintf(dlStats.cra, sizeof(dlStats.cra), "%3.1f", currentRetryAvg);
+  snprintf(dlStats.tr, sizeof(dlStats.tr), "%d", stats->totalRetries);
+  dlStats.stats = stats; 
+  dlStats.dlm = dlm;
+  
+  gtkSaveCall((GtkFunction) displayStats, &dlStats);
+}
+
+gint setDownloadEntry(SaveCall *call) {
+  gint row;
+  DownloadModel *dlm = ((SetDownloadEntry *) call->args)->dlm;
+
+  gtk_clist_freeze(GTK_CLIST(dlm->dlList));
+  row = gtk_clist_find_row_from_data(GTK_CLIST(dlm->dlList),
+                      dlm);
+  gtk_clist_set_foreground(GTK_CLIST(dlm->dlList), 
+                           row,
+                           ((SetDownloadEntry *) call->args)->color);
+  gtk_clist_set_text(GTK_CLIST(dlm->dlList),
+                      row,
+             1,
+             ((SetDownloadEntry *) call->args)->text);
+  gtk_clist_thaw(GTK_CLIST(dlm->dlList));
+
+  gtkSaveCallDone(call->sem);
+  
+  return FALSE;
+}
+
+gint disentangleFromCLIST(SaveCall *call) {
+  gint row;
+  DownloadModel *dlm = (DownloadModel *) call->args;
+
+  gtk_clist_freeze(GTK_CLIST(dlm->dlList));
+  row = gtk_clist_find_row_from_data(GTK_CLIST(dlm->dlList),
+                                    dlm);
+  gtk_clist_set_row_data(GTK_CLIST(dlm->dlList),
+                         row,
+                                          NULL);
+  gtk_clist_thaw(GTK_CLIST(dlm->dlList));
+
+  gtkSaveCallDone(call->sem);
+
+  return FALSE;
+}
+
+/**
+ * Main function of the download thread. This function terminates
+ * automagically once the download is complete or if the download
+ * entry is removed while dl.  It is responsible for stopping the
+ * requestmanager.
+ * 
+ * @param dlm the download model
+ **/
+static void downloadFile_(DownloadModel * dlm) {
+  char * mime;
+  SetDownloadEntry entry;
+
+  LOG(LOG_DEBUG,
+      "DEBUG: Entering downloadFile_ for %s (%x)\n", 
+      dlm->fileName,
+      dlm);
+
+  /* initiate download */
+  TIME(&dlm->downloadStartTime);
+  
+  /* this starts the "real" download thread in the background,
+     with "modelCallback" called back to tell us about the progress */
+  dlm->rm = downloadFile(&dlm->root.header.fileIdentifier,
+                        dlm->fileName,
+                        (ProgressModel)&modelCallback,
+                        dlm);
+  if (dlm->rm == NULL) {
+    guiMessage("ERROR: could not download %s.\nConsult logs.\n",
+              dlm->fileName);
+    SEMAPHORE_FREE(dlm->doneSem);
+    FREE(dlm->fileName);
+    FREE(dlm);
+    return;
+  }
+  /* Wait here until download is complete or
+     the window is closed or gnunet-gtk is terminated */
+  LOG(LOG_DEBUG,
+      "DEBUG: waiting for DL completion (%x)\n",
+      dlm);
+  SEMAPHORE_DOWN(dlm->doneSem);
+  LOG(LOG_DEBUG,
+      "DEBUG: download complete (%d) enter destroyRequestManager (%x)\n",
+      dlm->downloadStatus,
+      dlm);
+
+  /* stop the RequestManager */
+  if (dlm->rm != NULL) {
+    destroyRequestManager(dlm->rm); 
+  } else {
+    /* this can happen if the requestmanager initialization
+     * failed for reason or another (e.g. write permission denied) */
+    LOG(LOG_WARNING, 
+        "WARNING: dlm->rm was NULL\n");
+  }
+
+  /*
+    ok, now why are we here? 4 possibilities:
+     a) download aborted (user closed window)
+     b) gnunet-gtk terminated (same as download aborted)
+     c) download failed (gnunetd exit, out-of-space)
+     d) download completed 
+
+     In case "d" we show the "YAY" window
+     and wait for another signal.
+     In case "c" we show the "BAH" window
+     and wait for another signal.
+  */
+
+  switch (dlm->downloadStatus) {
+  case DOWNLOAD_COMPLETE:
+    /* color successful dl green */
+    entry.dlm = dlm;
+    entry.color = &textColors[3];
+    entry.text = "DONE";
+    gtkSaveCall((GtkFunction) setDownloadEntry, &entry);
+    
+    switch (ntohs(dlm->root.header.major_formatVersion)) {
+    case ROOT_MAJOR_VERSION:
+      mime = dlm->root.header.mimetype;
+      break;
+    case SBLOCK_MAJOR_VERSION:
+      mime = ((SBlock*)&dlm->root)->mimetype;
+      break;
+    default:
+      mime = "unknown";
+      break;
+    }
+    if (0 == strcmp(mime,
+                   GNUNET_DIRECTORY_MIME)) {
+      displayDirectory(dlm->fileName,
+                      &dlm->root);
+    }
+    SEMAPHORE_DOWN(dlm->doneSem); /* wait for window closing */
+    break;
+  case DOWNLOAD_FAILED:
+    /* color failed dl red */ 
+    entry.dlm = dlm;
+    entry.color = &textColors[4];
+    entry.text = "FAIL";
+    gtkSaveCall((GtkFunction) setDownloadEntry, &entry);
+  
+    SEMAPHORE_DOWN(dlm->doneSem); /* wait for window closing */    
+    break;
+  default:
+    /* do nothing */
+    break;
+  }
+
+  /* finally, disentangle from the clist and free dlm resources */
+  gtkSaveCall((GtkFunction) disentangleFromCLIST, dlm);
+  SEMAPHORE_FREE(dlm->doneSem);
+  FREE(dlm->fileName);
+  FREE(dlm);
+}
+
+/**
+ * Open the download window and start the download of a file in the
+ * background. The method executes during a signal handler, so a GTK
+ * lock is not required to to GUI operations.
+ *
+ * @param filename the name of the file to download
+ *        (must copy, will be freed by caller)
+ * @param root information about what to download 
+ *        (will be freed by caller, must copy!)
+ **/
+void startDownload(gchar * filename,
+                  RootNode * root) {
+  GtkWidget * clist;
+  char * fileNameRoot;
+  int i;
+  gint row;
+  DownloadModel * dlm;
+  gchar * fileInfo[8];
+  char * fstring;
+
+  dlm = MALLOC(sizeof(DownloadModel));
+  memset(dlm, 
+        0, 
+        sizeof(DownloadModel));
+  dlm->fileName = STRDUP(filename);
+  memcpy(&dlm->root,
+        root,
+        sizeof(RootNode));
+
+  fileNameRoot = dlm->fileName;
+  for (i=strlen(dlm->fileName)-1;i>=0;i--) {
+    if (dlm->fileName[i] == DIR_SEPARATOR) {
+      fileNameRoot = &dlm->fileName[i+1];
+      break;
+    }
+  }
+  
+  /* create new download window? */
+  if (!dlWindow) {
+    GtkWidget * scrolled_window;
+    GtkWidget * button;
+    GtkWidget * box;
+    GtkWidget * entry;
+    GtkWidget * menu;
+    GtkItemFactory *popupFactory;
+    static gchar * descriptions[] = {
+      "Filename",
+      "%",              /* Completion percentage */
+      "Position",      /* Bytes dl'ed so far */
+      "Size",
+      "AReq",          /* Active block requests */
+      "CR/A",           /* Current Retries per Active requests */
+      "totR",           /* Total Retries */
+      "BPS",            /* Current bytes per second -estimate */
+    };
+    static int widths[] = {
+      290, 50, 70, 70, 40, 30, 50, 50
+    };
+
+    dlWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+    gtk_window_set_title(GTK_WINDOW(dlWindow), 
+                        "gnunet-gtk: Downloads");
+    gtk_widget_set_usize(GTK_WIDGET(dlWindow),
+                         780, /* x-size */
+                         300); /* y-size */
+    gtk_signal_connect_object(GTK_OBJECT(dlWindow), "delete_event",
+                              GTK_SIGNAL_FUNC(hideWindow),
+                              GTK_OBJECT(dlWindow));
+    gtk_signal_connect_object(GTK_OBJECT(dlWindow), "destroy",
+                              GTK_SIGNAL_FUNC(hideWindow),
+                              GTK_OBJECT(dlWindow));
+
+
+    box = gtk_vbox_new(FALSE, 0);
+    gtk_container_add(GTK_CONTAINER(dlWindow), 
+                     box);
+    gtk_container_set_border_width(GTK_CONTAINER(dlWindow), 
+                                  8);
+   
+    /* scrolled window for the dl list */
+    scrolled_window = gtk_scrolled_window_new(NULL, NULL);
+    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
+                                   GTK_POLICY_AUTOMATIC,
+                                   GTK_POLICY_ALWAYS);
+    gtk_box_pack_start(GTK_BOX(box),
+                       scrolled_window,
+                       TRUE,
+                       TRUE,
+                       0);
+    gtk_widget_show(scrolled_window);
+    /* create a list to hold the downloads in */
+    clist = gtk_clist_new_with_titles(8, descriptions);
+    gtk_clist_set_selection_mode
+              (GTK_CLIST(clist),
+              GTK_SELECTION_EXTENDED);
+    for (i=0;i<8;i++) {
+      gtk_clist_set_column_width(GTK_CLIST(clist),
+                                 i,
+                                 widths[i]);
+      gtk_clist_column_title_active(GTK_CLIST(clist),
+                                 i);
+    }
+    gtk_signal_connect(GTK_OBJECT(clist),
+                       "click-column",
+                       GTK_SIGNAL_FUNC(sort_column_callback),
+                       NULL);
+    gtk_container_add(GTK_CONTAINER(scrolled_window),
+                     clist);
+    gtk_object_set_data(GTK_OBJECT(dlWindow),
+                       "LIST",
+                       clist);
+    gtk_widget_show(box);
+    gtk_widget_show(scrolled_window);
+    /* cancel/remove button */
+    button = gtk_button_new_with_label("Remove selected entries");
+    gtk_signal_connect (GTK_OBJECT(button),
+                      "clicked",
+                      GTK_SIGNAL_FUNC(abortSelectedDownloads),
+                      clist);
+    gtk_box_pack_start(GTK_BOX(box),
+                       button,
+                       FALSE,
+                       FALSE,
+                       0);
+    gtk_widget_show(button);
+
+    /* activate the pulldown menu option now */
+    entry = gtk_item_factory_get_widget(itemFactory,
+                                        "/File/Show downloads");
+    gtk_widget_set_sensitive(entry, TRUE);
+
+    /* add popup menu */
+    popupFactory = gtk_item_factory_new (GTK_TYPE_MENU, "<main>",
+                                         NULL);
+    gtk_item_factory_create_items(popupFactory, 
+                                  dlWindowMenuItems,
+                                 dlWindowMenu,
+                                 NULL);
+    menu = gtk_item_factory_get_widget (popupFactory, "<main>");
+    gtk_signal_connect(GTK_OBJECT(dlWindow),
+                     "event",
+                     GTK_SIGNAL_FUNC(popupCallback),
+                     menu);
+  } else {
+    /* use existing dlWindow and list */
+    clist = gtk_object_get_data(GTK_OBJECT(dlWindow),
+                               "LIST");
+  }
+
+  /* set default disp. values for new download */
+  fileInfo[0]=STRDUP(fileNameRoot);
+  fileInfo[1]="0%";
+  fileInfo[2]="-";
+  fileInfo[3]=MALLOC(32);
+  fileInfo[4]="-";
+  fileInfo[5]="-";
+  fileInfo[6]="-";
+  fileInfo[7]="-";
+  snprintf(fileInfo[3], 
+          32, 
+          "%u", 
+          (unsigned int) ntohl(dlm->root.header.fileIdentifier.file_length));
+
+  TIME(&dlm->lastDisplayTime);
+
+  gtk_clist_freeze(GTK_CLIST(clist));  
+  row = gtk_clist_append(GTK_CLIST(clist),
+                        fileInfo);
+  FREE(fileInfo[0]);
+  FREE(fileInfo[3]);
+  gtk_clist_set_foreground(GTK_CLIST(clist), 
+                          row,
+                          &textColors[11]);
+  gtk_clist_set_row_data(GTK_CLIST(clist),
+                        row,
+                        dlm);
+  gtk_clist_thaw(GTK_CLIST(clist));
+                        
+  /* remember clist for updates in the DLM struct */
+  dlm->dlList = clist;
+  dlm->downloadStatus = DOWNLOAD_PENDING;
+  dlm->doneSem = SEMAPHORE_NEW(0);
+  
+  /* display download window */
+  gtk_widget_show(clist);
+  gtk_widget_show(dlWindow);
+
+  /* append an info note */
+  fstring = fileIdentifierToString(&dlm->root.header.fileIdentifier);
+  infoMessage(NO, "gnunet-download -o \"%s\" %s\n",
+             dlm->fileName,
+             fstring);
+  FREE(fstring);
+
+  /* create thread that runs the download */
+  if (0 != PTHREAD_CREATE(&dlm->downloadThread,
+                         (PThreadMain) &downloadFile_,
+                         dlm,
+                         64 * 1024))
+    errexit("FATAL: could not create download thread (%s)!\n",
+           STRERROR(errno));
+  PTHREAD_DETACH(&dlm->downloadThread);  
+}
+
+/**
+ * Starts a file download when user has filled in the fields
+ **/ 
+void fetchURICallback(GtkWidget * widget,
+                        gpointer data) {
+  gchar * uri;
+  FileIdentifier * fid;
+  GtkWidget * entry;
+  RootNode root;
+
+  entry = gtk_object_get_data(GTK_OBJECT(data),
+                             "entry");
+
+  uri = gtk_entry_get_text(GTK_ENTRY(entry));
+  if(!uri)
+    return;
+
+  fid = stringToFileIdentifier(uri);
+  if(!fid) {
+    guiMessage("Invalid gnunet AFS URI");
+    gtk_widget_destroy(data);
+    return;
+  }
+
+  root.header.major_formatVersion = ROOT_MAJOR_VERSION;
+  root.header.minor_formatVersion = ROOT_MINOR_VERSION;
+  memcpy(&root.header.fileIdentifier,
+        fid,
+         sizeof(FileIdentifier));
+  strcpy(root.header.mimetype,
+        "unknown");
+  
+  /* FIXME: Future URLs should contain suggested filename 
+   * (or the code should ask one from user) */
+  sprintf(root.header.filename, "unknown.%ld", (unsigned long)time(NULL));
+
+  startDownload(root.header.filename,
+                &root);
+
+  gtk_widget_destroy(data);
+}
+
+void fetchURI(GtkWidget * widget,
+             gpointer data) {
+  GtkWidget * window;
+  GtkWidget * vbox;
+  GtkWidget * label;
+  GtkWidget * entry;
+  GtkWidget * hbox;
+  GtkWidget * button_ok;
+  GtkWidget * button_cancel;
+
+  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+  gtk_widget_set_usize(GTK_WIDGET(window),
+                      780,
+                      100);
+  gtk_window_set_title(GTK_WINDOW(window),
+                      "Download URI");
+  /* add container for window elements */
+  vbox = gtk_vbox_new(FALSE, 15);
+  gtk_container_add(GTK_CONTAINER(window),
+                    vbox);
+  gtk_widget_show(vbox);
+
+  /* when user clicks on close box, always "destroy" */
+  gtk_signal_connect(GTK_OBJECT(window),
+                     "destroy",
+                     GTK_SIGNAL_FUNC(destroyWidget),
+                     window);
+
+  gtk_container_set_border_width(GTK_CONTAINER(window),
+                                 10);
+  
+  hbox = gtk_hbox_new(FALSE, 0);
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    hbox,
+                    TRUE,
+                    TRUE,
+                    0);
+  gtk_widget_show(hbox);
+ 
+  label = gtk_label_new("GNUnet AFS URI: ");
+  gtk_box_pack_start(GTK_BOX(hbox),
+                    label,
+                    FALSE,
+                    FALSE,
+                    0);
+  gtk_widget_show(label);
+  entry = gtk_entry_new();
+  gtk_box_pack_start(GTK_BOX(hbox),
+                    entry,
+                     TRUE,
+                    TRUE,      
+                    0);
+  gtk_signal_connect(GTK_OBJECT(entry),
+                     "activate",
+                     GTK_SIGNAL_FUNC(fetchURICallback),
+                     window);
+  gtk_object_set_data(GTK_OBJECT(window),
+                     "entry",
+                     entry);
+  gtk_widget_show(entry);
+
+  button_ok = gtk_button_new_with_label("Ok");
+  button_cancel = gtk_button_new_with_label("Cancel");
+
+  hbox = gtk_hbox_new(FALSE, 0);
+  gtk_box_pack_start(GTK_BOX(vbox),
+                     hbox,
+                     FALSE,
+                     FALSE,
+                     0);
+  gtk_box_pack_start(GTK_BOX(hbox),
+                    button_ok,
+                    TRUE,
+                    TRUE,
+                    0);
+  gtk_box_pack_start(GTK_BOX(hbox),
+                    button_cancel,
+                    TRUE,
+                    TRUE,
+                    0);
+  gtk_signal_connect(GTK_OBJECT(button_cancel),
+                    "clicked",
+                    GTK_SIGNAL_FUNC(destroyWidget),
+                    window);
+  gtk_signal_connect(GTK_OBJECT(button_ok),
+                    "clicked",
+                    GTK_SIGNAL_FUNC(fetchURICallback),
+                    window);
+  gtk_widget_show(hbox);
+  gtk_widget_show(button_ok);
+  gtk_widget_show(button_cancel);
+  gtk_widget_show(window);
+ 
+
+
+}
+                
+
+/* end of download.c */

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/download.h
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/download.h    2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/download.h    2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,74 @@
+/*
+     This file is part of GNUnet
+
+     GNUnet 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, or (at your
+     option) any later version.
+
+     GNUnet 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 GNUnet; see the file COPYING.  If not, write to the
+     Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+     Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file applications/afs/gtkui/download.h
+ * @author Christian Grothoff
+ **/
+
+#ifndef GTKUI_DOWNLOAD_H
+#define GTKUI_DOWNLOAD_H
+
+/**
+ * @brief state associated with a GTK download window
+ **/
+typedef struct {
+  RootNode root;
+  char * fileName;
+  PTHREAD_T downloadThread;
+  RequestManager * rm;
+  Semaphore * doneSem;
+  TIME_T downloadStartTime;
+  int successfulStart;
+  int downloadStatus;
+  TIME_T lastDisplayTime;
+  GtkWidget * dlList;
+} DownloadModel;
+
+typedef struct {
+  char pos[16], kbs[14], perc[14], areq[14], cra[14], tr[14];
+  DownloadModel *dlm;
+  ProgressStats *stats;
+} DLStats;
+
+typedef struct {
+  DownloadModel *dlm;
+  GdkColor *color;
+  gchar *text;
+} SetDownloadEntry;
+
+/**
+ * Open the download window and start the download of a file in the
+ * background. The method executes during a signal handler, so a GTK
+ * lock is not required to to GUI operations.
+ *
+ * @param filename the name of the file to download
+ * (must copy, will be freed by caller)
+ * @param dlm some information about what to download 
+ * (will be freed by caller, must copy!)
+ **/
+void startDownload(gchar * filename,
+                  RootNode * root);
+
+void fetchURI(GtkWidget * widget,
+             gpointer data);
+
+extern GtkWidget * dlWindow;
+               
+#endif

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/helper.c
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/helper.c      2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/helper.c      2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,1009 @@
+/*
+     This file is part of GNUnet
+
+     GNUnet 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, or (at your
+     option) any later version.
+
+     GNUnet 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 GNUnet; see the file COPYING.  If not, write to the
+     Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+     Boston, MA 02111-1307, USA.
+*/
+/**
+ * @file src/applications/afs/gtkui/helper.c
+ * @brief This file contains some GUI helper functions
+ * @author Igor Wronsky
+ **/
+
+#include "gnunet_afs_esed2.h"
+#include "helper.h"
+#include <stdlib.h>
+#ifndef MINGW
+ #include <sys/wait.h>
+#endif
+#include "main.h"
+
+#define HELPER_DEBUG NO
+
+GtkWidget * infoWindow = NULL;
+static GtkWidget * infoText = NULL;
+
+/* are we waiting for gnunetd to start? */
+static int pollForLaunch = FALSE;
+
+/* PID of the main thread */
+static pthread_t mainThread;
+
+
+static SaveCall ** psc;
+static unsigned int pscCount;
+static Mutex sclock;
+
+/**
+ * Call a callback function from the mainloop/main thread ("SaveCall").
+ * Since GTK doesn't work with multi-threaded applications under Windows,
+ * all GTK operations have to be done in the main thread
+ **/
+void gtkSaveCall(GtkFunction func, void *args) {
+  SaveCall call;
+  int i;
+
+  call.args = args;
+  call.func = func;
+  MUTEX_LOCK(&sclock);
+  if ( (mainThread != 0) &&
+       (mainThread != pthread_self()) ) {
+    call.sem = SEMAPHORE_NEW(0);
+    GROW(psc,
+        pscCount,
+        pscCount+1);
+    psc[pscCount-1] = &call;
+    MUTEX_UNLOCK(&sclock);
+    gtk_idle_add(func, &call);
+    SEMAPHORE_DOWN(call.sem);
+    /* remove from psc list */
+    MUTEX_LOCK(&sclock);
+    for (i=0;i<pscCount;i++)
+      if (psc[i] == &call) {
+       psc[i] = psc[pscCount-1];
+       break;
+      }
+    if (i == pscCount)
+      errexit("ASSERTION failed at %s:%d\n",
+             __FILE__, __LINE__);
+    GROW(psc,
+        pscCount,
+        pscCount-1);
+    MUTEX_UNLOCK(&sclock);
+    SEMAPHORE_FREE(call.sem);
+  } else {
+    MUTEX_UNLOCK(&sclock);
+    call.sem = NULL;
+    func(&call);
+  }
+}
+
+/**
+ * Initialize "SaveCalls"
+ **/
+void gtkInitSaveCalls() {
+  MUTEX_CREATE_RECURSIVE(&sclock);
+  mainThread = pthread_self();
+}
+
+void gtkRunSomeSaveCalls() {
+  int i;
+
+  if (mainThread != pthread_self())
+    return;
+  MUTEX_LOCK(&sclock);
+  if (pscCount == 0) {
+    MUTEX_UNLOCK(&sclock);
+    return;
+  }
+  i = randomi(pscCount);
+  if (TRUE == g_idle_remove_by_data(psc[i]))
+    psc[i]->func(psc[i]);
+  MUTEX_UNLOCK(&sclock);
+  gnunet_util_sleep(50 * cronMILLIS);
+  /* sleep here is somewhat important, first of
+     all, after completion we need to give the
+     semaphore-mechanism time to remove the save-call
+     from the list to avoid running it twice; 
+     also, this function might be called in a tight
+     loop (see search.c), so we should give the
+     other threads some time to run.  */
+     
+}
+
+void gtkDoneSaveCalls() {
+  int i;
+  mainThread = 0;
+  MUTEX_LOCK(&sclock);
+  for (i=0;i<pscCount;i++) 
+    psc[i]->func(psc[i]);
+  i = pscCount;
+  MUTEX_UNLOCK(&sclock);  
+  /* wait until all PSC-jobs have left
+     the gtkSaveCall method before destroying
+     the mutex! */
+  while (i != 0) {
+    gnunet_util_sleep(50 * cronMILLIS);    
+    MUTEX_LOCK(&sclock);
+    i = pscCount;
+    MUTEX_UNLOCK(&sclock);
+  }
+  MUTEX_DESTROY(&sclock);
+}
+
+
+/**
+ * Called from a "SaveCall"-function to indicate that it is done
+ **/
+void gtkSaveCallDone(Semaphore *sem) {
+  if (sem)
+    SEMAPHORE_UP(sem);
+}
+
+/**
+ * Destroy a widget. Called from threads other than the main thread
+ **/
+gint doDestroyWidget(SaveCall *call) {
+  gtk_widget_destroy((GtkWidget *) call->args);
+
+  gtkSaveCallDone(call->sem);
+
+  return FALSE;
+}
+
+/**
+ * Callback for handling "delete_event": close the window 
+ **/
+gint deleteEvent(GtkWidget * widget,
+                GdkEvent * event,
+                gpointer data) {
+#if DEBUG_HELPER
+  LOG(LOG_DEBUG, 
+      "DEBUG: deleteEvent\n");
+#endif
+  return FALSE;
+}
+
+/**
+ * A callback to destroy any widget given as second argument
+ **/
+void destroyWidget(GtkWidget * dummy, 
+                  GtkWidget * widget) {
+#if DEBUG_HELPER
+  LOG(LOG_DEBUG, 
+      "DEBUG: destroyWidget %d\n", 
+      widget);
+#endif
+  gtk_widget_destroy(widget);
+}
+
+/**
+ * Callback function for guiMessage()
+ **/
+gint doGuiMessage(SaveCall *call) {
+  GtkWidget * window;
+  GtkWidget * label;
+  GtkWidget * box;
+  GtkWidget * button;
+
+  window = gtk_window_new(GTK_WINDOW_DIALOG);
+  gtk_container_set_border_width(GTK_CONTAINER(window), 10);
+  gtk_window_set_title(GTK_WINDOW(window), 
+                      "Note");
+  gtk_signal_connect(GTK_OBJECT(window), 
+                    "delete_event",
+                    GTK_SIGNAL_FUNC(deleteEvent), 
+                    NULL);
+
+  box = gtk_vbox_new(FALSE, 0);
+  gtk_container_add(GTK_CONTAINER(window), 
+                   box);
+
+  label = gtk_label_new((gchar *) call->args);
+  free((gchar *) call->args); /* allocated in g_strdup_vprintf */
+  gtk_box_pack_start(GTK_BOX(box),
+                    label,
+                    FALSE,
+                    FALSE,
+                    0);
+  
+  button = gtk_button_new_with_label("Ok");
+  gtk_signal_connect(GTK_OBJECT (button),
+                     "clicked",
+                     GTK_SIGNAL_FUNC(destroyWidget),
+                    window);
+  gtk_box_pack_start(GTK_BOX(box),button,FALSE,FALSE,0);
+  
+  gtk_window_set_position(GTK_WINDOW(window),
+                         GTK_WIN_POS_MOUSE);
+  gtk_widget_show_all(window);
+  gtk_widget_grab_focus(button);
+  
+  gtkSaveCallDone(call->sem);
+
+  return FALSE;
+}
+
+/** 
+ * Displays an informative message to the user in a fresh window 
+ **/
+void guiMessage(const char * format, ...) {
+  va_list args;
+  gchar *note;
+
+  va_start(args, format);
+  note = g_strdup_vprintf(format, args);
+  va_end(args);
+  
+  gtkSaveCall((GtkFunction) doGuiMessage, note);
+}
+
+/**
+ * Callback for infoMessage()
+ **/
+gint doInfoMessage(SaveCall *call) {
+  if(!infoWindow) {
+    GtkWidget * box1;
+    GtkWidget * button;
+    GtkWidget * scrolled_window;
+
+    infoWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+    gtk_signal_connect(GTK_OBJECT(infoWindow),
+                       "delete_event",
+                       GTK_SIGNAL_FUNC(deleteEvent),
+                       NULL);
+
+    gtk_window_set_title(GTK_WINDOW(infoWindow),
+                         "Information");
+    gtk_widget_set_usize(GTK_WIDGET(infoWindow),
+                         780,
+                         300);
+
+    box1 = gtk_vbox_new(FALSE, 0);
+    gtk_container_add(GTK_CONTAINER (infoWindow),
+                      box1);
+    gtk_widget_show(box1);
+    
+    /* create a scrollable window */
+    scrolled_window = gtk_scrolled_window_new(NULL, NULL);
+    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
+                                  GTK_POLICY_AUTOMATIC,
+                                  GTK_POLICY_ALWAYS);
+    gtk_box_pack_start(GTK_BOX(box1),
+                      scrolled_window,
+                      TRUE,
+                      TRUE,
+                      0);
+    gtk_widget_show(scrolled_window);
+
+    /* create a text widget */
+    infoText = gtk_text_new(NULL, NULL);
+    gtk_text_set_editable(GTK_TEXT (infoText),
+                          FALSE);
+    gtk_container_add(GTK_CONTAINER(scrolled_window),
+                     infoText);
+    gtk_widget_show(infoText);
+    gtk_widget_realize(infoText);
+  
+    /* finish with a close button */
+    button = gtk_button_new_with_label("Close");
+    gtk_box_pack_start(GTK_BOX (box1),
+                       button,
+                       FALSE,
+                       FALSE,
+                       0);
+    gtk_signal_connect_object(GTK_OBJECT(button),
+                              "clicked",
+                              GTK_SIGNAL_FUNC(hideWindow),
+                              GTK_OBJECT(infoWindow));
+    gtk_signal_connect_object(GTK_OBJECT(infoWindow), "delete_event",
+                              GTK_SIGNAL_FUNC(hideWindow),
+                              GTK_OBJECT(infoWindow));
+    gtk_signal_connect_object(GTK_OBJECT(infoWindow), "destroy",
+                              GTK_SIGNAL_FUNC(hideWindow),
+                              GTK_OBJECT(infoWindow));
+    gtk_widget_show(button);
+  }
+  if(((InfoMessage *) call->args)->doPopup==YES)
+    gtk_widget_show(infoWindow);
+
+  /* append the text */
+  gtk_text_freeze(GTK_TEXT(infoText));
+  gtk_text_insert(GTK_TEXT(infoText),
+                  NULL,
+                  &infoText->style->black,
+                  NULL,
+                  ((InfoMessage *) call->args)->note, -1);
+  gtk_text_thaw(GTK_TEXT(infoText));
+
+  gtkSaveCallDone(call->sem);
+
+  return FALSE;
+}
+
+/** 
+ * Appends a message to the info window
+ *
+ * @param doPopup do we open the window, YES or NO
+ *
+ **/
+void infoMessage(int doPopup, const char * format, ...) {
+  va_list args;
+  InfoMessage info;
+
+  va_start(args, format);
+  info.note = g_strdup_vprintf(format, args);
+  va_end(args);
+  info.doPopup = doPopup;
+  gtkSaveCall((GtkFunction) doInfoMessage, &info);
+  g_free(info.note);
+}
+
+/** 
+ * Appends a log entry to the info window
+ *
+ * @param txt the log entry
+ *
+ **/
+void addLogEntry(const char *txt) {
+  infoMessage(NO, txt);
+}
+
+GtkNotebook * notebook = NULL;
+
+gint doAddToNotebook(SaveCall *call) {
+  GtkWidget * label = gtk_label_new(((AddNotebook *) call->args)->labelName);
+  gtk_notebook_append_page(notebook, ((AddNotebook *) call->args)->frame, 
label);
+  gtk_widget_show(((AddNotebook *) call->args)->frame);
+
+  gtkSaveCallDone(call->sem);
+  
+  return FALSE;
+}
+
+void addToNotebook(char * labelName,
+                  GtkWidget * frame) {
+  AddNotebook note;
+  
+  note.labelName = labelName;
+  note.frame = frame;
+  /* add a new notebook for the search results */
+  gtkSaveCall((GtkFunction) doAddToNotebook, &note);
+}
+
+void hideWindow(GtkWidget * widget,
+               gpointer data) {
+  if(widget)
+    gtk_widget_hide(widget);
+}
+
+/**
+ * A dirty way to pump some stats from gnunetd
+ **/
+void showStats(GtkWidget * widget,
+              gpointer data) {
+  FILE *fp;
+  char * ptr;
+  char buffer[512];
+  char fn[512];
+  char path[260];
+  char * cfgFile;
+  GtkWidget * window;
+  GtkWidget * scrolled_window;
+  GtkWidget * clist;
+  GtkWidget * vbox;
+  GtkWidget * button;
+  gchar * results[2];
+  static gchar * descriptions[] = {
+    "Statistic",
+    "Value"
+  };
+  static int widths[] = {
+    600, 70
+  };
+  int i;
+
+  /* create window etc */
+  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+  gtk_window_set_title(GTK_WINDOW(window),
+                      "GNUnet: gnunetd statistics");
+  gtk_widget_set_usize(GTK_WIDGET(window),
+                      780,
+                      300);
+  vbox = gtk_vbox_new(FALSE, 1);
+  gtk_container_add(GTK_CONTAINER(window), vbox);
+
+  scrolled_window = gtk_scrolled_window_new(NULL,NULL);
+  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
+                                GTK_POLICY_AUTOMATIC,
+                                GTK_POLICY_ALWAYS);
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    scrolled_window,
+                    TRUE,
+                    TRUE,
+                    0);
+
+  clist = gtk_clist_new_with_titles(2, descriptions);
+  for(i=0;i<2;i++)
+    gtk_clist_set_column_width(GTK_CLIST(clist),
+                              i,
+                              widths[i]);
+  gtk_clist_set_column_justification(GTK_CLIST(clist),
+                                    1,
+                                    GTK_JUSTIFY_RIGHT);
+  gtk_container_add(GTK_CONTAINER(scrolled_window),
+                   clist);
+
+  button = gtk_button_new_with_label("Close");
+  gtk_signal_connect(GTK_OBJECT(button),
+                    "clicked",
+                    GTK_SIGNAL_FUNC(destroyWidget),
+                    window);
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    button,
+                    FALSE,
+                    FALSE,
+                    0);
+
+  gtk_clist_freeze(GTK_CLIST(clist));
+
+  /* Poll for statistics */
+  /* FIXME: done with a kludge until someone bothers to
+   * use gnunet-stats code */
+
+  sprintf(fn, "gnunet-gtk_stats.XXXXXX");
+  mkstemp(fn);
+
+  cfgFile = getConfigurationString("FILES",
+                                  "gnunet.conf");
+  if(cfgFile == NULL) {
+    LOG(LOG_WARNING,
+       "WARNING: cfgFile was NULL (shouldn't happen ?!)\n");
+    cfgFile = STRDUP(DEFAULT_CLIENT_CONFIG_FILE);
+  }
+
+#ifdef MINGW
+  conv_to_win_path("/bin", path);
+  strcat(path, "\\");
+#else
+  strcpy(path, "");
+#endif  
+  
+  sprintf(buffer, 
+          "%sgnunet-stats -c \"%s\" >%s",
+    path, 
+         cfgFile,
+         fn);
+  system(buffer);
+  FREE(cfgFile);
+  fp=FOPEN(fn, "r");
+  if(!fp) {
+    gtk_widget_destroy(window);
+    guiMessage("Error reading gnunet-stats output from %s\n",
+              fn);
+    LOG(LOG_ERROR, "Unable to open %s for reading\n", 
+       fn);
+    return;
+  }
+  while(!feof(fp)) {
+    fgets(buffer, 512, fp);
+    if(buffer[0]==0 || buffer[0]=='\n' || feof(fp))
+      continue;
+    ptr = buffer;
+    while(*ptr!=':') ptr++;
+    *ptr=0;
+    ptr++;
+    while(*ptr==' ') ptr++;
+    results[0]=buffer;
+    results[1]=ptr;
+    
+    /* remove CR/LF */
+    while(*ptr != '\n') ptr++;
+    *ptr = 0;
+    if(*(ptr-1) == '\r') *(ptr-1) = 0;
+    
+    gtk_clist_append(GTK_CLIST(clist),
+                    results);
+  }
+  fclose(fp);
+  UNLINK(fn);
+  
+  gtk_clist_thaw(GTK_CLIST(clist));
+  
+  gtk_widget_show_all(window);
+}
+
+/** 
+ * Checks if gnunetd is running
+ * 
+ * NOTE: Uses CS_PROTO_CLIENT_COUNT query to determine if 
+ * gnunetd is running
+ **/
+static int checkDaemonRunning(void) {
+  GNUNET_TCP_SOCKET * sock;
+  CS_HEADER csHdr;
+  int ret;
+
+  sock = getClientSocket();
+  if(sock == NULL) {
+    LOG(LOG_WARNING,
+       "WARNING: socket create failed, shouldn't happen.\n");
+    return SYSERR;  
+  }    
+
+  csHdr.size
+    = htons(sizeof(CS_HEADER));
+  csHdr.tcpType
+    = htons(CS_PROTO_CLIENT_COUNT);
+  if (SYSERR == writeToSocket(sock,
+                              &csHdr)) {
+    LOG(LOG_DEBUG, "DEBUG: gnunetd is NOT running\n");
+    releaseClientSocket(sock);
+    return SYSERR;
+  } 
+  if (SYSERR == readTCPResult(sock, 
+                             &ret)) {
+    LOG(LOG_DEBUG, "DEBUG: failed to read reply from gnunetd\n");
+    releaseClientSocket(sock);
+    return SYSERR;
+  }
+  releaseClientSocket(sock);
+  
+  return OK;
+}
+
+#if LINUX || OSX || SOLARIS || SOMEBSD
+static int launchWithExec() {
+  pid_t pid;
+
+  pid = fork();
+  if (pid == 0) {
+    char * args[4];
+    char * path;
+    char * cp;
+
+    cp = getConfigurationString("MAIN", "ARGV[0]");
+    if (cp != NULL) {
+      int i = strlen(cp);
+      while ( (i >= 0) && 
+             (cp[i] != DIR_SEPARATOR) )
+       i--;
+      cp[i+1] = '\0';
+      path = MALLOC(i+1+strlen("gnunetd"));
+      strcpy(path, cp);
+      strcat(path, "gnunetd");      
+      args[0] = path;
+      FREE(cp);
+    } else {
+      path = NULL;
+      args[0] = "gnunetd";
+    }    
+    args[1] = "-c";
+    args[2] = getConfigurationString("FILES",
+                                     "gnunet.conf");
+    args[3] = NULL;
+    errno = 0;
+    nice(10); /* return value is not well-defined */
+    if (errno != 0) {
+      LOG(LOG_WARNING,
+         "WARNING: could not nice gnunetd (%s)\n",
+         STRERROR(errno));
+    }
+    if (path != NULL)
+      execv(path,
+           args);
+    else
+      execvp("gnunetd",
+            args);
+    LOG(LOG_FAILURE,
+       "FAILURE: could not exec gnunetd: %s\n",
+       STRERROR(errno));
+    if (path != NULL)
+      LOG(LOG_FAILURE,
+         "FAILURE: determined path to be %s\n",
+         path);
+    FREENONNULL(path); /* yeah, right, like we're likely to get
+                         here... */
+    exit(-1);
+  } else {
+    pid_t ret;
+    int status;
+
+    ret = waitpid(pid, &status, 0);
+    if (ret == -1) {
+      LOG(LOG_FAILURE,
+         "FAILURE: waitpid failed: %s\n",
+         STRERROR(errno));
+      return SYSERR;
+    }
+    if ( (WIFEXITED(status) &&
+         (0 != WEXITSTATUS(status)) ) ) {
+      guiMessage("Starting gnunetd failed, error code: %d",
+                WEXITSTATUS(status));
+      return SYSERR;
+    }
+#ifdef WCOREDUMP
+    if (WCOREDUMP(status)) {
+      guiMessage("Starting gnunetd failed (core dumped)");
+      return SYSERR;
+    }
+#endif
+    if (WIFSIGNALED(status) ||
+       WTERMSIG(status) ) {
+      guiMessage("Starting gnunetd failed (aborted by signal)");
+      return SYSERR;
+    }
+    return OK;
+  }
+}
+#endif
+
+static int doLaunch() {
+  
+#if LINUX || OSX || SOLARIS || SOMEBSD
+  return launchWithExec();
+#elif MINGW
+  char szCall[_MAX_PATH + 1], szWd[_MAX_PATH + 1], szCWd[_MAX_PATH + 1];
+  char *args[1];
+
+  conv_to_win_path("/bin/gnunetd.exe", szCall);
+  conv_to_win_path("/bin", szWd);
+  _getcwd(szCWd, _MAX_PATH);
+
+  chdir(szWd);
+  args[0] = NULL;
+  spawnvp(_P_NOWAIT, szCall, (const char *const *) args);
+  chdir(szCWd);
+  
+  return OK;
+#else
+  /* any system out there that does not support THIS!? */
+  system("gnunetd"); /* we may not have nice,
+                       so let's be minimalistic here. */
+  return OK;
+#endif  
+}
+
+/** 
+ * Launch gnunetd, don't check if its running
+ **/
+static void launchDaemonNoCheck(GtkWidget * widget,
+                               gpointer data) {
+  /* sanity checks, not critical for ports */
+  char * host = getConfigurationString("NETWORK",
+                                      "HOST");
+  if (host != NULL) {
+    if (0 != strcmp(host,
+                   "localhost")) {
+      char * hostname;
+      hostname = MALLOC(1024);
+      if (0 != gethostname(hostname, 1024)) {
+       LOG(LOG_ERROR,
+           "ERROR: failed to get hostname (%s)\n",
+           STRERROR(errno));
+      } else {
+       /* we could go crazy here and try to open a socket locally
+          and then attempt to connect to it using the NS lookup result
+          for "host" -- and do it for IPv4 and IPv6 and possibly still
+          be wrong due some crazy firewall configuration.  Or we can
+          just do the simpelst thing (strcmp) and expect the user to
+          fix it if he cares to have the warning go away... */
+       if (0 != strcmp(host,
+                       hostname)) {
+         guiMessage("WARNING: gnunetd is configured to run on host %s and\n"
+                    "gnunet-gtk is running on host %s, which seems to be a 
different machine.\n"
+                    "gnunet-gtk can only start gnunetd on host %s.\n"
+                    "This may not be what you want (it may not work).\n"
+                    "I will proceed anyway, good luck.",
+                    host,
+                    hostname,
+                    hostname);
+       }         
+      }
+      FREE(hostname);
+    }
+    FREE(host);
+  }
+  /* end of sanity checks */
+  doLaunch();
+  pollForLaunch = TRUE;
+  gtk_widget_destroy(GTK_WIDGET(data));
+}
+
+/** 
+ * Launch gnunetd w/ checks
+ **/
+void launchDaemon(GtkWidget * widget,
+                 gpointer data) {
+  if (OK == checkDaemonRunning() ) {
+    guiMessage("gnunetd is already running");
+    return;
+  } else {                                     
+    doLaunch();
+    pollForLaunch = TRUE;
+  }
+}
+
+/** 
+ * Kill gnunetd
+ **/
+void killDaemon(GtkWidget * widget,
+               gpointer data) {
+  if (OK == checkDaemonRunning() ) {
+    GNUNET_TCP_SOCKET * sock;
+    CS_HEADER csHdr;
+    int ret;
+
+    sock = getClientSocket();
+    if (sock == NULL) {
+      /* well, probably already dead */
+      return;
+    }
+    csHdr.size 
+      = htons(sizeof(CS_HEADER));
+    csHdr.tcpType
+      = htons(CS_PROTO_SHUTDOWN_REQUEST);
+    if (SYSERR == writeToSocket(sock,
+                               &csHdr)) {
+      guiMessage("Error sending shutdown request to gnunetd");
+      releaseClientSocket(sock);
+      return;
+    }
+    if (SYSERR == readTCPResult(sock,
+                               &ret)) {
+      guiMessage("Error reading shutdown reply from gnunetd");
+      releaseClientSocket(sock);
+      return;
+    }
+    if (ret == OK)
+      guiMessage("gnunetd agreed to shut down.");
+    else
+      guiMessage("gnunetd refuses to shut down (reply=%d).", 
+                 ret);
+    releaseClientSocket(sock);
+  } else {
+    guiMessage("gnunetd is not running...");
+  }
+}
+
+/**
+ * Ask if the user wishes to start gnunetd
+ **/
+static void initDaemonStartDialog(void) {
+   GtkWidget *dialog;
+   GtkWidget *label;
+   GtkWidget *okay_button;
+   GtkWidget *no_button;
+
+   dialog = gtk_dialog_new();
+   label = gtk_label_new("gnunetd (daemon) doesn't seem to be running.\nWould 
you like to start it?\n");
+   gtk_container_add (GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),
+                      label);
+
+   okay_button = gtk_button_new_with_label("Yes!");
+   no_button = gtk_button_new_with_label("Naah");
+   
+   gtk_signal_connect(GTK_OBJECT(okay_button), 
+                      "clicked",
+                      GTK_SIGNAL_FUNC(launchDaemonNoCheck), 
+                     dialog);
+   gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->action_area),
+                      okay_button);
+   gtk_signal_connect(GTK_OBJECT(no_button), 
+                     "clicked",
+                      GTK_SIGNAL_FUNC(destroyWidget), 
+                      dialog);
+   gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->action_area),
+                      no_button);
+
+   gtk_widget_show_all(dialog);
+}
+
+/**
+ * Checks if gnunetd is running and if not, prompts user 
+ * to run gnunetd. Always returns OK.
+ **/
+int checkForDaemon(void) {  
+  if (SYSERR == checkDaemonRunning()) {
+    char * host;
+
+    host = getConfigurationString("NETWORK",
+                                 "HOST");
+    if (host != NULL && strcmp(host,"localhost")==0 )
+      initDaemonStartDialog();
+    else
+      guiMessage("gnunetd doesn't seem to be running.\n"
+                 "Unfortunately, gnunet-gtk can't identify config entry"
+                "\n\nNETWORK/HOST '%s'\n\n"
+                "as a local machine, so gnunetd can not be\n"
+                "launched by gnunet-gtk.",
+                (host == NULL ? "" : host) );
+  }
+
+  return OK;
+}
+
+static gint doUpdateMenus(SaveCall * call) {
+  static GtkWidget * killEntry = NULL;
+  static GtkWidget * launchEntry = NULL;
+  static GtkWidget * statsEntry = NULL;
+  static int once = 1;
+  static int isLocal;
+  char * host;
+  int ret;
+
+  ret = * (int*) call->args;
+  if (once) {
+    once = 0;
+    killEntry = gtk_item_factory_get_widget(itemFactory,
+                                           "/Advanced/"
+                                           "Kill gnunetd");
+    launchEntry = gtk_item_factory_get_widget(itemFactory,
+                                             "/Advanced/"
+                                             "Launch gnunetd");
+    statsEntry = gtk_item_factory_get_widget(itemFactory,
+                                            "/File/"
+                                            "Show gnunetd stats");    
+    host = getConfigurationString("NETWORK",
+                                 "HOST");
+    if ( (host == NULL) ||
+        (strcmp(host, "localhost")==0) )
+      isLocal = TRUE;
+    else
+      isLocal = FALSE;
+    FREENONNULL(host);
+  }
+  if (ret == SYSERR) {
+    gtk_widget_set_sensitive(statsEntry, FALSE);
+    gtk_widget_set_sensitive(killEntry, FALSE);
+    gtk_widget_set_sensitive(launchEntry, (TRUE & isLocal) );
+  } else {
+    gtk_widget_set_sensitive(statsEntry, TRUE);
+    gtk_widget_set_sensitive(killEntry, TRUE);
+    gtk_widget_set_sensitive(launchEntry, FALSE);
+    
+    if (pollForLaunch == TRUE) {
+      pollForLaunch = FALSE;
+      guiMessage("gnunetd is now up and running");
+    }
+  }    
+  gtkSaveCallDone(call->sem);
+  return FALSE;
+}
+
+void cronCheckDaemon(void * dummy) {
+  static int last = 42;
+  int ret;
+  
+  ret = checkDaemonRunning();
+  if (ret != last) {
+    last = ret;    
+    gtkSaveCall((GtkFunction) doUpdateMenus, &ret);
+  }      
+}
+
+
+/**
+ * A function for numeric comparisons of strings
+ **/
+gint numericComp(GtkCList *clist,
+                 gconstpointer ptr1,
+                 gconstpointer ptr2) {
+  double value1;
+  double value2;
+  GtkCListRow * row1 = (GtkCListRow *) ptr1;
+  GtkCListRow * row2 = (GtkCListRow *) ptr2;
+
+  value1 = atof(GTK_CELL_TEXT(row1->cell[clist->sort_column])->text);
+  value2 = atof(GTK_CELL_TEXT(row2->cell[clist->sort_column])->text);
+
+  if(value1>value2)
+    return(-1);
+  else if(value1==value2)
+    return(0);
+  else
+    return(1);
+}
+
+/**
+ * A function for case-insensitive text comparisons
+ **/
+gint alphaComp(GtkCList *clist,
+               gconstpointer ptr1,
+               gconstpointer ptr2) {
+  char * text1;
+  char * text2;
+  GtkCListRow * row1 = (GtkCListRow *) ptr1;
+  GtkCListRow * row2 = (GtkCListRow *) ptr2;
+
+  text1 = GTK_CELL_TEXT(row1->cell[clist->sort_column])->text;
+  text2 = GTK_CELL_TEXT(row2->cell[clist->sort_column])->text;
+
+  return (strcasecmp(text1,text2));
+}
+
+/**
+ * A function for percentage comparisons 
+ **/
+gint percentComp(GtkCList *clist,
+                 gconstpointer ptr1,
+                 gconstpointer ptr2) {
+  char * tmp1;
+  char * tmp2;
+  double value1;
+  double value2;
+  GtkCListRow * row1 = (GtkCListRow *) ptr1;
+  GtkCListRow * row2 = (GtkCListRow *) ptr2;
+
+  tmp1 = GTK_CELL_TEXT(row1->cell[clist->sort_column])->text;
+  tmp2 = GTK_CELL_TEXT(row2->cell[clist->sort_column])->text;
+
+  /* Hack for DONE strings :) */
+  if(strstr(tmp1,"%") == 0) {
+    if(strstr(tmp2,"%") == 0)
+      return 0;        /* Both "DONE" */
+    else
+      return -1; /* A done, B not */
+  }
+  if(strstr(tmp2,"%")==0) 
+    return 1; /* B done, A not */
+
+  /* Both have %, must remove */
+  tmp1 = STRDUP(GTK_CELL_TEXT(row1->cell[clist->sort_column])->text);
+  tmp2 = STRDUP(GTK_CELL_TEXT(row2->cell[clist->sort_column])->text);
+ 
+  tmp1[strlen(tmp1)-1]=0;
+  tmp2[strlen(tmp2)-1]=0;
+  
+  value1 = atof(tmp1);
+  value2 = atof(tmp2);
+
+  FREE(tmp1);
+  FREE(tmp2);
+
+  if(value1>value2)
+    return(-1);
+  else if(value1==value2)
+    return(0);
+  else
+    return(1);
+}
+
+/**
+ * A general right-button popup menu callback
+ **/
+gboolean popupCallback(GtkWidget *widget,
+                       GdkEvent *event,
+                       GtkWidget *menu )
+{
+   GdkEventButton *bevent = (GdkEventButton *)event;
+
+   /* Only take button presses */
+   if (event->type != GDK_BUTTON_PRESS)
+     return FALSE;
+
+   if (bevent->button != 3)
+     return FALSE;
+
+   /* Show the menu */
+   gtk_widget_show(menu);
+   gtk_menu_popup (GTK_MENU(menu), NULL, NULL,
+                   NULL, NULL, bevent->button, bevent->time);
+
+   return TRUE;
+}
+
+/* end of helper.c */
+

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/helper.h
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/helper.h      2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/helper.h      2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,157 @@
+/*
+     This file is part of GNUnet
+
+     GNUnet 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, or (at your
+     option) any later version.
+
+     GNUnet 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 GNUnet; see the file COPYING.  If not, write to the
+     Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+     Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file applications/afs/gtkui/helper.h
+ * @author Igor Wronsky 
+ **/
+
+#ifndef GTKUI_HELPER_H
+#define GTKUI_HELPER_H
+
+
+#include "platform.h"
+
+/* for GTK 2 */
+#define GTK_ENABLE_BROKEN
+
+#include <gtk/gtk.h>
+#include <gtk/gtktext.h>
+
+typedef struct {
+  Semaphore *sem;
+  void *args;
+  GtkFunction func;
+} SaveCall;
+
+typedef struct {
+  int doPopup;
+  gchar *note;
+} InfoMessage;      
+
+typedef struct {
+  char *labelName;
+  GtkWidget *frame;
+} AddNotebook;
+
+
+/* callback: window close: close the window */
+gint deleteEvent(GtkWidget * widget,
+                GdkEvent * event,
+                gpointer data);
+
+/**
+ * A callback to destroy any widget given as second argument
+ *
+ */
+void destroyWidget(GtkWidget * dummy, GtkWidget * widget);
+
+/**
+ * Displays an informative message to the user
+ */
+void guiMessage(const char * format, ...);
+
+/**
+ * Appends a message to the info window 
+ */
+void infoMessage(int doPopup, const char * format, ...);
+
+/** 
+ * Appends a log entry to the info window
+ *
+ * @param txt the log entry
+ *
+ **/
+void addLogEntry(const char *txt);
+
+void addToNotebook(char * labelName,
+                  GtkWidget * frame);
+
+void hideWindow(GtkWidget * widget,
+               gpointer data);
+
+void showStats(GtkWidget * widget,
+              gpointer data);
+
+int checkForDaemon(void);
+
+void cronCheckDaemon(void * dummy);
+void launchDaemon(GtkWidget * widget,
+                 gpointer data);
+void killDaemon(GtkWidget * widget,
+               gpointer data);
+
+/**
+ * A function for numeric comparisons of strings
+ **/
+gint numericComp(GtkCList *clist,
+                 gconstpointer ptr1,
+                 gconstpointer ptr2);
+
+/**
+ * A function for case-insensitive text comparisons
+ **/
+gint alphaComp(GtkCList *clist,
+               gconstpointer ptr1,
+               gconstpointer ptr2);
+
+/**
+ * A function for comparisons of percentages
+ **/
+gint percentComp(GtkCList *clist,
+                 gconstpointer ptr1,
+                 gconstpointer ptr2);
+
+/**
+ * A general right-button popup menu callback
+ **/
+gboolean popupCallback(GtkWidget *widget,
+                       GdkEvent *event,
+                                  GtkWidget *menu );
+                      
+/**
+ * Call a callback function from the mainloop/main thread ("SaveCall").
+ * Since GTK doesn't work with multi-threaded applications under Windows,
+ * all GTK operations have to be done in the main thread
+ **/
+void gtkSaveCall(GtkFunction func, void *args);
+
+/**
+ * Initialize "SaveCalls"
+ **/
+void gtkInitSaveCalls();
+
+void gtkDoneSaveCalls();
+
+void gtkRunSomeSaveCalls();
+ 
+/**
+ * Called from a "SaveCall"-function to indicate that it is done
+ **/
+void gtkSaveCallDone(Semaphore *sem);
+
+/**
+ * Destroy a widget. Called from threads other than the main thread
+ **/
+gint doDestroyWidget(SaveCall *call);
+
+extern GtkNotebook * notebook;
+extern GtkWidget * infoWindow;
+
+#endif

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/insert.c
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/insert.c      2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/insert.c      2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,1297 @@
+/*
+     This file is part of GNUnet
+
+     GNUnet 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, or (at your
+     option) any later version.
+
+     GNUnet 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 GNUnet; see the file COPYING.  If not, write to the
+     Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+     Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file src/applications/afs/gtkui/insert.c
+ * @brief handles file insertions in the GTK GUI
+ * @author Igor Wronsky
+ * @author Christian Grothoff (refactoring, added bugs)
+ **/
+
+#include "gnunet_afs_esed2.h"
+#include "helper.h"
+#include "insertprogress.h"
+#include "insert.h"
+#include "main.h"
+
+/**
+ * @brief state of the edit RootNode window
+ **/
+typedef struct {
+  char * fileName;
+  GtkWidget * editAttributesWindow;
+  GtkWidget * fileNameLine;
+  GtkWidget * descriptionLine;
+  GtkWidget * mimeLine;
+  GtkWidget * indexButton;
+  GtkWidget * checkCopy;
+  GtkWidget * keywordLine;
+  GtkWidget * keywordList;
+} EditWindowModel;
+
+/**
+ * @brief state of the edit RootNode window
+ **/
+typedef struct {
+  char * fileName;
+  GtkWidget * editAttributesWindow;
+  GtkWidget * fileNameLine;
+  GtkWidget * descriptionLine;
+  GtkWidget * indexButton;
+  GtkWidget * checkCopy;
+  GtkWidget * keywordLine;
+  GtkWidget * keywordList;
+  GtkWidget * gkeywordLine;
+  GtkWidget * gkeywordList;
+} EditDirectoryWindowModel;
+
+
+/**
+ * Collects the results of editAttributes, creates an insertion 
+ * progressbar and launches the insertion thread.
+ *
+ * @param dummy not used
+ * @param ewm the state of the edit window
+ **/
+static void startInsert(GtkWidget * dummy, 
+                       EditWindowModel * ewm) {
+  InsertModel * ilm;
+  gchar * txt;
+  int i;
+  PTHREAD_T insertThread;
+
+  ilm = MALLOC(sizeof(InsertModel));
+  ilm->fileName = STRDUP(ewm->fileName);
+
+  /* get content indexing style */
+  if (gtk_toggle_button_get_active((GtkToggleButton *)ewm->indexButton) == 
TRUE)
+    ilm->indexContent = YES;
+  else
+    ilm->indexContent = NO;
+  ilm->copyFile = gtk_toggle_button_get_active(
+                    (GtkToggleButton *)ewm->checkCopy);
+
+  /* get the published filename */
+  txt = gtk_entry_get_text(GTK_ENTRY(ewm->fileNameLine));
+  if (txt == NULL)
+    ilm->fileNameRoot = STRDUP("none specified");
+  else
+    ilm->fileNameRoot = STRDUP(txt);
+  
+  /* get the new description, if any */
+  txt = gtk_entry_get_text(GTK_ENTRY(ewm->descriptionLine));
+  if (txt == NULL)
+    ilm->description = STRDUP("no description specified");
+  else
+    ilm->description = STRDUP(txt);
+
+  txt = gtk_entry_get_text(GTK_ENTRY(ewm->mimeLine));
+  if (txt == NULL)
+    ilm->mimetype = STRDUP("unknown");
+  else
+    ilm->mimetype = STRDUP(txt);
+
+  /* get new list of keywords */
+  ilm->num_keywords = GTK_CLIST(ewm->keywordList)->rows;
+
+  if (ilm->num_keywords > 0) {
+    ilm->keywords = (char**) MALLOC(ilm->num_keywords * sizeof(char*));
+    for(i=0;i<ilm->num_keywords;i++) {     
+      gtk_clist_get_text(GTK_CLIST(ewm->keywordList),
+                        i,
+                        0,
+                        &txt);
+      ilm->keywords[i] = STRDUP(txt);
+    } 
+  } else
+    ilm->keywords = NULL;
+
+  if(ilm->indexContent == YES)
+    strcpy(ilm->opDescription, "indexed");
+  else
+    strcpy(ilm->opDescription, "inserted");
+  createInsertProgressBar(ilm);
+  /* start the insert thread */
+  if (0 != PTHREAD_CREATE(&insertThread,
+                         (PThreadMain) insertFileGtkThread,
+                         ilm,
+                         16 * 1024))
+    errexit("FATAL: could not create insert thread (%s)!\n",
+           STRERROR(errno));
+  PTHREAD_DETACH(&insertThread);
+
+  /* destroy the "editAttributes" window */
+  gtk_widget_destroy(ewm->editAttributesWindow);
+}
+
+/**
+ * Collects the results of editAttributes, creates an insertion 
+ * progressbar and launches the insertion thread.
+ *
+ * @param dummy not used
+ * @param ewm the state of the edit window
+ **/
+static void startInsertDirectory(GtkWidget * dummy, 
+                                EditDirectoryWindowModel * ewm) {
+  InsertDirectoryModel * ilm;
+  gchar * txt;
+  int i;
+  PTHREAD_T insertThread;
+
+  ilm = MALLOC(sizeof(InsertDirectoryModel));
+  ilm->fileName = STRDUP(ewm->fileName);
+
+  /* get content indexing style */
+  if (gtk_toggle_button_get_active((GtkToggleButton *)ewm->indexButton) == 
TRUE)
+    ilm->indexContent = YES;
+  else
+    ilm->indexContent = NO;
+  ilm->copyFile = gtk_toggle_button_get_active(
+                    (GtkToggleButton *)ewm->checkCopy);
+  /* get the published filename */
+  txt = gtk_entry_get_text(GTK_ENTRY(ewm->fileNameLine));
+  if (txt == NULL)
+    ilm->fileNameRoot = STRDUP("none specified");
+  else
+    ilm->fileNameRoot = STRDUP(txt);
+  
+  /* get the new description, if any */
+  txt = gtk_entry_get_text(GTK_ENTRY(ewm->descriptionLine));
+  if (txt == NULL)
+    ilm->description = STRDUP("no description specified");
+  else
+    ilm->description = STRDUP(txt);
+
+  ilm->mimetype = STRDUP(GNUNET_DIRECTORY_MIME);
+
+  /* get new list of keywords */
+  ilm->num_keywords = GTK_CLIST(ewm->keywordList)->rows;
+
+  if (ilm->num_keywords > 0) {
+    ilm->keywords = (char**) MALLOC(ilm->num_keywords * sizeof(char*));
+    for(i=0;i<ilm->num_keywords;i++) {     
+      gtk_clist_get_text(GTK_CLIST(ewm->keywordList),
+                        i,
+                        0,
+                        &txt);
+      ilm->keywords[i] = STRDUP(txt);
+    } 
+  } else
+    ilm->keywords = NULL;
+
+  /* get new list of keywords */
+  ilm->num_gkeywords = GTK_CLIST(ewm->gkeywordList)->rows;
+
+  if (ilm->num_gkeywords > 0) {
+    ilm->gkeywords = (char**) MALLOC(ilm->num_gkeywords * sizeof(char*));
+    for(i=0;i<ilm->num_gkeywords;i++) {     
+      gtk_clist_get_text(GTK_CLIST(ewm->gkeywordList),
+                        i,
+                        0,
+                        &txt);
+      ilm->gkeywords[i] = STRDUP(txt);
+    } 
+  } else
+    ilm->gkeywords = NULL;
+
+  if(ilm->indexContent == YES)
+    strcpy(ilm->opDescription, "indexed");
+  else
+    strcpy(ilm->opDescription, "inserted");
+  /*
+    FIXME: allow setting this as an option in the dialog!
+  FREENONNULL(setConfigurationString("GNUNET-INSERT",
+                                    "EXTRACT-KEYWORDS",
+                                    "NO"));
+  */
+  createInsertDirectoryProgressBar(ilm);
+  /* start the insert thread */
+  if (0 != PTHREAD_CREATE(&insertThread,
+                         (PThreadMain) insertDirectoryGtkThread,
+                         ilm,
+                         16 * 1024))
+    errexit("FATAL: could not create insert thread (%s)!\n",
+           STRERROR(errno));
+  PTHREAD_DETACH(&insertThread);
+  /* destroy the "editAttributes" window */
+  gtk_widget_destroy(ewm->editAttributesWindow);
+}
+
+/**
+ * The keyword add button was clicked. Add whatever 
+ * is in the keyword box to the list of keywords.
+ *
+ * @param w not used
+ * @param ewm the state of the edit window
+ **/
+static void button_add_clicked(GtkWidget * w, 
+                              EditWindowModel * ewm) {
+  gchar * key;
+  gchar * newKeyword;
+  int i;
+
+  key = gtk_entry_get_text(GTK_ENTRY(ewm->keywordLine));
+  if (key == NULL) {
+    /* message to enter a string? */
+    return;
+  }    
+
+  newKeyword = STRDUP(key);
+  key = newKeyword;
+
+  /* remove trailing & heading spaces */
+  i = strlen(key)-1;
+  while ( (newKeyword[i] == ' ') && 
+         (i >= 0) ) {
+    newKeyword[i--] = '\0';
+  }
+  while (*newKeyword == ' ')
+    newKeyword++;
+
+  if ( *newKeyword == '\0' ) {
+    /* message to enter more than spaces? */    
+  } else {
+    gtk_clist_append(GTK_CLIST(ewm->keywordList),
+                    &newKeyword);
+  } 
+  FREE(key);
+  gtk_entry_set_text(GTK_ENTRY(ewm->keywordLine),
+                    "");
+}
+
+/**
+ * The keyword delete button was clicked. Delete the 
+ * currently selected keyword.
+ *
+ * @param w not used
+ * @param ewm state of the edit window
+ **/
+static void button_del_clicked(GtkWidget * w, 
+                              EditWindowModel * ewm) {
+  GList * tmp;
+
+  tmp = GTK_CLIST(ewm->keywordList)->selection;
+  if (NULL == tmp) {
+    /* message that keyword must be selected to delete one? */
+    return;
+  }  
+  gtk_clist_remove(GTK_CLIST(ewm->keywordList),
+                  (int)tmp->data);
+}
+
+/**
+ * The keyword add button was clicked. Add whatever 
+ * is in the keyword box to the list of keywords.
+ *
+ * @param w not used
+ * @param ewm the state of the edit window
+ **/
+static void button_dir_add_clicked1(GtkWidget * w, 
+                                   EditDirectoryWindowModel * ewm) {
+  gchar * key;
+  gchar * newKeyword;
+  int i;
+
+  key = gtk_entry_get_text(GTK_ENTRY(ewm->keywordLine));
+  if (key == NULL) {
+    /* message to enter a string? */
+    return;
+  }    
+
+  newKeyword = STRDUP(key);
+  key = newKeyword;
+
+  /* remove trailing & heading spaces */
+  i = strlen(key)-1;
+  while ( (newKeyword[i] == ' ') && 
+         (i >= 0) ) {
+    newKeyword[i--] = '\0';
+  }
+  while (*newKeyword == ' ')
+    newKeyword++;
+
+  if ( *newKeyword == '\0' ) {
+    /* message to enter more than spaces? */    
+  } else {
+    gtk_clist_append(GTK_CLIST(ewm->keywordList),
+                    &newKeyword);
+  } 
+  FREE(key);
+  gtk_entry_set_text(GTK_ENTRY(ewm->keywordLine),
+                    "");
+}
+
+/**
+ * The keyword delete button was clicked. Delete the 
+ * currently selected keyword.
+ *
+ * @param w not used
+ * @param ewm state of the edit window
+ **/
+static void button_dir_del_clicked1(GtkWidget * w, 
+                                   EditDirectoryWindowModel * ewm) {
+  GList * tmp;
+
+  tmp = GTK_CLIST(ewm->keywordList)->selection;
+  if (NULL == tmp) {
+    /* message that keyword must be selected to delete one? */
+    return;
+  }  
+  gtk_clist_remove(GTK_CLIST(ewm->keywordList),
+                  (int)tmp->data);
+}
+
+/**
+ * The keyword add button was clicked. Add whatever 
+ * is in the keyword box to the list of keywords.
+ *
+ * @param w not used
+ * @param ewm the state of the edit window
+ **/
+static void button_dir_add_clicked2(GtkWidget * w, 
+                                   EditDirectoryWindowModel * ewm) {
+  gchar * key;
+  gchar * newKeyword;
+  int i;
+
+  key = gtk_entry_get_text(GTK_ENTRY(ewm->gkeywordLine));
+  if (key == NULL) {
+    /* message to enter a string? */
+    return;
+  }    
+
+  newKeyword = STRDUP(key);
+  key = newKeyword;
+
+  /* remove trailing & heading spaces */
+  i = strlen(key)-1;
+  while ( (newKeyword[i] == ' ') && 
+         (i >= 0) ) {
+    newKeyword[i--] = '\0';
+  }
+  while (*newKeyword == ' ')
+    newKeyword++;
+
+  if ( *newKeyword == '\0' ) {
+    /* message to enter more than spaces? */    
+  } else {
+    gtk_clist_append(GTK_CLIST(ewm->gkeywordList),
+                    &newKeyword);
+  } 
+  FREE(key);
+  gtk_entry_set_text(GTK_ENTRY(ewm->gkeywordLine),
+                    "");
+}
+
+/**
+ * The keyword delete button was clicked. Delete the 
+ * currently selected keyword.
+ *
+ * @param w not used
+ * @param ewm state of the edit window
+ **/
+static void button_dir_del_clicked2(GtkWidget * w, 
+                                   EditDirectoryWindowModel * ewm) {
+  GList * tmp;
+
+  tmp = GTK_CLIST(ewm->gkeywordList)->selection;
+  if (NULL == tmp) {
+    /* message that keyword must be selected to delete one? */
+    return;
+  }  
+  gtk_clist_remove(GTK_CLIST(ewm->gkeywordList),
+                  (int)tmp->data);
+}
+
+/**
+ * The index/insert file button was clicked.
+ *
+ * @param w the button
+ * @param ewm state of the edit window
+ **/
+static void button_index_clicked(GtkWidget * w,
+                                 EditWindowModel * ewm) {
+  gtk_widget_set_sensitive(ewm->checkCopy, w == ewm->indexButton);
+}
+
+/**
+ * Exit the application (called when the main window
+ * is closed or the user selects File-Quit).
+ **/
+static void destroyEditWindow(GtkWidget * widget,
+                             EditWindowModel * ewm) {
+  FREE(ewm->fileName);
+  FREE(ewm);
+}
+
+/**
+ * Exit the application (called when the main window
+ * is closed or the user selects File-Quit).
+ **/
+static void destroyEditDirectoryWindow(GtkWidget * widget,
+                                      EditDirectoryWindowModel * ewm) {
+  FREE(ewm->fileName);
+  FREE(ewm);
+}
+
+/**
+ * Show user a window to edit information related to this file.
+ * After user is done, call startInsert.
+ *
+ * @param filename the name of the file that is inserted
+ * @param fileNameRoot the short name of the file
+ * @param description the description of the file
+ * @param mimetype the mimetype of the ile
+ * @param keywords the extracted keywords of the file
+ * @param num_keywords the number of keywords extracted
+ **/
+static void editAttributes(char * filename,
+                          char * fileNameRoot,
+                          char * description,
+                          char * mimetype,
+                          char ** keywords,
+                          int num_keywords) {
+  EditWindowModel * ewm;
+  GtkWidget * window;
+  GtkWidget * vbox, * hbox;
+  GtkWidget * clist;
+  GtkWidget * scrolled_window;
+  GtkWidget * label;
+  GtkWidget * separator; 
+  GtkWidget * button_add;
+  GtkWidget * button_delete;
+  GtkWidget * button_ok;
+  GtkWidget * button_cancel;
+  GtkWidget * keyword_line;
+  GSList * group;
+  GtkWidget * indexbutton1;
+  GtkWidget * indexbutton2;
+  GtkWidget * check_copy;
+  int doIndex;
+  gchar * titles[1] = { "Keyword(s) used" };
+  int i;
+
+  ewm = MALLOC(sizeof(EditWindowModel));
+  ewm->fileName = STRDUP(filename);
+  /* create new window for editing */
+  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+  ewm->editAttributesWindow = window;
+  gtk_widget_set_usize(GTK_WIDGET(window),
+                      400,
+                      480);
+  gtk_window_set_title(GTK_WINDOW(window), 
+                      "Edit attributes");
+
+  /* add container for window elements */
+  vbox = gtk_vbox_new(FALSE, 0);
+  gtk_container_add(GTK_CONTAINER(window),
+                   vbox);
+  gtk_widget_show(vbox);
+
+  /* when user clicks on close box, always "destroy" */
+  gtk_signal_connect(GTK_OBJECT(window),
+                    "delete_event",
+                    GTK_SIGNAL_FUNC(deleteEvent),
+                    ewm);
+  /* whenever edit window gets destroyed, 
+     free *ALL* ewm data */
+  gtk_signal_connect(GTK_OBJECT(window),
+                    "destroy",
+                    GTK_SIGNAL_FUNC(destroyEditWindow),
+                    ewm);
+
+  gtk_container_set_border_width(GTK_CONTAINER(window), 
+                                10);
+
+  /* Create a line to change the published filename */
+  label = gtk_label_new("Published filename:");
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    label, 
+                    FALSE, 
+                    FALSE, 
+                    0);
+  gtk_widget_show(label); 
+  ewm->fileNameLine = gtk_entry_new();
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    ewm->fileNameLine,
+                    TRUE,
+                    TRUE,
+                    0);
+  gtk_entry_set_text(GTK_ENTRY(ewm->fileNameLine), 
+                    fileNameRoot);
+  gtk_widget_show(ewm->fileNameLine);
+  
+  /* Create a line to change the mime type */
+  label = gtk_label_new("Mimetype:");
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    label, 
+                    FALSE, 
+                    FALSE, 
+                    0);
+  gtk_widget_show(label);  
+  ewm->mimeLine = gtk_entry_new();
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    ewm->mimeLine, 
+                    TRUE, 
+                    TRUE,
+                    0);
+  gtk_entry_set_text(GTK_ENTRY(ewm->mimeLine), 
+                    mimetype);
+  gtk_widget_show(ewm->mimeLine);
+
+  /* Create a line to change the description */
+  label = gtk_label_new("Description:");
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    label, 
+                    FALSE, 
+                    FALSE, 
+                    0);
+  gtk_widget_show(label);  
+  ewm->descriptionLine = gtk_entry_new();
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    ewm->descriptionLine, 
+                    TRUE, 
+                    TRUE,
+                    0);
+  gtk_entry_set_text(GTK_ENTRY(ewm->descriptionLine), 
+                    description);
+  gtk_widget_show(ewm->descriptionLine);
+  
+  /* add buttons to select the insertion method */
+  label = gtk_label_new("Insertion method:");
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    label,
+                    FALSE, 
+                    FALSE,
+                    0);
+  gtk_widget_show(label);  
+  hbox = gtk_hbox_new(FALSE,0);
+  gtk_box_pack_start(GTK_BOX(vbox), 
+                    hbox, 
+                    TRUE,
+                    TRUE,
+                    0);
+  gtk_widget_show(hbox);
+  indexbutton1 = gtk_radio_button_new_with_label(NULL,
+                                                "Index only");
+  ewm->indexButton = indexbutton1;
+  gtk_signal_connect(GTK_OBJECT(indexbutton1),
+                          "toggled",
+                     GTK_SIGNAL_FUNC(button_index_clicked),
+                     ewm);
+  gtk_box_pack_start(GTK_BOX (hbox),
+                    indexbutton1,
+                    TRUE,
+                    TRUE,
+                    0);
+  gtk_widget_show(indexbutton1);  
+  group = gtk_radio_button_group(GTK_RADIO_BUTTON(indexbutton1));
+  indexbutton2 = gtk_radio_button_new_with_label(group, 
+                                                "Full insertion");
+  gtk_signal_connect(GTK_OBJECT(indexbutton2),
+                          "toggled",
+                     GTK_SIGNAL_FUNC(button_index_clicked),
+                     ewm);
+  gtk_box_pack_start(GTK_BOX(hbox), 
+                    indexbutton2, 
+                    TRUE,
+                    TRUE,
+                    0);
+  gtk_widget_show(indexbutton2);
+  if (testConfigurationString("GNUNET-INSERT",
+                             "INDEX-CONTENT",
+                               "YES") == YES) {
+    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(indexbutton1), 
+                                TRUE);
+         doIndex = 1;
+       }
+  else {
+    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(indexbutton2), 
+                                TRUE);
+    doIndex = 0;
+       }
+       
+  check_copy = gtk_check_button_new_with_label("Copy file to shared dir");
+  ewm->checkCopy = check_copy;
+  gtk_box_pack_start(GTK_BOX(hbox),
+                     check_copy,
+                     TRUE,
+                     TRUE,
+                     0);
+  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_copy), 
+                              ! testConfigurationString("GNUNET-INSERT",
+                                                      "LINK",
+                                                        "YES"));
+  gtk_widget_set_sensitive(check_copy, doIndex);
+  gtk_widget_show(check_copy);
+
+  /* add a list of keywords */
+  scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
+                                GTK_POLICY_AUTOMATIC, 
+                                GTK_POLICY_ALWAYS);
+  gtk_box_pack_start(GTK_BOX(vbox), 
+                    scrolled_window, 
+                    TRUE, 
+                    TRUE, 
+                    0);
+  gtk_widget_show(scrolled_window);  
+  clist = gtk_clist_new_with_titles(1, titles); 
+  ewm->keywordList = clist;
+  gtk_container_add(GTK_CONTAINER(scrolled_window), 
+                   clist);
+
+  /* add the pre-extracted keywords to the list */
+  gtk_clist_freeze(GTK_CLIST(clist));
+  for(i=0;i<num_keywords;i++) {
+    gtk_clist_append(GTK_CLIST(clist), 
+                    &keywords[i]);
+  }
+  gtk_clist_thaw(GTK_CLIST(clist));
+  gtk_widget_show(clist);
+
+  /* add a line to input new keywords */
+  keyword_line = gtk_entry_new();
+  ewm->keywordLine = keyword_line;
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    keyword_line, 
+                    FALSE,
+                    FALSE,
+                    0);
+  gtk_entry_set_text(GTK_ENTRY(keyword_line), 
+                    "");
+  gtk_signal_connect(GTK_OBJECT(keyword_line),
+                    "activate",
+                     GTK_SIGNAL_FUNC(button_add_clicked),
+                     ewm);
+  gtk_widget_show(keyword_line);
+
+  /* add the buttons to add and delete keywords */
+  hbox = gtk_hbox_new(FALSE, 0);
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    hbox,
+                    FALSE,
+                    TRUE,
+                    0);
+  gtk_widget_show(hbox);
+  button_add = gtk_button_new_with_label("Add keyword");
+  button_delete = gtk_button_new_with_label("Delete keyword");
+  gtk_box_pack_start(GTK_BOX(hbox), 
+                    button_add, 
+                    TRUE, 
+                    TRUE, 
+                    0);
+  gtk_box_pack_start(GTK_BOX(hbox), 
+                    button_delete, 
+                    TRUE, 
+                    TRUE, 
+                    0);
+  gtk_signal_connect(GTK_OBJECT(button_add),
+                    "clicked",
+                    GTK_SIGNAL_FUNC(button_add_clicked),
+                    ewm);
+  gtk_signal_connect(GTK_OBJECT(button_delete), 
+                    "clicked",
+                    GTK_SIGNAL_FUNC(button_del_clicked),
+                    ewm);
+  gtk_widget_show(button_add);
+  gtk_widget_show(button_delete);
+
+  /* add the insertion ok/cancel buttons */
+  separator = gtk_hseparator_new();
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    separator,
+                    TRUE, 
+                    TRUE,
+                    0);
+  gtk_widget_show(separator);
+
+  hbox = gtk_hbox_new(FALSE, 0);
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    hbox, 
+                    FALSE, 
+                    TRUE, 
+                    0);
+  gtk_widget_show(hbox);
+  button_ok = gtk_button_new_with_label("Ok");
+  button_cancel = gtk_button_new_with_label("Cancel");
+  gtk_box_pack_start(GTK_BOX(hbox),
+                    button_ok,
+                    TRUE,
+                    TRUE,
+                    0);
+  gtk_box_pack_start(GTK_BOX(hbox), 
+                    button_cancel, 
+                    TRUE,
+                    TRUE, 
+                    0);
+  gtk_signal_connect(GTK_OBJECT(button_ok), 
+                    "clicked",
+                    GTK_SIGNAL_FUNC(startInsert),
+                    ewm);
+  gtk_signal_connect(GTK_OBJECT(button_cancel),
+                    "clicked",
+                    GTK_SIGNAL_FUNC(destroyWidget),
+                    window);
+  gtk_widget_show(button_ok);
+  gtk_widget_show(button_cancel);
+
+  /* all clear, show the window */
+  gtk_widget_show(window);
+}
+
+/**
+ * Launches the attribute edit routine for the selected file,
+ * after keywords have been extracted.
+ *
+ * @param filename the selected filename
+ */
+static void file_selected(char *filename) {
+  int i;
+  char * fileNameRoot;
+  char * description;
+  char * mimetype;
+  char ** keywords;
+  int num_keywords; 
+
+
+  /* if filename is '/home/user/foo', use 'foo' as the filenameRoot */
+  fileNameRoot = NULL;
+  for (i=strlen(filename)-1;i>=0;i--) {
+    if (filename[i] == DIR_SEPARATOR) {
+      fileNameRoot = STRDUP(&filename[i+1]);
+      break;
+    }
+  }  
+  if (i == -1)
+    errexit("FATAL: ASSERTION failed: filename does not start with a '/'");
+
+  /* try to extract keywords */ 
+  description = NULL;
+  mimetype = NULL; 
+  num_keywords = 0;
+  keywords = NULL;
+  extractKeywords(filename,
+                 &description,
+                 &mimetype,
+                 &keywords,
+                 &num_keywords);
+
+  if (description == NULL)
+    description = STRDUP("No description supplied");
+  if (mimetype == NULL)
+    mimetype = STRDUP("unknown");
+  
+  /* allow the user to edit the insertion related info */
+  editAttributes(filename,
+                fileNameRoot,
+                description,
+                mimetype,
+                keywords,
+                num_keywords);
+
+  for (i=0;i<num_keywords;i++)
+    FREE(keywords[i]);
+  FREENONNULL(keywords);
+  FREE(mimetype);
+  FREE(description);
+  FREE(filename);
+  FREE(fileNameRoot);
+}
+
+
+
+/**
+ * Show user a window to edit information related to this file.
+ * After user is done, call startInsert.
+ *
+ * @param filename the name of the file that is inserted
+ * @param fileNameRoot the short name of the file
+ **/
+static void editDirectoryAttributes(char * filename,
+                                   char * fileNameRoot) {
+  EditDirectoryWindowModel * ewm;
+  GtkWidget * window;
+  GtkWidget * vbox, * hbox;
+  GtkWidget * clist;
+  GtkWidget * scrolled_window;
+  GtkWidget * label;
+  GtkWidget * separator; 
+  GtkWidget * button_add;
+  GtkWidget * button_delete;
+  GtkWidget * button_ok;
+  GtkWidget * button_cancel;
+  GtkWidget * keyword_line;
+  GSList * group;
+  GtkWidget * indexbutton1;
+  GtkWidget * indexbutton2;
+  GtkWidget * check_copy;
+  int doIndex;
+  gchar * titles[1] = { "Keyword(s) used for directory" };
+  gchar * gtitles[1] = { "Keyword(s) used for all files in directory" };
+
+  ewm = MALLOC(sizeof(EditDirectoryWindowModel));
+  ewm->fileName = STRDUP(filename);
+  /* create new window for editing */
+  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+  ewm->editAttributesWindow = window;
+  gtk_widget_set_usize(GTK_WIDGET(window),
+                      400,
+                      480);
+  gtk_window_set_title(GTK_WINDOW(window), 
+                      "Edit attributes");
+
+  /* add container for window elements */
+  vbox = gtk_vbox_new(FALSE, 0);
+  gtk_container_add(GTK_CONTAINER(window),
+                   vbox);
+  gtk_widget_show(vbox);
+
+  /* when user clicks on close box, always "destroy" */
+  gtk_signal_connect(GTK_OBJECT(window),
+                    "delete_event",
+                    GTK_SIGNAL_FUNC(deleteEvent),
+                    ewm);
+  /* whenever edit window gets destroyed, 
+     free *ALL* ewm data */
+  gtk_signal_connect(GTK_OBJECT(window),
+                    "destroy",
+                    GTK_SIGNAL_FUNC(destroyEditDirectoryWindow),
+                    ewm);
+
+  gtk_container_set_border_width(GTK_CONTAINER(window), 
+                                10);
+
+  /* Create a line to change the published filename */
+  label = gtk_label_new("Published directoryname:");
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    label, 
+                    FALSE, 
+                    FALSE, 
+                    0);
+  gtk_widget_show(label); 
+  ewm->fileNameLine = gtk_entry_new();
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    ewm->fileNameLine,
+                    TRUE,
+                    TRUE,
+                    0);
+  gtk_entry_set_text(GTK_ENTRY(ewm->fileNameLine), 
+                    fileNameRoot);
+  gtk_widget_show(ewm->fileNameLine);
+  
+
+  /* Create a line to change the description */
+  label = gtk_label_new("Description:");
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    label, 
+                    FALSE, 
+                    FALSE, 
+                    0);
+  gtk_widget_show(label);  
+  ewm->descriptionLine = gtk_entry_new();
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    ewm->descriptionLine, 
+                    TRUE, 
+                    TRUE,
+                    0);
+  gtk_entry_set_text(GTK_ENTRY(ewm->descriptionLine), 
+                    "No description supplied");
+  gtk_widget_show(ewm->descriptionLine);
+  
+  /* add buttons to select the insertion method */
+  label = gtk_label_new("Insertion method (for files in directory):");
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    label,
+                    FALSE, 
+                    FALSE,
+                    0);
+  gtk_widget_show(label);  
+  hbox = gtk_hbox_new(FALSE,0);
+  gtk_box_pack_start(GTK_BOX(vbox), 
+                    hbox, 
+                    TRUE,
+                    TRUE,
+                    0);
+  gtk_widget_show(hbox);
+  indexbutton1 = gtk_radio_button_new_with_label(NULL,
+                                                "Index only");
+       gtk_signal_connect(GTK_OBJECT(indexbutton1),
+                          "toggled",
+                     GTK_SIGNAL_FUNC(button_index_clicked),
+                     ewm);
+  ewm->indexButton = indexbutton1;
+  gtk_box_pack_start(GTK_BOX (hbox),
+                    indexbutton1,
+                    TRUE,
+                    TRUE,
+                    0);
+  gtk_widget_show(indexbutton1);  
+  group = gtk_radio_button_group(GTK_RADIO_BUTTON(indexbutton1));
+  indexbutton2 = gtk_radio_button_new_with_label(group, 
+                                                "Full insertion");
+  gtk_signal_connect(GTK_OBJECT(indexbutton2),
+                          "toggled",
+                     GTK_SIGNAL_FUNC(button_index_clicked),
+                     ewm);
+  gtk_box_pack_start(GTK_BOX(hbox), 
+                    indexbutton2, 
+                    TRUE,
+                    TRUE,
+                    0);
+  gtk_widget_show(indexbutton2);
+  if (testConfigurationString("GNUNET-INSERT",
+                             "INDEX-CONTENT",
+                               "YES") == YES) {
+    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(indexbutton1), 
+                                TRUE);
+               doIndex = 1;
+  }
+  else {
+    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(indexbutton2), 
+                                TRUE);
+               doIndex = 0;
+  }
+
+
+  check_copy = gtk_check_button_new_with_label("Copy file to shared dir");
+  ewm->checkCopy = check_copy;
+  gtk_box_pack_start(GTK_BOX(hbox),
+                     check_copy,
+                     TRUE,
+                     TRUE,
+                     0);
+  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_copy), 
+                              ! testConfigurationString("GNUNET-INSERT",
+                                                      "LINK",
+                                                        "YES"));
+  gtk_widget_set_sensitive(check_copy, doIndex);
+  gtk_widget_show(check_copy);
+
+
+
+
+  /* add list of local keywords */
+  scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
+                                GTK_POLICY_AUTOMATIC, 
+                                GTK_POLICY_ALWAYS);
+  gtk_box_pack_start(GTK_BOX(vbox), 
+                    scrolled_window, 
+                    TRUE, 
+                    TRUE, 
+                    0);
+  gtk_widget_show(scrolled_window);  
+  clist = gtk_clist_new_with_titles(1, titles); 
+  ewm->keywordList = clist;
+  gtk_container_add(GTK_CONTAINER(scrolled_window), 
+                   clist);
+  gtk_widget_show(clist);
+
+  /* add a line to input new keywords */
+  keyword_line = gtk_entry_new();
+  ewm->keywordLine = keyword_line;
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    keyword_line, 
+                    FALSE,
+                    FALSE,
+                    0);
+  gtk_entry_set_text(GTK_ENTRY(keyword_line), 
+                    "");
+  gtk_signal_connect(GTK_OBJECT(keyword_line),
+                    "activate",
+                     GTK_SIGNAL_FUNC(button_dir_add_clicked1),
+                     ewm);
+  gtk_widget_show(keyword_line);
+
+  /* add the buttons to add and delete keywords */
+  hbox = gtk_hbox_new(FALSE, 0);
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    hbox,
+                    FALSE,
+                    TRUE,
+                    0);
+  gtk_widget_show(hbox);
+  button_add = gtk_button_new_with_label("Add keyword");
+  button_delete = gtk_button_new_with_label("Delete keyword");
+  gtk_box_pack_start(GTK_BOX(hbox), 
+                    button_add, 
+                    TRUE, 
+                    TRUE, 
+                    0);
+  gtk_box_pack_start(GTK_BOX(hbox), 
+                    button_delete, 
+                    TRUE, 
+                    TRUE, 
+                    0);
+  gtk_signal_connect(GTK_OBJECT(button_add),
+                    "clicked",
+                    GTK_SIGNAL_FUNC(button_dir_add_clicked1),
+                    ewm);
+  gtk_signal_connect(GTK_OBJECT(button_delete), 
+                    "clicked",
+                    GTK_SIGNAL_FUNC(button_dir_del_clicked1),
+                    ewm);
+  gtk_widget_show(button_add);
+  gtk_widget_show(button_delete);
+
+
+
+  /* add list of global keywords */
+  scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
+                                GTK_POLICY_AUTOMATIC, 
+                                GTK_POLICY_ALWAYS);
+  gtk_box_pack_start(GTK_BOX(vbox), 
+                    scrolled_window, 
+                    TRUE, 
+                    TRUE, 
+                    0);
+  gtk_widget_show(scrolled_window);  
+  clist = gtk_clist_new_with_titles(1, gtitles); 
+  ewm->gkeywordList = clist;
+  gtk_container_add(GTK_CONTAINER(scrolled_window), 
+                   clist);
+  gtk_widget_show(clist);
+
+  /* add a line to input new keywords */
+  keyword_line = gtk_entry_new();
+  ewm->gkeywordLine = keyword_line;
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    keyword_line, 
+                    FALSE,
+                    FALSE,
+                    0);
+  gtk_entry_set_text(GTK_ENTRY(keyword_line), 
+                    "");
+  gtk_signal_connect(GTK_OBJECT(keyword_line),
+                    "activate",
+                     GTK_SIGNAL_FUNC(button_dir_add_clicked2),
+                     ewm);
+  gtk_widget_show(keyword_line);
+
+  /* add the buttons to add and delete keywords */
+  hbox = gtk_hbox_new(FALSE, 0);
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    hbox,
+                    FALSE,
+                    TRUE,
+                    0);
+  gtk_widget_show(hbox);
+  button_add = gtk_button_new_with_label("Add keyword");
+  button_delete = gtk_button_new_with_label("Delete keyword");
+  gtk_box_pack_start(GTK_BOX(hbox), 
+                    button_add, 
+                    TRUE, 
+                    TRUE, 
+                    0);
+  gtk_box_pack_start(GTK_BOX(hbox), 
+                    button_delete, 
+                    TRUE, 
+                    TRUE, 
+                    0);
+  gtk_signal_connect(GTK_OBJECT(button_add),
+                    "clicked",
+                    GTK_SIGNAL_FUNC(button_dir_add_clicked2),
+                    ewm);
+  gtk_signal_connect(GTK_OBJECT(button_delete), 
+                    "clicked",
+                    GTK_SIGNAL_FUNC(button_dir_del_clicked2),
+                    ewm);
+  gtk_widget_show(button_add);
+  gtk_widget_show(button_delete);
+
+
+
+  /* add the insertion ok/cancel buttons */
+  separator = gtk_hseparator_new();
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    separator,
+                    TRUE, 
+                    TRUE,
+                    0);
+  gtk_widget_show(separator);
+
+  hbox = gtk_hbox_new(FALSE, 0);
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    hbox, 
+                    FALSE, 
+                    TRUE, 
+                    0);
+  gtk_widget_show(hbox);
+  button_ok = gtk_button_new_with_label("Ok");
+  button_cancel = gtk_button_new_with_label("Cancel");
+  gtk_box_pack_start(GTK_BOX(hbox),
+                    button_ok,
+                    TRUE,
+                    TRUE,
+                    0);
+  gtk_box_pack_start(GTK_BOX(hbox), 
+                    button_cancel, 
+                    TRUE,
+                    TRUE, 
+                    0);
+  gtk_signal_connect(GTK_OBJECT(button_ok), 
+                    "clicked",
+                    GTK_SIGNAL_FUNC(startInsertDirectory),
+                    ewm);
+  gtk_signal_connect(GTK_OBJECT(button_cancel),
+                    "clicked",
+                    GTK_SIGNAL_FUNC(destroyWidget),
+                    window);
+  gtk_widget_show(button_ok);
+  gtk_widget_show(button_cancel);
+
+  /* all clear, show the window */
+  gtk_widget_show(window);
+}
+
+
+/**
+ * Insert a directory.
+ **/
+static void directory_selected(char * filename) {
+  int i;
+  char * fileNameRoot;
+
+  /* if filename is '/home/user/foo/', use 'foo.gnd' as the filenameRoot */
+  fileNameRoot = NULL;
+  if (filename[strlen(filename)-1] == DIR_SEPARATOR)
+    filename[strlen(filename)-1] = '\0'; 
+  for (i=strlen(filename)-1;i>=0;i--) {
+    if (filename[i] == DIR_SEPARATOR) {
+      fileNameRoot = 
MALLOC(strlen(&filename[i+1])+1+strlen(GNUNET_DIRECTORY_EXT));
+      strcpy(fileNameRoot, &filename[i+1]);
+      strcat(fileNameRoot, GNUNET_DIRECTORY_EXT);
+      break;
+    }
+  }  
+  if (strlen(filename) == 0) {
+    fileNameRoot = STRDUP("");
+  } else {
+    if (i == -1)
+      errexit("FATAL: ASSERTION failed: filename does not start with a %c",
+             DIR_SEPARATOR);
+  }
+
+  /* allow the user to edit the insertion related info */
+  editDirectoryAttributes(filename,
+                         fileNameRoot);
+  FREE(filename);
+  FREE(fileNameRoot);
+}
+
+/**
+ * Callback for the file selection window.
+ *
+ * @param okButton not used
+ * @param window the file selection window
+ */
+static gint gtk_file_selected(GtkWidget * okButton, 
+                         GtkWidget * window) {
+  gchar * filename;
+  char * fn;  
+
+  filename 
+    = gtk_file_selection_get_filename(GTK_FILE_SELECTION(window));
+  if (filename == NULL) {
+    gtk_widget_destroy(window);
+    return FALSE;
+  }
+  if ( (NO == isDirectory(filename)) &&
+       (0 == assertIsFile(filename)) ) {
+    guiMessage("%s is not a file!\n",
+              filename);
+    gtk_widget_destroy(window);
+    return FALSE;
+  }
+
+  fn = STRDUP((char *) filename);
+
+  /* destroy the open-file window */
+  gtk_widget_destroy(window);
+  
+  if (isDirectory(fn))
+    directory_selected(fn);
+  else
+    file_selected(fn);
+
+  return FALSE;
+}
+
+/**
+ * Close the open-file window.
+ **/
+static gint destroyOpenFile(GtkWidget * widget,
+                           GtkWidget * window) {
+  LOG(LOG_DEBUG, 
+      "DEBUG: destroying open-file window (%x)\n", 
+      window);
+  return TRUE;
+}
+
+#ifdef MINGW
+/* Remember the previously selected path */
+static char szFilename[_MAX_PATH + 1] = "\0";
+#endif
+
+/**
+ * Pops up a file selector for the user.
+ *
+ * Explanation: In insertion, functions will be called in the
+ * following order,
+ *
+ * openSelectFile[OK click->]gtk_file_selected->file_selected->
+ * editAttributes[OK click]->startInsert->newthread.insertFile_
+ **/
+void openSelectFile(void) {
+#ifndef MINGW
+  GtkWidget * window;
+
+  window = gtk_file_selection_new("Choose file to be inserted");
+  gtk_signal_connect(GTK_OBJECT(window), 
+                    "destroy",
+                    GTK_SIGNAL_FUNC(destroyOpenFile),
+                    window);
+  gtk_signal_connect(GTK_OBJECT(GTK_FILE_SELECTION(window)->ok_button),
+                    "clicked", 
+                    GTK_SIGNAL_FUNC(gtk_file_selected),
+                    window);
+  gtk_signal_connect(GTK_OBJECT(GTK_FILE_SELECTION(window)->cancel_button),
+                    "clicked", 
+                    GTK_SIGNAL_FUNC(destroyWidget),
+                    window);
+  gtk_widget_show(window);
+#else
+  OPENFILENAME theDlg;
+  
+  memset(&theDlg, 0, sizeof(OPENFILENAME));
+  szFilename[0] = '\0';
+  
+  theDlg.lStructSize = sizeof(OPENFILENAME);
+  theDlg.hwndOwner = GetActiveWindow();
+  theDlg.lpstrFile = szFilename;
+  theDlg.nMaxFile = _MAX_PATH;
+  theDlg.Flags = OFN_FILEMUSTEXIST | OFN_SHAREAWARE;
+  if (GetOpenFileName(&theDlg))
+    file_selected(STRDUP(theDlg.lpstrFile));
+#endif
+}
+
+/* end of insert.c */

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/insert.h
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/insert.h      2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/insert.h      2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,30 @@
+/*
+     This file is part of GNUnet
+
+     GNUnet 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, or (at your
+     option) any later version.
+
+     GNUnet 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 GNUnet; see the file COPYING.  If not, write to the
+     Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+     Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file applications/afs/gtkui/insert.h
+ * @author Igor Wronsky 
+ **/
+
+#ifndef GTKUI_INSERT_H
+#define GTKUI_INSERT_H
+
+void openSelectFile(void);
+
+#endif

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/insertprogress.c
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/insertprogress.c      
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/insertprogress.c      
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,547 @@
+/*
+     This file is part of GNUnet
+
+     GNUnet 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, or (at your
+     option) any later version.
+
+     GNUnet 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 GNUnet; see the file COPYING.  If not, write to the
+     Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+     Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file src/applications/afs/gtkui/insertprogress.c
+ * @brief handles file insertions in the GTK GUI
+ * @author Igor Wronsky
+ * @author Christian Grothoff (refactoring, added bugs)
+ **/
+
+#include "gnunet_afs_esed2.h"
+#include "platform.h"
+#if USE_LIBEXTRACTOR
+#include <extractor.h>
+#endif
+#include "helper.h"
+#include "insertprogress.h"
+#include "insert.h"
+#include "main.h"
+
+typedef struct {
+  GtkWidget *bar;
+  size_t progress;
+} SetStat;
+
+
+static gint setInsertProgressVal(SaveCall *call) {
+  gtk_progress_set_value(GTK_PROGRESS(((SetStat *)call->args)->bar),
+                        ((SetStat *)call->args)->progress);
+
+  gtkSaveCallDone(call->sem);
+  
+  return FALSE;
+}
+
+typedef struct {
+  GtkWidget * bar;
+  size_t value;
+} SetAdj;
+
+
+static gint updateAdjustment(SaveCall * call) {
+  GtkObject * adj;
+
+  adj = gtk_adjustment_new(0, 0,
+                          ((SetAdj *)call->args)->value,
+                          1, 0, 0);
+  gtk_progress_set_adjustment(GTK_PROGRESS(((SetAdj *)call->args)->bar),
+                             GTK_ADJUSTMENT(adj));
+  gtkSaveCallDone(call->sem);
+  
+  return FALSE;
+}
+
+
+
+/**
+ * Callback function to update the insert progressbar.
+ *
+ * @param stats Statistics related to insert progression
+ * @param ilm Data related to the insertion
+ **/
+static void insertModelCallback(ProgressStats * stats,
+                               InsertModel * ilm) {
+  SetStat stat;
+  
+  stat.bar = ilm->progressBar;
+  stat.progress = stats->progress;
+  gtkSaveCall((GtkFunction) setInsertProgressVal, &stat);
+}
+
+
+/**
+ * Callback function to update the insert progressbar.
+ *
+ * @param stats Statistics related to insert progression
+ * @param ilm Data related to the insertion
+ **/
+static void insertDirectoryModelCallback(ProgressStats * stats,
+                                        InsertDirectoryModel * ilm) {
+  SetStat stat;
+  
+  stat.bar = ilm->progressBar;
+  stat.progress = stats->progress;
+  gtkSaveCall((GtkFunction) setInsertProgressVal, &stat);
+}
+
+static gint destroyInsertProgressBar(SaveCall *call) {
+  gtk_widget_destroy((GtkWidget *) call->args);  
+
+  gtkSaveCallDone(call->sem);
+
+  return FALSE;
+}
+
+/**
+ * A function to be run by the insert thread. Does the
+ * actual insertion.
+ *
+ * @param ilm Collected data related to the insertion
+ */
+void insertFileGtkThread(InsertModel * ilm) {
+  int res;
+  GNUNET_TCP_SOCKET * sock;
+  Block * top;
+  int i;
+  
+  SEMAPHORE_DOWN(refuseToDie);
+  if (ilm->indexContent == YES) {
+    FREENONNULL(setConfigurationString("GNUNET-INSERT",
+                                      "INDEX-CONTENT",
+                                      "YES"));
+
+    FREENONNULL(setConfigurationString("GNUNET-INSERT",
+                                       "LINK",
+                                       ilm->copyFile == YES ? "NO" : "YES"));
+
+  } else {
+    FREENONNULL(setConfigurationString("GNUNET-INSERT",
+                                      "INDEX-CONTENT",
+                                      "NO"));
+  }
+  sock = getClientSocket();
+  if (sock == NULL) {
+    SEMAPHORE_UP(refuseToDie);
+    return; /* warning should have been printed */
+  }
+  top = insertFile(sock,
+                  ilm->fileName,
+                  (ProgressModel)&insertModelCallback,
+                  ilm);
+  if (top != NULL) {
+    res = insertRoot(sock,
+                    top, 
+                    ilm->description,
+                    ilm->fileNameRoot,
+                    ilm->mimetype,
+                    ilm->num_keywords,
+                    ilm->keywords,
+                    NULL);
+  } else {
+    res = SYSERR;
+  }
+  gtkSaveCall((GtkFunction) destroyInsertProgressBar, 
+             ilm->progressBarWindow);
+  refreshMenuSensitivity();
+  if (res == OK) {
+    FileIdentifier fid;
+    char * fstring;
+
+    memcpy(&fid.chk, &top->chk, sizeof(CHK_Hashes));
+    fid.crc = htonl(crc32N(top->data, top->len));
+    {
+      unsigned int fs = (unsigned int) top->filesize;
+      fid.file_length = htonl(fs);
+    }
+    
+    fstring = fileIdentifierToString(&fid);
+    
+    infoMessage(NO,
+               "Successfully processed %s\n  => %s\n", 
+               ilm->fileName,
+               fstring);
+    LOG(LOG_DEBUG,
+        "DEBUG: Successfully processed %s\n  => %s\n",
+        ilm->fileName,
+        fstring);
+    FREE(fstring);
+  } else {
+    guiMessage("Insertion of %s FAILED!\n", 
+                ilm->fileName);
+  }
+  if(top != NULL)
+    top->vtbl->done(top, NULL);
+  releaseClientSocket(sock);
+
+  /* insert complete */
+  SEMAPHORE_UP(refuseToDie);
+  for (i=0;i<ilm->num_keywords;i++)
+    FREE(ilm->keywords[i]);
+  FREENONNULL(ilm->keywords);
+  if(ilm->deleteAfterInsert == YES) 
+    UNLINK(ilm->fileName);
+  FREE(ilm->fileName);
+  FREE(ilm->mimetype);
+  FREE(ilm->description);
+  FREE(ilm->fileNameRoot);
+  FREE(ilm);
+}
+
+/**
+ * Callback for handling "delete_event": keep the window OPEN.
+ **/
+static gint refuseDeleteEvent(GtkWidget * widget,
+                             GdkEvent * event,
+                             gpointer data) {
+  LOG(LOG_DEBUG, 
+      "DEBUG: refuseDeleteEvent\n");
+  return TRUE;
+}
+
+
+void createInsertProgressBar(InsertModel * ilm) {
+  GtkWidget * window;
+  GtkWidget * box;
+  GtkObject * adjustment;
+  char format[128];
+  int fileLength;
+
+  /* create a new window for a progressbar */
+  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+  ilm->progressBarWindow = window;
+  gtk_window_set_title(GTK_WINDOW(window), 
+                      ilm->fileName);
+  box = gtk_hbox_new(FALSE, 0);
+  gtk_container_add(GTK_CONTAINER(window), 
+                   box);
+  gtk_signal_connect(GTK_OBJECT(window),
+                    "delete_event",
+                    GTK_SIGNAL_FUNC(refuseDeleteEvent),
+                    NULL);
+  gtk_container_set_border_width(GTK_CONTAINER(window), 
+                                10);
+
+  sprintf(format, 
+         "%%v bytes %s",
+         ilm->opDescription);
+ 
+  /* create the actual progressbar */
+  fileLength = getFileSize(ilm->fileName);
+  ilm->progressBar = gtk_progress_bar_new();
+  gtk_progress_set_show_text(GTK_PROGRESS(ilm->progressBar),
+                            1);
+  gtk_progress_set_format_string(GTK_PROGRESS(ilm->progressBar),
+                                (gchar*)format);
+  adjustment = gtk_adjustment_new(0,
+                                 0,
+                                 fileLength,
+                                 1,
+                                 0,
+                                 0);
+  gtk_progress_set_adjustment(GTK_PROGRESS(ilm->progressBar),
+                             GTK_ADJUSTMENT(adjustment));
+  gtk_box_pack_start(GTK_BOX(box),
+                    ilm->progressBar, 
+                    TRUE, 
+                    TRUE, 
+                    0);
+  gtk_progress_bar_set_orientation(GTK_PROGRESS_BAR(ilm->progressBar),
+                                  GTK_PROGRESS_LEFT_TO_RIGHT);
+  gtk_widget_show(ilm->progressBar);
+  gtk_widget_show(box);
+  gtk_widget_show(window);
+}
+
+
+void createInsertDirectoryProgressBar(InsertDirectoryModel * ilm) {
+  GtkWidget * window;
+  GtkWidget * box;
+  char format[128];
+  int fileLength;
+
+  /* create a new window for a progressbar */
+  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+  ilm->progressBarWindow = window;
+  gtk_window_set_title(GTK_WINDOW(window), 
+                      ilm->fileName);
+  box = gtk_vbox_new(FALSE, 0);
+  gtk_container_add(GTK_CONTAINER(window), 
+                   box);
+  gtk_signal_connect(GTK_OBJECT(window),
+                    "delete_event",
+                    GTK_SIGNAL_FUNC(refuseDeleteEvent),
+                    NULL);
+  gtk_container_set_border_width(GTK_CONTAINER(window), 
+                                10);
+
+  sprintf(format, 
+         "%%v bytes %s",
+         ilm->opDescription);
+ 
+  /* create the actual progressbar */
+  fileLength = getFileSize(ilm->fileName);
+  ilm->progressBar = gtk_progress_bar_new();
+  gtk_progress_set_show_text(GTK_PROGRESS(ilm->progressBar),
+                            1);
+  gtk_progress_set_format_string(GTK_PROGRESS(ilm->progressBar),
+                                (gchar*)format);
+  ilm->adjustment = gtk_adjustment_new(0,
+                                      0,
+                                      10000,
+                                      1,
+                                      0,
+                                      0);
+  gtk_progress_set_adjustment(GTK_PROGRESS(ilm->progressBar),
+                             GTK_ADJUSTMENT(ilm->adjustment));
+  gtk_box_pack_start(GTK_BOX(box),
+                    ilm->progressBar, 
+                    TRUE, 
+                    TRUE, 
+                    0);
+  gtk_progress_bar_set_orientation(GTK_PROGRESS_BAR(ilm->progressBar),
+                                  GTK_PROGRESS_LEFT_TO_RIGHT);
+  gtk_widget_show(ilm->progressBar);
+
+  ilm->progressBar2 = gtk_progress_bar_new();
+  gtk_progress_set_show_text(GTK_PROGRESS(ilm->progressBar2),
+                            1);
+  gtk_progress_set_format_string(GTK_PROGRESS(ilm->progressBar2),
+                                (gchar*)format);
+  ilm->adjustment2 = gtk_adjustment_new(0,
+                                       0,
+                                       fileLength,
+                                       1,
+                                       0,
+                                       0);
+  gtk_progress_set_adjustment(GTK_PROGRESS(ilm->progressBar2),
+                             GTK_ADJUSTMENT(ilm->adjustment2));
+  gtk_box_pack_start(GTK_BOX(box),
+                    ilm->progressBar2, 
+                    TRUE, 
+                    TRUE, 
+                    0);
+  gtk_progress_bar_set_orientation(GTK_PROGRESS_BAR(ilm->progressBar2),
+                                  GTK_PROGRESS_LEFT_TO_RIGHT);
+  gtk_widget_show(ilm->progressBar2);
+  gtk_widget_show(box);
+  gtk_widget_show(window);
+}
+
+
+
+
+/**
+ * Insert a single file.
+ *
+ * @param filename the name of the file to insert
+ * @param fid resulting file identifier for the node
+ * @returns OK on success, SYSERR on error
+ **/
+static int gtkInsertDirectoryWrapper(GNUNET_TCP_SOCKET * sock,
+                                    char * filename,
+                                    FileIdentifier * fid,
+                                    InsertDirectoryModel * ilm) {
+  Block * top;
+  cron_t startTime;
+  InsertModel ifm;
+  SetAdj adj;
+
+  ifm.fileName = filename;
+  ifm.fileNameRoot = NULL;
+  ifm.description = NULL;
+  ifm.mimetype = NULL;
+  ifm.keywords = NULL;
+  ifm.num_keywords = 0;
+  memcpy(ifm.opDescription,
+        ilm->opDescription,
+        sizeof(ilm->opDescription));
+  ifm.indexContent = ilm->indexContent;
+  ifm.progressBar = ilm->progressBar;
+  ifm.progressBarWindow = ilm->progressBarWindow;
+  ifm.deleteAfterInsert = ilm->deleteAfterInsert;
+
+  adj.bar = ilm->progressBar;
+  adj.value = getFileSize(filename);
+  
+  gtkSaveCall((GtkFunction) updateAdjustment,
+             &adj);
+ 
+  cronTime(&startTime);
+  top = insertFile(sock,
+                  filename, 
+                  (ProgressModel) &insertDirectoryModelCallback,
+                  &ifm);
+  if (top == NULL) {
+    /* print error message here?  Probably better once
+       at the top-level... */
+    return SYSERR;
+  } else {
+    SetStat stat;
+
+    memcpy(&fid->chk, 
+          &top->chk, 
+          sizeof(CHK_Hashes));
+    fid->crc = htonl(crc32N(top->data, top->len));
+    fid->file_length = htonl(top->filesize);
+    if (NO == isDirectory(filename)) {
+      if (top->filesize != getFileSize(filename))
+       abort();
+      ilm->pos += top->filesize;
+      stat.bar = ilm->progressBar2;
+      stat.progress = ilm->pos;
+      gtkSaveCall((GtkFunction) setInsertProgressVal,
+                 &stat);
+    }
+    top->vtbl->done(top, NULL);
+    return OK;
+  }
+}
+
+
+/**
+ * A function to be run by the insert thread. Does the
+ * actual insertion.
+ *
+ * @param ilm Collected data related to the insertion
+ */
+void insertDirectoryGtkThread(InsertDirectoryModel * ilm) {
+  int res;
+  GNUNET_TCP_SOCKET * sock;
+  RootNode * top;
+  int i;
+  SetStat stat;
+  FileIdentifier fid;
+#if USE_LIBEXTRACTOR
+  EXTRACTOR_ExtractorList * extractors;
+#endif
+  
+  SEMAPHORE_DOWN(refuseToDie);
+  ilm->pos = 0;
+  stat.bar = ilm->progressBar2;
+  stat.progress = ilm->pos;
+  gtkSaveCall((GtkFunction) setInsertProgressVal,
+             &stat);
+  FREENONNULL(setConfigurationString("GNUNET-INSERT",
+                                    "BUILDDIR",
+                                    "YES"));
+  FREENONNULL(setConfigurationString("GNUNET-INSERT",
+                                    "RECURSIVE",
+                                    "YES"));
+
+  if (ilm->indexContent == YES) {
+    FREENONNULL(setConfigurationString("GNUNET-INSERT",
+                                      "INDEX-CONTENT",
+                                      "YES"));
+
+    FREENONNULL(setConfigurationString("GNUNET-INSERT",
+                                       "LINK",
+                                       ilm->copyFile == YES ? "NO" : "YES"));
+  } else {
+    FREENONNULL(setConfigurationString("GNUNET-INSERT",
+                                      "INDEX-CONTENT",
+                                      "NO"));
+  }
+  sock = getClientSocket();
+  if (sock == NULL) {
+    SEMAPHORE_UP(refuseToDie);
+    return; /* warning should have been printed */
+  }
+
+
+#if USE_LIBEXTRACTOR
+  extractors = getExtractors();
+#endif
+  top = insertRecursively(sock,
+                         ilm->fileName,
+                         &fid,
+                         ilm->gkeywords,
+                         ilm->num_gkeywords,   
+#if USE_LIBEXTRACTOR
+                         extractors,
+#else
+                         NULL,
+#endif
+                         (ProgressModel)&insertModelCallback,
+                         ilm,
+                         (InsertWrapper)&gtkInsertDirectoryWrapper,
+                         ilm);
+#if USE_LIBEXTRACTOR
+  EXTRACTOR_removeAll(extractors);
+#endif
+  if (top != NULL) {
+    unsigned int priority;
+
+    priority = getConfigurationInt("GNUNET-INSERT",
+                                  "CONTENT-PRIORITY");
+    res = OK;
+    for (i=0;i<ilm->num_keywords;i++) {
+      if (SYSERR == insertRootWithKeyword(sock,
+                                         top, 
+                                         ilm->keywords[i], 
+                                         priority))
+       res = SYSERR;
+    }
+    makeRootNodeAvailable(top, DIR_CONTEXT_INSERT);
+  } else {
+    res = SYSERR;
+  }
+
+  gtkSaveCall((GtkFunction) destroyInsertProgressBar, 
+             ilm->progressBarWindow);
+
+  refreshMenuSensitivity();
+  if (res == OK) {
+    char * fstring;
+
+    fstring = fileIdentifierToString(&top->header.fileIdentifier);
+    
+    infoMessage(NO,
+               "Successfully processed %s\n  => %s\n", 
+               ilm->fileName,
+               fstring);
+    LOG(LOG_DEBUG,
+        "DEBUG: Successfully processed %s\n  => %s\n",
+        ilm->fileName,
+        fstring);
+    FREE(fstring);
+  } else {
+    guiMessage("Insertion of %s FAILED!\n", 
+                ilm->fileName);
+  }
+  FREENONNULL(top);
+  releaseClientSocket(sock);
+
+  /* insert complete */
+  SEMAPHORE_UP(refuseToDie);
+  for (i=0;i<ilm->num_keywords;i++)
+    FREE(ilm->keywords[i]);
+  FREENONNULL(ilm->keywords);
+  if(ilm->deleteAfterInsert == YES) 
+    UNLINK(ilm->fileName);
+  FREE(ilm->fileName);
+  FREE(ilm->mimetype);
+  FREE(ilm->description);
+  FREE(ilm->fileNameRoot);
+  FREE(ilm);
+}
+
+
+
+/* end of insertprogress.c */

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/insertprogress.h
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/insertprogress.h      
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/insertprogress.h      
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,95 @@
+/*
+     This file is part of GNUnet
+
+     GNUnet 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, or (at your
+     option) any later version.
+
+     GNUnet 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 GNUnet; see the file COPYING.  If not, write to the
+     Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+     Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file applications/afs/gtkui/insertprogress.h
+ * @author Christian Grothoff
+ **/
+
+#ifndef GTKUI_INSERT_PROGRESS_H
+#define GTKUI_INSERT_PROGRESS_H
+
+#include "platform.h"
+#include <gtk/gtk.h>
+
+/**
+ * @brief state associated with an insertion
+ **/
+typedef struct {
+  char * fileName;
+  char * fileNameRoot;
+  char * description;
+  char * mimetype;
+  char ** keywords;
+  int num_keywords;
+  char opDescription[32];              /* used in progressBar */
+  int indexContent;
+  int copyFile;
+  GtkWidget * progressBar;
+  GtkWidget * progressBarWindow;
+  int deleteAfterInsert;
+} InsertModel;
+
+/**
+ * @brief state associated with an insertion
+ **/
+typedef struct {
+  char * fileName;
+  char * fileNameRoot;
+  char * description;
+  char * mimetype;
+  char ** keywords;
+  int num_keywords;
+  char opDescription[32];              /* used in progressBar */
+  int indexContent;
+  int copyFile;
+  GtkWidget * progressBar;
+  GtkWidget * progressBarWindow;
+  int deleteAfterInsert;
+  char ** gkeywords;
+  int num_gkeywords;
+  GtkObject * adjustment;
+  GtkObject * adjustment2;
+  GtkWidget * progressBar2;
+  unsigned long long pos;
+} InsertDirectoryModel;
+
+/**
+ * Main function of the insert thread.  Does the
+ * actual insertion.
+ *
+ * @param ilm Collected data related to the insertion
+ */
+void insertFileGtkThread(InsertModel * ilm);
+
+
+/**
+ * A function to be run by the insert thread. Does the
+ * actual insertion for directories.
+ *
+ * @param ilm Collected data related to the insertion
+ */
+void insertDirectoryGtkThread(InsertDirectoryModel * ilm);
+
+ 
+void createInsertProgressBar(InsertModel * ilm);
+
+void createInsertDirectoryProgressBar(InsertDirectoryModel * ilm);
+
+#endif

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/main.c
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/main.c        2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/main.c        2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,867 @@
+/*
+     This file is part of GNUnet
+
+     GNUnet 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, or (at your
+     option) any later version.
+
+     GNUnet 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 GNUnet; see the file COPYING.  If not, write to the
+     Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+     Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file src/applications/afs/gtkui/main.c
+ * @brief This is the main file for the gtk+ user interface.
+ * @author Christian Grothoff
+ * @author Igor Wronsky
+ *
+ *
+ * Basic structure of the code is this:
+ * main
+ * -> search -> saveas -> download
+ * -> insert
+ * -> directory -> insert
+ * -> pseudonyms (create|delete)
+ * -> namespace insert/update
+ * -> about
+ *
+ **/
+
+#include "gnunet_afs_esed2.h"
+#include "helper.h"
+#include "search.h"
+#include "insert.h"
+#include "delete.h"
+#include "about.h"
+#include "directory.h"
+#include "download.h"
+#include "namespace.h"
+#include "pseudonyms.h"
+#include "main.h"
+#include "statistics.h"
+
+/**
+ * This semaphore can be used to prevent the main window
+ * from killing GTK at an unhealty time. It is used by
+ * the un-interruptable but GUI-updating insert thread.
+ **/
+Semaphore * refuseToDie;
+
+/**
+ * Provides access to toggling pulldown menu shadings
+ **/
+GtkItemFactory * itemFactory = NULL;
+
+static GtkWidget * main_window_input_line = NULL;
+
+/**
+ * Shows the info window 
+ **/
+static void show_infowindow(GtkButton * button,
+                           gpointer dummy) {
+  if(infoWindow)
+    gtk_widget_show(infoWindow);
+  else
+    infoMessage(YES, "This window will show messages and the URIs of inserted 
content etc\ninfo that might require copy-pasting elsewhere to be used...\n\n");
+}
+
+/**
+ * Shows the download window if some dl has been started 
+ **/
+static void show_dlwindow(GtkButton * button,
+                         gpointer dummy) {
+  if(dlWindow)
+    gtk_widget_show(dlWindow);
+}
+
+
+/**
+ * This method is called whenever the user clicks the
+ * search button of the main window. 
+ * 
+ * @param widget not used
+ * @param notebook the notebook where the opened result window will be put.
+ **/
+static void search(GtkWidget * widget,
+                  GtkNotebook * notebook) {
+  gchar * searchString;
+  gchar * searchStringStart;
+  GtkWidget * tmp;
+  int i; 
+
+  searchString 
+    = gtk_entry_get_text(GTK_ENTRY(main_window_input_line));
+  if (searchString == NULL) {
+    guiMessage("searchString == NULL\n");
+    return;
+  }
+  searchString = STRDUP(searchString);
+
+  /* Remove heading spaces (parsing bugs if not) */  
+  i = strlen(searchString)-1;
+  while ( (i>=0) &&
+         (searchString[i]==' ') ) {
+    searchString[i] = '\0';
+    i--;
+  }
+  searchStringStart = searchString;
+  while (*searchStringStart == ' ')
+    searchStringStart++;
+  if (*searchStringStart=='\0') {
+    guiMessage("No search key given!\n");
+    FREE(searchString);
+    return;
+  }
+
+  /* add a new page in the notebook with the
+     search results. getSearchWindow returns
+     the page */
+  tmp = getSearchWindow(searchStringStart);
+  if (tmp != NULL) 
+    addToNotebook(searchStringStart,
+                 tmp);
+
+  /* reset search line to empty */
+  gtk_entry_set_text(GTK_ENTRY(main_window_input_line), 
+                    ""); 
+  FREE(searchString);
+}
+
+/**
+ * Exit the application (called when the main window
+ * is closed or the user selects File-Quit).
+ **/
+static void destroyMain(GtkWidget * widget,
+                       gpointer data) {
+  gdk_threads_leave(); /* avoid deadlock! */
+  stopCron();
+  delCronJob((CronJob)&cronCheckDaemon,
+            30 * cronSECONDS,
+            NULL);
+  startCron();
+  gdk_threads_enter();
+  if (notebook != NULL) {
+    int i = 0;    
+    while(gtk_notebook_get_nth_page(notebook, 0) != NULL) {
+      LOG(LOG_DEBUG, 
+         "DEBUG: Removing search page %d\n", 
+         i++);
+      gtk_notebook_remove_page(notebook, 0);
+    }
+  }
+  gdk_threads_leave(); /* avoid deadlock! */
+  SEMAPHORE_DOWN(refuseToDie);
+  gdk_threads_enter();
+  gtk_main_quit(); /* back to main method! */
+}
+
+/**
+ * Remove all of the root-nodes of a particular type
+ * from the directory database.
+ *
+ * @param unused GTK handle that is not used
+ * @param contexts bitmask of the databases that should be emptied.
+ **/ 
+static void emptyDirectoryDatabaseInd(GtkWidget * unused,
+                                     unsigned int contexts) {
+  emptyDirectoryDatabase(contexts);
+  refreshMenuSensitivity();
+}
+
+
+/**
+ * Method called from menu bar "File-Quit". Wrapper around
+ * destroy.
+ **/ 
+static void destroy_stub(void) {
+  destroyMain(NULL, NULL);
+}
+
+/**
+ * The pulldown menus.
+ **/
+static GtkItemFactoryEntry menu_items[] = {
+  { "/_File",         
+    NULL, 
+    NULL,
+    0,
+    "<Branch>" },
+  { "/File/_Insert",  
+    "<control>I", 
+    openSelectFile, 
+    0, 
+    NULL },
+  { "/File/_Download URI",  
+    "<control>D", 
+    fetchURI, 
+    0, 
+    NULL },
+  { "/File/Import di_rectory",  
+    "<control>r", 
+    importDirectory, 
+    0, 
+    NULL },
+  { "/File/sep1",     
+    NULL, 
+    NULL, 
+    0,
+    "<Separator>" },
+  { "/File/_Unindex file", 
+    "<control>U", 
+    openDeleteFile, 
+    0, 
+    NULL },
+  { "/File/sep1",     
+    NULL, 
+    NULL, 
+    0,
+    "<Separator>" },
+  { "/File/Show downloads",      
+    "<control>w", 
+    show_dlwindow, 
+    0, 
+    NULL },
+  { "/File/Show messages", 
+    "<control>m", 
+    show_infowindow, 
+    0, 
+    NULL },
+  { "/File/Show gnunetd stats", 
+    NULL,
+    showStats, 
+    0, 
+    NULL },
+  { "/File/_Plot gnunetd stats", 
+    NULL,
+    NULL,
+    0,
+    "<Branch>" },
+  { "/File/Plot gnunetd stats/_Connectivity", 
+    NULL,
+    displayStatistics, 
+    STAT_CONNECTIVITY,
+    NULL },
+  { "/File/Plot gnunetd stats/C_PU Load", 
+    NULL,
+    displayStatistics, 
+    STAT_CPU_LOAD,
+    NULL },
+  { "/File/Plot gnunetd stats/_Inbound Traffic", 
+    NULL,
+    displayStatistics, 
+    STAT_IN_TRAFFIC,
+    NULL },
+  { "/File/Plot gnunetd stats/_Outbound Traffic", 
+    NULL,
+    displayStatistics, 
+    STAT_OUT_TRAFFIC,
+    NULL },
+  { "/File/sep1",     
+    NULL, 
+    NULL, 
+    0,
+    "<Separator>" },
+  { "/File/_Quit",     
+    "<control>Q", 
+    destroy_stub,
+    0, 
+    NULL },
+  { "/_Advanced",
+    NULL,
+    NULL,
+    0,
+    "<Branch>" },
+
+  { "/Advanced/_Assemble Directory",
+    NULL,
+    NULL,
+    0,
+    "<Branch>" },
+  { "/Advanced/Assemble Directory/from _search results",
+    NULL,
+    openAssembleDirectoryDialog,
+    DIR_CONTEXT_SEARCH,
+    NULL },
+  { "/Advanced/Assemble Directory/from _inserted files",
+    NULL,
+    openAssembleDirectoryDialog,
+    DIR_CONTEXT_INSERT,
+    NULL },
+  { "/Advanced/Assemble Directory/from local _namespaces",
+    NULL,
+    openAssembleDirectoryDialog,
+    DIR_CONTEXT_INSERT_SB,
+    NULL },
+  { "/Advanced/Assemble Directory/from file identifiers from downloaded 
_directories",
+    NULL,
+    openAssembleDirectoryDialog,
+    DIR_CONTEXT_DIRECTORY,
+    NULL }, 
+  { "/Advanced/Assemble Directory/sepx1",     
+    NULL, 
+    NULL, 
+    0,
+    "<Separator>" },
+  { "/Advanced/Assemble Directory/from _all known file identifiers",
+    NULL,
+    openAssembleDirectoryDialog,
+    DIR_CONTEXT_ALL,
+    NULL },
+
+  { "/Advanced/sep1",     
+    NULL, 
+    NULL, 
+    0,
+    "<Separator>" },
+
+  { "/Advanced/Manage _Pseudonyms",     
+    NULL,  
+    NULL, 
+    0,
+    "<Branch>" },
+  { "/Advanced/Manage Pseudonyms/_Create new pseudonym",     
+    NULL,  
+    &openCreatePseudonymDialog,
+    0,
+    NULL },
+  { "/Advanced/Manage Pseudonyms/_Delete pseudonym",     
+    NULL,  
+    &openDeletePseudonymDialog,
+    0,
+    NULL },
+  { "/Advanced/_Insert into Namespace",     
+    NULL,  
+    NULL,
+    0,
+    "<Branch>" },
+  { "/Advanced/Insert into Namespace/Select from _search results",     
+    NULL,  
+    &openAssembleNamespaceDialog, 
+    DIR_CONTEXT_SEARCH,
+    NULL },
+  { "/Advanced/Insert into Namespace/Select from _inserted files",     
+    NULL,  
+    &openAssembleNamespaceDialog, 
+    DIR_CONTEXT_INSERT,
+    NULL },
+  { "/Advanced/Insert into Namespace/Select from results from downloaded 
_directories",     
+    NULL,  
+    &openAssembleNamespaceDialog, 
+    DIR_CONTEXT_INSERT,
+    NULL },
+  { "/Advanced/Insert into Namespace/Select from results from local 
_namespaces",     
+    NULL,  
+    &openAssembleNamespaceDialog, 
+    DIR_CONTEXT_INSERT_SB,
+    NULL },
+  { "/Advanced/Insert into Namespace/sepx2",     
+    NULL, 
+    NULL, 
+    0,
+    "<Separator>" },
+  { "/Advanced/Insert into Namespace/Select from _all known file identifiers", 
    
+    NULL,  
+    &openAssembleNamespaceDialog, 
+    DIR_CONTEXT_ALL,
+    NULL },
+  /*  { "/Advanced/_Update content in Namespace",     
+    NULL,  
+    NULL,  
+    0,
+    NULL }, */
+  { "/Advanced/_Search Namespace",     
+    "<control>S",  
+    &searchNamespace,  
+    0,
+    NULL }, 
+
+  { "/Advanced/sep2",     
+    NULL, 
+    NULL, 
+    0,
+    "<Separator>" },
+
+  { "/Advanced/_Reset File Identifiers",
+    NULL,
+    NULL,
+    0,
+    "<Branch>" },
+  { "/Advanced/Reset File Identifiers/List of _search results",
+    NULL,
+    emptyDirectoryDatabaseInd,
+    DIR_CONTEXT_SEARCH,
+    NULL },
+  { "/Advanced/Reset File Identifiers/List of _inserted files",
+    NULL,
+    emptyDirectoryDatabaseInd,
+    DIR_CONTEXT_INSERT,
+    NULL },
+  { "/Advanced/Reset File Identifiers/List of entries in local _namespaces",
+    NULL,
+    emptyDirectoryDatabaseInd,
+    DIR_CONTEXT_INSERT_SB,
+    NULL },
+  { "/Advanced/Reset File Identifiers/List of files from downloaded 
_directories",
+    NULL,
+    emptyDirectoryDatabaseInd,
+    DIR_CONTEXT_DIRECTORY,
+    NULL }, 
+  { "/Advanced/Reset File Identifiers/sepx3",     
+    NULL, 
+    NULL, 
+    0,
+    "<Separator>" },
+  { "/Advanced/Reset File Identifiers/_All known file identifiers",
+    NULL,
+    emptyDirectoryDatabaseInd,
+    DIR_CONTEXT_ALL,
+    NULL },
+  
+  { "/Advanced/sep3",     
+    NULL, 
+    NULL, 
+    0,
+    "<Separator>" },
+  
+  { "/Advanced/Launch gnunetd",     
+    NULL, 
+    launchDaemon, 
+    0,
+    NULL },
+  
+  { "/Advanced/Kill gnunetd",     
+    NULL, 
+    killDaemon, 
+    0,
+    NULL },
+
+/*
+  { "/_Options",     
+    NULL,  
+    NULL, 
+    0,
+    "<Branch>" },
+  { "/Options/Preferences(not impl)",
+    NULL,  
+    NULL, 
+    0, 
+    NULL },
+*/
+  { "/_Help",     
+    NULL,  
+    NULL,
+    0,
+    "<LastBranch>" },
+  { "/Help/_About",   
+    NULL,
+    about,
+    0, 
+    NULL },
+};
+
+gint doRefreshMenuSensitivity(SaveCall *call) {
+  int havePseudo;
+  int haveSearch;
+  int haveInsert;
+  int haveDirect;
+  int haveNamesp;
+  int haveAny;
+  int value;
+  GtkWidget * entry;  
+
+  havePseudo = havePseudonyms(); 
+  if (NO == havePseudo)
+    value = FALSE;
+  else
+    value = TRUE;
+  haveSearch = 0 < iterateDirectoryDatabase(DIR_CONTEXT_SEARCH, NULL, NULL);
+  haveInsert = 0 < iterateDirectoryDatabase(DIR_CONTEXT_INSERT, NULL, NULL);
+  haveDirect = 0 < iterateDirectoryDatabase(DIR_CONTEXT_DIRECTORY, NULL, NULL);
+  haveNamesp = 0 < iterateDirectoryDatabase(DIR_CONTEXT_INSERT_SB, NULL, NULL);
+  haveAny = 0 < iterateDirectoryDatabase(DIR_CONTEXT_ALL, NULL, NULL);
+
+  entry = gtk_item_factory_get_widget(itemFactory,
+                                     "/Advanced/Manage Pseudonyms/"
+                                     "Delete pseudonym");
+  gtk_widget_set_sensitive(entry, value);
+  
+  
+  if ( (NO == havePseudo) || (NO == haveSearch) )
+    value = FALSE;
+  else
+    value = TRUE;
+  entry = gtk_item_factory_get_widget(itemFactory,
+                                     "/Advanced/Insert into Namespace/"
+                                     "Select from search results");
+  gtk_widget_set_sensitive(entry, value);  
+
+  if ( (NO == havePseudo) || (NO == haveInsert) )
+    value = FALSE;
+  else
+    value = TRUE;
+  entry = gtk_item_factory_get_widget(itemFactory,
+                                     "/Advanced/Insert into Namespace/"
+                                     "Select from inserted files");
+  gtk_widget_set_sensitive(entry, value);  
+  if ( (NO == havePseudo) || (NO == haveDirect) )
+    value = FALSE;
+  else
+    value = TRUE;
+  entry = gtk_item_factory_get_widget(itemFactory,
+                                     "/Advanced/Insert into Namespace/"
+                                     "Select from results from downloaded 
directories");
+  gtk_widget_set_sensitive(entry, value);  
+
+  if ( (NO == havePseudo) || (NO == haveNamesp) )
+    value = FALSE;
+  else
+    value = TRUE;
+  entry = gtk_item_factory_get_widget(itemFactory,
+                                     "/Advanced/Insert into Namespace/"
+                                     "Select from results from local 
namespaces");
+  gtk_widget_set_sensitive(entry, value);  
+
+  if ( (NO == havePseudo) || (NO == haveAny) )
+    value = FALSE;
+  else
+    value = TRUE;
+  entry = gtk_item_factory_get_widget(itemFactory,
+                                     "/Advanced/Insert into Namespace/"
+                                     "Select from all known file identifiers");
+  gtk_widget_set_sensitive(entry, value);  
+  if ( NO == haveAny) 
+    value = FALSE;
+  else
+    value = TRUE;
+  
+  entry = gtk_item_factory_get_widget(itemFactory,
+                                     "/Advanced/Assemble Directory/"
+                                     "from all known file identifiers");
+  gtk_widget_set_sensitive(entry, value);  
+  entry = gtk_item_factory_get_widget(itemFactory,
+                                     "/Advanced/Reset File Identifiers/"
+                                     "All known file identifiers");
+  gtk_widget_set_sensitive(entry, value);  
+
+
+  if ( NO == haveSearch) 
+    value = FALSE;
+  else
+    value = TRUE;
+  entry = gtk_item_factory_get_widget(itemFactory,
+                                     "/Advanced/Assemble Directory/"
+                                     "from search results");
+  gtk_widget_set_sensitive(entry, value);  
+  entry = gtk_item_factory_get_widget(itemFactory,
+                                     "/Advanced/Reset File Identifiers/"
+                                     "List of search results");
+  gtk_widget_set_sensitive(entry, value);  
+
+  if ( NO == haveInsert) 
+    value = FALSE;
+  else
+    value = TRUE;
+
+  entry = gtk_item_factory_get_widget(itemFactory,
+                                     "/Advanced/Assemble Directory/"
+                                     "from inserted files");
+  gtk_widget_set_sensitive(entry, value);  
+  entry = gtk_item_factory_get_widget(itemFactory,
+                                     "/Advanced/Reset File Identifiers/"
+                                     "List of inserted files");
+  gtk_widget_set_sensitive(entry, value);  
+
+
+  if ( NO == haveDirect) 
+     value = FALSE;
+  else
+    value = TRUE;
+  entry = gtk_item_factory_get_widget(itemFactory,
+                                     "/Advanced/Assemble Directory/"
+                                     "from file identifiers from downloaded 
directories");
+  gtk_widget_set_sensitive(entry, value);  
+  entry = gtk_item_factory_get_widget(itemFactory,
+                                     "/Advanced/Reset File Identifiers/"
+                                     "List of files from downloaded 
directories");
+  gtk_widget_set_sensitive(entry, value);  
+
+
+  if ( NO == haveNamesp) 
+    value = FALSE;
+  else
+    value = TRUE;
+  entry = gtk_item_factory_get_widget(itemFactory,
+                                     "/Advanced/Assemble Directory/"
+                                     "from local namespaces");
+  gtk_widget_set_sensitive(entry, value);  
+  entry = gtk_item_factory_get_widget(itemFactory,
+                                     "/Advanced/Insert into Namespace/"
+                                     "Select from results from local 
namespaces");
+  gtk_widget_set_sensitive(entry, value);  
+  entry = gtk_item_factory_get_widget(itemFactory,
+                                     "/Advanced/Reset File Identifiers/"
+                                     "List of entries in local namespaces");
+  gtk_widget_set_sensitive(entry, value);  
+
+  gtkSaveCallDone(call->sem);
+
+  return FALSE;
+}
+
+void refreshMenuSensitivity() {
+  gtkSaveCall((GtkFunction) doRefreshMenuSensitivity, NULL);
+}
+
+/**
+ * This creates the main window
+ **/
+static void makeMainWindow() {
+  GtkWidget * window;
+  GtkWidget * button;
+  GtkWidget * hbox;
+  GtkWidget * vbox;
+  GtkWidget * menubar;
+  GtkWidget * table;
+  GtkWidget * label;
+  GtkWidget * entry;
+  GtkAccelGroup * accel_group;
+
+  gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
+
+  /* create main window */
+  window = gtk_window_new(GTK_WINDOW_TOPLEVEL); 
+  gtk_window_set_title(GTK_WINDOW(window), 
+                      "GNUnet: gtk+ GUI");
+  gtk_widget_set_usize(GTK_WIDGET(window), 
+                      780, /* x-size */
+                      300); /* y-size */
+  vbox = gtk_vbox_new(FALSE, 1);
+  gtk_container_add(GTK_CONTAINER(window), vbox);
+  gtk_signal_connect(GTK_OBJECT(window), 
+                     "delete_event",
+                     GTK_SIGNAL_FUNC(deleteEvent), 
+                    NULL);
+  gtk_signal_connect(GTK_OBJECT(window), 
+                     "destroy",
+                     GTK_SIGNAL_FUNC(destroyMain),
+                    NULL);
+  gtk_widget_show(vbox);
+
+  
+  /* create pulldown menues */  
+  accel_group = gtk_accel_group_new ();
+  itemFactory = gtk_item_factory_new(GTK_TYPE_MENU_BAR, 
+                                    "<main>",
+                                    accel_group);
+  gtk_item_factory_create_items(itemFactory, 
+                               nmenu_items,
+                               menu_items, 
+                               NULL);
+  gtk_window_add_accel_group(GTK_WINDOW (window), 
+                            accel_group);
+  menubar = gtk_item_factory_get_widget(itemFactory, 
+                                       "<main>");
+  gtk_box_pack_start(GTK_BOX (vbox), 
+                    menubar,
+                    FALSE, 
+                    TRUE, 
+                    0);
+  
+
+
+  /* set some default options and show */
+
+  
+  entry = gtk_item_factory_get_widget(itemFactory,
+                                     "/File/Show downloads");
+  gtk_widget_set_sensitive(entry, FALSE);
+  refreshMenuSensitivity();
+
+  gtk_widget_show(menubar);
+
+  /* a table to put the search results notebook to */
+  table = gtk_table_new(6, 6, TRUE);
+  gtk_box_pack_start(GTK_BOX (vbox),
+                    table,
+                    TRUE,
+                    TRUE, 0);
+  gtk_widget_show(table);
+  
+  /* add "notebook" for search results */
+  notebook = GTK_NOTEBOOK(gtk_notebook_new());
+  gtk_notebook_set_scrollable(notebook,
+                             TRUE);
+  gtk_notebook_set_tab_pos(GTK_NOTEBOOK(notebook),
+                          GTK_POS_TOP);
+  gtk_table_attach_defaults(GTK_TABLE(table), 
+                           GTK_WIDGET(notebook),
+                           0, 6, 0, 6);
+  gtk_widget_show(GTK_WIDGET(notebook));
+
+  /* BEGIN of SEARCH BOX CODE */
+  /* At the bottom, put the search box */
+  hbox = gtk_hbox_new(FALSE, 1);
+  gtk_box_pack_start(GTK_BOX (vbox),
+                    hbox, 
+                    FALSE, 
+                    FALSE, 
+                    0);
+  gtk_widget_show(hbox);
+ 
+  /* search entry label */
+  label = gtk_label_new("Keyword(s):");
+  gtk_box_pack_start(GTK_BOX(hbox),
+                    label,
+                    FALSE, 
+                    FALSE,
+                    0);
+  gtk_widget_show(label);
+
+  /* search input line */
+  main_window_input_line = gtk_entry_new();
+  gtk_box_pack_start(GTK_BOX(hbox), 
+                    main_window_input_line, 
+                    TRUE, 
+                    TRUE, 
+                    0);
+  gtk_signal_connect(GTK_OBJECT(main_window_input_line),
+                    "activate",
+                     GTK_SIGNAL_FUNC(search),
+                     notebook);
+  gtk_widget_show(main_window_input_line);
+
+  /* search button */
+  button = gtk_button_new_with_label("Search");
+  gtk_signal_connect(GTK_OBJECT (button), 
+                    "clicked",
+                    GTK_SIGNAL_FUNC(search), notebook);
+  gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
+  gtk_widget_show(button);
+  /* END of SEARCH BOX CODE */
+
+  /* show the main window */
+  gtk_widget_show(window);
+}
+
+/**
+ * Perform option parsing from the command line. 
+ **/
+static int parseOptions(int argc, 
+                       char ** argv) {
+  int c;
+
+  while (1) {
+    int option_index=0;
+    static struct GNoption long_options[] = {
+      LONG_DEFAULT_OPTIONS,
+      { 0,0,0,0 }
+    };
+    
+    c = GNgetopt_long(argc,
+                     argv, 
+                     "vhdc:L:H:", 
+                     long_options, 
+                     &option_index);
+    
+    if (c == -1) 
+      break;  /* No more flags to process */
+    if (YES == parseDefaultOptions(c, GNoptarg))
+      continue;    
+    switch(c) {
+    case 'v': 
+      printf("GNUnet v%s, AFS v%s\n",
+            VERSION,
+            AFS_VERSION);
+      return SYSERR;
+    case 'h': {
+      static Help help[] = {
+       HELP_CONFIG,
+       HELP_HELP,
+       HELP_HOSTNAME,
+       HELP_LOGLEVEL,
+       HELP_VERSION,
+       HELP_END,
+      };
+      formatHelp("gnunet-gtk [OPTIONS]",
+                "Start GNUnet GTK user interface.",
+                help);
+      return SYSERR;
+    }
+    default:
+      LOG(LOG_FAILURE,
+         "Unknown option %c. Aborting.\n"
+         "Use --help to get a list of options.\n",
+         c);
+      return SYSERR;    
+    } /* end of parsing commandline */
+  }
+  if (GNoptind < argc) {
+    fprintf(stderr,
+           "Invalid arguments: ");
+    while (GNoptind < argc)
+      fprintf(stderr,
+             "%s ", 
+             argv[GNoptind++]);
+    fprintf(stderr, 
+           "\n");
+    return SYSERR;
+  }
+  return OK;
+}
+
+/**
+ * The main function.
+ **/
+int main(int argc,
+         char * argv[]) {
+
+  if (SYSERR == initUtil(argc, argv, &parseOptions))
+    return(0);
+  initGTKStatistics();
+   
+  startCron();
+  refuseToDie = SEMAPHORE_NEW(1);
+
+  g_thread_init(NULL);
+  /*  gdk_threads_init(); */
+  gtk_init(&argc, &argv);
+  gtkInitSaveCalls();
+
+  makeMainWindow();
+  /* Check if gnunetd is running */
+  checkForDaemon();
+ 
+  /* refresh the kill/launch sensitivities once per 30 secs */
+  addCronJob((CronJob)&cronCheckDaemon,
+            0,
+            30 * cronSECONDS,
+            NULL);
+  startAFSPriorityTracker();
+  gdk_threads_enter();  
+  setCustomLogProc(addLogEntry);
+#ifdef MINGW
+  FreeConsole();
+#endif
+  gtk_main();
+  setCustomLogProc(NULL);
+  gdk_threads_leave(); 
+  gtkDoneSaveCalls();
+  stopCron();
+  stopAFSPriorityTracker();
+  LOG(LOG_DEBUG, "DEBUG: GUI leaving...\n");
+  SEMAPHORE_FREE(refuseToDie);
+
+  doneGTKStatistics();
+  doneUtil();  
+  return 0;
+}
+
+/* end of main.c */

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/main.h
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/main.h        2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/main.h        2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,39 @@
+/*
+     This file is part of GNUnet
+
+     GNUnet 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, or (at your
+     option) any later version.
+
+     GNUnet 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 GNUnet; see the file COPYING.  If not, write to the
+     Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+     Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file applications/afs/gtkui/main.h
+ * @author Igor Wronsky
+ **/
+
+#ifndef GTKUI_MAIN_H
+#define GTKUI_MAIN_H
+
+/**
+ * This semaphore can be up'ed by threads that will
+ * need the GUI and that want to prevent gnunet-gtk
+ * from exiting while they are running.
+ **/
+extern Semaphore * refuseToDie;
+
+extern GtkItemFactory * itemFactory;
+
+void refreshMenuSensitivity();
+
+#endif

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/namespace.c
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/namespace.c   2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/namespace.c   2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,1492 @@
+/*
+     This file is part of GNUnet
+
+     GNUnet 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, or (at your
+     option) any later version.
+
+     GNUnet 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 GNUnet; see the file COPYING.  If not, write to the
+     Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+     Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file src/applications/afs/gtkui/namespace.c
+ * @brief Namespace dialog for the AFS interface
+ * @author Christian Grothoff
+ **/
+
+#include "gnunet_afs_esed2.h"
+#include "helper.h"
+#include "namespace.h"
+#include "search.h"
+#include "main.h"
+
+/**
+ * @brief state of the insert into namespace window
+ **/
+typedef struct {
+  char * fileName;
+  GtkWidget * window;
+  GtkWidget * passwordLine;
+  GtkWidget * pseudonymList;
+  GtkWidget * sblockList;
+  GtkWidget * availableList;
+  GtkWidget * updateInterval;
+  GtkWidget * currentKey;
+  GtkWidget * nextKey;
+  SBlock ** updateableEntries;
+  int updateableCount;
+  HashCode160 selectedPseudonym;
+} NamespaceInsertWindowModel;
+
+static int parseTime(char * t) {
+  int pos;
+  int start;
+  int ret;
+  unsigned int val;
+  char * tmp;
+  
+  ret = 0;
+  pos = 0;
+
+  while (t[pos] != '\0') {
+    start = pos;
+    while ( (t[pos] != ' ') &&
+           (t[pos] != '\0') )
+      pos++;
+    tmp = STRNDUP(&t[start],
+                 pos - start);
+    if (1 != sscanf(tmp,
+                   "%u",
+                   &val) ) 
+      return -1; /* parse error */
+    FREE(tmp);
+    while ( t[pos] == ' ')
+      pos++;
+    start = pos;
+    while ( (t[pos] != ' ') &&
+           (t[pos] != '\0') )
+      pos++;
+    if (0 == strncasecmp(&t[start],
+                        "minute",
+                        strlen("minute")))
+      ret += 60 * val;
+    else if (0 == strncasecmp(&t[start],
+                             "second",
+                             strlen("second")))
+      ret += val;
+    else if (0 == strncasecmp(&t[start],
+                             "hour",
+                             strlen("hour")))
+      ret += 60 * 60 * val;
+    else if (0 == strncasecmp(&t[start],
+                             "day",
+                             strlen("day")))
+      ret += 24 * 60 * 60 * val;
+    else
+      return -1; /* parse error */ 
+    while ( t[pos] == ' ')
+      pos++;
+  }  
+  return ret;
+}
+
+/**
+ * Collects the results of the assembly dialog, creates an insertion 
+ * progressbar and launches the insertion thread.
+ *
+ * @param dummy not used
+ * @param ewm the state of the edit window
+ **/
+static void buildNSEntry(GtkWidget * dummy, 
+                        NamespaceInsertWindowModel * ewm) {
+  GList * tmp;
+  int row;
+  gchar * key[1];
+  char * currentKey;
+  char * nextKey;
+  char * name;
+  char * pass;
+  char * message;
+  Hostkey pseudo;
+  HashCode160 k; /* = key, next - increment */
+  HashCode160 n; /* = next or namespace ID depending where in code */
+  TIME_T interval;
+  GNUNET_TCP_SOCKET * sock;
+  SBlock * sb;
+  RootNode * rn;
+  HexName hex1;
+  HexName hex2;
+  char * updateInterval;
+  TIME_T now;
+  TIME_T creationTime;
+  char * desc;
+  char * mime;
+
+  updateInterval = 
gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(ewm->updateInterval)->entry));
+  if (updateInterval == NULL) {
+    guiMessage("ERROR: you must specify an update frequency.\n");
+    return;
+  }
+  if (strcmp(updateInterval, "--no updates--") == 0) {
+    interval = 0;    
+  } else if (strcmp(updateInterval, "--sporadic updates--") == 0) {
+    interval = -1;
+  } else {
+    interval = parseTime(updateInterval);
+    if (interval == -1) {
+      guiMessage("ERROR: parsing of time interval failed. "
+                "Use \"(INT [seconds|minutes|hours])*\" format.\n");
+      return;
+    }
+  } 
+  tmp = GTK_CLIST(ewm->pseudonymList)->selection;
+  if (NULL == tmp) {
+    guiMessage("ERROR: you must select a pseudonym (Error #1).\n");    
+    return;  
+  }
+  row = (int) tmp->data;
+  if ( row < 0 ) {
+    guiMessage("ERROR: you must select a pseudonym (Error #2).\n");    
+    return; 
+  }
+
+  key[0] = NULL;
+  gtk_clist_get_text(GTK_CLIST(ewm->pseudonymList),
+                    row,
+                    0,
+                    &key[0]);
+  name = key[0];
+  if (name == NULL) {
+    guiMessage("ERROR: you must select a pseudonym (Error #3).\n");    
+    return;  /* should never happen... */
+  }
+  pass = gtk_entry_get_text(GTK_ENTRY(ewm->passwordLine));
+  if (strlen(pass) == 0)
+    pass = NULL;
+  pseudo = readPseudonym(name, pass);
+  if (pseudo == NULL) {
+    guiMessage("ERROR: password specified invalid for pseudonym.\n");
+    return;
+  }
+
+  currentKey = gtk_entry_get_text(GTK_ENTRY(ewm->currentKey));
+  nextKey = gtk_entry_get_text(GTK_ENTRY(ewm->nextKey));
+
+  tmp = GTK_CLIST(ewm->availableList)->selection;
+  if (NULL == tmp) {
+    guiMessage("ERROR: you must select a file.\n");
+    return;  
+  }
+  row = (int) tmp->data;
+  if ( row < 0 ) {
+    guiMessage("ERROR: you must select a file.\n");
+    freeHostkey(pseudo);
+    return;
+  }
+  rn = gtk_clist_get_row_data(GTK_CLIST(ewm->availableList),
+                             row);
+
+  /* select/derive current & next IDs! */
+  tmp = GTK_CLIST(ewm->sblockList)->selection;
+  if ( (NULL == tmp) || 
+       ( ((int)tmp->data) == 0) ) {
+    /* "--no update--" or nothing selected, pick random IDs */
+    tryhex2hashOrHashString(currentKey,
+                           &k);
+    switch(interval) {
+      case SBLOCK_UPDATE_NONE:
+        /* no updates => next == this */
+        memcpy(&n,
+              &k,
+              sizeof(HashCode160));
+       break;
+      case SBLOCK_UPDATE_SPORADIC:
+        /* sporadic update; pick specified ID if given,
+           otherwise go random */
+        tryhex2hashOrHashString(nextKey,
+                                &n);
+       break;
+      default:
+        /* periodic update, the very first next id will be random */
+        makeRandomId(&n);
+       break;
+    } 
+
+    TIME(&creationTime);
+  } else {
+    SBlock * pred;
+
+    row = ((int) tmp->data) - 1; /* -1: first item is always "no update" */
+    if (row >= ewm->updateableCount) {
+      guiMessage("ERROR: this should never happen.\n");    
+      freeHostkey(pseudo);
+      return; 
+    }
+    pred = ewm->updateableEntries[row];
+    /* now, compute CURRENT ID and next ID */
+    TIME(&now);
+    computeIdAtTime(pred, 
+                   now, 
+                   &k);
+    if ( (interval != SBLOCK_UPDATE_NONE) &&
+        (interval != SBLOCK_UPDATE_SPORADIC) ) {
+      int delta;
+      /* periodic update */
+      delta = now - ntohl(pred->creationTime);
+      delta = delta / ntohl(pred->updateInterval);
+      if (delta == 0)
+       delta = 1; /* force to be in the future from the updated block! */
+      creationTime = ntohl(pred->creationTime) + delta * 
ntohl(pred->updateInterval);
+
+      /* periodic update, compute _next_ ID as increment! */
+      addHashCodes(&k,
+                  &pred->identifierIncrement,
+                  &n); /* n = k + inc */      
+    } else {
+      if (interval == SBLOCK_UPDATE_SPORADIC) {
+       /* sporadic update, pick random next ID 
+          if not specified! */
+       tryhex2hashOrHashString(nextKey, 
+                               &n);
+       TIME(&creationTime);
+      } else {
+       /* updating non-updateable SBlock!? Should
+          never happen! */
+       guiMessage("ERROR: attempt to update an non-updateble SBlock, this 
should never happen!\n"); 
+       BREAK();
+       return;
+      }
+    }
+  } /* end of selecting k and n based on update mechanism */
+  
+  name = getFilenameFromNode(rn);
+  desc = getDescriptionFromNode(rn);
+  mime = getMimetypeFromNode(rn);
+  sb = buildSBlock(pseudo,
+                  &rn->header.fileIdentifier,
+                  desc,
+                  name,
+                  mime,
+                  creationTime,
+                  interval,
+                  &k,
+                  &n);
+  FREE(desc);
+  FREE(mime);
+  freeHostkey(pseudo);
+  if (sb == NULL) {
+    FREE(name);
+    guiMessage("ERROR: failed to build SBlock. Consult logs.");
+    return;
+  }
+  sock = getClientSocket();
+  if (sock == NULL) {
+    FREE(sb);
+    FREE(name);
+    guiMessage("ERROR: could not connect to gnunetd.");
+    return;
+  }
+  if (OK != insertSBlock(sock,
+                        sb)) {
+    guiMessage("ERROR: failed to insert SBlock. "
+              "Consult logs.");    
+    releaseClientSocket(sock);
+    FREE(name);
+    FREE(sb);
+    return;
+  }
+  releaseClientSocket(sock);
+  /* obtain "n = S", the namespace ID */
+  hash(&sb->subspace,
+       sizeof(PublicKey),
+       &n);
+  FREE(sb);
+ 
+  currentKey = STRDUP(currentKey); /* would be destroyed with window */ 
+
+  /* destroy the window */
+  gtk_widget_destroy(ewm->window);
+  refreshMenuSensitivity();
+
+  hash2hex(&n, &hex1);
+  hash2hex(&k, &hex2);
+  message = MALLOC(512);
+  sprintf(message,
+         "%s inserted into namespace as\n"
+         "  gnunet://afs/%s/%s\n",
+         name,
+         (char*)&hex1,
+         (currentKey[0] != '\0') ? currentKey : (char*)&hex2);
+  LOG(LOG_DEBUG,
+      "DEBUG: %s\n",
+      message);
+  infoMessage(NO, message);
+  /* FIXME remind about the next key ... if its a verbal one,
+     it can't be reconstructed from the sblock later :( */
+  FREE(currentKey);
+  FREE(message);
+  FREE(name);  
+}  
+
+
+/**
+ * Exit the application (called when the main window
+ * is closed or the user selects File-Quit).
+ **/
+static void destroyNamespaceInsertWindow(GtkWidget * widget,
+                                        NamespaceInsertWindowModel * ewm) {
+  int i;
+  GList * tmp;
+  int row;
+
+  gtk_clist_freeze(GTK_CLIST(ewm->availableList));
+  tmp = GTK_CLIST(ewm->availableList)->selection;
+  while (tmp) {
+    row = (int)tmp->data;
+    tmp = tmp->next;
+
+    FREENONNULL(gtk_clist_get_row_data(GTK_CLIST(ewm->availableList),
+                                      row));
+    gtk_clist_remove(GTK_CLIST(ewm->availableList), 
+                    row);
+  }
+  gtk_clist_thaw(GTK_CLIST(ewm->availableList));
+  
+  for (i=0;i<ewm->updateableCount;i++)
+    FREE(ewm->updateableEntries[i]);
+  GROW(ewm->updateableEntries,
+       ewm->updateableCount,
+       0);
+  FREE(ewm);
+}
+
+static void appendToCList(RootNode * root,
+                         NamespaceInsertWindowModel * ewm) {
+  gchar * entry[1];
+  char * name;
+  char * desc;
+  char * mime;
+  RootNode * copy;
+  int row;
+ 
+  entry[0] = MALLOC(strlen(root->header.filename)+
+                   strlen(root->header.description)+
+                   strlen(root->header.mimetype)+
+                   128);
+  name = getFilenameFromNode(root);
+  desc = getDescriptionFromNode(root);
+  mime = getMimetypeFromNode(root);
+  sprintf(entry[0],
+         "%s, %s (%s, %u bytes)",
+         name,
+         desc,
+         mime,
+         (unsigned int) ntohl(root->header.fileIdentifier.file_length)); 
+  FREE(name);
+  FREE(desc);
+  FREE(mime);
+  row = gtk_clist_append(GTK_CLIST(ewm->availableList), 
+                        entry);
+  FREE(entry[0]);
+  copy = MALLOC(sizeof(RootNode));
+  memcpy(copy,
+        root,
+        sizeof(RootNode));
+  /* note: if you wish any clist to be sortable, you must
+     store the associated data in the list itself, not in an external
+     array! (same goes for all clists, but currently only "Files"
+     is sortable of the namespace related lists) - IW */
+  gtk_clist_set_row_data(GTK_CLIST(ewm->availableList),
+                        row,
+                        copy);
+}
+
+static void checkUpdateableSBlocks(SBlock * sb,
+                                  NamespaceInsertWindowModel * ewm) {
+  gchar * entry[1];
+  HashCode160 namespace;
+  SBlock * tmp;
+  int i;
+  
+  if (SBLOCK_UPDATE_NONE == ntohl(sb->updateInterval))
+    return; /* non-updateable SBlock */
+
+  /* check if namespace *matches* selected Pseudonym! */
+  hash(&sb->subspace,
+       sizeof(PublicKey),
+       &namespace);
+  if (! equalsHashCode160(&ewm->selectedPseudonym,
+                         &namespace) )
+    return;
+
+  /* check if SBlock is valid */
+  if (SYSERR == verifySBlock(sb))
+    return;
+  
+  /* skip if duplicate periodical (essentially irrelevant which
+   * of the blocks gets updated, the result is the same).
+   * FIXME? this causes trouble if two unrelated sblocks have 
+   * identical identifierIncrement, which should be unlikely? */
+  if(ntohl(sb->updateInterval)>0) {
+    for(i=0;i<ewm->updateableCount;i++) {
+      tmp = ewm->updateableEntries[i];
+      if ((0 == memcmp(&tmp->identifierIncrement,
+                              &sb->identifierIncrement,
+                      sizeof(HashCode160))) ) {
+        LOG(LOG_DEBUG, 
+            "DEBUG: skipping duplicate SBlock entry ...\n");
+        return;
+      }
+    }
+  }
+
+  /* ok, all checks pass: add */
+  sb->filename[MAX_FILENAME_LEN/2-1] = 0;
+  sb->description[MAX_DESC_LEN-1] = 0;
+  sb->mimetype[MAX_MIMETYPE_LEN/2-1] = 0;
+  entry[0] = MALLOC(strlen(sb->filename)+
+                   strlen(sb->description)+
+                   strlen(sb->mimetype)+
+                   128);
+  sprintf(entry[0],
+         "%s, %s (%s, %u bytes)",
+         sb->filename,
+         sb->description,
+         sb->mimetype,
+         (unsigned int) ntohl(sb->fileIdentifier.file_length)); 
+  gtk_clist_append(GTK_CLIST(ewm->sblockList), 
+                  entry);
+  FREE(entry[0]);
+  GROW(ewm->updateableEntries,
+       ewm->updateableCount,
+       ewm->updateableCount+1);
+  ewm->updateableEntries[ewm->updateableCount-1]
+    = MALLOC(sizeof(SBlock));
+  memcpy(ewm->updateableEntries[ewm->updateableCount-1],
+        sb,
+        sizeof(SBlock));
+}
+
+/**
+ * Only the ewm argument may be used since
+ * we may also be called from enter_callback
+ * which is called whenever the user presses
+ * ENTER in the password line.
+ */
+static void pselectCallback(GtkWidget * unused,
+                           gint rowX,
+                           gint column,
+                           GdkEventButton * event,
+                           NamespaceInsertWindowModel * ewm) {
+  GList * tmp;
+  int row;
+  gchar * key[1];
+  char * name;
+  char * pass;
+  Hostkey pseudo;
+  PublicKey pkey;
+  int i;
+  gchar * titlesNo[1] = { "--no update--" };
+
+  /* first, clear off the old sblock list */
+  gtk_clist_freeze(GTK_CLIST(ewm->sblockList));
+  gtk_clist_clear(GTK_CLIST(ewm->sblockList));
+  gtk_clist_append(GTK_CLIST(ewm->sblockList),
+                  &titlesNo[0]);
+  gtk_clist_thaw(GTK_CLIST(ewm->sblockList));   
+  
+  /* update ewm->selectedPseudonym */
+  tmp = GTK_CLIST(ewm->pseudonymList)->selection;
+  if (NULL == tmp) {
+    return;  
+  }
+  row = (int) tmp->data;
+  if ( row < 0 ) {
+    return;   
+  }
+
+  key[0] = NULL;
+  gtk_clist_get_text(GTK_CLIST(ewm->pseudonymList),
+                    row,
+                    0,
+                    &key[0]);
+  name = key[0];
+  if (name == NULL) {
+    return;   
+  }
+  pass = gtk_entry_get_text(GTK_ENTRY(ewm->passwordLine));
+  if (strlen(pass) == 0)
+    pass = NULL;
+  pseudo = readPseudonym(name, pass);
+  if (pseudo == NULL) {
+    return; /* wait for password to be entered... */  
+  }
+  getPublicKey(pseudo, 
+              &pkey);
+  freeHostkey(pseudo);
+  hash(&pkey,
+       sizeof(PublicKey),
+       &ewm->selectedPseudonym);
+  
+  /* clear entries from possible previous selection */
+  for (i=0;i<ewm->updateableCount;i++)
+    FREE(ewm->updateableEntries[i]);
+  GROW(ewm->updateableEntries,
+       ewm->updateableCount,
+       0);
+  
+  gtk_clist_freeze(GTK_CLIST(ewm->sblockList));
+  iterateDirectoryDatabase(DIR_CONTEXT_INSERT_SB,
+                          (RootNodeCallback)&checkUpdateableSBlocks,
+                          ewm);
+  gtk_clist_thaw(GTK_CLIST(ewm->sblockList));   
+}
+ 
+static void enter_callback(GtkWidget * unused,
+                          NamespaceInsertWindowModel * ewm) {
+  pselectCallback(NULL, 0, 0, NULL, ewm);
+}
+
+static void selectFrequencyCallback(GtkWidget * unused,
+                                   NamespaceInsertWindowModel * ewm) {
+  gchar * choice;
+  GList * tmp;
+
+  tmp = GTK_CLIST(ewm->sblockList)->selection;
+  if ( (NULL == tmp) || 
+       ( ((int)tmp->data) == 0) ) {    
+    choice = 
gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(ewm->updateInterval)->entry)); 
+    if (strcmp(choice, "--sporadic updates--") == 0) {
+      gtk_widget_set_sensitive(ewm->currentKey, TRUE);
+      gtk_widget_set_sensitive(ewm->nextKey, TRUE);
+    } else if (strcmp(choice, "--no updates--") == 0) {
+      gtk_widget_set_sensitive(ewm->currentKey, TRUE);
+      gtk_widget_set_sensitive(ewm->nextKey, FALSE);
+      gtk_entry_set_text(GTK_ENTRY(ewm->nextKey), 
+                        "");
+    } else {
+      /* periodic */
+      gtk_widget_set_sensitive(ewm->currentKey, TRUE);
+      gtk_widget_set_sensitive(ewm->nextKey, FALSE);
+      gtk_entry_set_text(GTK_ENTRY(ewm->nextKey), 
+                        "");
+    }
+  } else {
+    /* determined by SBlock, and SBlock has
+       already set the entries correctly */
+  }
+}
+
+/**
+ * The user selected an SBlock for an update.  Set the "update
+ * interval" field according to the update interval
+ * found in the SBlock.
+ */
+static void selectSBlockCallback(GtkWidget * unused,
+                                gint rowX,
+                                gint column,
+                                GdkEventButton * event,
+                                NamespaceInsertWindowModel * ewm) {
+  SBlock * pred;
+  TIME_T interval;
+  GList * tmp;
+  int row;
+  unsigned int days, hours, minutes, seconds;
+  char * txt;
+  HexName hex;
+  HashCode160 currentId;
+  HashCode160 nextId;
+  TIME_T now;
+    
+  gtk_entry_set_text(GTK_ENTRY(ewm->nextKey), 
+                    "");
+    
+  tmp = GTK_CLIST(ewm->sblockList)->selection;
+  if ( (NULL == tmp) || 
+       ( ((int)tmp->data) == 0) ) {
+    gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(ewm->updateInterval)->entry), 
+                      "--no updates--");
+    gtk_widget_set_sensitive(ewm->currentKey, TRUE);
+    gtk_widget_set_sensitive(ewm->nextKey, FALSE);
+    gtk_widget_set_sensitive(ewm->updateInterval, TRUE);
+    gtk_entry_set_text(GTK_ENTRY(ewm->currentKey), 
+                      "");
+    gtk_entry_set_text(GTK_ENTRY(ewm->nextKey), 
+                      "");    
+    return;
+  }
+
+  row = ((int) tmp->data) - 1; /* -1: first item is always "no update" */
+  if (row >= ewm->updateableCount) {
+    guiMessage("ERROR: this should never happen.\n");    
+    gtk_widget_set_sensitive(ewm->currentKey, FALSE);
+    gtk_widget_set_sensitive(ewm->nextKey, FALSE);
+    gtk_widget_set_sensitive(ewm->updateInterval, FALSE);
+    return; 
+  }
+  pred = ewm->updateableEntries[row];
+
+  interval = ntohl(pred->updateInterval);
+  if (interval == SBLOCK_UPDATE_SPORADIC) {
+    gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(ewm->updateInterval)->entry), 
+                      "--sporadic updates--");
+    hash2hex(&pred->nextIdentifier,
+            &hex);
+    gtk_entry_set_text(GTK_ENTRY(ewm->currentKey), 
+                      (gchar*)&hex);
+
+    gtk_widget_set_sensitive(ewm->currentKey, FALSE);
+    gtk_widget_set_sensitive(ewm->nextKey, TRUE);
+    gtk_widget_set_sensitive(ewm->updateInterval, FALSE);
+    return;
+  }
+  seconds = interval % 60;
+  interval = interval / 60;
+  minutes = interval % 60;
+  interval = interval / 60;
+  hours = interval % 24;
+  interval = interval / 24;
+  days = interval;
+  txt = MALLOC(256);
+  sprintf(txt,
+         "%u day%s %u hour%s %u minute%s %u second%s",
+         days, 
+         (days == 1) ? "" : "s",
+         hours, 
+         (hours == 1) ? "" : "s",
+         minutes,
+         (minutes == 1) ? "" : "s",
+         seconds,
+         (seconds == 1) ? "" : "s");
+  gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(ewm->updateInterval)->entry), 
+                    txt);  
+  FREE(txt);
+  /* periodic: all are pre-determined! */
+
+  TIME(&now);
+  computeIdAtTime(pred, now, &currentId);
+  computeIdAtTime(pred, now + ntohl(pred->updateInterval), &nextId);
+  hash2hex(&currentId,
+          &hex);
+  gtk_entry_set_text(GTK_ENTRY(ewm->currentKey), 
+                    (gchar*)&hex);
+  hash2hex(&nextId,
+          &hex);
+  gtk_entry_set_text(GTK_ENTRY(ewm->nextKey), 
+                    (gchar*)&hex);
+  gtk_widget_set_sensitive(ewm->nextKey, FALSE);
+  gtk_widget_set_sensitive(ewm->currentKey, FALSE);
+  gtk_widget_set_sensitive(ewm->updateInterval, FALSE);
+}
+
+
+/**
+ * Open a window to allow the user to build a namespace entry.
+ *
+ * @param unused GTK handle that is not used
+ * @param context selector for a subset of the known RootNodes
+ **/
+void openAssembleNamespaceDialog(GtkWidget * unused,
+                                unsigned int context) {
+  NamespaceInsertWindowModel * ewm;
+  GtkWidget * window;
+  GtkWidget * vbox, *vboxX, * hbox, * hboxX;
+  GtkWidget * clist;
+  GtkWidget * scrolled_window;
+  GtkWidget * label;
+  GtkWidget * separator; 
+  GtkWidget * button_ok;
+  GtkWidget * button_cancel;
+  GtkWidget * combo;
+  GList * glist;
+  int i;
+  int cnt;
+  char ** list;
+  gchar * titles[1] = { "Pseudonyms" };
+  gchar * titlesNo[1] = { "--no update--" };
+  gchar * titlesSBlocks[1] = { "Updateable SBlocks for pseudonym" };
+  gchar * titlesAvailable[1] = { "Files available" };
+
+  ewm = MALLOC(sizeof(NamespaceInsertWindowModel));
+  memset(ewm, 0, sizeof(NamespaceInsertWindowModel));
+
+  /* create new window for editing */
+  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+  ewm->window = window;
+  gtk_widget_set_usize(GTK_WIDGET(window),
+                      780,
+                      580);
+  gtk_window_set_title(GTK_WINDOW(window), 
+                      "Insert into Namespace");
+
+  /* add container for window elements */
+  vbox = gtk_vbox_new(FALSE, 15);
+  gtk_container_add(GTK_CONTAINER(window),
+                   vbox);
+  gtk_widget_show(vbox);
+
+  /* when user clicks on close box, always "destroy" */
+  gtk_signal_connect(GTK_OBJECT(window),
+                    "delete_event",
+                    GTK_SIGNAL_FUNC(deleteEvent),
+                    ewm);
+  /* whenever edit window gets destroyed, 
+     free *ALL* ewm data */
+  gtk_signal_connect(GTK_OBJECT(window),
+                    "destroy",
+                    GTK_SIGNAL_FUNC(destroyNamespaceInsertWindow),
+                    ewm);
+
+  gtk_container_set_border_width(GTK_CONTAINER(window), 
+                                10);
+
+
+  /* arrange a pseudonym box left to a "select SBlock to update" box */
+  hbox = gtk_hbox_new(FALSE, 5);
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    hbox, 
+                    TRUE, 
+                    TRUE, 
+                    0);
+  gtk_widget_show(hbox);
+
+  /* add a list of pseudonyms */
+  vboxX = gtk_vbox_new(FALSE, 0);
+  gtk_container_add(GTK_CONTAINER(hbox),
+                   vboxX);
+  gtk_widget_show(vboxX);
+
+  scrolled_window = gtk_scrolled_window_new(NULL, NULL);
+  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
+                                GTK_POLICY_AUTOMATIC, 
+                                GTK_POLICY_ALWAYS);
+  gtk_box_pack_start(GTK_BOX(vboxX), 
+                    scrolled_window, 
+                    TRUE, 
+                    TRUE, 
+                    0);
+  gtk_widget_show(scrolled_window);  
+  clist = gtk_clist_new_with_titles(1, titles); 
+  ewm->pseudonymList = clist;
+  gtk_clist_set_column_width(GTK_CLIST(clist),
+                            0,
+                            150);
+  gtk_container_add(GTK_CONTAINER(scrolled_window), 
+                   clist);
+  gtk_widget_show(clist);
+  /* add the known Pseudonyms to the list */
+  list = NULL;
+  cnt = listPseudonyms(&list);
+  if (cnt > 0) {
+    gtk_clist_freeze(GTK_CLIST(clist));
+    for (i=0;i<cnt;i++) {
+      gtk_clist_append(GTK_CLIST(clist),
+                      &list[i]);
+      FREE(list[i]);
+    }
+    gtk_clist_thaw(GTK_CLIST(clist));
+  }
+  FREENONNULL(list);
+  /* add callback: if a pseudonym is
+     selected, the "updateable SBlocks" list
+     must be updated! */
+  gtk_signal_connect(GTK_OBJECT(clist),
+                    "select_row",
+                    GTK_SIGNAL_FUNC(pselectCallback),
+                    ewm);
+
+
+
+  /* Create a line to enter the password */
+  hboxX = gtk_hbox_new(FALSE, 0);
+  gtk_box_pack_start(GTK_BOX(vboxX),
+                    hboxX,
+                    FALSE,
+                    FALSE,
+                    0);
+  gtk_widget_show(hboxX);
+  label = gtk_label_new("Pseudonym Password:");
+  gtk_box_pack_start(GTK_BOX(hboxX),
+                    label, 
+                    FALSE, 
+                    FALSE, 
+                    0);
+  gtk_widget_show(label); 
+  ewm->passwordLine = gtk_entry_new();
+  gtk_entry_set_visibility(GTK_ENTRY(ewm->passwordLine), FALSE);
+  gtk_box_pack_start(GTK_BOX(hboxX),
+                    ewm->passwordLine,
+                    TRUE,
+                    TRUE,
+                    0);
+  gtk_entry_set_text(GTK_ENTRY(ewm->passwordLine), 
+                    "");
+  gtk_widget_show(ewm->passwordLine);
+  gtk_signal_connect(GTK_OBJECT(ewm->passwordLine), 
+                    "activate",
+                    GTK_SIGNAL_FUNC(enter_callback),
+                    ewm);
+
+  /* add separator */
+  separator = gtk_vseparator_new();
+  gtk_box_pack_start(GTK_BOX(hbox),
+                    separator,
+                    FALSE, 
+                    FALSE,
+                    0);
+  gtk_widget_show(separator);
+  /* ok, now another feature in the hbox:
+     select which SBlock to update! */
+
+  scrolled_window = gtk_scrolled_window_new(NULL, NULL);
+  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
+                                GTK_POLICY_AUTOMATIC, 
+                                GTK_POLICY_ALWAYS);
+  gtk_box_pack_start(GTK_BOX(hbox), 
+                    scrolled_window, 
+                    TRUE, 
+                    TRUE, 
+                    0);
+  gtk_widget_show(scrolled_window);  
+  clist = gtk_clist_new_with_titles(1, titlesSBlocks); 
+  ewm->sblockList = clist;
+  gtk_container_add(GTK_CONTAINER(scrolled_window), 
+                   clist);
+  gtk_widget_show(clist);
+  /* add the known SBlocks to the list */
+  list = NULL;
+  gtk_clist_freeze(GTK_CLIST(clist));
+  gtk_clist_append(GTK_CLIST(clist),
+                  &titlesNo[0]);
+  gtk_clist_thaw(GTK_CLIST(clist)); 
+  gtk_signal_connect(GTK_OBJECT(clist),
+                    "select_row",
+                    GTK_SIGNAL_FUNC(selectSBlockCallback),
+                    ewm);
+  gtk_signal_connect(GTK_OBJECT(clist),
+                    "unselect_row",
+                    GTK_SIGNAL_FUNC(selectSBlockCallback),
+                    ewm);
+  
+  /* add separator */
+  separator = gtk_hseparator_new();
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    separator,
+                    FALSE, 
+                    FALSE,
+                    0);
+  gtk_widget_show(separator);
+
+  /* add interval / non-periodic selection */
+  hbox = gtk_hbox_new(FALSE, 10);
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    hbox,
+                    FALSE,
+                    FALSE,
+                    0);
+  gtk_widget_show(hbox);
+  label = gtk_label_new("Update frequency:");
+  gtk_box_pack_start(GTK_BOX(hbox),
+                    label, 
+                    FALSE, 
+                    FALSE, 
+                    0);
+  gtk_widget_show(label); 
+
+  combo = gtk_combo_new(); 
+  ewm->updateInterval = combo;
+  gtk_container_add(GTK_CONTAINER(hbox),
+                   combo);
+  gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(combo)->entry), 
+                    "--no updates--");
+  glist = NULL;
+  glist = g_list_append(glist, "--no updates--");
+  glist = g_list_append(glist, "--sporadic updates--");
+  glist = g_list_append(glist, "12 hours"); 
+  glist = g_list_append(glist, "1 day"); 
+  glist = g_list_append(glist, "2 days"); 
+  glist = g_list_append(glist, "7 days"); 
+  glist = g_list_append(glist, "30 days"); 
+  glist = g_list_append(glist, "2 hours 30 minutes"); 
+
+  gtk_combo_set_popdown_strings(GTK_COMBO(combo), 
+                               glist) ;
+  gtk_signal_connect(GTK_OBJECT(GTK_COMBO(combo)->entry),
+                    "changed",
+                    GTK_SIGNAL_FUNC(selectFrequencyCallback),
+                    ewm);
+  gtk_widget_show(combo);
+  
+  /* add keyword boxes */ 
+  hbox = gtk_hbox_new(FALSE, 0);
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    hbox,
+                    FALSE,
+                    TRUE,
+                    0);
+  gtk_widget_show(hbox);
+  label = gtk_label_new("Current keyword: ");
+  gtk_box_pack_start(GTK_BOX(hbox),
+                    label, 
+                    FALSE, 
+                    FALSE, 
+                    0);
+  gtk_widget_show(label);
+  ewm->currentKey = gtk_entry_new();
+  gtk_entry_set_text(GTK_ENTRY(ewm->currentKey), 
+                    "");
+  gtk_box_pack_start(GTK_BOX(hbox),
+                    ewm->currentKey, 
+                    TRUE, 
+                    TRUE, 
+                    0);
+  gtk_widget_show(ewm->currentKey);
+  hbox = gtk_hbox_new(FALSE, 0);
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    hbox,
+                    FALSE,
+                    TRUE,
+                    0);
+  gtk_widget_show(hbox);
+  label = gtk_label_new("Future keyword: ");
+  gtk_box_pack_start(GTK_BOX(hbox),
+                    label, 
+                    FALSE, 
+                    FALSE, 
+                    0);
+  gtk_widget_show(label);
+  ewm->nextKey = gtk_entry_new();
+  gtk_entry_set_text(GTK_ENTRY(ewm->nextKey), 
+                    "");
+  gtk_box_pack_start(GTK_BOX(hbox),
+                    ewm->nextKey, 
+                    TRUE, 
+                    TRUE, 
+                    0);
+  gtk_widget_set_sensitive(ewm->nextKey, FALSE);
+  gtk_widget_show(ewm->nextKey);
+
+  /* add separator */
+  separator = gtk_hseparator_new();
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    separator,
+                    FALSE, 
+                    FALSE,
+                    0);
+  gtk_widget_show(separator);
+
+
+  /* add the box for the two lists */
+  hbox = gtk_hbox_new(FALSE, 0);
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    hbox,
+                    TRUE,
+                    TRUE,
+                    0);
+  gtk_widget_show(hbox);
+
+  /* add a list of available entries */
+  scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
+                                GTK_POLICY_AUTOMATIC, 
+                                GTK_POLICY_ALWAYS);
+  gtk_box_pack_start(GTK_BOX(hbox), 
+                    scrolled_window, 
+                    TRUE, 
+                    TRUE, 
+                    0);
+  gtk_widget_show(scrolled_window);  
+  clist = gtk_clist_new_with_titles(1, titlesAvailable); 
+  ewm->availableList = clist;
+  gtk_container_add(GTK_CONTAINER(scrolled_window), 
+                   clist);
+  gtk_clist_set_sort_column(GTK_CLIST(clist),0);
+  gtk_clist_set_auto_sort(GTK_CLIST(clist),TRUE);
+  /* add the known RootNodes to the list */
+  gtk_clist_freeze(GTK_CLIST(clist));
+  iterateDirectoryDatabase(context,
+                          (RootNodeCallback)&appendToCList,
+                          ewm);
+  gtk_clist_thaw(GTK_CLIST(clist));
+  gtk_widget_show(clist);
+
+
+  /* add the insertion ok/cancel buttons */
+  separator = gtk_hseparator_new();
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    separator,
+                    FALSE, 
+                    FALSE,
+                    0);
+  gtk_widget_show(separator);
+
+  hbox = gtk_hbox_new(FALSE, 0);
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    hbox, 
+                    FALSE, 
+                    FALSE, 
+                    0);
+  gtk_widget_show(hbox);
+  button_ok = gtk_button_new_with_label("Ok");
+  button_cancel = gtk_button_new_with_label("Cancel");
+  gtk_box_pack_start(GTK_BOX(hbox),
+                    button_ok,
+                    TRUE,
+                    TRUE,
+                    0);
+  gtk_box_pack_start(GTK_BOX(hbox), 
+                    button_cancel, 
+                    TRUE,
+                    TRUE, 
+                    0);
+  gtk_signal_connect(GTK_OBJECT(button_ok), 
+                    "clicked",
+                    GTK_SIGNAL_FUNC(buildNSEntry),
+                    ewm);
+  gtk_signal_connect(GTK_OBJECT(button_cancel),
+                    "clicked",
+                    GTK_SIGNAL_FUNC(destroyWidget),
+                    window);
+  gtk_widget_show(button_ok);
+  gtk_widget_show(button_cancel);
+
+  /* all clear, show the window */
+  gtk_widget_show(window);
+}
+
+
+/* ********************** SEARCH ******************** */
+/* ********************** SEARCH ******************** */
+/* ********************** SEARCH ******************** */
+
+/**
+ * @brief state of the namespace search window
+ **/
+typedef struct {
+  GtkWidget * window;
+  GtkWidget * namespaceCombo;
+  GtkWidget * searchkeyLine;
+} NamespaceSearchWindowModel;
+
+
+/**
+ * Exit the application (called when the main window
+ * is closed or the user selects File-Quit).
+ **/
+static void destroyNamespaceSearchWindow(GtkWidget * widget,
+                                        NamespaceSearchWindowModel * ewm) {
+  FREE(ewm);
+}
+
+typedef struct {
+  HashCode160 n;
+  HashCode160 k;
+  ListModel * model;
+  HashCode160 * seen;
+  int seenCount;
+  HashCode160 * results;
+  int resultCount;
+} NSSearchThreadData;
+
+/**
+ * The main method of the search-thread.
+ *
+ * @param n namespace to search
+ * @param k code to search for
+ * @param model Data related to the search
+ * @return OK on success, SYSERR on error
+ **/
+static int startNamespaceSearchThread(HashCode160 * n,
+                                     HashCode160 * k,
+                                     ListModel * model);
+
+/**
+ * Run the namespace search.  Starts the search
+ * thread and adds a new tab to the window list.
+ */
+static void startSearch(HashCode160 * n,
+                       HashCode160 * k) {
+  ListModel * model;  
+  int ok;
+  GtkWidget * box;
+
+  /* start search! */
+  model = (ListModel*) MALLOC(sizeof(ListModel));
+  model->type = LM_TYPE_NSSEARCH;
+  model->doTerminate = NO;
+  model->skipMenuRefresh = NO;
+  model->SEARCH_socket_ = NULL;
+
+  box = initializeSearchResultList(model);
+
+  /* start searching */
+  ok = startNamespaceSearchThread(n, 
+                                 k,
+                                 model);
+  if (ok == SYSERR) {
+    LOG(LOG_DEBUG,
+        "DEBUG: startNamespaceSearchThread failed\n");
+    releaseClientSocket(model->SEARCH_socket_);
+    gtkSaveCall((GtkFunction) doDestroyWidget, box);
+    FREE(model);
+  } else {
+    char * label;
+    HexName hex1;
+    HexName hex2;
+
+    label = MALLOC(128);    
+    hash2hex(n, &hex1);
+    hash2hex(k, &hex2);
+    sprintf(label, 
+           "%.8s/%.8s", /* %8s: otherwise MUCH too long... */
+           (char*)&hex1,
+           (char*)&hex2);
+    addToNotebook(label,
+                 box);
+    FREE(label);
+
+    LOG(LOG_DEBUG, 
+       "DEBUG: namespace search initiated for %s %s\n",
+        (char *)&hex1,
+       (char *)&hex2);
+  }
+}
+
+static void displayResultGTK_(SBlock * sb,
+                             NSSearchThreadData * sqc) {
+  HashCode160 curK;
+  int i;
+  HexName hex;
+  
+  hash(sb, sizeof(SBlock), &curK);
+  hash2hex(&curK,
+          &hex);
+  LOG(LOG_DEBUG, 
+      "DEBUG: got namespace result for %s\n", 
+      (char *)&hex);
+  for (i=0;i<sqc->resultCount;i++)
+    if (equalsHashCode160(&curK,
+                         &sqc->results[i])) {
+      LOG(LOG_DEBUG, 
+          "DEBUG: displayResultGTK_ skipping previously seen entry %s\n",
+          (char *)&hex);
+      return; /* displayed already */
+    }
+  GROW(sqc->results,
+       sqc->resultCount,
+       sqc->resultCount+1);
+  memcpy(&sqc->results[sqc->resultCount-1],
+        &curK,
+        sizeof(HashCode160));
+  displayResultGTK((RootNode*)sb, 
+                  sqc->model);
+  refreshMenuSensitivity();
+  GROW(sqc->seen,
+       sqc->seenCount,
+       sqc->seenCount+1);
+  memcpy(&sqc->seen[sqc->seenCount-1], 
+        &sqc->k, 
+        sizeof(HashCode160));
+  
+  /* now search for update if possible! */
+  computeIdAtTime(sb,
+                 TIME(NULL),
+                 &curK);
+  for (i=0;i<sqc->seenCount;i++)         
+    if (equalsHashCode160(&curK,
+                         &sqc->seen[i])) {
+      HashCode160 ns;
+      hash(&sb->subspace,
+          sizeof(PublicKey),
+          &ns);
+      hash2hex(&ns,
+              &hex);
+      guiMessage("Found the most recent version for a hit\n"
+                 "in your original search in namespace\n\n"
+                 "%s\n\nGood.",
+                STRDUP((char *)&hex));
+      LOG(LOG_DEBUG, 
+          "DEBUG: namespace result %s is the most recent\n",
+         (char*)&hex);
+      return; /* found most up-to-date / all versions! */
+    }
+  /* else: start new parallel search! */
+  LOG(LOG_DEBUG, 
+      "DEBUG: starting parallel search for the current version of %s\n",
+      (char*)&hex);
+  startSearch(&sqc->n,
+             &curK);
+}
+
+int searchSBlock_(NSSearchThreadData * sqc) {
+  LOG(LOG_DEBUG, 
+      "DEBUG: enter searchSBlock_\n");
+
+  sqc->seen = NULL;
+  sqc->seenCount = 0;
+  sqc->results = NULL;
+  sqc->resultCount = 0;
+  sqc->model->SEARCH_socket_ = getClientSocket();
+  if (sqc->model->SEARCH_socket_ != NULL) {
+    searchSBlock(sqc->model->SEARCH_socket_,
+                &sqc->n,
+                &sqc->k,
+                (TestTerminateThread)&testTermination,
+                sqc->model,
+                (NSSearchResultCallback)&displayResultGTK_,
+                sqc);
+  } else {
+    LOG(LOG_DEBUG, 
+        "DEBUG: socket was NULL in searchSBlock_\n");
+  }
+  GROW(sqc->seen,
+       sqc->seenCount,
+       0);
+  GROW(sqc->results,
+       sqc->resultCount,
+       0);
+  FREE(sqc);
+  return 0;
+}
+
+/**
+ * The main method of the search-thread.
+ *
+ * @param n namespace to search
+ * @param k code to search for
+ * @param model Data related to the search
+ * @return OK on success, SYSERR on error
+ **/
+static int startNamespaceSearchThread(HashCode160 * n,
+                                     HashCode160 * k,
+                                     ListModel * model) {
+  NSSearchThreadData * sqc;
+
+  sqc = MALLOC(sizeof(NSSearchThreadData));
+  memcpy(&sqc->n,
+        n,
+        sizeof(HashCode160));
+  memcpy(&sqc->k,
+        k,
+        sizeof(HashCode160));
+  sqc->model = model;
+  if (0 != PTHREAD_CREATE(&model->thread,
+                         (PThreadMain) &searchSBlock_,
+                         sqc,
+                         16 * 1024)) {
+    errexit("FATAL: could not create SBlock search thread (%s)!\n",
+           STRERROR(errno));    
+  }
+  return OK;
+}
+
+
+/**
+ * Start the namespace search.  This method obtains
+ * n and k from the input window and then calls
+ * the actual startSearch function.
+ *
+ * @param dummy not used
+ * @param ewm the state of the search window
+ **/
+static void searchNS(GtkWidget * dummy, 
+                    NamespaceSearchWindowModel * ewm) {
+  HashCode160 n;
+  HashCode160 k;
+  char * c;
+
+  c = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(ewm->namespaceCombo)->entry));
+  if (SYSERR == tryhex2hash(c, &n)) {
+    guiMessage("ERROR: must specify valid HEX code for namespace."); 
+    return;
+  }
+  c = gtk_entry_get_text(GTK_ENTRY(ewm->searchkeyLine));
+  if (strlen(c) == 0) {
+    guiMessage("ERROR: must specify string (or HEX code) for search key."); 
+    return;
+  }  
+  tryhex2hashOrHashString(c, &k);
+
+  /* destroy the window */
+  gtk_widget_destroy(ewm->window);
+  startSearch(&n, &k);
+}
+
+/**
+ * Open a window to allow the user to search a namespace
+ *
+ * @param unused GTK handle that is not used
+ * @param unused2 argument that is always 0
+ **/
+void searchNamespace(GtkWidget * unused,
+                    unsigned int unused2) {
+  NamespaceSearchWindowModel * ewm;
+  GtkWidget * window;
+  GtkWidget * vbox, * hbox;
+  GtkWidget * label;
+  GtkWidget * button_ok;
+  GtkWidget * button_cancel;
+  GList * glist;
+  HashCode160 * list;
+  int ret;
+  int i;
+
+  ewm = MALLOC(sizeof(NamespaceSearchWindowModel));
+  
+  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+  ewm->window = window;
+  gtk_widget_set_usize(GTK_WIDGET(window),
+                      650,
+                      120);
+  gtk_window_set_title(GTK_WINDOW(window), 
+                      "Search Namespace");
+
+  /* add container for window elements */
+  vbox = gtk_vbox_new(FALSE, 10);
+  gtk_container_add(GTK_CONTAINER(window),
+                   vbox);
+  gtk_widget_show(vbox);
+
+  /* when user clicks on close box, always "destroy" */
+  gtk_signal_connect(GTK_OBJECT(window),
+                    "delete_event",
+                    GTK_SIGNAL_FUNC(deleteEvent),
+                    ewm);
+  /* whenever edit window gets destroyed, 
+     free *ALL* ewm data */
+  gtk_signal_connect(GTK_OBJECT(window),
+                    "destroy",
+                    GTK_SIGNAL_FUNC(destroyNamespaceSearchWindow),
+                    ewm);
+
+  gtk_container_set_border_width(GTK_CONTAINER(window), 
+                                10);
+
+
+  /* Create a line to enter the namespace identifier */
+  hbox = gtk_hbox_new(FALSE, 0);
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    hbox,
+                    TRUE,
+                    TRUE,
+                    0);
+  gtk_widget_show(hbox);
+  label = gtk_label_new("Namespace identifier:");
+  gtk_box_pack_start(GTK_BOX(hbox),
+                    label, 
+                    FALSE, 
+                    FALSE, 
+                    0);
+  gtk_widget_show(label); 
+
+  ewm->namespaceCombo = gtk_combo_new();
+  gtk_box_pack_start(GTK_BOX(hbox),
+                    ewm->namespaceCombo,
+                    TRUE,
+                    TRUE,
+                    0);
+  gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(ewm->namespaceCombo)->entry), 
+                    "");
+  glist = NULL;
+  glist = g_list_append(glist, 
+                       "");
+  list = NULL;
+  ret = listNamespaces(&list);
+
+  for (i=0;i<ret;i++) {
+    HexName hex;
+    hash2hex(&list[i], 
+             &hex);
+    LOG(LOG_DEBUG, 
+        "DEBUG: appending namespace id %s\n", 
+        (char*)&hex);
+    glist = g_list_append(glist, 
+                         STRDUP((char*)&hex));
+    /* FIXME: memory from STRDUP above is probably not
+     * freed anywhere - but must be DUPED or gnunet-gtk
+     * will display only a single hash multiple times */
+  }
+  if (ret > 0)
+    FREE(list);
+  gtk_combo_set_popdown_strings(GTK_COMBO(ewm->namespaceCombo), 
+                               glist) ;
+  gtk_widget_show(ewm->namespaceCombo);
+
+
+  /* Create a line to enter the search key identifier */
+  hbox = gtk_hbox_new(FALSE, 0);
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    hbox,
+                    TRUE,
+                    TRUE,
+                    0);
+  gtk_widget_show(hbox);
+  label = gtk_label_new("Search key identifier:");
+  gtk_box_pack_start(GTK_BOX(hbox),
+                    label, 
+                    FALSE, 
+                    FALSE, 
+                    0);
+  gtk_widget_show(label); 
+  ewm->searchkeyLine = gtk_entry_new();
+  gtk_box_pack_start(GTK_BOX(hbox),
+                    ewm->searchkeyLine,
+                    TRUE,
+                    TRUE,
+                    0);
+  gtk_entry_set_text(GTK_ENTRY(ewm->searchkeyLine), 
+                    "");
+  gtk_signal_connect(GTK_OBJECT(ewm->searchkeyLine),
+                     "activate",
+                    GTK_SIGNAL_FUNC(searchNS),
+                    ewm);
+  gtk_widget_show(ewm->searchkeyLine);
+ 
+  /* add the ok/cancel buttons */
+
+  hbox = gtk_hbox_new(FALSE, 0);
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    hbox, 
+                    FALSE, 
+                    FALSE, 
+                    0);
+  gtk_widget_show(hbox);
+  button_ok = gtk_button_new_with_label("Search");
+  button_cancel = gtk_button_new_with_label("Cancel");
+  gtk_box_pack_start(GTK_BOX(hbox),
+                    button_ok,
+                    TRUE,
+                    TRUE,
+                    0);
+  gtk_box_pack_start(GTK_BOX(hbox), 
+                    button_cancel, 
+                    TRUE,
+                    TRUE, 
+                    0);
+  gtk_signal_connect(GTK_OBJECT(button_ok), 
+                    "clicked",
+                    GTK_SIGNAL_FUNC(searchNS),
+                    ewm);
+  gtk_signal_connect(GTK_OBJECT(button_cancel),
+                    "clicked",
+                    GTK_SIGNAL_FUNC(destroyWidget),
+                    window);
+  gtk_widget_show(button_ok);
+  gtk_widget_show(button_cancel);
+
+  /* all clear, show the window */
+  gtk_widget_show(window);
+
+}
+
+
+/* end of namespace.c */

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/namespace.h
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/namespace.h   2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/namespace.h   2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,48 @@
+/*
+     This file is part of GNUnet
+
+     GNUnet 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, or (at your
+     option) any later version.
+
+     GNUnet 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 GNUnet; see the file COPYING.  If not, write to the
+     Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+     Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file applications/afs/gtkui/namespace.h
+ * @brief namespace insert and search entry points
+ * @author Christian Grothoff
+ **/
+
+#ifndef GTKUI_NAMESPACE_H
+#define GTKUI_NAMESPACE_H
+
+
+
+/**
+ * Open a window to allow the user to build a namespace.
+ *
+ * @param context selector for a subset of the known RootNodes
+ **/
+void openAssembleNamespaceDialog(GtkWidget * unused,
+                                unsigned int unused2);
+
+/**
+ * Open a window to allow the user to search a namespace
+ *
+ * @param unused GTK handle that is not used
+ * @param unused2 argument that is always 0
+ **/
+void searchNamespace(GtkWidget * unused,
+                    unsigned int unused2);
+
+#endif

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/pseudonyms.c
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/pseudonyms.c  2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/pseudonyms.c  2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,402 @@
+/*
+     This file is part of GNUnet
+
+     GNUnet 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, or (at your
+     option) any later version.
+
+     GNUnet 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 GNUnet; see the file COPYING.  If not, write to the
+     Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+     Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file src/applications/afs/gtkui/pseudonyms.c
+ * @brief dialogs for creating and deleting pseudonyms
+ * @author Christian Grothoff
+ **/
+#include "gnunet_afs_esed2.h"
+#include "helper.h"
+#include "insertprogress.h"
+#include "pseudonyms.h"
+#include "main.h"
+
+/**
+ * @brief state of the CreatePseudonym window
+ **/
+typedef struct {
+  GtkWidget * window;
+  GtkWidget * pseudonymLine;
+  GtkWidget * passwordLine;
+} CreatePseudonymWindowModel;
+
+
+/**
+ * Collects the results of the assembly dialog, creates an insertion 
+ * progressbar and launches the insertion thread.
+ *
+ * @param dummy not used
+ * @param ewm the state of the edit window
+ **/
+static void create_ok(GtkWidget * dummy, 
+                     CreatePseudonymWindowModel * ewm) {
+  char * name;
+  char * pass;
+  Hostkey ps;
+  
+  name = gtk_entry_get_text(GTK_ENTRY(ewm->pseudonymLine));
+  if (name == NULL) {
+    guiMessage("WARNING: cowardly refusing to create pseudonym without 
name.\n");
+    return;
+  }
+  name = STRDUP(name);
+  pass = gtk_entry_get_text(GTK_ENTRY(ewm->passwordLine));
+  if (pass != NULL) 
+    pass = STRDUP(pass);
+  gtk_widget_destroy(ewm->window);
+
+  /* we may want to do this in another thread
+     to keep the event manager running (and potentially
+     even give feedback in the form of a popup window).
+     After all, this can take a while... */
+  ps = createPseudonym(name, pass);
+  if (ps == NULL)
+    guiMessage("WARNING: failed to create pseudonym (see logs).\n");
+  else
+    freeHostkey(ps);
+  refreshMenuSensitivity();
+  FREE(name);
+  FREENONNULL(pass);
+}
+
+/**
+ * Exit the application (called when the main window
+ * is closed or the user selects File-Quit).
+ **/
+static void destroyPCWindow(GtkWidget * widget,
+                           CreatePseudonymWindowModel * ewm) {
+  FREE(ewm);
+}
+
+
+/**
+ * Open a window to allow the user to create a pseudonym
+ *
+ * @param unused GTK handle that is not used
+ * @param unused2 not used
+ **/
+void openCreatePseudonymDialog(GtkWidget * unused,
+                              unsigned int unused2) {
+  CreatePseudonymWindowModel * ewm;
+  GtkWidget * vbox;
+  GtkWidget * hbox;
+  GtkWidget * label;
+  GtkWidget * button_ok;
+  GtkWidget * button_cancel;
+  GtkWidget * separator;
+
+  ewm = MALLOC(sizeof(CreatePseudonymWindowModel));
+  /* create new window for editing */
+  ewm->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+  gtk_widget_set_usize(GTK_WIDGET(ewm->window),
+                      400,
+                      120);
+  gtk_window_set_title(GTK_WINDOW(ewm->window), 
+                      "Create Pseudonym");
+
+  /* add container for window elements */
+  vbox = gtk_vbox_new(FALSE, 0);
+  gtk_container_add(GTK_CONTAINER(ewm->window),
+                   vbox);
+  gtk_widget_show(vbox);
+
+  /* when user clicks on close box, always "destroy" */
+  gtk_signal_connect(GTK_OBJECT(ewm->window),
+                    "delete_event",
+                    GTK_SIGNAL_FUNC(deleteEvent),
+                    ewm);
+  /* whenever edit window gets destroyed, 
+     free *ALL* ewm data */
+  gtk_signal_connect(GTK_OBJECT(ewm->window),
+                    "destroy",
+                    GTK_SIGNAL_FUNC(destroyPCWindow),
+                    ewm);
+
+  gtk_container_set_border_width(GTK_CONTAINER(ewm->window), 
+                                10);
+
+  /* Create a line to change the pseudonym */
+  hbox = gtk_hbox_new(FALSE, 0);
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    hbox,
+                    FALSE,
+                    TRUE,
+                    0);
+  gtk_widget_show(hbox);
+  label = gtk_label_new("Pseudonym:");
+  gtk_box_pack_start(GTK_BOX(hbox),
+                    label, 
+                    FALSE, 
+                    FALSE, 
+                    0);
+  gtk_widget_show(label); 
+  ewm->pseudonymLine = gtk_entry_new();
+  gtk_box_pack_start(GTK_BOX(hbox),
+                    ewm->pseudonymLine,
+                    TRUE,
+                    TRUE,
+                    0);
+  gtk_entry_set_text(GTK_ENTRY(ewm->pseudonymLine), 
+                    "");
+  gtk_widget_show(ewm->pseudonymLine);
+  
+  /* Create a line to change the description */
+  hbox = gtk_hbox_new(FALSE, 0);
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    hbox,
+                    FALSE,
+                    TRUE,
+                    0);
+  gtk_widget_show(hbox);
+  label = gtk_label_new("Password:");
+  gtk_box_pack_start(GTK_BOX(hbox),
+                    label, 
+                    FALSE, 
+                    FALSE, 
+                    0);
+  gtk_widget_show(label);  
+  ewm->passwordLine = gtk_entry_new();
+  gtk_box_pack_start(GTK_BOX(hbox),
+                    ewm->passwordLine, 
+                    TRUE, 
+                    TRUE,
+                    0);
+  gtk_entry_set_text(GTK_ENTRY(ewm->passwordLine), 
+                    "");
+  gtk_widget_show(ewm->passwordLine);
+  
+  separator = gtk_hseparator_new();
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    separator,
+                    TRUE, 
+                    TRUE,
+                    0);
+  gtk_widget_show(separator);
+
+  /* add the insertion ok/cancel buttons */
+  hbox = gtk_hbox_new(FALSE, 0);
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    hbox, 
+                    FALSE, 
+                    TRUE, 
+                    0);
+  gtk_widget_show(hbox);
+  button_ok = gtk_button_new_with_label("Ok");
+  button_cancel = gtk_button_new_with_label("Cancel");
+  gtk_box_pack_start(GTK_BOX(hbox),
+                    button_ok,
+                    TRUE,
+                    TRUE,
+                    0);
+  gtk_box_pack_start(GTK_BOX(hbox), 
+                    button_cancel, 
+                    TRUE,
+                    TRUE, 
+                    0);
+  gtk_signal_connect(GTK_OBJECT(button_ok), 
+                    "clicked",
+                    GTK_SIGNAL_FUNC(create_ok),
+                    ewm);
+  gtk_signal_connect(GTK_OBJECT(button_cancel),
+                    "clicked",
+                    GTK_SIGNAL_FUNC(destroyWidget),
+                    ewm->window);
+  gtk_widget_show(button_ok);
+  gtk_widget_show(button_cancel);
+
+  /* all clear, show the window */
+  gtk_widget_show(ewm->window);
+}
+
+
+
+/**
+ * @brief state of the DeletePseudonym window
+ **/
+typedef struct {
+  GtkWidget * window;
+  char * selected;
+  GtkWidget * pseudonymList;
+} DeletePseudonymWindowModel;
+
+/**
+ * Exit the application (called when the main window
+ * is closed or the user selects File-Quit).
+ **/
+static void destroyDPWindow(GtkWidget * widget,
+                           DeletePseudonymWindowModel * ewm) {
+  FREE(ewm);
+}
+
+/**
+ * The keyword delete button was clicked. Delete the 
+ * currently selected pseudonym.
+ *
+ * @param w not used
+ * @param ewm state of the edit window
+ **/
+static void button_del_clicked(GtkWidget * w, 
+                              DeletePseudonymWindowModel * ewm) {
+  GList * tmp;
+  gchar * key[1];
+  int row;
+ 
+  tmp = GTK_CLIST(ewm->pseudonymList)->selection;
+  if (NULL == tmp) {
+    /* message that keyword must be selected to delete one? */
+    return;
+  }  
+  row = (int) tmp->data;
+  if (row < 0) 
+    return; /* should never happen... */
+  key[0] = NULL;
+  gtk_clist_get_text(GTK_CLIST(ewm->pseudonymList),
+                    row,
+                    0,
+                    &key[0]);
+  if (key[0] == NULL)
+    return;
+  if (OK != deletePseudonym(key[0]))
+    guiMessage("WARNING: failed to delete pseudonym (see logs).\n");
+  gtk_clist_remove(GTK_CLIST(ewm->pseudonymList),
+                  row);
+  refreshMenuSensitivity();
+}
+
+/**
+ * Open a window to allow the user to delete a pseudonym
+ *
+ * @param unused GTK handle that is not used
+ * @param unused2 not used
+ **/
+void openDeletePseudonymDialog(GtkWidget * unused,
+                              unsigned int unused2) {
+  DeletePseudonymWindowModel * ewm;
+  GtkWidget * window;
+  GtkWidget * vbox, * hbox;
+  GtkWidget * clist;
+  GtkWidget * scrolled_window;
+  GtkWidget * button_delete;
+  GtkWidget * button_cancel;
+  gchar * titles[1] = { "Pseudonyms" };
+  int i;
+  int cnt;
+  char ** list;
+
+  ewm = MALLOC(sizeof(DeletePseudonymWindowModel));
+  /* create new window for editing */
+  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+  ewm->window = window;
+  gtk_widget_set_usize(GTK_WIDGET(window),
+                      250,
+                      300);
+  gtk_window_set_title(GTK_WINDOW(window), 
+                      "Delete Pseudonym");
+
+  /* add container for window elements */
+  vbox = gtk_vbox_new(FALSE, 0);
+  gtk_container_add(GTK_CONTAINER(window),
+                   vbox);
+  gtk_widget_show(vbox);
+
+  /* when user clicks on close box, always "destroy" */
+  gtk_signal_connect(GTK_OBJECT(window),
+                    "delete_event",
+                    GTK_SIGNAL_FUNC(deleteEvent),
+                    ewm);
+  /* whenever edit window gets destroyed, 
+     free *ALL* ewm data */
+  gtk_signal_connect(GTK_OBJECT(window),
+                    "destroy",
+                    GTK_SIGNAL_FUNC(destroyDPWindow),
+                    ewm);
+
+  gtk_container_set_border_width(GTK_CONTAINER(window), 
+                                10);
+
+  /* add a list of pseudonyms */
+  scrolled_window = gtk_scrolled_window_new(NULL, NULL);
+  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
+                                GTK_POLICY_AUTOMATIC, 
+                                GTK_POLICY_ALWAYS);
+  gtk_box_pack_start(GTK_BOX(vbox), 
+                    scrolled_window, 
+                    TRUE, 
+                    TRUE, 
+                    0);
+  gtk_widget_show(scrolled_window);  
+  clist = gtk_clist_new_with_titles(1, titles); 
+  ewm->pseudonymList = clist;
+  gtk_container_add(GTK_CONTAINER(scrolled_window), 
+                   clist);
+  gtk_widget_show(clist);
+  /* add the known RootNodes to the list */
+  list = NULL;
+  cnt = listPseudonyms(&list);
+  if (cnt > 0) {
+    gtk_clist_freeze(GTK_CLIST(clist));
+    for (i=0;i<cnt;i++) {
+      gtk_clist_append(GTK_CLIST(clist),
+                      &list[i]);
+      FREE(list[i]);
+    }
+    gtk_clist_thaw(GTK_CLIST(clist));
+  }
+  FREENONNULL(list);
+
+  /* add the buttons to add and delete keywords */
+  hbox = gtk_hbox_new(FALSE, 0);
+  gtk_box_pack_start(GTK_BOX(vbox),
+                    hbox,
+                    FALSE,
+                    TRUE,
+                    0);
+  gtk_widget_show(hbox);
+  button_delete = gtk_button_new_with_label("Delete Pseudonym");
+  gtk_box_pack_start(GTK_BOX(hbox), 
+                    button_delete, 
+                    TRUE, 
+                    TRUE, 
+                    0);
+  gtk_signal_connect(GTK_OBJECT(button_delete), 
+                    "clicked",
+                    GTK_SIGNAL_FUNC(button_del_clicked),
+                    ewm);
+  gtk_widget_show(button_delete);
+
+
+  button_cancel = gtk_button_new_with_label("Cancel");
+  gtk_box_pack_start(GTK_BOX(hbox), 
+                    button_cancel, 
+                    TRUE,
+                    TRUE, 
+                    0);
+  gtk_signal_connect(GTK_OBJECT(button_cancel),
+                    "clicked",
+                    GTK_SIGNAL_FUNC(destroyWidget),
+                    window);
+  gtk_widget_show(button_cancel);
+
+  /* all clear, show the window */
+  gtk_widget_show(window);
+}
+
+
+/* end of pseudonyms.c */

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/pseudonyms.h
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/pseudonyms.h  2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/pseudonyms.h  2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,46 @@
+/*
+     This file is part of GNUnet
+
+     GNUnet 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, or (at your
+     option) any later version.
+
+     GNUnet 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 GNUnet; see the file COPYING.  If not, write to the
+     Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+     Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file applications/afs/gtkui/pseudonyms.h
+ * @brief Pseudonym creation and deletion dialogs
+ * @author Christian Grothoff
+ **/
+#ifndef GTKUI_PSEUDONYMS_H
+#define GTKUI_PSEUDONYMS_H
+
+/**
+ * Open a window to allow the user to create a pseudonym
+ *
+ * @param unused GTK handle that is not used
+ * @param unused2 not used
+ **/
+void openCreatePseudonymDialog(GtkWidget * unused,
+                              unsigned int unused2);
+
+/**
+ * Open a window to allow the user to delete a pseudonym
+ *
+ * @param unused GTK handle that is not used
+ * @param unused2 not used
+ **/
+void openDeletePseudonymDialog(GtkWidget * unused,
+                              unsigned int unused2);
+
+#endif

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/saveas.c
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/saveas.c      2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/saveas.c      2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,192 @@
+/*
+     This file is part of GNUnet
+
+     GNUnet 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, or (at your
+     option) any later version.
+
+     GNUnet 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 GNUnet; see the file COPYING.  If not, write to the
+     Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+     Boston, MA 02111-1307, USA.
+*/
+/**
+ * @file src/applications/afs/gtkui/saveas.c
+ * @brief open a save-as window.
+ * @author Christian Grothoff
+ * @author Igor Wronsky
+ **/
+
+#include "gnunet_afs_esed2.h"
+#include "helper.h"
+#include "download.h"
+#include "saveas.h"
+
+/**
+ * @brief state of the SaveAs window
+ **/
+typedef struct {
+  RootNode root;
+  GtkWidget * w;
+  char * filename;
+} SaveAs;
+
+/**
+ * Destroy the DownloadModel data structure of the
+ * saveas dialog.
+ *
+ * @param widget not used
+ * @param saveas state associated with the SaveAs window
+ **/
+static gint destroySaveAs(GtkWidget * widget,
+                         SaveAs * saveas) {
+  LOG(LOG_DEBUG, 
+      "DEBUG: destroying saveas window (%x)\n", 
+      saveas);
+  FREENONNULL(saveas->filename);
+  FREE(saveas);
+  return TRUE;
+}
+ 
+/**
+ * Get the selected filename and start downloading 
+ *
+ * @param okButton not used
+ * @param saveas state of the saveas window
+ **/
+static gint file_ok_sel(GtkWidget * okButton,
+                       SaveAs * saveas) {
+  gchar * filename;
+
+  LOG(LOG_DEBUG, 
+      "DEBUG: file_ok_sel (%x)\n", 
+      saveas);
+  filename
+    = gtk_file_selection_get_filename(GTK_FILE_SELECTION(saveas->w));
+  startDownload(filename, &saveas->root);
+  gtk_widget_destroy(saveas->w);
+  /* destroySaveAs does: "FREE(saveas);" */
+  return FALSE;
+}
+
+/**
+ * Open the window that prompts the user for the filename.  This
+ * method must open the window, copy the arguments and return.  After
+ * the method returns, the arguments passed to it will be freed, so
+ * pointer should not be retained.  The method executes during a
+ * signal handler, so a GTK lock is not required to to GUI operations.
+ * 
+ * @param root search result of the file to download
+ **/
+void openSaveAs(RootNode * root) {
+  SaveAs * saveas;
+  int i;
+  
+  saveas = MALLOC(sizeof(SaveAs));
+  memcpy(&saveas->root,
+        root,
+        sizeof(RootNode));
+  saveas->filename = NULL;
+
+  /* if the search result specified a suggested
+     filename, fill it in! */
+  switch (ntohs(root->header.major_formatVersion)) {
+  case ROOT_MAJOR_VERSION:
+    /* if it is a GNUnet directory, replace suffix '/' with ".gnd" */
+    if (0 == strcmp(root->header.mimetype,
+                   GNUNET_DIRECTORY_MIME)) {
+      saveas->filename = expandDirectoryName(root->header.filename);
+    } else 
+      saveas->filename = STRDUP(root->header.filename);
+    break;
+  case SBLOCK_MAJOR_VERSION:
+    if (0 == strcmp(&((SBlock*)root)->mimetype[0],
+                   GNUNET_DIRECTORY_MIME)) {      
+      saveas->filename = expandDirectoryName(root->header.filename);
+    } else 
+      saveas->filename = STRDUP(((SBlock*)root)->filename);
+    break;
+  default:
+    LOG(LOG_WARNING,
+       "WARNING: unknown format version: %d\n",
+       ntohs(root->header.major_formatVersion));
+    /* how did we get here!? */
+    break;
+  }
+
+  if ( (saveas->filename == NULL) ||
+       (saveas->filename[0] == 0) ||
+       (testConfigurationString("GNUNET-GTK",
+                               "ALWAYS-ASK-SAVEAS",
+                               "YES")) ) {
+    GtkWidget * window;
+
+    window = gtk_file_selection_new("save as");
+    saveas->w = window;
+        
+    /* callbacks (destroy, ok, cancel) */
+    gtk_signal_connect(GTK_OBJECT(window),
+                      "destroy",
+                      GTK_SIGNAL_FUNC(destroySaveAs),
+                      saveas);
+    /* Connect the ok_button to file_ok_sel function */
+    gtk_signal_connect(GTK_OBJECT(GTK_FILE_SELECTION(window)->ok_button),
+                      "clicked", 
+                      GTK_SIGNAL_FUNC(file_ok_sel), 
+                      saveas);
+    /* Connect the cancel_button to destroy the widget */
+    gtk_signal_connect(GTK_OBJECT(GTK_FILE_SELECTION(window)->cancel_button),
+                      "clicked", 
+                      GTK_SIGNAL_FUNC(destroyWidget), 
+                      window);
+    gtk_widget_show(window);
+  } else {
+    char * tmp;
+    char * downloadDir;
+
+    /* sanity check the filename */
+    for(i=0;i<strlen(saveas->filename);i++) {
+      switch(saveas->filename[i]) {
+      case '*':
+      case '/':
+      case '\\':
+      case '?':
+      case ':':
+       saveas->filename[i]='_';
+       break;
+      default:
+       break;
+      }
+    }
+
+    downloadDir = getConfigurationString("AFS",
+                                         "DOWNLOADDIR");
+    if(downloadDir != NULL) {
+      char * expanded;
+  
+      expanded = expandFileName(downloadDir);
+      if((SYSERR == mkdirp(expanded)))
+        LOG(LOG_WARNING, 
+                 "WARNING: Unable to create %s\n", 
+         expanded);
+      CHDIR(expanded);
+      FREE(expanded);
+    }
+    FREENONNULL(downloadDir);
+
+    tmp = expandFileName(saveas->filename);
+    FREE(saveas->filename);
+    startDownload(tmp,
+                 &saveas->root);
+    FREE(saveas);
+    FREE(tmp);
+  }
+}
+
+/* end of saveas.c */

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/saveas.h
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/saveas.h      2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/saveas.h      2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,43 @@
+/*
+     This file is part of GNUnet
+
+     GNUnet 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, or (at your
+     option) any later version.
+
+     GNUnet 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 GNUnet; see the file COPYING.  If not, write to the
+     Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+     Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file applications/afs/gtkui/saveas.h
+ * @author Christian Grothoff
+ **/
+
+#ifndef GTKUI_SAVEAS_H
+#define GTKUI_SAVEAS_H
+
+#include "platform.h"
+
+/**
+ * Open the window that prompts the user for the 
+ * filename.
+ * This method must open the window,
+ * copy the arguments and return. After the method
+ * returns, the arguments passed to it will be
+ * freed, so pointer should not be retained.
+ * The method executes during a signal handler,
+ * so a GTK lock is not required to to GUI 
+ * operations.
+ **/
+void openSaveAs(RootNode * root);
+
+#endif

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/search.c
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/search.c      2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/search.c      2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,852 @@
+/*
+     This file is part of GNUnet
+
+     GNUnet 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, or (at your
+     option) any later version.
+
+     GNUnet 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 GNUnet; see the file COPYING.  If not, write to the
+     Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+     Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file src/applications/afs/gtkui/search.c
+ * @brief box displaying search results for the gtk+ client.
+ * @author Christian Grothoff
+ * @author Igor Wronsky
+ **/
+
+#include "gnunet_afs_esed2.h"
+#include "helper.h"
+#include "download.h"
+#include "saveas.h"
+#include "search.h"
+#include "main.h"
+
+static void searchSelectAll(void);
+static void searchSelectNone(void);
+static void searchSelectByName(void);
+static void searchSelectByDesc(void);
+static void searchSelectByMime(void);
+static void searchClose(void);
+static void searchDownloadSelected(void); 
+
+static GtkItemFactoryEntry searchWindowMenu[] = {
+  { "/Select all",            NULL,   searchSelectAll,      0, "<Item>" },
+  { "/Unselect all",          NULL,   searchSelectNone,     0, "<Item>" },
+  { "/sep1",                  NULL,   NULL,                 0, "<Separator>" },
+  { "/Select by filename",    NULL,   searchSelectByName,   0, "<Item>" },
+  { "/Select by description", NULL,   searchSelectByDesc,   0, "<Item>" },
+  { "/Select by mimetype",    NULL,   searchSelectByMime,   0, "<Item>" },
+  { "/sep2",                  NULL,   NULL,                 0, "<Separator>" },
+  { "/Download selected",     NULL,   searchDownloadSelected,  0, "<Item>" },
+  { "/sep3",                  NULL,   NULL,                 0, "<Separator>" },
+  { "/Abort search",          NULL,   searchClose,          0, "<Item>" }
+};
+
+static gint searchWindowMenuItems
+  = sizeof (searchWindowMenu) / sizeof (searchWindowMenu[0]);
+
+/**
+ * Selects all search results from the current search page.
+ **/
+static void searchSelectAll(void)
+{
+  gint pagenr;
+  GtkWidget * page;
+  ListModel * model;
+
+  pagenr = gtk_notebook_get_current_page(notebook);
+  if(pagenr<0)
+    return;
+  page = gtk_notebook_get_nth_page(notebook,
+                                   pagenr);
+  if(!page)
+    return;
+  model = gtk_object_get_data(GTK_OBJECT(page),
+                              "MODEL");
+  if(model)
+    gtk_clist_select_all(GTK_CLIST(model->search_result_list));
+}
+
+/**
+ * Unselects all search results from the current search page.
+ **/
+static void searchSelectNone(void)
+{
+  gint pagenr;
+  GtkWidget * page;
+  ListModel * model;
+
+  pagenr = gtk_notebook_get_current_page(notebook);
+  if(pagenr<0)
+    return;
+  page = gtk_notebook_get_nth_page(notebook,
+                                   pagenr);
+  if(!page)
+    return;
+  model = gtk_object_get_data(GTK_OBJECT(page),
+                              "MODEL");
+  if(model)
+    gtk_clist_unselect_all(GTK_CLIST(model->search_result_list));
+}
+
+static void selectByCallback(GtkWidget * dummy,
+                            GtkWidget * entry) {
+  gchar * nameString;
+  gint pagenr;
+  GtkWidget * page;
+  GtkCList * clist;
+  ListModel * model;
+  int i;
+  int j; 
+  int * data;
+  int column;
+
+  data = gtk_object_get_data(GTK_OBJECT(entry),
+                            "COLUMNID");
+  column = *data;
+  FREE(data);
+
+  nameString = gtk_entry_get_text(GTK_ENTRY(entry));
+  if(nameString == NULL) {
+    guiMessage("nameString == NULL\n");
+    return;
+  }
+  if(nameString[0] == 0) {
+    guiMessage("Naah...\n");
+    return;
+  }
+ 
+  pagenr = gtk_notebook_get_current_page(notebook);
+  if(pagenr<0)
+    return;
+  page = gtk_notebook_get_nth_page(notebook,
+                                   pagenr);
+  if(!page)                        
+    return;
+  model = gtk_object_get_data(GTK_OBJECT(page),
+                              "MODEL");
+  if(model) {
+    gchar * tmp;
+    gchar * haystack;
+    gchar * needle;  
+    int hits = 0;
+
+    needle = STRDUP(nameString);
+    for(i=0;i<strlen(needle);i++)
+      needle[i]=tolower(needle[i]);
+
+    clist = GTK_CLIST(model->search_result_list);
+
+    gtk_clist_freeze(clist);
+    for(i=0;i<clist->rows;i++) {
+      gtk_clist_get_text(clist,
+                         i,
+                         column,
+                         &tmp);
+      haystack = STRDUP(tmp);
+      for(j=0;j<strlen(haystack);j++)
+        haystack[j]=tolower(haystack[j]);
+ 
+      if(strstr(haystack, needle)!=NULL) {
+         gtk_clist_select_row(clist,
+                              i,
+                              1);
+         hits++;
+      }
+      FREE(haystack);
+    }
+     
+    gtk_clist_thaw(clist);
+    if(hits==0)
+      guiMessage("No matches...");
+    FREE(needle);
+  }
+}
+
+
+static void searchSelectByColumn(int column)
+{
+  GtkWidget *window;
+  GtkWidget *vbox;
+  GtkWidget *label; 
+  GtkWidget *entry;
+  GtkWidget *button;
+  int * data;
+
+  data = MALLOC(sizeof(int));
+  *data = column;
+
+  window = gtk_window_new(GTK_WINDOW_DIALOG);
+  vbox = gtk_vbox_new(FALSE, 0);
+  gtk_container_add(GTK_CONTAINER(window),
+                    vbox);
+  label = gtk_label_new("Pattern? ");
+  gtk_container_add (GTK_CONTAINER(vbox),
+                     label);
+  entry = gtk_entry_new();
+  gtk_object_set_data(GTK_OBJECT(entry),
+                     "COLUMNID",
+                     data);
+  gtk_entry_set_text(GTK_ENTRY(entry), 
+                    "");
+  gtk_signal_connect(GTK_OBJECT(entry),
+                    "activate",
+                    GTK_SIGNAL_FUNC(selectByCallback),
+                    entry);
+  gtk_signal_connect(GTK_OBJECT(entry),
+                    "activate",
+                    GTK_SIGNAL_FUNC(destroyWidget),
+                    window);
+  gtk_container_add(GTK_CONTAINER(vbox),
+                   entry);
+  button = gtk_button_new_with_label("Ok");
+  gtk_container_add(GTK_CONTAINER(vbox),
+                   button);
+  gtk_signal_connect(GTK_OBJECT(button),
+                    "clicked",
+                    GTK_SIGNAL_FUNC(selectByCallback),
+                    entry);
+  gtk_signal_connect(GTK_OBJECT(button),
+                    "clicked",
+                    GTK_SIGNAL_FUNC(destroyWidget),
+                    window);
+  gtk_window_set_position(GTK_WINDOW(window),
+                         GTK_WIN_POS_MOUSE);
+  gtk_widget_show_all(window);
+  gtk_widget_grab_focus(entry);
+
+}
+
+static void searchSelectByName(void)
+{
+  searchSelectByColumn(2);
+}
+
+static void searchSelectByDesc(void)
+{
+  searchSelectByColumn(0);
+}
+
+static void searchSelectByMime(void) {
+  searchSelectByColumn(6);
+}
+
+/**
+ * Remove the active page from the search results notebook.
+ * The respective search will be stopped as well
+ * (by a callback assigned to the page earlier on).
+ **/
+void searchClose(void) {
+  gint pagenr;
+  
+  pagenr = gtk_notebook_get_current_page(notebook);
+  gtk_notebook_remove_page(notebook, pagenr);
+  /* Need to refresh the widget --
+     This forces the widget to redraw itself. */
+  gtk_widget_draw(GTK_WIDGET(notebook), NULL);
+}
+
+/**
+ * This method is called whenever the user clicks the
+ * download button.  It opens the "save-as" dialog.
+ *
+ * @param widget not used
+ * @param listModel Data related to the search
+ **/
+static void downloadGTK(GtkWidget * widget,
+                       ListModel * listModel);
+
+
+static void searchDownloadSelected(void) {
+  gint pagenr;
+  GtkWidget * page;
+  ListModel * model;
+
+  pagenr = gtk_notebook_get_current_page(notebook);
+  if(pagenr<0)
+    return;
+  page = gtk_notebook_get_nth_page(notebook,
+                                   pagenr);
+  if(!page)
+    return;
+  model = gtk_object_get_data(GTK_OBJECT(page),
+                              "MODEL");
+  if(model)
+    downloadGTK(NULL, model);
+}
+
+/**
+ * This method is called whenever the user clicks the
+ * download button.  It opens the "save-as" dialog.
+ *
+ * @param widget not used
+ * @param listModel Data related to the search
+ **/
+static void downloadGTK(GtkWidget * widget,
+                       ListModel * listModel) {  
+  gint row;
+  GList * tmp;
+  
+  tmp=GTK_CLIST(listModel->search_result_list)->selection;
+         
+  if ( !tmp ) {
+    guiMessage("Nothing selected!\n");
+    return;
+  }
+    
+  gtk_clist_freeze(GTK_CLIST(listModel->search_result_list));
+
+  /* download all selected entries */
+  while (tmp) {
+    RootNode * rootNode;
+    
+    row = (int)tmp->data;
+    tmp = tmp->next;
+    rootNode = (RootNode *)
+      gtk_clist_get_row_data(GTK_CLIST(listModel->search_result_list),
+                             row);
+    openSaveAs(rootNode);
+  
+    /* Remove entry from search results.  Yes,
+       if the user cancel's the download, the
+       entry does not re-appear.  That's intended,
+       after all, if you cancel, it's probably because
+       it took too long to download anyway... 
+       If you really need it back, just search again! */
+    gtk_clist_remove(GTK_CLIST(listModel->search_result_list),
+                    row);
+
+    FREE(rootNode);
+  }
+  gtk_clist_thaw(GTK_CLIST(listModel->search_result_list));
+}
+
+static void freeSearchList(GtkWidget * dummy,
+                          GtkCList * clist) {
+  int i;
+
+  /* Free clist stored rootNode data */
+  gtk_clist_freeze(clist);
+  for(i=0;i<clist->rows;i++) {
+    RootNode * rootNode;
+    
+    rootNode = (RootNode *)
+      gtk_clist_get_row_data(clist,
+                             i);
+    FREE(rootNode);
+  }
+  gtk_clist_clear(clist);
+  gtk_clist_thaw(clist);
+}
+
+static gint doDisplayResult(SaveCall *call) {
+  int newrow;
+  GtkWidget *search_result_list = ((Result *) call->args)->search_result_list;
+
+  gtk_clist_freeze(GTK_CLIST(search_result_list));
+  newrow = gtk_clist_append(GTK_CLIST(search_result_list),
+                                     ((Result *) call->args)->results);
+  gtk_clist_set_row_data(GTK_CLIST(search_result_list),
+                                        newrow,
+                                          ((Result *) call->args)->rootCopy);
+  gtk_clist_thaw(GTK_CLIST(search_result_list));
+
+  gtkSaveCallDone(call->sem);
+  
+  return FALSE;
+}
+
+/**
+ * Display results.  This is a callback from receiveResults that is
+ * called on every new result.
+ *
+ * @param rootNode Data about a file
+ * @param model Data related to the search
+ **/
+void displayResultGTK(RootNode * rootNode,
+                     ListModel * model) {
+  RootNode * rootCopy;
+  SBlock * sb;
+  gchar * results[7];
+  Result result;
+  int i;
+  
+  if(model->doTerminate == YES)
+    return;
+
+  rootCopy = MALLOC(sizeof(RootNode));
+  memcpy(rootCopy,
+         rootNode,
+        sizeof(RootNode));
+
+  switch (ntohs(rootNode->header.major_formatVersion)) {
+  case ROOT_MAJOR_VERSION:
+    /* ensure well-formed strings */
+    rootNode->header.description[MAX_DESC_LEN-1] = 0;
+    rootNode->header.filename[MAX_FILENAME_LEN-1] = 0;
+    rootNode->header.mimetype[MAX_MIMETYPE_LEN-1] = 0;
+    
+    results[0] = STRDUP(rootNode->header.description);
+    results[1] = MALLOC(32);
+    sprintf(results[1],
+           "%u", 
+           (unsigned int) ntohl(rootNode->header.fileIdentifier.file_length));
+    if ( (0 == strcmp(rootNode->header.mimetype,
+                     GNUNET_DIRECTORY_MIME)) &&
+        (rootNode->header.filename[strlen(rootNode->header.filename)-1] != 
DIR_SEPARATOR) ) {
+      results[2] = MALLOC(strlen(rootNode->header.filename)+2);
+      strcpy(results[2], rootNode->header.filename);
+      strcat(results[2], "/");      
+    } else
+      results[2] = STRDUP(rootNode->header.filename);
+    results[3] = MALLOC(12);
+    sprintf(results[3], 
+           "%X", 
+           (unsigned int) ntohl(rootNode->header.fileIdentifier.crc));
+    results[4] = MALLOC(sizeof(HexName));
+    hash2hex(&rootNode->header.fileIdentifier.chk.query,
+            (HexName*) results[4]);
+    results[5] = MALLOC(sizeof(HexName));
+    hash2hex(&rootNode->header.fileIdentifier.chk.key,
+            (HexName*) results[5]);
+    results[6] = STRDUP(rootNode->header.mimetype);
+    break;
+  case SBLOCK_MAJOR_VERSION:
+    sb = (SBlock*) rootNode;
+    sb->description[MAX_DESC_LEN-1] = 0;
+    sb->filename[MAX_FILENAME_LEN/2-1] = 0;
+    sb->mimetype[MAX_MIMETYPE_LEN/2-1] = 0;
+    
+    results[0] = STRDUP(rootNode->header.description);
+    results[1] = MALLOC(32);
+    sprintf(results[1], 
+           "%u", 
+           (unsigned int) ntohl(sb->fileIdentifier.file_length));
+    results[2] = STRDUP(sb->filename);
+    results[3] = MALLOC(32);
+    sprintf(results[3],
+           "%u", 
+           (unsigned int) ntohl(sb->fileIdentifier.crc));
+    results[4] = MALLOC(sizeof(HexName));
+    hash2hex(&sb->fileIdentifier.chk.query,
+            (HexName*) results[4]);
+    results[5] = MALLOC(sizeof(HexName));
+    hash2hex(&sb->fileIdentifier.chk.key,
+            (HexName*) results[5]);
+    results[6] = STRDUP(sb->mimetype);    
+    break;
+  default:
+    LOG(LOG_ERROR,
+       "ERROR: search result received of unsupported type %d\n",
+       ntohs(rootNode->header.major_formatVersion));
+    return;
+  }
+  result.search_result_list = model->search_result_list;
+  result.rootCopy = rootCopy;
+  memcpy(&result.results, &results, sizeof(results));
+  gtkSaveCall((GtkFunction) doDisplayResult, &result);
+  if (model->skipMenuRefresh != YES)
+    refreshMenuSensitivity();
+
+  for (i=0;i<7;i++)
+    FREE(results[i]);
+}
+
+/**
+ * Struct to pass a couple of arguments to a new
+ * thread "receiveResults_".
+ **/
+typedef struct {
+  char * searchString;
+  ListModel * model;
+} _receiveResultArgs_;
+
+/**
+ * Should the receive thread abort? (has the window been closed?)
+ * This is a callback for the receiveResults method (since
+ * not every "error" on the socket corresponds to a closed
+ * window!).
+ *
+ * @return YES if we should abort
+ **/
+int testTermination(ListModel * model) {
+  return model->doTerminate;
+}
+
+/**
+ * Main method of the receiveResults-threads.
+ * Runs the receiveResults method and frees some
+ * data structures once the search is aborted.
+ * See also "stopSearch".
+ **/
+static void * receiveResults_(_receiveResultArgs_ * args) {
+  char ** keywords;
+  int num_Words;
+  int inWord;
+  char * c;
+
+
+  num_Words = 0;
+  for (inWord = 0, c = args->searchString; *c != '\0'; ++c) {
+    if (isspace(*c)) {
+      inWord = 0;
+    } else if (!inWord) {
+      inWord = 1;
+      ++num_Words;
+    }
+  }
+
+  if (num_Words == 0) {
+    FREENONNULL(args->searchString);
+    FREE(args);
+    LOG(LOG_FAILURE, "No keywords specified!\n");
+    return NULL;
+  }
+  keywords = MALLOC(num_Words * sizeof(char *));
+  num_Words = 0;
+  for (inWord = 0, c = args->searchString; *c != '\0'; ++c) {
+    if (isspace(*c)) {
+      inWord = 0;
+      *c = '\0';
+    } else if (!inWord) {
+      keywords[num_Words] = c;
+      inWord = 1;
+      ++num_Words;
+    }
+  }
+  searchRBlock(args->model->SEARCH_socket_,
+              keywords,
+              num_Words,
+              (SearchResultCallback) &displayResultGTK,
+              args->model,
+              (TestTerminateThread)&testTermination,
+              args->model);  
+  FREE(args->searchString);
+  FREE(keywords);
+  FREE(args);
+  return NULL;
+}
+
+/**
+ * The main method of the search-thread.
+ *
+ * @param searchString What to look for
+ * @param model Data related to the search
+ * @return OK on success, SYSERR on error
+ **/
+static int startSearchThread(char * searchString,
+                            ListModel * model) {
+  _receiveResultArgs_ * receiveArgs;
+
+  receiveArgs = MALLOC(sizeof(_receiveResultArgs_));
+  receiveArgs->searchString = STRDUP(searchString);
+  receiveArgs->model = model;  
+  if (0 != PTHREAD_CREATE(&model->thread,
+                         (PThreadMain) &receiveResults_,
+                         receiveArgs,
+                         16 * 1024)) {
+    errexit("FATAL: could not create receive thread (%s)!\n",
+           STRERROR(errno));    
+  }
+  return OK;
+}
+
+/**
+ * Cron job that stops the search.
+ */
+static void stopSearch_(ListModel * model) {
+  void * unused;
+
+  switch (model->type) {
+  case LM_TYPE_DIRECTORY:
+    break;
+  case LM_TYPE_SEARCH:
+    /* the terminated search thread ups this semaphore
+       once it is done and we can free data structures */
+    model->doTerminate = YES;
+    
+    /* this signals the download thread to terminate */
+    closeSocketTemporarily(model->SEARCH_socket_);
+    
+    /* wait for download thread signal */
+    PTHREAD_JOIN(&model->thread,
+                &unused);
+    
+    /* Now we can finally free the shared data structures.
+       Note that the terminated search thread freed 
+       some of the memory that was allocated in
+       startSearchThread (see receiveResults_)
+       we free the rest. */
+    releaseClientSocket(model->SEARCH_socket_);  
+    break;
+  case LM_TYPE_NSSEARCH:
+    model->doTerminate = YES;
+    /* this signals the download thread to terminate */
+    closeSocketTemporarily(model->SEARCH_socket_);    
+    /* wait for download thread signal */
+    PTHREAD_JOIN(&model->thread,
+                &unused);
+    releaseClientSocket(model->SEARCH_socket_);
+    break;
+  default:
+    LOG(LOG_ERROR, 
+       "ERROR: Unknown model->type %d\n",
+       model->type);
+    break;
+  }
+  if (model->sem != NULL)
+    SEMAPHORE_UP(model->sem);
+  FREE(model);  
+}
+
+/**
+ * Stop the search thread and free the model.  This method MUST always
+ * be called when the search ends, either because the search was
+ * aborted or because gnunet-gtk exists as a whole.  
+ *
+ * @param widget the window (not used)
+ * @param model the model with the socket 
+ **/
+static void stopSearch(GtkWidget * widget,
+                      ListModel * model) { 
+  Semaphore * cs;
+  LOG(LOG_DEBUG, 
+      "DEBUG: stopSearch called\n");
+  /* this must be done as a cron-job, since otherwise
+     it may deadlock (this is called from the
+     gtk event thread, and cron may be waiting for
+     the gtk event lock, so we can't delete a cron
+     job in this thread */
+  model->doTerminate = YES;
+  cs = SEMAPHORE_NEW(0);
+  model->sem = cs;
+  addCronJob((CronJob) &stopSearch_,
+            0, 0, model);
+  /* this is always the gtk-thread, so we must
+     not block; instead, run gtk-savecalls! */
+  while (SYSERR == SEMAPHORE_DOWN_NONBLOCKING(cs)) 
+    gtkRunSomeSaveCalls();
+  SEMAPHORE_FREE(cs);
+}
+
+/**
+ * Changes the current sort column and sorts the list.
+ **/
+static void sort_column_callback(GtkCList * clist,
+                                gint column,
+                                gpointer data) {
+  static int sortOrder[7]={0,0,0,0,0,0,0};
+
+  sortOrder[column]^=1;
+
+  if(sortOrder[column]==1)
+    gtk_clist_set_sort_type(clist,
+                            GTK_SORT_ASCENDING);
+  else
+    gtk_clist_set_sort_type(clist,
+                            GTK_SORT_DESCENDING);
+
+  /* Sort all columns as strings except 1 (size) */
+  if (column == 1)
+    gtk_clist_set_compare_func(clist, 
+                              (GtkCListCompareFunc)numericComp);
+  else
+    gtk_clist_set_compare_func(clist, 
+                              (GtkCListCompareFunc)alphaComp);  
+  gtk_clist_set_sort_column(clist, column);
+  gtk_clist_freeze(clist);
+  gtk_clist_sort(clist);
+  gtk_clist_thaw(clist);
+}
+
+static gint doInitSearchResultList(SaveCall *call) {
+  ListModel *model;
+  GtkWidget * scrolled_window;
+  GtkWidget * button;
+  GtkWidget * box;
+  GtkWidget * search_result_list;
+  GtkWidget * menu;
+  GtkItemFactory * popupFactory;
+  static gchar * descriptions[] = {
+    "Description",
+    "Size",
+    "Filename",
+    "CRC",
+    "HASH1",
+    "HASH2",
+    "Mimetype"
+  };
+  /* widths of the colums in the search results */
+  static int widths[] = {
+    470, 70, 200, 40, 40, 40, 200,
+  };
+  int i;
+  
+  model = ((InitResultList *)call->args)->model;
+
+  box = gtk_vbox_new(FALSE, 0); 
+  /* scrolled window for the list */
+  scrolled_window = gtk_scrolled_window_new(NULL, NULL);
+  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
+                                 GTK_POLICY_AUTOMATIC, 
+                                GTK_POLICY_ALWAYS);
+  gtk_box_pack_start(GTK_BOX(box), 
+                    scrolled_window, 
+                    TRUE,
+                    TRUE, 
+                    0);
+  gtk_widget_show(scrolled_window);
+  
+  /* result set list */
+  search_result_list 
+    = gtk_clist_new_with_titles(7, descriptions);
+  model->search_result_list 
+    = search_result_list;
+  gtk_signal_connect(GTK_OBJECT(search_result_list), 
+                     "destroy",
+                    GTK_SIGNAL_FUNC(freeSearchList), 
+                    search_result_list); 
+  gtk_container_add(GTK_CONTAINER(scrolled_window), 
+                   search_result_list);
+  gtk_widget_show(search_result_list);
+
+  gtk_clist_set_selection_mode
+    (GTK_CLIST(search_result_list),
+     GTK_SELECTION_EXTENDED);
+  /* set passive titles as default */
+  gtk_clist_column_titles_passive
+    (GTK_CLIST(search_result_list));
+
+  /* allow sorting by description, size, filename and mimetype */
+  gtk_clist_column_title_active(GTK_CLIST(search_result_list),
+                               0);
+  gtk_clist_column_title_active(GTK_CLIST(search_result_list),
+                               1);
+  gtk_clist_column_title_active(GTK_CLIST(search_result_list),
+                               2);
+  gtk_clist_column_title_active(GTK_CLIST(search_result_list),
+                               6);
+  gtk_signal_connect(GTK_OBJECT(search_result_list),
+                    "click-column",
+                    GTK_SIGNAL_FUNC(sort_column_callback),
+                    NULL);
+
+  /* description left, size right justification */
+  gtk_clist_set_column_justification
+    (GTK_CLIST(search_result_list),
+     0,
+     GTK_JUSTIFY_LEFT);
+  gtk_clist_set_column_justification
+    (GTK_CLIST(search_result_list),
+     1,
+     GTK_JUSTIFY_RIGHT);
+
+  /* set column widths */
+  for (i=0;i<7;i++)
+    gtk_clist_set_column_width(GTK_CLIST(search_result_list), 
+                              i,
+                              widths[i]);
+
+  /* download button */
+  button = gtk_button_new_with_label("Download");
+  gtk_signal_connect (GTK_OBJECT(button), 
+                     "clicked",
+                     GTK_SIGNAL_FUNC(downloadGTK), 
+                     model);
+  gtk_box_pack_start(GTK_BOX(box), 
+                    button,
+                    FALSE,
+                    FALSE,
+                    0);
+  gtk_widget_show(button);
+  /* generic: on delete request, just do it (always OK) */
+  gtk_signal_connect(GTK_OBJECT(scrolled_window), 
+                     "delete_event",
+                     GTK_SIGNAL_FUNC(deleteEvent), 
+                     NULL);
+  /* when we are destroyed (e.g. search aborted), stop the search thread */
+  gtk_signal_connect(GTK_OBJECT(scrolled_window), 
+                     "destroy",
+                     GTK_SIGNAL_FUNC(stopSearch), 
+                     model); 
+  /* store a pointer to the model for main-menu access */
+  gtk_object_set_data(GTK_OBJECT(box),
+                     "MODEL",
+                     model);
+
+  /* add a right button popup menu */
+  popupFactory = gtk_item_factory_new (GTK_TYPE_MENU, "<main>",
+                                       NULL);
+  gtk_item_factory_create_items(popupFactory,
+                                searchWindowMenuItems,
+                                searchWindowMenu,
+                                NULL);
+  menu = gtk_item_factory_get_widget (popupFactory, "<main>");
+  gtk_signal_connect(GTK_OBJECT(box),
+                     "event",
+                     GTK_SIGNAL_FUNC(popupCallback),
+                     menu);
+
+  ((InitResultList *)call->args)->ret = box;
+
+  gtkSaveCallDone(call->sem);
+  
+  return FALSE;
+}
+
+GtkWidget * initializeSearchResultList(ListModel * model) {
+  InitResultList init;
+  
+  init.model = model;
+  gtkSaveCall((GtkFunction) doInitSearchResultList,
+             &init);
+  return init.ret;
+}
+
+/** 
+ * Returns a box containing the search results list.
+ **/
+GtkWidget * getSearchWindow(gchar * title) {
+  GtkWidget * box;
+  ListModel * model;  
+  int ok;
+
+  model = (ListModel*) MALLOC(sizeof(ListModel));
+  model->sem = NULL;
+  model->type = LM_TYPE_SEARCH;
+  model->doTerminate = NO;
+  model->skipMenuRefresh = NO;
+  model->SEARCH_socket_
+    = getClientSocket();
+  if (model->SEARCH_socket_ == NULL) {
+    FREE(model);
+    return NULL;
+  }
+
+  box = initializeSearchResultList(model);
+
+  /* start searching */
+  ok = startSearchThread(title,
+                        model);
+  if (ok == SYSERR) {
+    releaseClientSocket(model->SEARCH_socket_);
+    gtk_widget_destroy(box);
+    FREE(model);
+    return NULL;
+  } else {
+    /* return complete box such that it gets displayed */
+    return box;
+  }
+}
+
+/* end of search.c */

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/search.h
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/search.h      2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/search.h      2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,101 @@
+/*
+     This file is part of GNUnet
+
+     GNUnet 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, or (at your
+     option) any later version.
+
+     GNUnet 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 GNUnet; see the file COPYING.  If not, write to the
+     Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+     Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file applications/afs/gtkui/search.h
+ * @author Christian Grothoff
+ **/
+
+#ifndef GTKUI_SEARCH_H
+#define GTKUI_SEARCH_H
+
+/**
+ * Data for a search process
+ **/
+typedef struct {
+  int type;
+  GtkWidget * search_result_list;  
+  int doTerminate;
+  GNUNET_TCP_SOCKET * SEARCH_socket_;
+  PTHREAD_T thread; /* search thread */
+  int skipMenuRefresh; /* don't refresh gtk menus (its slow)? (YES/NO) */
+  Semaphore * sem; /* shutdown signaling */
+} ListModel;
+
+typedef struct {
+  GtkWidget *search_result_list;
+  RootNode *rootCopy;
+  gchar * results[7];
+} Result;
+
+typedef struct {
+  ListModel *model;
+  GtkWidget *ret;
+} InitResultList;
+
+#define LM_TYPE_SEARCH 1
+#define LM_TYPE_DIRECTORY 2
+#define LM_TYPE_NSSEARCH 3
+
+/**
+ * Get the window with the search results.
+ **/
+GtkWidget * getSearchWindow(gchar * searchString);
+
+GtkWidget * initializeSearchResultList(ListModel * model);
+
+/**
+ * Should the receive thread abort? (has the window been closed?)
+ * This is a callback for the receiveResults method (since
+ * not every "error" on the socket corresponds to a closed
+ * window!).
+ *
+ * @return YES if we should abort
+ **/
+int testTermination(ListModel * model);
+
+/**
+ * Display results.  This is a callback from receiveResults that is
+ * called on every new result.
+ *
+ * @param rootNode Data about a file
+ * @param model Data related to the search
+ **/
+void displayResultGTK(RootNode * rootNode,
+                     ListModel * model);
+ 
+/**
+ * Some search list row has been unselected
+ **/
+void unselect_row_callbackGTK(GtkWidget *widget,
+                             gint row,
+                             gint column,
+                             GdkEventButton *event,
+                             gpointer data);
+
+/**
+ * User has selected some search list row
+ **/
+void select_row_callbackGTK(GtkWidget *widget,
+                           gint row,
+                           gint column,
+                           GdkEventButton *event,
+                           gpointer data);
+
+#endif

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/statistics.c
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/statistics.c  2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/statistics.c  2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,963 @@
+/*
+     This file is part of GNUnet
+
+     GNUnet 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, or (at your
+     option) any later version.
+
+     GNUnet 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 GNUnet; see the file COPYING.  If not, write to the
+     Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+     Boston, MA 02111-1307, USA.
+
+     Portions of this code were adopted from the
+     gnome-system-monitor v2.0.5, (C) 2001 Kevin Vandersloot
+
+
+     Todo:
+     - add any more StatEntries, update menu accordingly.
+*/
+
+#include "gnunet_afs_esed2.h"
+#include "statistics.h"
+#include "helper.h"
+#include "main.h"
+#include <glib.h>
+
+#define UPDATE_INTERVAL (30 * cronSECONDS)
+
+typedef struct {
+  char * statName;
+  long long value;
+  long long lvalue;
+  cron_t delta;
+} StatPair;
+
+static StatPair * lastStatValues;
+static unsigned int lsv_size;
+static cron_t lastUpdate;
+static Mutex lock;
+
+static void updateStatValues(GNUNET_TCP_SOCKET * sock) {
+  STATS_CS_MESSAGE * statMsg;
+  CS_HEADER csHdr;
+  unsigned int count;
+  unsigned int i;
+  int j;
+  int mpos;
+  int found;
+  char * optName;
+  cron_t now;
+  cron_t prev;
+    
+  cronTime(&now);
+  MUTEX_LOCK(&lock);
+  if (now - lastUpdate < UPDATE_INTERVAL) { 
+    MUTEX_UNLOCK(&lock);
+    return;
+  }
+  prev = lastUpdate;
+  lastUpdate = now;
+  csHdr.size 
+    = htons(sizeof(CS_HEADER));
+  csHdr.tcpType
+    = htons(STATS_CS_PROTO_GET_STATISTICS);
+  if (SYSERR == writeToSocket(sock,
+                             &csHdr)) {
+    MUTEX_UNLOCK(&lock);
+    return;
+  }
+  statMsg 
+    = MALLOC(MAX_BUFFER_SIZE);
+  statMsg->totalCounters 
+    = htonl(1); /* to ensure we enter the loop */
+  count = 0;
+  while ( count < ntohl(statMsg->totalCounters) ) {
+    if (SYSERR == readFromSocket(sock,
+                                (CS_HEADER**)&statMsg)) {
+      FREE(statMsg);
+      MUTEX_UNLOCK(&lock);
+      return;    
+    }
+    if (ntohs(statMsg->header.size) < sizeof(STATS_CS_MESSAGE)) {
+      LOG(LOG_WARNING,
+         "WARNING: received malformed stats message (%d < %d)\n",
+         ntohs(statMsg->header.size), 
+         sizeof(STATS_CS_MESSAGE) );
+      break;
+    }
+    mpos = sizeof(unsigned long long) * ntohl(statMsg->statCounters);
+    if ( ((char*)(((STATS_CS_MESSAGE_GENERIC*)statMsg)->values))
+        [ntohs(statMsg->header.size) - sizeof(STATS_CS_MESSAGE) - 1] != '\0') {
+      LOG(LOG_WARNING,
+         "WARNING: received malformed stats message (does not end with 
0-termination)\n");
+      break;
+    }      
+    for (i=0;i<ntohl(statMsg->statCounters);i++) {
+      optName = &((char*)(((STATS_CS_MESSAGE_GENERIC*)statMsg)->values))[mpos];
+      if ( (mpos > ntohs(statMsg->header.size) - sizeof(STATS_CS_MESSAGE)) ||
+          (mpos + strlen(optName) + 1 > 
+           ntohs(statMsg->header.size) - sizeof(STATS_CS_MESSAGE)) ) {
+       LOG(LOG_WARNING,
+           "WARNING: received malformed stats message (%d > %d)\n",
+           mpos+strlen(optName)+1,
+           ntohs(statMsg->header.size) - sizeof(STATS_CS_MESSAGE));
+       break; /* out of bounds! */      
+      }
+      found = -1;
+      for (j=0;j<lsv_size;j++) 
+       if (0 == strcmp(optName,
+                       lastStatValues[j].statName))
+         found = j;
+      if (found == -1) {
+       found = lsv_size;
+       GROW(lastStatValues,
+            lsv_size,
+            lsv_size+1);
+       lastStatValues[found].statName
+         = STRDUP(optName);
+      }
+      lastStatValues[found].lvalue
+       = lastStatValues[found].value;
+      lastStatValues[found].value
+       = ntohll(((STATS_CS_MESSAGE_GENERIC*)statMsg)->values[i]);      
+      lastStatValues[found].delta
+       = now-prev;
+      mpos += strlen(optName)+1;
+    }    
+    count += ntohl(statMsg->statCounters);
+  } /* end while */
+  FREE(statMsg);
+  MUTEX_UNLOCK(&lock);
+}
+
+static int getStatValue(long long * value,
+                       long long * lvalue,
+                       cron_t * dtime,
+                       GNUNET_TCP_SOCKET * sock,
+                       const char * optName) {
+  unsigned int i;
+
+  *value = 0;
+  if (lvalue != NULL)
+    *lvalue = 0;
+  updateStatValues(sock);
+  MUTEX_LOCK(&lock);
+  for (i=0;i<lsv_size;i++) {
+    if (0 == strcmp(optName,
+                   lastStatValues[i].statName)) {
+      *value = lastStatValues[i].value;
+      if (lvalue != NULL)
+       *lvalue = lastStatValues[i].lvalue;
+      if (dtime != NULL)
+       *dtime = lastStatValues[i].delta;
+      MUTEX_UNLOCK(&lock);
+      return OK;
+    }      
+  }
+  MUTEX_UNLOCK(&lock);
+  return SYSERR;
+}
+
+/**
+ * Callback function to obtain the latest stats
+ * data for this stat display.
+ */
+typedef int (*UpdateData)(GNUNET_TCP_SOCKET * sock,
+                         const void * closure,
+                         gfloat ** data);
+
+static int getConnectedNodesStat(GNUNET_TCP_SOCKET * sock,
+                                const void * closure,
+                                gfloat ** data) {
+  long long val;
+  char * cmh;
+  long long cval;
+
+  cmh = getConfigurationOptionValue(sock,
+                                   "gnunetd",
+                                   "connection-max-hosts");
+  if (cmh == NULL)
+    return SYSERR;
+  cval = atoll(cmh);
+  FREE(cmh);
+  if (OK != getStatValue(&val,
+                        NULL,
+                        NULL,
+                        sock,
+                        "# currently connected nodes")) 
+    return SYSERR;
+  data[0][0] = 0.8 * val / cval;
+  return OK;
+}
+
+static int getCPULoadStat(GNUNET_TCP_SOCKET * sock,
+                         const void * closure,
+                         gfloat ** data) {
+  long long val;
+
+  if (OK != getStatValue(&val,
+                        NULL,
+                        NULL,
+                        sock,
+                        "% of allowed cpu load"))
+    return SYSERR;
+  data[0][0] = val / 125.0;
+  return OK;
+}
+
+static int getTrafficRecvStats(GNUNET_TCP_SOCKET * sock,
+                              const void * closure,
+                              gfloat ** data) {
+  long long total;
+  long long noise;
+  long long content;
+  long long queries;
+  long long ltotal;
+  long long lnoise;
+  long long lcontent;
+  long long lqueries;
+  long long band;
+  long long tmp;
+  long long ltmp;
+  cron_t dtime;
+  char * available;
+
+  MUTEX_LOCK(&lock);
+  if (OK != getStatValue(&total,       
+                        &ltotal,
+                        &dtime,
+                        sock,
+                        "# bytes decrypted"))
+    return SYSERR;
+  if (OK != getStatValue(&noise,
+                        &lnoise,
+                        NULL,
+                        sock,
+                        "# bytes of noise received"))
+    return SYSERR;
+  content = lcontent = 0;
+  if (OK == getStatValue(&tmp,
+                        &ltmp,
+                        NULL,
+                        sock,
+                        "# bytes received of type 17")) {
+    content += tmp;
+    lcontent += ltmp;
+  }
+  if (OK == getStatValue(&tmp,
+                        &ltmp,
+                        NULL,
+                        sock,
+                        "# bytes received of type 18")) {
+    content += tmp;
+    lcontent += ltmp;
+  }
+  if (OK == getStatValue(&tmp,
+                        &ltmp,
+                        NULL,
+                        sock,
+                        "# bytes received of type 20")) {
+    content += tmp;
+    lcontent += ltmp;
+  }
+  queries = lqueries = 0;
+  if (OK == getStatValue(&tmp,
+                        &ltmp,
+                        NULL,
+                        sock,
+                        "# bytes received of type 16")) {
+    queries += tmp;
+    lqueries += ltmp;
+  }
+  if (OK == getStatValue(&tmp,
+                        &ltmp,
+                        NULL,
+                        sock,
+                        "# bytes received of type 19")) {
+    queries += tmp;
+    lqueries += ltmp;
+  }
+  
+  MUTEX_UNLOCK(&lock);
+  available = getConfigurationOptionValue(sock,
+                                         "LOAD",
+                                         "MAXNETDOWNBPSTOTAL");
+  if (available == NULL)
+    return SYSERR; 
+  band = atoll(available) * dtime / cronSECONDS;
+  FREE(available);
+  total -= ltotal;
+  noise -= lnoise;
+  queries -= lqueries;
+  content -= lcontent;
+  if (band <= 0) {
+    data[0][0] = 0.0;
+    data[0][1] = 0.0;
+    data[0][2] = 0.0;
+    data[0][3] = 0.0;
+    return OK;
+  }
+  data[0][0] = 0.8 * noise / band; /* red */
+  data[0][1] = 0.8 * (content+noise) / band; /* green */
+  data[0][2] = 0.8 * (queries+content+noise) / band; /* yellow */
+  data[0][3] = 0.8 * total / band; /* blue */
+  /*printf("I: %f %f %f\n", 
+        data[0][0],
+        data[0][1],
+        data[0][2]);*/
+
+  return OK;
+}
+
+
+static int getTrafficSendStats(GNUNET_TCP_SOCKET * sock,
+                              const void * closure,
+                              gfloat ** data) {
+  long long total;
+  long long noise;
+  long long content;
+  long long queries;
+  long long ltotal;
+  long long lnoise;
+  long long lcontent;
+  long long lqueries;
+  long long band;
+  long long tmp;
+  long long ltmp;
+  cron_t dtime;
+  char * available;
+
+  MUTEX_LOCK(&lock);
+  if (OK != getStatValue(&total,       
+                        &ltotal,
+                        &dtime,
+                        sock,
+                        "# encrypted bytes sent"))
+    return SYSERR;
+  if (OK != getStatValue(&noise,
+                        &lnoise,
+                        NULL,
+                        sock,
+                        "# bytes noise sent"))
+    return SYSERR;
+  content = lcontent = 0;
+  if (OK == getStatValue(&tmp,
+                        &ltmp,
+                        NULL,
+                        sock,
+                        "# bytes transmitted of type 17")) {
+    content += tmp;
+    lcontent += ltmp;
+  }
+  if (OK == getStatValue(&tmp,
+                        &ltmp,
+                        NULL,
+                        sock,
+                        "# bytes transmitted of type 18")) {
+    content += tmp;
+    lcontent += ltmp;
+  }
+  if (OK == getStatValue(&tmp,
+                        &ltmp,
+                        NULL,
+                        sock,
+                        "# bytes transmitted of type 20")) {
+    content += tmp;
+    lcontent += ltmp;
+  }
+  queries = lqueries = 0;
+  if (OK == getStatValue(&tmp,
+                        &ltmp,
+                        NULL,
+                        sock,
+                        "# bytes transmitted of type 16")) {
+    queries += tmp;
+    lqueries += ltmp;
+  }
+  if (OK == getStatValue(&tmp,
+                        &ltmp,
+                        NULL,
+                        sock,
+                        "# bytes transmitted of type 19")) {
+    queries += tmp;
+    lqueries += ltmp;
+  }
+  MUTEX_UNLOCK(&lock);
+  available = getConfigurationOptionValue(sock,
+                                         "LOAD",
+                                         "MAXNETUPBPSTOTAL");
+  if (available == NULL)
+    return SYSERR;
+  band = atoll(available) * dtime / cronSECONDS;
+  FREE(available);
+  total -= ltotal;
+  noise -= lnoise;
+  queries -= lqueries;
+  content -= lcontent;
+  if (band <= 0) {
+    data[0][0] = 0.0;
+    data[0][1] = 0.0;
+    data[0][2] = 0.0;
+    data[0][3] = 0.0;
+    return OK;
+  }
+  data[0][0] = 0.8 * noise / band; /* red */
+  data[0][1] = 0.8 * (noise + content) / band; /* green */
+  data[0][2] = 0.8 * (noise + content + queries) / band; /* yellow */
+  data[0][3] = 0.8 * total / band; /* yellow */
+  /* printf("O: %f %f %f\n", 
+     data[0][0],
+     data[0][1],
+        data[0][2]);*/
+  
+  return OK;
+}
+
+
+
+typedef struct SE_ {
+  char * paneName;
+  char * frameName;
+  UpdateData getData;
+  void * get_closure;
+  unsigned int count;
+  int fill; /* YES / NO */
+} StatEntry;
+
+#define STATS_COUNT 4
+
+static StatEntry stats[] = {
+  { 
+    "Connectivity",
+    "# connected nodes (100% = connection table size)",
+    &getConnectedNodesStat,
+    NULL,
+    1,
+    NO,
+  }, 
+  { 
+    "CPU load",
+    "CPU load (in percent of allowed load)",
+    &getCPULoadStat,
+    NULL,
+    1,
+    NO,
+  },
+  { 
+    "Inbound Traffic",
+    "Noise (red), Content (green), Queries (yellow), other (blue)",
+    &getTrafficRecvStats,
+    NULL,
+    4,
+    YES,
+  },
+  { 
+    "Outbound Traffic",
+    "Noise (red), Content (green), Queries (yellow), other (blue)",
+    &getTrafficSendStats,
+    NULL,
+    4,
+    YES,
+  },
+  {
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    1,
+    NO,
+  },
+};
+
+static void statClose(void);
+
+static GtkItemFactoryEntry statWindowMenu[] = {
+  { "/Close display",   
+    NULL, 
+    statClose, 
+    0, 
+    "<Item>" 
+  }
+};
+static gint statWindowMenuItems 
+  = sizeof (statWindowMenu) / sizeof (statWindowMenu[0]);
+
+typedef struct {
+  gint type;
+  guint count;
+  guint speed;
+  guint draw_width, draw_height;
+  guint num_points; 
+  guint allocated;  
+  GdkColor *colors;
+  gfloat **data, **odata;
+  guint data_size;
+  gint colors_allocated;
+  GtkWidget *main_widget;
+  GtkWidget *disp;
+  GtkWidget *label;
+  GdkPixmap *pixmap;
+  GdkGC *gc;
+  int timer_index;  
+  gboolean draw;
+  GNUNET_TCP_SOCKET * sock;
+  int statIdx;
+} LoadGraph;
+
+#define MAX_COLOR 4
+
+typedef struct {
+  gint            graph_update_interval;
+  GdkColor        bg_color;
+  GdkColor        frame_color;
+  GdkColor        mem_color[MAX_COLOR];
+} ProcConfig;
+
+typedef struct ProcData {
+  ProcConfig      config;
+  LoadGraph       *mem_graph;
+  int statIdx;
+} ProcData;
+
+#define GNOME_PAD_SMALL 2
+#define FRAME_WIDTH 0
+
+/**
+ * Remove the active page from the notebook.
+ **/
+static void statClose(void) {
+  gint pagenr;
+
+  pagenr = gtk_notebook_get_current_page(notebook);
+  gtk_notebook_remove_page(notebook, pagenr);
+  /* Need to refresh the widget --
+     This forces the widget to redraw itself. */
+  gtk_widget_draw(GTK_WIDGET(notebook), NULL);
+}
+
+/* Redraws the backing pixmap for the load graph and updates the window */
+static void load_graph_draw(LoadGraph *g) {
+  guint i;
+  guint j;
+  gint dely;
+  float delx;
+  
+  if (!g->disp->window)
+    return;
+  
+  g->draw_width = g->disp->allocation.width;
+  g->draw_height = g->disp->allocation.height;
+  
+  if (!g->pixmap)
+    g->pixmap = gdk_pixmap_new (g->disp->window,
+                               g->draw_width, g->draw_height,
+                               gtk_widget_get_visual (g->disp)->depth);
+  
+  /* Create GC if necessary. */
+  if (!g->gc) {
+    g->gc = gdk_gc_new (g->disp->window);
+    gdk_gc_copy (g->gc, g->disp->style->white_gc);
+  }
+  
+  /* Allocate colors. */
+  if (!g->colors_allocated) {
+    GdkColormap *colormap;
+    
+    colormap = gdk_window_get_colormap (g->disp->window);
+    for (i=0;i<2+g->count;i++) 
+      gdk_color_alloc (colormap, &(g->colors [i]));    
+    
+    g->colors_allocated = 1;
+  }
+  /* Erase Rectangle */
+  gdk_gc_set_foreground (g->gc, &(g->colors [0]));
+  gdk_draw_rectangle (g->pixmap,
+                     g->gc,
+                     TRUE, 0, 0,
+                     g->disp->allocation.width,
+                     g->disp->allocation.height);
+  
+  /* draw frame */
+  gdk_gc_set_foreground (g->gc, &(g->colors [1]));
+  gdk_draw_rectangle (g->pixmap,
+                     g->gc,
+                     FALSE, 0, 0,
+                     g->draw_width,
+                     g->disp->allocation.height);
+  
+  dely = g->draw_height / 5;
+  for (i = 1; i <5; i++) {
+    gint y1 = g->draw_height + 1 - i * dely;
+    gdk_draw_line (g->pixmap, g->gc,
+                  0, y1, g->draw_width, y1);
+  }
+  
+  gdk_gc_set_line_attributes (g->gc, 2, GDK_LINE_SOLID, GDK_CAP_ROUND, 
GDK_JOIN_MITER );
+  delx = (float)g->draw_width / ( g->num_points - 1);
+  
+  for (j=0;j<g->count;j++) {
+    gdk_gc_set_foreground (g->gc, &(g->colors [j + 2]));
+    for (i = 0; i < g->num_points - 1; i++) {    
+      gint x1 = i * delx;
+      gint x2 = (i + 1) * delx;
+      gint y1 = g->data[i][j] * g->draw_height - 1;
+      gint y2 = g->data[i+1][j] * g->draw_height - 1;
+      
+      if ((g->data[i][j] != -1) && (g->data[i+1][j] != -1)) {
+       if (stats[g->statIdx].fill == NO) {
+         gdk_draw_line(g->pixmap, g->gc,
+                       g->draw_width - x2, 
+                       g->draw_height - y2,
+                       g->draw_width - x1,
+                       g->draw_height - y1);
+       } else {
+         GdkPoint points[4];
+         
+         points[0].x = g->draw_width - x2;
+         points[0].y = g->draw_height - y2;
+         points[1].x = g->draw_width - x1;
+         points[1].y = g->draw_height - y1;
+         points[2].x = g->draw_width - x1;
+         points[3].x = g->draw_width - x2;
+         if (j == 0) {
+           points[2].y = g->draw_height;
+           points[3].y = g->draw_height;
+         } else {
+           gint ly1 = g->data[i][j-1] * g->draw_height - 1;
+           gint ly2 = g->data[i+1][j-1] * g->draw_height - 1;
+           points[2].y = g->draw_height - ly1;
+           points[3].y = g->draw_height - ly2;
+         }
+         gdk_draw_polygon(g->pixmap,
+                          g->gc,
+                          1,
+                          points,
+                          4);  
+       }
+      }
+    }
+  }
+
+  gdk_gc_set_line_attributes (g->gc, 1, GDK_LINE_SOLID, GDK_CAP_ROUND, 
GDK_JOIN_MITER );
+  
+  gdk_draw_pixmap (g->disp->window,
+                  g->disp->style->fg_gc [GTK_WIDGET_STATE(g->disp)],
+                  g->pixmap,
+                  0, 0,
+                  0, 0,
+                  g->disp->allocation.width,
+                  g->disp->allocation.height);
+}
+
+
+/* Updates the load graph when the timeout expires */
+static int load_graph_update(LoadGraph *g) {
+  guint i;  
+  guint j;
+
+  for (i=0;i<g->num_points;i++)
+    memcpy(g->odata[i], 
+          g->data[i],
+          g->data_size * g->count); 
+  stats[g->statIdx].getData(g->sock,
+                           stats[g->statIdx].get_closure,
+                           g->data);
+  for (i=0;i<g->num_points-1;i++)
+    for (j=0;j<g->count;j++)
+      g->data[i+1][j] = g->odata[i][j];  
+  if (g->draw)
+    load_graph_draw (g);  
+  return TRUE;
+}
+
+static void load_graph_unalloc (LoadGraph *g) {
+  int i;
+  if (!g->allocated)
+    return;
+  for (i = 0; i < g->num_points; i++) {
+    FREE(g->data[i]);
+    FREE(g->odata[i]);
+  }
+  FREE(g->data);
+  FREE(g->odata);
+  g->data = g->odata = NULL;
+  if (g->pixmap) {
+    gdk_pixmap_unref(g->pixmap);
+    g->pixmap = NULL;
+  }
+  g->allocated = FALSE;
+}
+
+static void load_graph_alloc (LoadGraph *g) {
+  int i;
+  int j;
+
+  if (g->allocated)
+    return;
+  
+  g->data = MALLOC(sizeof(gfloat *) * g->num_points);
+  g->odata = MALLOC(sizeof(gfloat*) * g->num_points);
+  g->data_size = sizeof (gfloat);  
+  for (i = 0; i < g->num_points; i++) {
+    g->data[i] = MALLOC(g->data_size * g->count);
+    g->odata[i] = MALLOC(g->data_size * g->count);
+  }  
+  for (i=0;i<g->num_points;i++) 
+    for (j=0;j<g->count;j++)
+      g->data[i][j] = -1;
+  g->allocated = TRUE;
+}
+
+static gint load_graph_configure(GtkWidget *widget, 
+                                GdkEventConfigure *event,
+                                gpointer data_ptr) {
+  LoadGraph *c = (LoadGraph *) data_ptr;
+  
+  if (c->pixmap) {
+    gdk_pixmap_unref (c->pixmap);
+    c->pixmap = NULL;
+  }
+  
+  if (!c->pixmap)
+    c->pixmap = gdk_pixmap_new (widget->window,
+                               widget->allocation.width,
+                               widget->allocation.height,
+                               gtk_widget_get_visual (c->disp)->depth);
+  
+  gdk_draw_rectangle (c->pixmap,
+                     widget->style->black_gc,
+                     TRUE, 0,0,
+                     widget->allocation.width,
+                     widget->allocation.height);
+  gdk_draw_pixmap (widget->window,
+                  c->disp->style->fg_gc [GTK_WIDGET_STATE(widget)],
+                  c->pixmap,
+                  0, 0,
+                  0, 0,
+                  c->disp->allocation.width,
+                  c->disp->allocation.height);  
+  load_graph_draw (c); 
+  return TRUE;
+}
+
+static gint load_graph_expose(GtkWidget *widget,
+                             GdkEventExpose *event,
+                             gpointer data_ptr) {
+  LoadGraph *g = (LoadGraph *) data_ptr;
+  
+  gdk_draw_pixmap (widget->window,
+                  widget->style->fg_gc [GTK_WIDGET_STATE(widget)],
+                  g->pixmap,
+                  event->area.x, event->area.y,
+                  event->area.x, event->area.y,
+                  event->area.width, event->area.height);
+  return FALSE;
+}
+
+static void load_graph_stop (LoadGraph *g) {
+  if (g->timer_index != -1) {
+    gtk_timeout_remove (g->timer_index);
+    g->timer_index = -1;
+  }
+  if (!g)
+    return;
+  g->draw = FALSE;
+}
+
+static void load_graph_destroy(GtkWidget *widget, 
+                              gpointer data_ptr) {
+  LoadGraph *g = (LoadGraph *) data_ptr;  
+  load_graph_stop(g);  
+  if (g->timer_index != -1)
+    gtk_timeout_remove (g->timer_index);
+  if (g->sock != NULL)
+    releaseClientSocket(g->sock);
+  load_graph_unalloc(g);
+  FREE(g->colors);
+  FREE(g);
+}
+
+static LoadGraph * load_graph_new(ProcData *procdata) {
+  LoadGraph *g;
+  unsigned int i;
+
+  if ( (procdata->statIdx < 0) ||
+       (procdata->statIdx >= STATS_COUNT) ) {
+    LOG(LOG_ERROR,
+       "ERROR: invalid statIdex %d!",
+       procdata->statIdx);
+    return NULL;
+  }
+  if (stats[procdata->statIdx].count > MAX_COLOR) {
+    LOG(LOG_ERROR,
+       "ERROR: more colors requested than available!\n");
+    return NULL;
+  }
+  
+  g = MALLOC(sizeof(LoadGraph));
+  g->sock = getClientSocket();
+  g->statIdx = procdata->statIdx;
+  g->count = stats[g->statIdx].count;
+  g->speed = procdata->config.graph_update_interval;
+  g->num_points = 600;
+  g->colors = MALLOC(sizeof(GdkColor) * (2+g->count));
+  g->colors[0] = procdata->config.bg_color;
+  g->colors[1] = procdata->config.frame_color;  
+  for (i=0;i<g->count;i++) 
+    g->colors[2+i] = procdata->config.mem_color[i];
+  g->timer_index = -1;
+  g->draw = FALSE;
+  g->main_widget = gtk_vbox_new (FALSE, FALSE);
+  gtk_widget_show (g->main_widget);
+  g->disp = gtk_drawing_area_new();
+  gtk_widget_show (g->disp);
+  gtk_signal_connect (GTK_OBJECT (g->disp),
+                     "expose_event",
+                     GTK_SIGNAL_FUNC (load_graph_expose), g);
+  gtk_signal_connect (GTK_OBJECT(g->disp), 
+                     "configure_event",
+                     GTK_SIGNAL_FUNC (load_graph_configure), g);
+  gtk_signal_connect (GTK_OBJECT(g->disp),
+                     "destroy",
+                     GTK_SIGNAL_FUNC (load_graph_destroy), g); 
+  gtk_widget_set_events(g->disp, GDK_EXPOSURE_MASK);  
+  gtk_box_pack_start(GTK_BOX (g->main_widget), g->disp, TRUE, TRUE, 0);  
+  load_graph_alloc(g);  
+  gtk_widget_show_all (g->main_widget);  
+  g->timer_index = gtk_timeout_add(g->speed,
+                                  (GtkFunction) load_graph_update, g);
+  
+  return g;
+}
+
+static void load_graph_start(LoadGraph *g) {
+  if (!g)
+    return;
+  
+  if (g->timer_index == -1)
+    g->timer_index = gtk_timeout_add(g->speed,
+                                    (GtkFunction) load_graph_update, g);
+  
+  g->draw = TRUE;
+}
+
+static GtkWidget * create_sys_view(ProcData * procdata) {
+  GtkWidget * vbox;
+  GtkWidget * mem_frame;
+  GtkWidget * menu;
+  GtkItemFactory * popupFactory;
+  LoadGraph * mem_graph;
+
+  mem_graph = load_graph_new(procdata);
+  if (mem_graph == NULL)
+    return NULL; /* oops */
+  vbox = gtk_vbox_new (FALSE, 0);
+
+  mem_frame = gtk_frame_new (stats[procdata->statIdx].frameName);
+  gtk_container_set_border_width (GTK_CONTAINER (mem_frame), GNOME_PAD_SMALL);
+  gtk_box_pack_start (GTK_BOX (vbox), mem_frame, TRUE, TRUE, 0);
+  
+ 
+  gtk_container_add (GTK_CONTAINER (mem_frame), mem_graph->main_widget);
+  gtk_container_set_border_width (GTK_CONTAINER (mem_graph->main_widget),
+                                 GNOME_PAD_SMALL);
+  procdata->mem_graph = mem_graph;
+
+  /* FIXME: this should add a right-button popup-menu 
+     but does not. Somehow popupCallback() never gets called,
+     i.e. event does not occur or has been blocked or 
+     overridden somewhere. Trying to connect to some other
+     widget instead of vbox stays equally unresponsive */
+  popupFactory = gtk_item_factory_new (GTK_TYPE_MENU, "<main>",
+                                      NULL);
+  gtk_item_factory_create_items(popupFactory,
+                               statWindowMenuItems,
+                               statWindowMenu,
+                               NULL);
+  menu = gtk_item_factory_get_widget(popupFactory, "<main>");
+  gtk_signal_connect(GTK_OBJECT(vbox),
+                    "event",
+                    GTK_SIGNAL_FUNC(popupCallback),
+                    menu);
+  gtk_widget_show_all (vbox);
+  
+  return vbox;
+}
+
+
+static GtkWidget * create_main_window(int stat) {
+  GtkWidget *sys_box;
+  ProcData procdata;
+
+    
+  memset(&procdata, 0, sizeof(ProcData));
+  procdata.config.graph_update_interval 
+    = UPDATE_INTERVAL / cronMILLIS;
+  procdata.statIdx = stat;
+  gdk_color_parse("black",
+                 &procdata.config.bg_color);
+  gdk_color_parse("gray",
+                 &procdata.config.frame_color);
+  gdk_color_parse("red",
+                 &procdata.config.mem_color[0]);
+  gdk_color_parse("green",
+                 &procdata.config.mem_color[1]);
+  gdk_color_parse("yellow",
+                 &procdata.config.mem_color[2]);
+  gdk_color_parse("blue",
+                 &procdata.config.mem_color[3]);
+  if (MAX_COLOR != 4)
+    errexit("Assertion failed! MAX_COLOR wrong!");
+  sys_box = create_sys_view(&procdata);
+  if (sys_box == NULL)
+    return NULL;
+  load_graph_start(procdata.mem_graph); 
+  return sys_box;
+}
+
+
+/**
+ * Display the statistics.
+ */
+void displayStatistics(GtkWidget * widget,
+                      gpointer data) {
+  int dptr;
+  GtkWidget * wid;
+
+  dptr = (int) data;
+  if ( (dptr < 0) ||
+       (dptr >= STATS_COUNT) ) {
+    LOG(LOG_ERROR,
+       "ERROR: displayStatistics called with invalid argument (%d)\n",
+       dptr);
+  } else {    
+    wid = create_main_window(dptr);
+    if (wid != NULL)
+      addToNotebook(stats[dptr].paneName,
+                   wid);
+  }
+}
+
+void initGTKStatistics() {
+  MUTEX_CREATE_RECURSIVE(&lock);
+}
+
+void doneGTKStatistics() {
+  unsigned int i;
+  for (i=0;i<lsv_size;i++)
+    FREE(lastStatValues[i].statName);
+  GROW(lastStatValues,
+       lsv_size,
+       0);
+  MUTEX_DESTROY(&lock);
+}
+
+ 
+/* end of statistics.c */

Added: freeway/src/org/gnu/freeway/protocol/fs/swing/statistics.h
===================================================================
--- freeway/src/org/gnu/freeway/protocol/fs/swing/statistics.h  2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/protocol/fs/swing/statistics.h  2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,52 @@
+/*
+     This file is part of GNUnet
+
+     GNUnet 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, or (at your
+     option) any later version.
+
+     GNUnet 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 GNUnet; see the file COPYING.  If not, write to the
+     Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+     Boston, MA 02111-1307, USA.
+*/
+
+/**
+ * @file applications/afs/gtkui/statistics.h
+ * @author Christian Grothoff 
+ **/
+
+#ifndef GTKUI_STATISTICS_H
+#define GTKUI_STATISTICS_H
+
+/* for GTK 2 */
+#define GTK_ENABLE_BROKEN
+
+#include "platform.h"
+#include <gtk/gtk.h>
+#include <gtk/gtktext.h>
+
+/**
+ * Display the statistics.
+ */
+void displayStatistics(GtkWidget * widget,
+                      gpointer data);
+
+
+void initGTKStatistics();
+ 
+void doneGTKStatistics();
+
+#define STAT_CONNECTIVITY 0
+#define STAT_CPU_LOAD 1
+#define STAT_IN_TRAFFIC 2
+#define STAT_OUT_TRAFFIC 3
+
+
+#endif

Modified: freeway/src/org/gnu/freeway/server/CPluginLoader.java
===================================================================
--- freeway/src/org/gnu/freeway/server/CPluginLoader.java       2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/server/CPluginLoader.java       2006-08-21 
11:53:42 UTC (rev 3286)
@@ -28,8 +28,10 @@
 import org.gnu.freeway.AbstractApplication;
 import org.gnu.freeway.cwrappers.util.SwitchTableGenerator;
 import org.gnu.freeway.transport.Session;
+import org.gnu.freeway.util.DatastoreValue;
 import org.gnu.freeway.util.LoggedObject;
 import org.gnu.freeway.util.NativeService;
+import org.gnu.freeway.util.crypto.HashCode512;
 import org.gnu.freeway.util.crypto.HostIdentity;
 import org.gnu.freeway.util.net.CSMessage;
 import org.gnu.freeway.util.net.CSNativeMessage;
@@ -286,4 +288,8 @@
                cCallClientExitHandler(CoreAPI._, handle._, client);
        }
 
+       public int callDatumIterator(Handle handle, HashCode512 key, 
DatastoreValue value, Handle closure) {
+               return 0;
+       }
+
 }

Modified: freeway/src/org/gnu/freeway/server/NativeCallback.java
===================================================================
--- freeway/src/org/gnu/freeway/server/NativeCallback.java      2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/server/NativeCallback.java      2006-08-21 
11:53:42 UTC (rev 3286)
@@ -22,6 +22,9 @@
 import java.nio.ByteBuffer;
 
 import org.gnu.freeway.transport.Session;
+import org.gnu.freeway.util.DatastoreValue;
+import org.gnu.freeway.util.DatumIterator;
+import org.gnu.freeway.util.crypto.HashCode512;
 import org.gnu.freeway.util.crypto.HostIdentity;
 import org.gnu.freeway.util.net.CSHandler;
 import org.gnu.freeway.util.net.CSMessage;
@@ -36,7 +39,7 @@
  * @brief 
  * @author mdonoughe
  */
-public class NativeCallback implements BufferFillCallback, PerNodeCallback, 
ClientExitHandler, CSHandler, P2PHandler {
+public class NativeCallback implements BufferFillCallback, PerNodeCallback, 
ClientExitHandler, CSHandler, P2PHandler, DatumIterator {
 
        private CPluginLoader.Handle handle;
        
@@ -87,4 +90,12 @@
                        return false;
                return CPluginLoader._.callP2PHandle(handle, 
session.getRemote(), (P2PNativeMessage) message);
        }
+       
+       //DatumIterator
+       public int datumIteration(HashCode512 key, DatastoreValue value, Object 
closure) {
+               assert(closure instanceof CPluginLoader.Handle);
+               if(handle == null || handle._ == 0)
+                       return 0;
+               return CPluginLoader._.callDatumIterator(handle, key, value, 
(CPluginLoader.Handle) closure);
+       }
 }

Added: freeway/src/org/gnu/freeway/services/DatastoreService.java
===================================================================
--- freeway/src/org/gnu/freeway/services/DatastoreService.java  2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/services/DatastoreService.java  2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,41 @@
+ /*
+      This file is part of Freeway
+
+      Freeway 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, or (at your
+      option) any later version.
+
+      Freeway 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 Freeway; see the file COPYING.  If not, write to the
+      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+      Boston, MA 02111-1307, USA.
+ */
+
+package org.gnu.freeway.services;
+import org.gnu.freeway.server.CPluginLoader;
+import org.gnu.freeway.util.NativeService;
+import org.gnu.freeway.cwrappers.*;
+
+/**
+ * @file StatsService.java
+ * @brief 
+ * @author mdonoughe
+ */
+public interface DatastoreService extends NativeService {
+       public CLong getSize();
+       public CInt put(ConstCHashCode512 key, ConstCDatastoreValue value);
+       public CInt putUpdate(ConstCHashCode512 key, ConstCDatastoreValue 
value);
+       //public CInt get(ConstCHashCode512 key, CUnsignedInt type, 
CPluginLoader.Handle iter, CPluginLoader.Handle closure);
+       //TODO: we need a CCallback type so we can getValue() C callbacks into 
NativeCallbacks and Java callbacks as themselves
+       //      the C side would need the oposite done so Java callbacks could 
be called as if they were C
+       public CInt fast_get(ConstCHashCode512 key);
+       //TODO: implement array handling
+       //public CInt getRandom(ConstCHashCode512 approx, CUnsignedInt 
sizeLimit, CHashCode512 key, CDatastoreValue[] value, CUnsignedInt type);
+       public CInt del(ConstCHashCode512 key, ConstCDatastoreValue cvalue);
+}

Added: freeway/src/org/gnu/freeway/services/c/DatastoreService.java
===================================================================
--- freeway/src/org/gnu/freeway/services/c/DatastoreService.java        
2006-08-21 05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/services/c/DatastoreService.java        
2006-08-21 11:53:42 UTC (rev 3286)
@@ -0,0 +1,39 @@
+// This class was autogenerated by SwitchTableGenerator
+package org.gnu.freeway.services.c;
+
+import org.gnu.freeway.cwrappers.CInt;
+import org.gnu.freeway.cwrappers.CLong;
+import org.gnu.freeway.cwrappers.ConstCHashCode512;
+import org.gnu.freeway.cwrappers.ConstCDatastoreValue;
+import org.gnu.freeway.server.CPluginLoader;
+
+public class DatastoreService extends AbstractNativeService implements 
NativeService, org.gnu.freeway.services.DatastoreService {
+
+       public CLong getSize() {
+               return (CLong) loader.callC(handle, 0, 4, new Object[] {});
+       }
+
+       public CInt put(ConstCHashCode512 arg0, ConstCDatastoreValue arg1) {
+               return (CInt) loader.callC(handle, 1, 85, new Object[] {arg0, 
arg1});
+       }
+
+       public CInt putUpdate(ConstCHashCode512 arg0, ConstCDatastoreValue 
arg1) {
+               return (CInt) loader.callC(handle, 2, 85, new Object[] {arg0, 
arg1});
+       }
+
+       public CInt fast_get(ConstCHashCode512 arg0) {
+               return (CInt) loader.callC(handle, 3, 13, new Object[] {arg0});
+       }
+
+       public CInt del(ConstCHashCode512 arg0, ConstCDatastoreValue arg1) {
+               return (CInt) loader.callC(handle, 4, 85, new Object[] {arg0, 
arg1});
+       }
+
+       public DatastoreService() {
+               this(CPluginLoader._);
+       }
+
+       public DatastoreService(CPluginLoader loader) {
+               super(loader);
+       }
+}


Property changes on: 
freeway/src/org/gnu/freeway/services/c/DatastoreService.java
___________________________________________________________________
Name: svn:mime-type
   + text/cpp

Modified: freeway/src/org/gnu/freeway/services/c/StatsService.java
===================================================================
--- freeway/src/org/gnu/freeway/services/c/StatsService.java    2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/services/c/StatsService.java    2006-08-21 
11:53:42 UTC (rev 3286)
@@ -1,14 +1,10 @@
 // This class was autogenerated by SwitchTableGenerator
 package org.gnu.freeway.services.c;
 
-import org.gnu.freeway.util.AbstractNativeService;
-import org.gnu.freeway.util.NativeService;
-import org.gnu.freeway.util.ServiceException;
 import org.gnu.freeway.cwrappers.CInt;
 import org.gnu.freeway.cwrappers.ConstCLong;
 import org.gnu.freeway.cwrappers.CLong;
 import org.gnu.freeway.cwrappers.ConstCInt;
-import org.gnu.freeway.cwrappers.CString;
 import org.gnu.freeway.cwrappers.ConstCString;
 import org.gnu.freeway.server.CPluginLoader;
 
@@ -30,23 +26,6 @@
                loader.callC(handle, 3, 42, new Object[] {arg0, arg1});
        }
 
-       public void init() throws ServiceException {
-               super.init();
-               System.err.println("Loaded stats service.");
-               CInt c = create(new CString("test"));
-               System.err.println("created stat \"test\":" + c.getValue());
-               set(c, new CLong(12345));
-               c = create(new CString("test2"));
-               System.err.println("created stat \"test2\":" + c.getValue());
-               set(c, new CLong(54321));
-               c = create(new CString("-1"));
-               System.err.println("created stat \"-1\":" + c.getValue());
-               set(c, new CLong(-1));
-               c = create(new CString("-2"));
-               System.err.println("created stat \"-2\":" + c.getValue());
-               set(c, new CLong(-2));
-       }
-
        public StatsService() {
                this(CPluginLoader._);
        }

Modified: freeway/src/org/gnu/freeway/test/MySQLTest.java
===================================================================
--- freeway/src/org/gnu/freeway/test/MySQLTest.java     2006-08-21 05:13:22 UTC 
(rev 3285)
+++ freeway/src/org/gnu/freeway/test/MySQLTest.java     2006-08-21 11:53:42 UTC 
(rev 3286)
@@ -4,8 +4,8 @@
 
 package org.gnu.freeway.test;
 
-import org.gnu.freeway.protocol.afs.*;
-import org.gnu.freeway.protocol.afs.esed2.*;
+import org.gnu.freeway.protocol.fs.*;
+import org.gnu.freeway.protocol.fs.esed2.*;
 import org.gnu.freeway.util.*;
 import org.gnu.freeway.util.crypto.*;
 

Added: freeway/src/org/gnu/freeway/util/DatastoreValue.java
===================================================================
--- freeway/src/org/gnu/freeway/util/DatastoreValue.java        2006-08-21 
05:13:22 UTC (rev 3285)
+++ freeway/src/org/gnu/freeway/util/DatastoreValue.java        2006-08-21 
11:53:42 UTC (rev 3286)
@@ -0,0 +1,81 @@
+ /*
+      This file is part of Freeway
+
+      Freeway 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, or (at your
+      option) any later version.
+
+      Freeway 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 Freeway; see the file COPYING.  If not, write to the
+      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+      Boston, MA 02111-1307, USA.
+ */
+
+package org.gnu.freeway.util;
+
+/**
+ * @file DatastoreValue.java
+ * @brief 
+ * @author mdonoughe
+ */
+public class DatastoreValue {
+       private int size;
+       private int type;
+       private int prio;
+       private int anonymityLevel;
+       private long expirationTime;
+       
+       public DatastoreValue(int size, int type, int prio, int anonymityLevel, 
long expirationTime) {
+               this.size = size;
+               this.type = type;
+               this.prio = prio;
+               this.anonymityLevel = anonymityLevel;
+               this.expirationTime = expirationTime;
+       }
+       
+       public void setSize(int size) {
+               this.size = size;
+       }
+       
+       public void setType(int type) {
+               this.type = type;
+       }
+       
+       public void setPrio(int prio) {
+               this.prio = prio;
+       }
+       
+       public void setAnonymityLevel(int anonymityLevel) {
+               this.anonymityLevel = anonymityLevel;
+       }
+       
+       public void setExpirationTime(long expirationTime) {
+               this.expirationTime = expirationTime;
+       }
+       
+       public int getSize() {
+               return size;
+       }
+       
+       public int getType() {
+               return type;
+       }
+       
+       public int getPrio() {
+               return prio;
+       }
+       
+       public int getAnonymityLevel() {
+               return anonymityLevel;
+       }
+       
+       public long getExpirationTime() {
+               return expirationTime;
+       }
+}

Added: freeway/src/org/gnu/freeway/util/DatumIterator.java
===================================================================
--- freeway/src/org/gnu/freeway/util/DatumIterator.java 2006-08-21 05:13:22 UTC 
(rev 3285)
+++ freeway/src/org/gnu/freeway/util/DatumIterator.java 2006-08-21 11:53:42 UTC 
(rev 3286)
@@ -0,0 +1,31 @@
+ /*
+      This file is part of Freeway
+
+      Freeway 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, or (at your
+      option) any later version.
+
+      Freeway 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 Freeway; see the file COPYING.  If not, write to the
+      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+      Boston, MA 02111-1307, USA.
+ */
+
+package org.gnu.freeway.util;
+
+import org.gnu.freeway.util.crypto.HashCode512;
+
+/**
+ * @file DatumIterator.java
+ * @brief 
+ * @author mdonoughe
+ */
+public interface DatumIterator {
+       int datumIteration(HashCode512 key, DatastoreValue value, Object 
closure);
+}





reply via email to

[Prev in Thread] Current Thread [Next in Thread]