Author: nacktschneck
Date: 2006-03-07 20:25:13 +0000 (Tue, 07 Mar 2006)
New Revision: 8183

Added:
   trunk/apps/bookmarkplugin/.classpath
   trunk/apps/bookmarkplugin/.project
   trunk/apps/bookmarkplugin/build.xml
   trunk/apps/bookmarkplugin/lib/
   trunk/apps/bookmarkplugin/lib/commons-lang-2.1.jar
   trunk/apps/bookmarkplugin/lib/commons-logging.jar
   trunk/apps/bookmarkplugin/lib/dom4j-1.6.1.jar
   trunk/apps/bookmarkplugin/lib/falimat-beanstore.jar
   trunk/apps/bookmarkplugin/lib/falimat-util.jar
   trunk/apps/bookmarkplugin/lib/junit.jar
   trunk/apps/bookmarkplugin/lib/lucene-1.5-rc1-dev.jar
   trunk/apps/bookmarkplugin/src/
   trunk/apps/bookmarkplugin/src/falimat/
   trunk/apps/bookmarkplugin/src/falimat/freenet/
   trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/
   
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/BookmarkPlugin.java
   
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/LoginCredentials.java
   trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/components/
   
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/components/BookmarkEditor.java
   
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/components/BookmarkList.java
   
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/components/BookmarkSearchForm.java
   
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/components/LoginMessage.java
   
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/components/SideBar.java
   trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/model/
   
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/model/AbstractSendable.java
   
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/model/Bookmark.java
   
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/model/Channel.java
   trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/model/Ping.java
   trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/model/Pong.java
   trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/model/Slot.java
   trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/model/User.java
   trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/storage/
   
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/storage/Store.java
   trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/ui/
   
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/ui/AddBookmarkPage.java
   
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/ui/BookmarksPage.java
   
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/ui/ChannelsPage.java
   trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/ui/HomePage.java
   trunk/apps/bookmarkplugin/src/falimat/freenet/crypt/
   trunk/apps/bookmarkplugin/src/falimat/freenet/crypt/CryptoUtil.java
   trunk/apps/bookmarkplugin/src/falimat/freenet/crypt/XmlCryptoTest.java
   trunk/apps/bookmarkplugin/src/falimat/freenet/network/
   trunk/apps/bookmarkplugin/src/falimat/freenet/network/RegExes.java
   trunk/apps/bookmarkplugin/src/falimat/freenet/network/SlotReader.java
   trunk/apps/bookmarkplugin/src/falimat/freenet/network/SlotWriter.java
   trunk/apps/bookmarkplugin/src/falimat/freenet/network/XmlMessageCodec.java
   trunk/apps/bookmarkplugin/src/falimat/freenet/network/XmlSlotReader.java
   trunk/apps/bookmarkplugin/src/falimat/freenet/network/XmlSlotWriter.java
   trunk/apps/bookmarkplugin/src/falimat/freenet/tests/
   trunk/apps/bookmarkplugin/src/falimat/freenet/tests/RegexTest.java
   trunk/apps/bookmarkplugin/src/falimat/freenet/tests/StoreTest.java
   trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/
   
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/AbstractHtmlComponent.java
   trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/Action.java
   trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/ActionComponent.java
   trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/ErrorPage.java
   trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/FormAction.java
   trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/FormComponent.java
   trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/HtmlComponent.java
   trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/HtmlPage.java
   trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/HtmlWriter.java
   
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/KeyNotFoundException.java
   
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/PageNotFoundException.java
   trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/SimpleAction.java
   trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/StylesheetLoader.java
   
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/WebinterfacePlugin.java
   trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/
   
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/AbstractFormComponent.java
   
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/ActionButton.java
   
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/BeanTable.java
   
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/BeanTableDataSource.java
   
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/DefinitionList.java
   trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/Form.java
   
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/Heading.java
   
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/LabeledDropdown.java
   
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/LabeledTextArea.java
   
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/LabeledTextField.java
   
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/MessageArea.java
   
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/NavigationMenu.java
   
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/SubmitButton.java
   trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/style.css
   trunk/apps/bookmarkplugin/src/plugins/
   trunk/apps/bookmarkplugin/src/plugins/BookmarkPlugin.java
Log:
Don't blame for committing all this , I was threatened to do so ;)

Source code for BookmarkPlugin that
 - WILL CREATE A DIRECTORY 'bookmarks' IN YOUR WORKING DIRECTORY ON STARTUP
 - ... that is used for persistence of your bookmarks, settings and everything
 - has a lot of dependencies
 - needs java 1.5 to compile and run
 - is totally uncommented
 - has problems with urldecoding with the current freenet trunk
 - is mostly untested, including the persistence layer
 - has way too many classes
 - is a huge hack meant as a prototype and for getting to know the Freenet API

Some parts may be useful for other plugins or toadlets, but need heavy 
refactoring
to be used outside of its context.

Added: trunk/apps/bookmarkplugin/.classpath
===================================================================
--- trunk/apps/bookmarkplugin/.classpath        2006-03-07 18:55:50 UTC (rev 
8182)
+++ trunk/apps/bookmarkplugin/.classpath        2006-03-07 20:25:13 UTC (rev 
8183)
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+       <classpathentry kind="src" path="src"/>
+       <classpathentry kind="con" 
path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/jre1.5.0_05"/>
+       <classpathentry combineaccessrules="false" kind="src" path="/Freenet 
0.7"/>
+       <classpathentry kind="lib" path="lib/commons-lang-2.1.jar"/>
+       <classpathentry kind="lib" path="lib/commons-logging.jar"/>
+       <classpathentry kind="lib" path="lib/dom4j-1.6.1.jar"/>
+       <classpathentry kind="lib" path="lib/falimat-beanstore.jar"/>
+       <classpathentry kind="lib" path="lib/falimat-util.jar"/>
+       <classpathentry kind="lib" path="lib/junit.jar"/>
+       <classpathentry kind="lib" path="lib/lucene-1.5-rc1-dev.jar"/>
+       <classpathentry kind="output" path="build"/>
+</classpath>

Added: trunk/apps/bookmarkplugin/.project
===================================================================
--- trunk/apps/bookmarkplugin/.project  2006-03-07 18:55:50 UTC (rev 8182)
+++ trunk/apps/bookmarkplugin/.project  2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>bookmarkplugin</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>

Added: trunk/apps/bookmarkplugin/build.xml
===================================================================
--- trunk/apps/bookmarkplugin/build.xml 2006-03-07 18:55:50 UTC (rev 8182)
+++ trunk/apps/bookmarkplugin/build.xml 2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE project >
+
+<project basedir="." default="dist" name="freenet-bookmarks">
+
+       <!-- this property must point to a directory with a recent 
freenet-cvs-snapshot.ar and freenet-ext.jar -->
+       <property name="freenet-lib" value="../../projects/freenet-svn/lib" />
+
+       <property name="dist" value="./dist" />
+       <property name="build" value="./build" />
+       <property name="src" value="./src" />
+       <property name="lib" value="./lib" />
+       <property name="temp" value="./temp" />
+
+       <target name="clean" >
+               <delete includeemptydirs="false" >
+                       <fileset dir="${dist}" />
+                       <fileset dir="${build}" />
+               </delete>
+       </target>
+
+       <target name="build" >
+               <mkdir dir="${build}" />
+               <javac srcdir="${src}" destdir="${build}" debug="true" 
target="1.5" optimize="false">
+                       <classpath>
+                               <fileset dir="${lib}" />
+                               <fileset dir="${freenet-lib}" />
+                       </classpath>
+               </javac>
+               <copy todir="${build}">
+                       <fileset dir="${src}" >
+                               <include name="**/*.css"/>
+                               <include name="**/*.properties"/>
+                       </fileset>
+               </copy>
+       </target>
+
+       <target name="dist" depends="build">
+               <mkdir dir="${temp}" />
+               <unjar dest="${temp}">
+                       <fileset dir="${lib}">
+
+                       </fileset>
+               </unjar>
+               <!-- TODO: Include licenses in combined jar! -->
+               <copy todir="${temp}">
+                       <fileset dir="${build}" />
+               </copy>
+               <mkdir dir="${dist}" />
+               <jar destfile="${dist}/freenet-bookmarkplugin.jar" 
compress="true">
+                       <fileset dir="${temp}" />
+               </jar>
+               <delete dir="${temp}" />
+       </target>
+
+</project>
+
+
+

Added: trunk/apps/bookmarkplugin/lib/commons-lang-2.1.jar
===================================================================
(Binary files differ)


Property changes on: trunk/apps/bookmarkplugin/lib/commons-lang-2.1.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/apps/bookmarkplugin/lib/commons-logging.jar
===================================================================
(Binary files differ)


Property changes on: trunk/apps/bookmarkplugin/lib/commons-logging.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/apps/bookmarkplugin/lib/dom4j-1.6.1.jar
===================================================================
(Binary files differ)


Property changes on: trunk/apps/bookmarkplugin/lib/dom4j-1.6.1.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/apps/bookmarkplugin/lib/falimat-beanstore.jar
===================================================================
(Binary files differ)


Property changes on: trunk/apps/bookmarkplugin/lib/falimat-beanstore.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/apps/bookmarkplugin/lib/falimat-util.jar
===================================================================
(Binary files differ)


Property changes on: trunk/apps/bookmarkplugin/lib/falimat-util.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/apps/bookmarkplugin/lib/junit.jar
===================================================================
(Binary files differ)


Property changes on: trunk/apps/bookmarkplugin/lib/junit.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/apps/bookmarkplugin/lib/lucene-1.5-rc1-dev.jar
===================================================================
(Binary files differ)


Property changes on: trunk/apps/bookmarkplugin/lib/lucene-1.5-rc1-dev.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/BookmarkPlugin.java
===================================================================
--- 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/BookmarkPlugin.java
    2006-03-07 18:55:50 UTC (rev 8182)
+++ 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/BookmarkPlugin.java
    2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,408 @@
+package falimat.freenet.bookmarkplugin;
+
+import java.net.MalformedURLException;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.commons.lang.time.DateUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import xomat.util.ParamException;
+import xomat.util.ParamRuntimeException;
+import xomat.util.StopWatch;
+import falimat.freenet.bookmarkplugin.components.LoginMessage;
+import falimat.freenet.bookmarkplugin.components.SideBar;
+import falimat.freenet.bookmarkplugin.model.AbstractSendable;
+import falimat.freenet.bookmarkplugin.model.Bookmark;
+import falimat.freenet.bookmarkplugin.model.Channel;
+import falimat.freenet.bookmarkplugin.model.Ping;
+import falimat.freenet.bookmarkplugin.model.Pong;
+import falimat.freenet.bookmarkplugin.model.Slot;
+import falimat.freenet.bookmarkplugin.model.User;
+import falimat.freenet.bookmarkplugin.storage.Store;
+import falimat.freenet.bookmarkplugin.ui.AddBookmarkPage;
+import falimat.freenet.bookmarkplugin.ui.BookmarksPage;
+import falimat.freenet.bookmarkplugin.ui.ChannelsPage;
+import falimat.freenet.bookmarkplugin.ui.HomePage;
+import falimat.freenet.network.SlotWriter;
+import falimat.freenet.network.XmlSlotReader;
+import falimat.freenet.network.XmlSlotWriter;
+import falimat.freenet.webplugin.WebinterfacePlugin;
+import falimat.freenet.webplugin.components.NavigationMenu;
+import freenet.client.ClientMetadata;
+import freenet.client.FetchException;
+import freenet.client.FetchResult;
+import freenet.client.HighLevelSimpleClient;
+import freenet.client.InsertBlock;
+import freenet.client.InserterException;
+import freenet.keys.FreenetURI;
+import freenet.pluginmanager.PluginRespirator;
+import freenet.support.ArrayBucket;
+import freenet.support.Logger;
+
+public class BookmarkPlugin extends WebinterfacePlugin {
+
+    private final static Log log = LogFactory.getLog(BookmarkPlugin.class);
+
+    HighLevelSimpleClient client;
+
+    private boolean terminationRequested = false;
+
+    private HomePage homePage;
+
+    private ChannelsPage channelsPage;
+
+    private AddBookmarkPage addBookmarkPage;
+
+    private BookmarksPage bookmarksPage;
+
+    private LoginMessage loginMessage;
+
+    private SideBar sidebar;
+
+    @Override
+    public void runPlugin(PluginRespirator pr) {
+        Logger.normal(BookmarkPlugin.class, this + " was started.");
+
+        this.constructInterface();
+
+        this.client = pr.getHLSimpleClient();
+        while (!terminationRequested) {
+            try {
+                Thread.sleep(1 * DateUtils.MILLIS_PER_MINUTE);
+                this.endlessLoop();
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+                this.terminationRequested = true;
+            }
+        }
+    }
+
+    private void constructInterface() {
+        this.homePage = new HomePage("Bookmark Plugin");
+        super.setRootPage(this.homePage);
+
+        this.channelsPage = new ChannelsPage("Channels");
+        super.getRootPage().addSubpage(this.channelsPage, "channels");
+
+        this.addBookmarkPage = new AddBookmarkPage(this, "Add Bookmark");
+        super.getRootPage().addSubpage(this.addBookmarkPage, "addBookmark");
+
+        this.bookmarksPage = new BookmarksPage("Find Bookmarks");
+        super.getRootPage().addSubpage(this.bookmarksPage, "bookmarks");
+
+        this.sidebar = new SideBar();
+        NavigationMenu menu = new NavigationMenu(super.getRootPage());
+        this.sidebar.setNavigation(menu);
+        super.getRootPage().addToAll(this.sidebar);
+
+        this.loginMessage = new LoginMessage(this.addBookmarkPage);
+        super.getRootPage().addToAll(this.loginMessage);
+
+        this.homePage.constructComponents();
+        this.channelsPage.constructComponents();
+        this.addBookmarkPage.constructComponents();
+        this.bookmarksPage.constructComponents();
+
+    }
+
+    private void endlessLoop() {
+
+        long now = System.currentTimeMillis();
+
+        User user = LoginCredentials.instance().getCurrentUser();
+
+        if (user != null) {
+
+            // check if the next slot of a current user's channel should be
+            // inserted
+            Channel personalChannel = 
Store.instance().getChannelBySender(user.getPublicSSK());
+            if (personalChannel == null) {
+                String msg = "No personal channel found in database for user 
{0}";
+                throw new ParamRuntimeException(msg, user.getPublicSSK());
+            }
+            long nextInsertTime = personalChannel.getLastInsertTime() + 
personalChannel.getInsertInterval();
+            if (now < nextInsertTime) {
+                log.debug("There are " + (nextInsertTime - now) / 1000 + " 
seconds until the next insert.");
+            } else {
+                this.insertNextSlotOnPersonalChannel(user, personalChannel);
+            }
+        }
+
+        List<Slot> slotsToBeFetched = 
Store.instance().getSlotsToBeFetched(user);
+
+        if (!slotsToBeFetched.isEmpty()) {
+            Slot slot = slotsToBeFetched.get(0);
+
+            Channel requestedChannel = 
Store.instance().getChannel(slot.getChannel());
+            try {
+                FetchResult result = this.fetch(slot);
+
+                String contentType = result.getMimeType();
+
+                XmlSlotReader reader = new XmlSlotReader();
+                if (!reader.getContentType().equals(contentType)) {
+                    String msg = "Will not try to parse slot data beecause its 
content type was {0} instead of {1}";
+                    throw new ParamException(msg, contentType, 
reader.getContentType());
+                }
+
+                byte[] data = result.asByteArray();
+                reader.readMessages(data);
+
+                List<User> users = reader.getUsers();
+                Store.instance().saveUsers(users);
+
+                now = System.currentTimeMillis();
+
+                List<Channel> channels = reader.getChannels();
+                List<Bookmark> bookmarks = reader.getBookmarks();
+                List<Ping> pings = reader.getPings();
+                List<Pong> pongs = reader.getPongs();
+
+                for (Channel channel : channels) {
+                    Store.instance().saveChannel(channel);
+
+                    Slot lastSlot = new Slot(channel, channel.getLastSlot());
+                    if (lastSlot.getUri().equals(slot.getUri())) {
+                        lastSlot = slot;
+                    }
+
+                    if 
(!Store.instance().slotIsAvailable(lastSlot.getChannel(), lastSlot.getIndex())) 
{
+                        lastSlot.setExpectedTime(channel.getLastInsertTime());
+                        if (now > lastSlot.getExpectedTime()) {
+                            lastSlot.setRequestTime(now);
+                        } else {
+                            
lastSlot.setRequestTime(channel.getLastInsertTime());
+                        }
+                        Store.instance().saveSlot(lastSlot);
+                    }
+
+                    Slot nextSlot = new Slot(channel, channel.getLastSlot() + 
1);
+                    if 
(!Store.instance().slotIsAvailable(nextSlot.getChannel(), nextSlot.getIndex())) 
{
+                        nextSlot.setExpectedTime(lastSlot.getExpectedTime() + 
channel.getInsertInterval());
+                        if (now > nextSlot.getExpectedTime()) {
+                            nextSlot.setRequestTime(now);
+                        } else {
+                            nextSlot.setRequestTime(nextSlot.getExpectedTime() 
+ 3 * DateUtils.MILLIS_PER_MINUTE);
+                        }
+                        Store.instance().saveSlot(nextSlot);
+                    }
+                }
+                Store.instance().saveBookmarks(bookmarks);
+                Store.instance().savePings(pings);
+                Store.instance().savePongs(pongs);
+
+                if (user != null) {
+                    for (Ping ping : pings) {
+                        if (Store.instance().shouldAnswerPing(ping, user)) {
+                            Pong pong = new Pong(ping, user);
+                            Store.instance().savePong(pong);
+                        }
+                    }
+                }
+
+                slot.setAvailable(true);
+                slot.setRequestTime(System.currentTimeMillis());
+                Store.instance().saveSlot(slot);
+
+            } catch (FetchException e) {
+                log.warn("Could not fetch slot at " + slot.getUri() + ", will 
retry later...", e);
+                slot.setAvailable(false);
+                slot.setFailureCount(slot.getFailureCount() + 1);
+                if (slot.getExpectedTime() + 
requestedChannel.getInsertInterval() > now) {
+                    slot.setRequestTime(slot.getRequestTime() + 
requestedChannel.getInsertInterval());
+                } else {
+                    slot.setRequestTime(slot.getRequestTime() + 
slot.getFailureCount()
+                            * requestedChannel.getInsertInterval() / 10);
+                }
+                Store.instance().saveSlot(slot);
+
+                if (slot.getFailureCount() == 5
+                        && Store.instance().slotIsAvailable(slot.getChannel(), 
slot.getIndex() - 2)) {
+                    // create the next slot in the database
+                    Slot nextSlot = new Slot();
+                    nextSlot.setChannel(slot.getChannel());
+                    nextSlot.setIndex(slot.getIndex() + 1);
+                    if 
(!Store.instance().slotIsAvailable(nextSlot.getChannel(), nextSlot.getIndex())) 
{
+                        log.error("It's the fifth time I couldn't fetch slot 
at " + slot.getUri()
+                                + ", will try the next slot as well)");
+                        nextSlot.setExpectedTime(slot.getExpectedTime() + 
requestedChannel.getInsertInterval());
+                        nextSlot.setRequestTime(now);
+                        Store.instance().saveSlot(nextSlot);
+                    }
+                }
+            } catch (Exception e) {
+                log.warn("Failed to fetch slot at " + slot.getUri() + ", 
skipping this slot.", e);
+                slot.setAvailable(true);
+                slot.setFailureCount(slot.getFailureCount() + 1);
+                slot.setInvalid(true);
+                Store.instance().saveSlot(slot);
+
+                // create the next slot in the database
+                Slot nextSlot = new Slot();
+                nextSlot.setChannel(slot.getChannel());
+                nextSlot.setIndex(slot.getIndex() + 1);
+                if (!Store.instance().slotIsAvailable(nextSlot.getChannel(), 
nextSlot.getIndex())) {
+                    nextSlot.setExpectedTime(slot.getExpectedTime() + 
requestedChannel.getInsertInterval());
+                    nextSlot.setRequestTime(now);
+                    Store.instance().saveSlot(nextSlot);
+                }
+            }
+        }
+    }
+
+    public FetchResult fetch(Slot slot) throws FetchException {
+        FreenetURI freenetURI;
+        StopWatch.start("fetching slot at " + slot.getUri());
+        try {
+            freenetURI = new FreenetURI(slot.getUri());
+            HighLevelSimpleClient client = this.obtainClient();
+            FetchResult result = client.fetch(freenetURI);
+            return result;
+        } catch (MalformedURLException e) {
+            String msg = "Slot {0} has an invalid uri";
+            throw new ParamRuntimeException(msg, slot.getUri(), e);
+        } finally {
+            StopWatch.stop();
+        }
+
+    }
+
+    public final static Comparator<Slot> slotsPriorityComparator = new 
Comparator<Slot>() {
+
+        public int compare(Slot o1, Slot o2) {
+
+            int failureCompare = new 
Integer(o1.getFailureCount()).compareTo(new Integer(o2.getFailureCount()));
+            if (failureCompare != 0) {
+                return failureCompare;
+            }
+
+            if (!o1.getChannel().equals(o2.getChannel())) {
+                int indexCompare = new Integer(o1.getIndex()).compareTo(new 
Integer(o2.getIndex()));
+                if (indexCompare != 0) {
+                    return indexCompare;
+                }
+            }
+
+            long expected1 = o1.getRequestTime();
+            long expected2 = o2.getRequestTime();
+
+            return -(new Long(expected1).compareTo(new Long(expected2)));
+        }
+
+    };
+
+    private void insertNextSlotOnPersonalChannel(User user, Channel 
personalChannel) {
+        long lastInsertTime = personalChannel.getLastInsertTime();
+        int lastSlot = personalChannel.getLastSlot();
+        Slot slot = personalChannel.createNextSlot();
+
+        log.info("starting insert of slot " + slot.getIndex() + " of channel " 
+ personalChannel.getBasename());
+        Store.instance().saveChannel(personalChannel);
+        Store.instance().saveSlot(slot);
+
+        Ping ping = new Ping();
+        ping.setInsertTime(System.currentTimeMillis());
+        ping.setSender(user.getPublicSSK());
+        Store.instance().savePing(ping);
+
+        List<AbstractSendable> messagesToBeSend = new 
LinkedList<AbstractSendable>();
+
+        
messagesToBeSend.addAll(Store.instance().getUnpublishedPings(user.getPublicSSK()));
+        
messagesToBeSend.addAll(Store.instance().getUnpublishedPongs(user.getPublicSSK()));
+        
messagesToBeSend.addAll(Store.instance().getUnpublishedBookmarks(user.getPublicSSK()));
+        
messagesToBeSend.addAll(Store.instance().getUnpublishedChannels(user.getPublicSSK()));
+
+        try {
+            log.info("there are " + messagesToBeSend.size() + " messages to be 
published");
+
+            SlotWriter slotWriter = new XmlSlotWriter();
+            slotWriter.setSlot(slot);
+            slotWriter.setKeypair(user);
+
+            slotWriter.writeObjects(messagesToBeSend);
+            this.insert(slotWriter.getInsertUri(), 
slotWriter.getContentType(), slotWriter.toByteArray());
+
+            log.info("Succesfully inserted slot at " + slot.getUri());
+            slot.setAvailable(true);
+            slot.setRequestTime(System.currentTimeMillis());
+            Store.instance().saveSlot(slot);
+
+            for (AbstractSendable sendable : messagesToBeSend) {
+                sendable.setPublished(true);
+                if (sendable instanceof Bookmark) {
+                    Store.instance().saveBookmark((Bookmark) sendable);
+                } else if (sendable instanceof Ping) {
+                    Store.instance().savePing((Ping) sendable);
+                } else if (sendable instanceof Pong) {
+                    Pong pong = (Pong) sendable;
+
+                    Store.instance().savePong(pong);
+                } else if (sendable instanceof Channel) {
+                    Store.instance().saveChannel((Channel) sendable);
+                }
+            }
+
+        } catch (InserterException e) {
+            log.warn("Could not insert slot " + slot.getUri() + ", will 
retry...", e);
+            personalChannel.setLastInsertTime(lastInsertTime);
+            personalChannel.setLastSlot(lastSlot);
+            Store.instance().saveChannel(personalChannel);
+        } catch (Exception e) {
+            log.error("Failed to insert slot " + slot.getUri(), e);
+            personalChannel.setLastInsertTime(lastInsertTime);
+            personalChannel.setLastSlot(lastSlot);
+            Store.instance().saveChannel(personalChannel);
+        }
+    }
+
+    private void insert(String uri, String contentType, byte[] slotData) 
throws InserterException {
+        try {
+            FreenetURI insertUri = new FreenetURI(uri);
+            log.info("Inserting" + slotData.length + " bytes of data with 
content type " + contentType + " with URI "
+                    + uri);
+
+            InsertBlock insertBlock = new InsertBlock(new 
ArrayBucket(slotData), new ClientMetadata(contentType),
+                    insertUri);
+            HighLevelSimpleClient client = this.obtainClient();
+            client.getFetcherContext().localRequestOnly = true;
+            StopWatch.start("Inserting " + slotData.length + " bytes");
+            client.insert(insertBlock, false);
+            StopWatch.stop();
+
+        } catch (InserterException e) {
+            throw e;
+        } catch (Exception e) {
+            String msg = "Failed to insert {0} bytes of data with content type 
{1} with URI {2}";
+            throw new ParamRuntimeException(msg, slotData.length, contentType, 
uri, e);
+        }
+    }
+
+    public HighLevelSimpleClient obtainClient() {
+        return this.client;
+    }
+
+    static String randomString(int length) {
+        StringBuffer buf = new StringBuffer();
+        for (int i = 0; i < length; i++) {
+            char c = (char) (Math.random() * 96 + 32);
+            buf.append(c);
+        }
+        return buf.toString();
+    }
+
+    @Override
+    public void terminate() {
+        Logger.normal(BookmarkPlugin.class, this + " was asked to terminate: 
TODO implenent this");
+        this.terminationRequested = true;
+        Store.instance().shutDown();
+    }
+
+    @Override
+    public String toString() {
+        return "FredPlugin " + this.getClass().getName();
+    }
+
+}
\ No newline at end of file

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/LoginCredentials.java
===================================================================
--- 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/LoginCredentials.java
  2006-03-07 18:55:50 UTC (rev 8182)
+++ 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/LoginCredentials.java
  2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,81 @@
+package falimat.freenet.bookmarkplugin;
+
+import java.net.MalformedURLException;
+
+import xomat.util.ParamException;
+import xomat.util.ParamRuntimeException;
+import falimat.freenet.bookmarkplugin.model.User;
+import falimat.freenet.bookmarkplugin.storage.Store;
+import falimat.freenet.crypt.CryptoUtil;
+
+public class LoginCredentials {
+
+    private final static LoginCredentials instance = new LoginCredentials();
+
+    private User currentUser;
+
+    private LoginCredentials() {
+
+    }
+
+    public static LoginCredentials instance() {
+        return instance;
+    }
+
+    public void login(String publicSSK, String privateSSK) throws 
InvalidLoginException {
+        try {
+            privateSSK = CryptoUtil.convertSSKString(privateSSK, true, false);
+            publicSSK = CryptoUtil.convertSSKString(publicSSK, true, false);
+
+            User user = Store.instance().getUser(publicSSK);
+            boolean keypairCorrect = CryptoUtil.verifyKeypair(publicSSK, 
privateSSK);
+            if (!keypairCorrect) {
+                String msg = "This is not the valid private key for the 
provided public key";
+                throw new InvalidLoginException(msg);
+            }
+            if (user != null) {
+                CryptoUtil.calculateKeys(privateSSK, user);
+            } else {
+                user = new User(null, privateSSK);
+            }
+            this.currentUser = user;
+        } catch (RuntimeException e) {
+            e.printStackTrace();
+            String msg = "The was an unexpected exception during login.";
+            throw new ParamRuntimeException(msg, e);
+        } catch (InvalidLoginException e) {
+            String msg = "Failed to login as user {0}.";
+            throw new InvalidLoginException(msg, publicSSK, e);
+        } catch (MalformedURLException e) {
+            String msg = "This is nt a valid SSK keypair.";
+            throw new InvalidLoginException(msg, e); 
+        }
+
+    }
+
+    public class InvalidLoginException extends ParamException {
+
+        public InvalidLoginException(String msg, String param, Exception e) {
+            super(msg, param, e);
+        }
+
+        public InvalidLoginException(String msg) {
+            super(msg);
+        }
+
+        public InvalidLoginException(String msg, Exception e) {
+            super(msg, e);
+        }
+
+
+    }
+
+    public User getCurrentUser() {
+        return this.currentUser;
+    }
+
+    public void logout() {
+        this.currentUser = null;
+    }
+
+}

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/components/BookmarkEditor.java
===================================================================
--- 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/components/BookmarkEditor.java
 2006-03-07 18:55:50 UTC (rev 8182)
+++ 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/components/BookmarkEditor.java
 2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,153 @@
+package falimat.freenet.bookmarkplugin.components;
+
+import java.text.DateFormat;
+import java.text.NumberFormat;
+import java.util.Date;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.StringTokenizer;
+import java.util.TreeSet;
+
+import falimat.freenet.bookmarkplugin.model.Bookmark;
+import falimat.freenet.bookmarkplugin.storage.Store;
+import falimat.freenet.network.RegExes;
+import falimat.freenet.webplugin.AbstractHtmlComponent;
+import falimat.freenet.webplugin.Action;
+import falimat.freenet.webplugin.ActionComponent;
+import falimat.freenet.webplugin.FormAction;
+import falimat.freenet.webplugin.HtmlPage;
+import falimat.freenet.webplugin.HtmlWriter;
+import falimat.freenet.webplugin.components.DefinitionList;
+import falimat.freenet.webplugin.components.Form;
+import falimat.freenet.webplugin.components.Heading;
+import falimat.freenet.webplugin.components.LabeledDropdown;
+import falimat.freenet.webplugin.components.LabeledTextArea;
+import falimat.freenet.webplugin.components.LabeledTextField;
+import falimat.freenet.webplugin.components.SubmitButton;
+import freenet.pluginmanager.PluginHTTPRequest;
+
+public class BookmarkEditor extends AbstractHtmlComponent implements 
ActionComponent {
+
+    private Bookmark bookmark;
+
+    private Form form;
+
+    private DefinitionList readOnlyProperties = new DefinitionList();
+
+    private LabeledTextField titleField;
+
+    private LabeledTextField tagsField;
+
+    private LabeledDropdown ratingDropdown;
+
+    private LabeledTextArea descriptionArea;
+
+    private SubmitButton saveButton;
+
+    public BookmarkEditor() {
+    }
+
+    public void setBookmark(Bookmark b) {
+        this.bookmark = b;
+        this.form = new Form();
+
+        if (this.bookmark == null) {
+            return;
+        }
+
+        this.form.addComponent(new Heading(2, "Edit Bookmark"));
+        
+        this.readOnlyProperties = new DefinitionList();
+        this.readOnlyProperties.addEntry("URI", bookmark.getUri());
+        this.readOnlyProperties.addEntry("Time", 
DateFormat.getDateTimeInstance().format(new Date(bookmark.getLastModified())));
+        this.readOnlyProperties.addEntry("Content-Type", 
bookmark.getContentType());
+        this.readOnlyProperties
+                .addEntry("Size", 
NumberFormat.getNumberInstance().format(bookmark.getSize()) + " bytes");
+        this.form.addComponent(this.readOnlyProperties);
+
+        this.titleField = new LabeledTextField("title", "Title");
+        this.titleField.setRequired(true);
+        this.titleField.setValue(this.bookmark.getTitle());
+        this.form.addComponent(this.titleField);
+
+        this.tagsField = new LabeledTextField("tags", "Tags");
+        this.tagsField.setRequired(true);
+        this.tagsField.setValidation("Tags must be lower-case and seperated by 
comma or whitespace", RegExes.TAGS);
+        this.tagsField.setValue(tagsAsString(this.bookmark.getTags()));
+        this.form.addComponent(this.tagsField);
+
+        this.ratingDropdown = new LabeledDropdown("rating", "Rating", true, 
"5", "4", "3", "2", "1", "0");
+        this.form.addComponent(this.ratingDropdown);
+
+        this.descriptionArea = new LabeledTextArea("description", 
"Description");
+        this.descriptionArea.setRequired(false);
+        this.descriptionArea.setValue(this.bookmark.getDescription());
+        this.form.addComponent(this.descriptionArea);
+
+        this.saveButton = new SubmitButton("Save");
+        this.saveButton.setAction(new FormAction(this.form) {
+
+            @Override
+            protected void onSubmit(PluginHTTPRequest request) {
+                bookmark.setTitle(titleField.getValue());
+                bookmark.setDescription(descriptionArea.getValue());
+                
bookmark.setRating(request.getIntParam(ratingDropdown.getName(), -1));
+                bookmark.setPublished(false);
+                bookmark.setLastModified(System.currentTimeMillis());
+                bookmark.setTags(tagsAsSet(tagsField.getValue()));
+
+                Store.instance().saveBookmark(bookmark);
+                setBookmark(null);
+            }
+
+            @Override
+            protected void onInvalidSubmission(PluginHTTPRequest request) {
+
+            }
+
+        });
+        this.form.addComponent(this.saveButton);
+
+    }
+
+    private String tagsAsString(Set<String> tags) {
+       StringBuffer buf = new StringBuffer();
+       for(String tag : tags) {
+           if (buf.length()>0) {
+               buf.append(' ');
+           }
+           buf.append(tag);
+       }
+       return buf.toString();
+    }
+
+    public static Set<String> tagsAsSet(String tagString) {
+        StringTokenizer tagTokenizer = new StringTokenizer(tagString, " ,;");
+        SortedSet<String> tags = new TreeSet<String>();
+        while (tagTokenizer.hasMoreTokens()) {
+            tags.add(tagTokenizer.nextToken());
+        }       
+        return tags;
+    }
+    
+    public Action getAction(String id) {
+        if (this.form == null) {
+            return null;
+        } else {
+            return this.form.getAction(id);
+        }
+    }
+
+    public void renderHtml(HtmlWriter out, HtmlPage contextPage) {
+
+        if (this.bookmark != null) {
+            out.beginDiv("bookmark_editor");
+
+            this.form.renderHtml(out, contextPage);
+
+            out.endDiv();
+        }
+
+    }
+
+}

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/components/BookmarkList.java
===================================================================
--- 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/components/BookmarkList.java
   2006-03-07 18:55:50 UTC (rev 8182)
+++ 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/components/BookmarkList.java
   2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,173 @@
+package falimat.freenet.bookmarkplugin.components;
+
+import java.text.NumberFormat;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import falimat.freenet.bookmarkplugin.model.Bookmark;
+import falimat.freenet.bookmarkplugin.storage.Store;
+import falimat.freenet.webplugin.AbstractHtmlComponent;
+import falimat.freenet.webplugin.HtmlPage;
+import falimat.freenet.webplugin.HtmlWriter;
+
+public class BookmarkList extends AbstractHtmlComponent {
+
+    public final static String FREENET_LINK_WARNING = "WARNING: This is a link 
to freenet content. Follow it on your own risk.";
+
+    private String title = "Search results";
+
+    private Map<String, List<Bookmark>> uriBookmarkMap = new TreeMap<String, 
List<Bookmark>>();
+
+    public void renderHtml(HtmlWriter out, HtmlPage contextPage) {
+        out.beginDiv("bookmark_list");
+
+        out.write("<h2>" + this.title + "</h2>");
+
+        out.write("<ul class=\"results\">");
+
+        for (String key : this.uriBookmarkMap.keySet()) {
+            out.write("<li>");
+            this.writeBookmarkTeaser(out, contextPage, 
this.uriBookmarkMap.get(key));
+            out.write("</li>");
+        }
+
+        out.write("</ul>");
+
+        out.endDiv();
+    }
+
+    private Map<String, StringBuffer> createTagMap(List<Bookmark> bookmarks) {
+        Map<String, StringBuffer> map = new TreeMap<String, StringBuffer>();
+        for (Bookmark b : bookmarks) {
+            for (String tag : b.getTags()) {
+                if (map.containsKey(tag)) {
+                    map.get(tag).append(", " + 
Store.instance().getNick(b.getSender()));
+                } else {
+                    StringBuffer buf = new StringBuffer();
+                    buf.append("tagged '"+tag+"' by " + 
Store.instance().getNick(b.getSender()));
+                    map.put(tag, buf);
+                }
+            }
+        }
+        return map;
+    }
+
+    private void writeBookmarkTeaser(HtmlWriter out, HtmlPage contextPage, 
List<Bookmark> groupedBookmarks) {
+        out.beginDiv("teaser");
+
+        boolean isFirstTitle = true;
+        Set<String> titlesDisplayed = new TreeSet<String>();
+
+        int ratingCount = 0;
+        double averageRating = 0;
+        StringBuffer ratingTooltip = new StringBuffer("rated by ");
+        for (Bookmark b : groupedBookmarks) {
+            if (titlesDisplayed.contains(b.getTitle())) {
+                continue;
+            }
+            if (isFirstTitle) {
+                out.write("<h3>");
+                out.writeTooltipItem("Title chosen by " + 
Store.instance().getNick(b.getSender()), b.getTitle(), null);
+                out.write("</h3>");
+                isFirstTitle = false;
+            } else {
+                out.write("<h4>");
+                out.writeTooltipItem("Title chosen by " + 
Store.instance().getNick(b.getSender()), "aka '" + b.getTitle()
+                        + "'", null);
+                out.write("</h4>");
+
+            }
+            titlesDisplayed.add(b.getTitle());
+            if (b.getRating() != -1) {
+                ratingTooltip.append(Store.instance().getNick(b.getSender()) + 
", ");
+                ratingCount++;
+                averageRating += b.getRating();
+            }
+            
+        }
+        if (ratingCount > 0) {
+            averageRating /= ratingCount;
+        }
+        Bookmark b = groupedBookmarks.get(0);
+
+        out.beginDiv("metadata");
+
+        out.beginSpan("attributes");
+        out.write(b.getContentType());
+        out.write(", ");
+        out.write(NumberFormat.getNumberInstance().format(b.getSize()) + " 
bytes");
+        out.endSpan();
+        out.write(" ");
+
+        Map<String, StringBuffer> tagMap = this.createTagMap(groupedBookmarks);
+
+        if (ratingCount > 0) {
+            out.beginSpan("rating");
+            String ratingString = this.createRatingString(averageRating);
+            out.writeTooltipItem(ratingTooltip.toString(), ratingString, null);
+            out.endSpan();
+            out.write(" ");
+        }
+
+        out.beginSpan("tags");
+        for (String tag : tagMap.keySet()) {
+            String tooltip = tagMap.get(tag).toString();
+            out.beginSpan("tag");
+            out.writeTooltipItem(tooltip, tag, null);
+            out.endSpan();
+            out.write(" ");
+        }
+        out.endSpan();
+
+        out.endDiv();
+
+        out.write("<p class=\"description\">");
+
+        for(Bookmark bo : groupedBookmarks) {
+            if (bo.getDescription()!=null && bo.getDescription().length()>0) {
+                String nick = Store.instance().getNick(bo.getSender());
+                out.writeTooltipItem(bo.getSender(), nick+": ", "nick");
+                out.append(bo.getDescription());
+                out.write(" ");
+            }
+        }
+        
+        out.write("</p>");
+
+        out.beginDiv("freenet_link");
+        out.writeLink("/" + groupedBookmarks.get(0).getUri(), b.getUri(), 
FREENET_LINK_WARNING);
+        out.endDiv();
+
+        out.endDiv();
+    }
+
+    private String createRatingString(double averageRating) {
+        StringBuffer out = new StringBuffer();
+
+        String ratingString = 
NumberFormat.getNumberInstance().format(averageRating);
+        if (ratingString.length() > 3) {
+            ratingString = ratingString.substring(0, 3);
+        }
+        out.append("" + ratingString + "/5");
+        return out.toString();
+    }
+
+    public void setBookmarks(List<Bookmark> bookmarks) {
+        this.uriBookmarkMap.clear();
+
+        for (Bookmark b : bookmarks) {
+            String uri = b.getUri();
+            List<Bookmark> bookmarksForUri = this.uriBookmarkMap.get(uri);
+            if (bookmarksForUri == null) {
+                bookmarksForUri = new LinkedList<Bookmark>();
+                this.uriBookmarkMap.put(uri, bookmarksForUri);
+            }
+            bookmarksForUri.add(b);
+        }
+    }
+
+}

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/components/BookmarkSearchForm.java
===================================================================
--- 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/components/BookmarkSearchForm.java
     2006-03-07 18:55:50 UTC (rev 8182)
+++ 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/components/BookmarkSearchForm.java
     2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,70 @@
+package falimat.freenet.bookmarkplugin.components;
+
+import java.util.List;
+import java.util.Set;
+
+import falimat.freenet.bookmarkplugin.model.Bookmark;
+import falimat.freenet.bookmarkplugin.storage.Store;
+import falimat.freenet.network.RegExes;
+import falimat.freenet.webplugin.FormAction;
+import falimat.freenet.webplugin.components.Form;
+import falimat.freenet.webplugin.components.Heading;
+import falimat.freenet.webplugin.components.LabeledDropdown;
+import falimat.freenet.webplugin.components.LabeledTextField;
+import falimat.freenet.webplugin.components.SubmitButton;
+import freenet.pluginmanager.PluginHTTPRequest;
+
+public class BookmarkSearchForm extends Form {
+
+    private Heading heading = new Heading(2, "Search for bookmarks...");
+
+    private LabeledTextField textQueryField = new 
LabeledTextField("textQuery", "...that contain one of these words");
+
+    private LabeledTextField tagQueryField = new LabeledTextField("tagQuery", 
"...that have one of these tags");
+
+    private LabeledDropdown contentTypeDropdown = new 
LabeledDropdown("contentTypeQuery",
+            "...that have a specific content type", true, "text/plain", 
"text/html", "image/jpeg");
+
+    private SubmitButton searchButton = new SubmitButton("Search");
+
+    private BookmarkList resultList = new BookmarkList();
+    
+    public BookmarkSearchForm() {
+        this.constructComponents();
+    }
+
+    protected void constructComponents() {
+        super.addComponent(this.heading);
+        this.addComponent(this.textQueryField);
+        this.addComponent(this.tagQueryField);
+        this.addComponent(this.contentTypeDropdown);
+        this.addComponent(this.searchButton);
+
+        this.tagQueryField.setValidation("Tags must be lower case and a-z 
only.", RegExes.TAGS);
+        
+        this.searchButton.setAction(new FormAction(this) {
+
+            @Override
+            protected void onSubmit(PluginHTTPRequest request) {
+                String text = textQueryField.getValue();
+                Set<String> tags = 
BookmarkEditor.tagsAsSet(tagQueryField.getValue());
+                String contentType = contentTypeDropdown.getValue();
+                List<Bookmark> results = Store.instance().queryBookmarks(text, 
tags, contentType);
+                if (resultList!=null) {
+                    resultList.setBookmarks(results);
+                }
+            }
+
+            @Override
+            protected void onInvalidSubmission(PluginHTTPRequest request) {
+                // TODO Auto-generated method stub
+
+            }
+
+        });
+    }
+
+    public void setResultList(BookmarkList list) {
+       this.resultList = list;
+    }
+}

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/components/LoginMessage.java
===================================================================
--- 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/components/LoginMessage.java
   2006-03-07 18:55:50 UTC (rev 8182)
+++ 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/components/LoginMessage.java
   2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,52 @@
+package falimat.freenet.bookmarkplugin.components;
+
+import falimat.freenet.bookmarkplugin.LoginCredentials;
+import falimat.freenet.bookmarkplugin.model.User;
+import falimat.freenet.bookmarkplugin.ui.AddBookmarkPage;
+import falimat.freenet.webplugin.AbstractHtmlComponent;
+import falimat.freenet.webplugin.Action;
+import falimat.freenet.webplugin.ActionComponent;
+import falimat.freenet.webplugin.HtmlPage;
+import falimat.freenet.webplugin.HtmlWriter;
+import falimat.freenet.webplugin.SimpleAction;
+import falimat.freenet.webplugin.components.ActionButton;
+import freenet.pluginmanager.PluginHTTPRequest;
+
+public class LoginMessage extends AbstractHtmlComponent implements 
ActionComponent {
+    ActionButton logoutButton = new ActionButton("x", "Logout");
+    
+    public LoginMessage(final AddBookmarkPage addBookmarkPage) {
+        
+        logoutButton.setAction(new SimpleAction() {
+    
+            @Override
+            public void execute(PluginHTTPRequest request) {
+                LoginCredentials.instance().logout();
+                addBookmarkPage.onLogout();
+            }
+    
+        });
+        
+    }    
+    public void renderHtml(HtmlWriter out, HtmlPage contextPage) {
+        
+        out.beginDiv("login_message");
+        User user = LoginCredentials.instance().getCurrentUser();
+        
+        if (user==null) {
+            out.write("You are not logged in.");
+        } else {
+            out.write("You are logged in as ");
+            out.writeTooltipItem(user.getPublicSSK(), user.getName(), "nick");
+            this.logoutButton.renderHtml(out, contextPage);            
+        }
+        
+
+        
+        out.endDiv();
+    }
+    public Action getAction(String id) {
+       return this.logoutButton.getAction(id);
+    }
+
+}

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/components/SideBar.java
===================================================================
--- 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/components/SideBar.java
        2006-03-07 18:55:50 UTC (rev 8182)
+++ 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/components/SideBar.java
        2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,30 @@
+package falimat.freenet.bookmarkplugin.components;
+
+import falimat.freenet.webplugin.AbstractHtmlComponent;
+import falimat.freenet.webplugin.HtmlPage;
+import falimat.freenet.webplugin.HtmlWriter;
+import falimat.freenet.webplugin.components.NavigationMenu;
+
+public class SideBar extends AbstractHtmlComponent {
+
+    private NavigationMenu navigation;
+    
+    public void renderHtml(HtmlWriter out, HtmlPage contextPage) {
+       out.beginDiv("sidebar");
+       
+       if (this.navigation!=null) {
+           navigation.renderHtml(out, contextPage);
+       }
+       
+       out.endDiv();
+    }
+
+    public NavigationMenu getNavigation() {
+        return this.navigation;
+    }
+
+    public void setNavigation(NavigationMenu navigation) {
+        this.navigation = navigation;
+    }
+
+}

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/model/AbstractSendable.java
===================================================================
--- 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/model/AbstractSendable.java
    2006-03-07 18:55:50 UTC (rev 8182)
+++ 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/model/AbstractSendable.java
    2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,34 @@
+package falimat.freenet.bookmarkplugin.model;
+
+public class AbstractSendable {
+    String sender;
+
+    private boolean published = false;
+
+    private long lastModified;
+
+    public String getSender() {
+        return this.sender;
+    }
+
+    public void setSender(String user) {
+        this.sender = user;
+    }
+
+    public boolean isPublished() {
+        return this.published;
+    }
+
+    public void setPublished(boolean published) {
+        this.published = published;
+    }
+
+    public long getLastModified() {
+        return this.lastModified;
+    }
+
+    public void setLastModified(long time) {
+        this.lastModified = time;
+    }
+
+}

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/model/Bookmark.java
===================================================================
--- 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/model/Bookmark.java
    2006-03-07 18:55:50 UTC (rev 8182)
+++ 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/model/Bookmark.java
    2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,81 @@
+package falimat.freenet.bookmarkplugin.model;
+
+import java.util.Set;
+import java.util.TreeSet;
+
+public class Bookmark  extends AbstractSendable{
+    private String uri; 
+
+    private long size;
+
+    private String contentType;
+
+    private String title;
+
+
+    private String description;
+
+    private int rating;
+ 
+    private Set<String> tags = new TreeSet<String>();
+
+    public String getId() {
+        return this.uri + this.getSender(); 
+    }
+
+    public String getContentType() {
+        return this.contentType;
+    }
+
+    public void setContentType(String contentType) {
+        this.contentType = contentType;
+    }
+
+    public String getDescription() {
+        return this.description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public int getRating() {
+        return this.rating;
+    }
+
+    public void setRating(int rating) {
+        this.rating = rating;
+    }
+
+    public long getSize() {
+        return this.size;
+    }
+
+    public void setSize(long size) {
+        this.size = size;
+    }
+
+    public Set<String> getTags() {
+        return this.tags;
+    }
+
+    public void setTags(Set<String> tags) {
+        this.tags = tags;
+    }
+
+    public String getTitle() {
+        return this.title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getUri() {
+        return this.uri;
+    }
+
+    public void setUri(String uri) {
+        this.uri = uri;
+    }
+}

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/model/Channel.java
===================================================================
--- 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/model/Channel.java 
    2006-03-07 18:55:50 UTC (rev 8182)
+++ 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/model/Channel.java 
    2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,82 @@
+package falimat.freenet.bookmarkplugin.model;
+
+import java.io.Serializable;
+
+public class Channel extends AbstractSendable implements Serializable {
+
+    /**
+     * 
+     */
+    private static final long serialVersionUID = -2560470916611131394L;
+
+    public static Channel A = new Channel();
+
+    public static Channel B = new Channel();
+
+    static {
+        A.sender = "SSK at 
5xiiXlXU16c3ERR4mBEH-KKSnWHH8TWJlY0og8Lb2YI,m1WIfXV-pDMLy1GYYSmBgj98u~y9PPYwmq-vLqVUFf4,AQABAAE/";
+        A.basename = "tags";
+        A.lastSlot = 0;
+        A.lastInsertTime = 0;
+        A.insertInterval = 5 * 60 * 1000;
+
+        B.sender = "SSK at 
6IBcICgd8hXMnIBUOO9LXwrm0YTG0s6YkGRt0-Hgwm4,sNACThZojXbr8DrMuXD5VEsOvkD5jx3-ckbue0-q9AM,AQABAAE/";
+        B.basename = "tags";
+        B.lastSlot = 3;
+        B.lastInsertTime = System.currentTimeMillis();
+        B.insertInterval = 1 * 60 * 1000;
+    }
+
+    private String basename;
+
+    private int lastSlot = -1;
+
+    private long lastInsertTime = -1;
+
+    private int insertInterval = 10 * 60 * 1000;
+
+    public String getBasename() {
+        return this.basename;
+    }
+
+    public void setBasename(String baseName) {
+        this.basename = baseName;
+    }
+
+    public int getInsertInterval() {
+        return this.insertInterval;
+    }
+
+    public void setInsertInterval(int insertInterval) {
+        this.insertInterval = insertInterval;
+    }
+
+    public long getLastInsertTime() {
+        return this.lastInsertTime;
+    }
+
+    public void setLastInsertTime(long lastInsertTime) {
+        this.lastInsertTime = lastInsertTime;
+    }
+
+    public int getLastSlot() {
+        return this.lastSlot;
+    }
+
+    public void setLastSlot(int lastSlot) {
+        this.lastSlot = lastSlot;
+    }
+
+    public String getId() {
+        return this.sender + "/" +this.basename;
+    }
+
+    public Slot createNextSlot() {
+        this.lastSlot++;
+        Slot slot = new Slot(this, this.lastSlot);
+        slot.setExpectedTime(System.currentTimeMillis());
+        this.lastInsertTime = slot.getExpectedTime();
+        this.setPublished(false);
+        return slot;
+    }
+}

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/model/Ping.java
===================================================================
--- 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/model/Ping.java    
    2006-03-07 18:55:50 UTC (rev 8182)
+++ 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/model/Ping.java    
    2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,31 @@
+package falimat.freenet.bookmarkplugin.model;
+
+import java.util.LinkedList;
+import java.util.List;
+
+public class Ping extends AbstractSendable {
+
+    private List<String> recipientList = new LinkedList<String>();
+
+    protected long insertTime;
+
+    public long getInsertTime() {
+        return this.insertTime;
+    }
+
+    public void setInsertTime(long insertTime) {
+        this.insertTime = insertTime;
+    }
+
+    public List<String> getRecipientList() {
+        return this.recipientList;
+    }
+
+    public void setRecipientList(List<String> recipientList) {
+        this.recipientList = recipientList;
+    }
+
+    public String getId() {
+        return super.getSender() + "-" + this.getInsertTime();
+    }
+}

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/model/Pong.java
===================================================================
--- 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/model/Pong.java    
    2006-03-07 18:55:50 UTC (rev 8182)
+++ 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/model/Pong.java    
    2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,46 @@
+package falimat.freenet.bookmarkplugin.model;
+
+public class Pong extends Ping {
+    private long pingInsertTime;
+
+    private long pingFetchTime;
+
+    private String pingId;
+
+    public Pong() {
+
+    }
+
+    public Pong(Ping ping, User user) {
+        this.sender = user.getPublicSSK();
+        this.pingId = ping.getId();
+        this.pingInsertTime = ping.getInsertTime();
+        this.pingFetchTime = System.currentTimeMillis();
+        this.insertTime = System.currentTimeMillis();
+    }
+
+    public long getPingFetchTime() {
+        return this.pingFetchTime;
+    }
+
+    public void setPingFetchTime(long pingFetchTime) {
+        this.pingFetchTime = pingFetchTime;
+    }
+
+    public long getPingInsertTime() {
+        return this.pingInsertTime;
+    }
+
+    public String getPingId() {
+        return this.pingId;
+    }
+
+    public void setPingId(String pingId) {
+        this.pingId = pingId;
+    }
+
+    public void setPingInsertTime(long pingInsertTime) {
+        this.pingInsertTime = pingInsertTime;
+    }
+
+}

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/model/Slot.java
===================================================================
--- 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/model/Slot.java    
    2006-03-07 18:55:50 UTC (rev 8182)
+++ 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/model/Slot.java    
    2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,99 @@
+package falimat.freenet.bookmarkplugin.model;
+
+public class Slot {
+
+    private String channel;
+
+    private int index;
+
+    private boolean available = false;
+
+    private boolean invalid = false;
+    
+    private int failureCount = 0;
+
+    private long expectedTime;
+
+    private long requestTime;
+
+    private String sender;
+
+    public Slot() {
+        
+    }
+    
+    public Slot(Channel channel, int index) {
+       this.channel = channel.getId();
+       this.sender = channel.getSender();
+       this.index = index;
+    }
+
+    public String getSender() {
+        return this.sender;
+    }
+
+    public void setSender(String sender) {
+        this.sender = sender;
+    }
+
+    public String getChannel() {
+        return this.channel;
+    }
+
+    public void setChannel(String channel) {
+        this.channel = channel;
+    }
+
+    public int getIndex() {
+        return this.index;
+    }
+
+    public void setIndex(int index) {
+        this.index = index;
+    }
+
+    public boolean isAvailable() {
+        return this.available;
+    }
+
+    public void setAvailable(boolean inserted) {
+        this.available = inserted;
+    }
+
+    public int getFailureCount() {
+        return this.failureCount;
+    }
+
+    public void setFailureCount(int insertFailures) {
+        this.failureCount = insertFailures;
+    }
+
+    public long getRequestTime() {
+        return this.requestTime;
+    }
+
+    public void setRequestTime(long insertTime) {
+        this.requestTime = insertTime;
+    }
+
+    public String getUri() {
+        return this.channel + "-" + this.index;
+    }
+
+    public long getExpectedTime() {
+        return this.expectedTime;
+    }
+
+    public void setExpectedTime(long expectedTime) {
+        this.expectedTime = expectedTime;
+    }
+
+    public boolean isInvalid() {
+        return this.invalid;
+    }
+
+    public void setInvalid(boolean invalid) {
+        this.invalid = invalid;
+    }
+
+}

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/model/User.java
===================================================================
--- 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/model/User.java    
    2006-03-07 18:55:50 UTC (rev 8182)
+++ 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/model/User.java    
    2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,102 @@
+package falimat.freenet.bookmarkplugin.model;
+
+import java.net.MalformedURLException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import xomat.util.ParamRuntimeException;
+
+import falimat.freenet.crypt.CryptoUtil;
+import freenet.crypt.RandomSource;
+import freenet.keys.InsertableClientSSK;
+
+public class User extends AbstractSendable{
+
+    private final static Log log = LogFactory.getLog(User.class);
+
+    private String name;
+
+    private String publicSSK;
+    
+    private String privateSSK;
+
+    private byte[] dsaPublicKey;
+
+    private byte[] dsaPrivateKey;
+
+    private byte[] dhPublicKey;
+
+    public User(String name, String privateSSK) throws MalformedURLException {
+        this.name = name;
+        this.privateSSK = privateSSK;
+        CryptoUtil.calculateKeys(privateSSK, this);
+    }
+
+    public User() {
+
+    }
+
+    public User(String name, RandomSource random) {
+        try {
+            this.name = name;
+            this.privateSSK = 
CryptoUtil.convertSSKString(InsertableClientSSK.createRandom(random).getInsertURI().toString(),
 true, false);
+            CryptoUtil.calculateKeys(this.privateSSK, this);
+        } catch (MalformedURLException e) {
+            String msg = ("Failed to create user {0} with new random keypair");
+            throw new ParamRuntimeException(msg, name, e);
+        }
+    }
+
+    public void calculateKeys(String publicSSK, byte[] dsaPrivateKey, byte[] 
dasPublicKey, byte[] dhPublicKey, String privateSSK) {
+        this.publicSSK = publicSSK;
+        this.dsaPrivateKey = dsaPrivateKey;
+        this.dsaPublicKey = dasPublicKey;
+        this.dhPublicKey = dhPublicKey;
+        this.privateSSK = privateSSK;
+    }
+
+    public byte[] getDhPublicKey() {
+        return this.dhPublicKey;
+    }
+
+    public void setDhPublicKey(byte[] dhPublicKey) {
+        this.dhPublicKey = dhPublicKey;
+    }
+
+    public byte[] getDhPrivateKey() {
+        return this.dsaPrivateKey;
+    }
+
+    public byte[] getDsaPrivateKey() {
+        return this.dsaPrivateKey;
+    }
+
+    public byte[] getDsaPublicKey() {
+        return this.dsaPublicKey;
+    }
+
+    public void setDsaPublicKey(byte[] dsaPublicKey) {
+        this.dsaPublicKey = dsaPublicKey;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public void setName(String nick) {
+        this.name = nick;
+    }
+
+    public String getPublicSSK() {
+        return this.publicSSK;
+    }
+
+    public void setPublicSSK(String publicSSK) {
+        this.publicSSK = publicSSK;
+    }
+
+    public String getPrivateSSK() {
+        return this.privateSSK;
+    }
+}

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/storage/Store.java
===================================================================
--- 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/storage/Store.java 
    2006-03-07 18:55:50 UTC (rev 8182)
+++ 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/storage/Store.java 
    2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,333 @@
+package falimat.freenet.bookmarkplugin.storage;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import falimat.beanstore.BeanStore;
+import falimat.beanstore.BeanStoreFactory;
+import falimat.beanstore.GenericQuery;
+import falimat.beanstore.StoreQuery;
+import falimat.beanstore.BeanStore.KeyProvider;
+import falimat.freenet.bookmarkplugin.BookmarkPlugin;
+import falimat.freenet.bookmarkplugin.model.Bookmark;
+import falimat.freenet.bookmarkplugin.model.Channel;
+import falimat.freenet.bookmarkplugin.model.Ping;
+import falimat.freenet.bookmarkplugin.model.Pong;
+import falimat.freenet.bookmarkplugin.model.Slot;
+import falimat.freenet.bookmarkplugin.model.User;
+
+public class Store {
+
+    private final static Log log = LogFactory.getLog(Store.class);
+
+    private BeanStore<Channel> channelStore;
+
+    private BeanStore<Slot> slotStore;
+
+    private BeanStore<Bookmark> bookmarkStore;
+
+    private BeanStore<User> userStore;
+
+    private BeanStore<Ping> pingStore;
+
+    private BeanStore<Pong> pongStore;
+
+    private static Store instance;
+
+    private BeanStoreFactory beanStoreFactory;
+
+    private final static KeyProvider<Channel> channelKeyProvider = new 
KeyProvider<Channel>() {
+        public String getKey(Channel data) {
+            return data.getId();
+        }
+    };
+
+    private final static KeyProvider<Bookmark> bookmarkKeyProvider = new 
KeyProvider<Bookmark>() {
+        public String getKey(Bookmark data) {
+            return data.getId();
+        }
+    };
+
+    private final static KeyProvider<Ping> pingKeyProvider = new 
KeyProvider<Ping>() {
+        public String getKey(Ping data) {
+            return data.getId();
+        }
+    };
+
+    private final static KeyProvider<Pong> pongKeyProvider = new 
KeyProvider<Pong>() {
+        public String getKey(Pong data) {
+            return data.getId();
+        }
+    };
+
+    private Store() {
+        this.beanStoreFactory = new BeanStoreFactory();
+        this.beanStoreFactory.setStorageDirectory(new File("./bookmarks"));
+
+        this.channelStore = this.beanStoreFactory.getBeanStore("channels", 
Channel.class);
+        this.channelStore.setPropertiesToIndexAsKeywords("sender", "baseName", 
"lastInsertTime", "insertInterval",
+                "lastSlot", "published");
+
+        this.slotStore = this.beanStoreFactory.getBeanStore("slots", 
Slot.class);
+        this.slotStore.setPropertiesToIndexAsKeywords("channel", "sender", 
"available", "failureCount", "requestTime",
+                "insertTime");
+
+        this.bookmarkStore = this.beanStoreFactory.getBeanStore("bookmarks", 
Bookmark.class);
+        this.bookmarkStore.setPropertiesToIndexAsKeywords("tags", 
"contentType", "sender", "uri", "rating", "size",
+                "time", "published");
+        this.bookmarkStore.setPropertiesToIndexAsText("description", "title", 
"tags");
+
+        this.userStore = this.beanStoreFactory.getBeanStore("users", 
User.class);
+        this.userStore.setPropertiesToIndexAsKeywords("name");
+
+        this.pingStore = this.beanStoreFactory.getBeanStore("pings", 
Ping.class);
+        this.pingStore.setPropertiesToIndexAsKeywords("sender", "insertTime", 
"published");
+
+        this.pongStore = this.beanStoreFactory.getBeanStore("pongs", 
Pong.class);
+        this.pongStore.setPropertiesToIndexAsKeywords("sender", "pingId", 
"published");
+    }
+
+    public Channel getChannel(String id) {
+        return this.channelStore.get(id);
+    }
+
+    public boolean isOutdated(Channel channel) {
+        Channel existing = this.channelStore.get(channel.getId());
+        if (existing != null && existing.getLastModified() > 
channel.getLastModified()) {
+            log.info("not saving channel " + channel.getId()
+                    + " because there already is a more recent version in the 
store");
+            return true;
+        }
+        return false;
+    }
+
+    public List<Channel> getUnpublishedChannels(String publicSSK) {
+        GenericQuery query = StoreQuery.is("sender", 
publicSSK).andIs("published", false);
+        return this.channelStore.executeQuery(query);
+    }
+
+    public void saveChannel(Channel channel) {
+        if (!isOutdated(channel)) {
+            this.channelStore.put(channel.getId(), channel);
+        }
+    }
+
+    public void saveBookmarks(List<Bookmark> bookmarks) {
+        for (Iterator<Bookmark> it = bookmarks.iterator(); it.hasNext();) {
+            Bookmark bookmark = it.next();
+            if (isOutdated(bookmark)) {
+                it.remove();
+            }
+        }
+        this.bookmarkStore.put(bookmarkKeyProvider, bookmarks);
+    }
+
+    public void saveBookmark(Bookmark bookmark) {
+        if (!isOutdated(bookmark)) {
+            this.bookmarkStore.put(bookmark.getId(), bookmark);
+        }
+    }
+
+    public boolean isOutdated(Bookmark bookmark) {
+        Bookmark existing = this.bookmarkStore.get(bookmark.getId());
+        if (existing != null && existing.getLastModified() > 
bookmark.getLastModified()) {
+            log.info("not saving bookmark " + bookmark.getId()
+                    + " because there already is a more recent version in the 
store");
+            return true;
+        }
+        return false;
+    }
+
+    public List<Bookmark> getUnpublishedBookmarks(String publicSSK) {
+        GenericQuery query = StoreQuery.is("sender", 
publicSSK).andIs("published", false);
+        return this.bookmarkStore.executeQuery(query);
+    }
+
+    public List<Bookmark> queryBookmarks(String text, Set<String> tags, String 
contentType) {
+        GenericQuery combinedQuery = null;
+        if (text != null && text.length() > 0) {
+            combinedQuery = StoreQuery.contains(text);
+        }
+
+        if (tags.size() > 0) {
+            GenericQuery tagSubQuery = null;
+            for (String tag : tags) {
+                if (tagSubQuery == null) {
+                    tagSubQuery = StoreQuery.is("tags", tag);
+                } else {
+                    tagSubQuery = tagSubQuery.orIs("tags", tag);
+                }
+            }
+            if (combinedQuery == null) {
+                combinedQuery = tagSubQuery;
+            } else {
+                combinedQuery = combinedQuery.andMatches(tagSubQuery);
+            }
+        }
+
+        if (contentType != null && contentType.length() > 0) {
+            GenericQuery typeSubQuery = StoreQuery.is("contentType", 
contentType);
+            if (combinedQuery == null) {
+                combinedQuery = typeSubQuery;
+            } else {
+                combinedQuery = combinedQuery.andMatches(typeSubQuery);
+            }
+        }
+
+        if (combinedQuery != null) {
+            return this.bookmarkStore.executeQuery(combinedQuery);
+        } else {
+            return this.getAllBookmarks();
+        }
+
+    }
+
+    public boolean containsBookmark(Bookmark bookmark) {
+        return this.bookmarkStore.get(bookmark.getId()) != null;
+    }
+
+    public List<Bookmark> getAllBookmarks() {
+        return this.bookmarkStore.listValues();
+    }
+
+    public List<Channel> getAllChannels() {
+        return this.channelStore.listValues();
+    }
+
+    public Channel getChannelBySender(String publicSSK) {
+        return this.channelStore.queryUniqueKeyword("sender", publicSSK);
+    }
+
+    public final static synchronized Store instance() {
+        if (instance == null) {
+            instance = new Store();
+        }
+        return instance;
+    }
+
+    public void shutDown() {
+        this.channelStore.shutDown();
+    }
+
+    public String getNick(String publicKey) {
+        if (publicKey == null) {
+            return "null";
+        }
+        User user = getUser(publicKey);
+        if (user != null) {
+            return user.getName();
+        }
+        return "unknown user";
+    }
+
+    public User getUserByName(String name) {
+        return this.userStore.queryUniqueKeyword("name", name);
+    }
+
+    public Bookmark loadBookmark(Bookmark bookmark) {
+        return this.bookmarkStore.get(bookmark.getId());
+    }
+
+    public User getUser(String publicSSK) {
+        return this.userStore.get(publicSSK);
+    }
+
+    public void saveUsers(List<User> users) {
+        for (User u : users) {
+            this.saveUser(u);
+        }
+    }
+
+    public boolean isOutdated(User user) {
+        User existing = this.userStore.get(user.getPublicSSK());
+        if (existing != null && existing.getLastModified() > 
user.getLastModified()) {
+            log.info("not saving user " + user.getPublicSSK()
+                    + " because there already is a more recent version in the 
store");
+            return true;
+        }
+        return false;
+    }
+
+    public void saveUser(User user) {
+        if (!isOutdated(user)) {
+            this.userStore.put(user.getPublicSSK(), user);
+        }
+    }
+
+    public void saveSlot(Slot slot) {
+        this.slotStore.put(slot.getUri(), slot);
+    }
+
+    public List<Slot> getSlotsToBeFetched(User user) {
+        GenericQuery query = StoreQuery.is("available", false);
+        List<Slot> slotsToBeFetched = this.slotStore.executeQuery(query);
+        // TODO: Implement range queries so we don't have to sort out here
+        long now = System.currentTimeMillis();
+        for (Iterator<Slot> it = slotsToBeFetched.iterator(); it.hasNext();) {
+            Slot nextSlot = it.next();
+            if (nextSlot.getRequestTime() > now) {
+                it.remove();
+            }
+        }
+        Collections.sort(slotsToBeFetched, 
BookmarkPlugin.slotsPriorityComparator);
+        return slotsToBeFetched;
+    }
+
+    public void savePing(Ping ping) {
+        this.pingStore.put(ping.getId(), ping);
+    }
+
+    public void savePong(Pong pong) {
+        this.pongStore.put(pong.getId(), pong);
+    }
+
+    public List<Ping> getUnpublishedPings(String publicSSK) {
+        GenericQuery query = StoreQuery.is("sender", 
publicSSK).andIs("published", false);
+        return this.pingStore.executeQuery(query);
+    }
+
+    public List<Pong> getUnpublishedPongs(String publicSSK) {
+        GenericQuery query = StoreQuery.is("sender", 
publicSSK).andIs("published", false);
+        return this.pongStore.executeQuery(query);
+    }
+
+    public void savePings(List<Ping> pings) {
+        this.pingStore.put(pingKeyProvider, pings);
+    }
+
+    public void savePongs(List<Pong> pongs) {
+        this.pongStore.put(pongKeyProvider, pongs);
+    }
+
+    public List<Slot> getAllSlots() {
+        return this.slotStore.listValues();
+    }
+
+    public Object getSlot(String uri) {
+        return this.slotStore.get(uri);
+    }
+
+    public boolean slotIsAvailable(String channel, int i) {
+        Slot slot = new Slot();
+        slot.setChannel(channel);
+        slot.setIndex(i);
+        Slot existing = this.slotStore.get(slot.getUri());
+        if (existing != null && existing.isAvailable()) {
+            return true;
+        }
+        return false;
+    }
+
+    public boolean shouldAnswerPing(Ping ping, User user) {
+        GenericQuery query = StoreQuery.is("pingId", 
ping.getId()).andIs("sender", user.getPublicSSK());
+        List<Pong> pongs = this.pongStore.executeQuery(query);
+        return pongs.size()==0;
+    }
+
+}

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/ui/AddBookmarkPage.java
===================================================================
--- 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/ui/AddBookmarkPage.java
        2006-03-07 18:55:50 UTC (rev 8182)
+++ 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/ui/AddBookmarkPage.java
        2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,324 @@
+/**
+ * 
+ */
+package falimat.freenet.bookmarkplugin.ui;
+
+import falimat.freenet.bookmarkplugin.BookmarkPlugin;
+import falimat.freenet.bookmarkplugin.LoginCredentials;
+import falimat.freenet.bookmarkplugin.LoginCredentials.InvalidLoginException;
+import falimat.freenet.bookmarkplugin.components.BookmarkEditor;
+import falimat.freenet.bookmarkplugin.model.Bookmark;
+import falimat.freenet.bookmarkplugin.model.Channel;
+import falimat.freenet.bookmarkplugin.model.User;
+import falimat.freenet.bookmarkplugin.storage.Store;
+import falimat.freenet.network.RegExes;
+import falimat.freenet.webplugin.FormAction;
+import falimat.freenet.webplugin.HtmlPage;
+import falimat.freenet.webplugin.KeyNotFoundException;
+import falimat.freenet.webplugin.SimpleAction;
+import falimat.freenet.webplugin.components.ActionButton;
+import falimat.freenet.webplugin.components.DefinitionList;
+import falimat.freenet.webplugin.components.Form;
+import falimat.freenet.webplugin.components.Heading;
+import falimat.freenet.webplugin.components.LabeledTextField;
+import falimat.freenet.webplugin.components.MessageArea;
+import falimat.freenet.webplugin.components.SubmitButton;
+import freenet.client.FetchResult;
+import freenet.client.HighLevelSimpleClient;
+import freenet.crypt.Yarrow;
+import freenet.keys.FreenetURI;
+import freenet.pluginmanager.PluginHTTPRequest;
+
+public class AddBookmarkPage extends HtmlPage {
+    /**
+     * 
+     */
+    private final BookmarkPlugin bookmarkPlugin;
+
+    public AddBookmarkPage(BookmarkPlugin plugin, String name) {
+        super(name);
+        this.bookmarkPlugin = plugin;
+    }
+
+    private Form addBookmarkForm;
+
+    private LabeledTextField uriTextField;
+
+    private SubmitButton checkButton;
+
+    private MessageArea messageArea;
+
+    private Bookmark bookmark;
+
+    private LoginForm loginForm;
+    
+    private NewUserForm newUserForm;
+
+    private BookmarkEditor editor = new BookmarkEditor();
+
+    @Override
+    public void constructComponents() {
+
+        this.loginForm = new LoginForm();
+        this.loginForm.constructComponents();
+        this.addComponent(this.loginForm);
+        
+        this.newUserForm = new NewUserForm();
+        this.newUserForm.constructComponents();
+        this.addComponent(this.newUserForm);
+        this.newUserForm.hide();
+
+        this.addBookmarkForm = new Form();
+
+        this.addBookmarkForm.addComponent(new Heading(2, "Create new or edit 
existing bookmark..."));
+
+        this.uriTextField = new LabeledTextField("uri", "Enter URI to 
bookmark");
+        this.uriTextField.setRequired(true);
+        this.uriTextField.setValidation("This is not a valid freenet URI", 
RegExes.FREENET_URI);
+
+        this.addBookmarkForm.addComponent(this.uriTextField);
+
+        this.checkButton = new SubmitButton("Create");
+        this.checkButton.setAction(new FormAction(this.addBookmarkForm) {
+            @Override
+            protected void onSubmit(PluginHTTPRequest request) {
+                messageArea.hide();
+                try {
+                    bookmark = createBookmark(uriTextField.getValue());
+                    if (Store.instance().containsBookmark(bookmark)) {
+                        bookmark = Store.instance().loadBookmark(bookmark);
+                    }
+                    editor.setBookmark(bookmark);
+                } catch (KeyNotFoundException e) {
+                    messageArea.show("The key could not be retrieved from your 
local node",
+                            "You can only bookmark keys that you have recently 
requested.");
+                    messageArea.showStackTrace(e);
+                } finally {
+                    addBookmarkForm.resetFormValues();
+                }
+            }
+
+            @Override
+            protected void onInvalidSubmission(PluginHTTPRequest request) {
+                messageArea.hide();
+                editor.setBookmark(null);
+            }
+        });
+
+        this.addBookmarkForm.addComponent(this.checkButton);
+        this.addBookmarkForm.hide();
+
+        super.addComponent(this.addBookmarkForm);
+
+        this.messageArea = new MessageArea();
+        this.messageArea.hide();
+
+        this.addComponent(this.messageArea);
+
+        this.addComponent(this.editor);
+
+        ActionButton newAccountButton = new ActionButton("New Account", 
"Create a new account with a random keypair");
+        newAccountButton.setAction(new SimpleAction() {
+
+            @Override
+            public void execute(PluginHTTPRequest request) {
+                LoginCredentials.instance().logout();
+                addBookmarkForm.hide();       
+                loginForm.hide();
+                newUserForm.show();
+                newUserForm.resetFormValues();
+                editor.setBookmark(null);
+            }
+
+        });
+        this.addComponent(newAccountButton);        
+        
+
+    }
+    
+    class NewUserForm extends Form {
+        
+        private User user;
+        
+        NewUserForm() {
+
+        }
+        
+        @Override
+        public void show() {
+            this.constructComponents();
+            super.show();
+        }
+        
+        public void constructComponents() {
+            this.user = new User(null, new Yarrow());
+            
+            super.removeAllComponents();
+            
+            Heading heading = new Heading(2, "Create User Account");
+            this.addComponent(heading);
+            
+            MessageArea area = new MessageArea();
+            area.show("Your generated keypair", "This is a randomly generated 
keypair that you should use from now on to log into this plugin. Please store 
it in a safe place.");
+            this.addComponent(area);
+            
+            DefinitionList definitionList = new DefinitionList();
+            definitionList.addEntry("Public SSK", this.user.getPublicSSK());
+            definitionList.addEntry("Private SSK", this.user.getPrivateSSK());
+            this.addComponent(definitionList);
+            
+            final LabeledTextField nickTextField = new 
LabeledTextField("nick", "Please choose a nickname");
+            nickTextField.setRequired(true);
+            this.addComponent(nickTextField);
+            
+            final SubmitButton submitButton = new SubmitButton("Create 
Account");
+            submitButton.setAction(new FormAction(this) {
+
+                @Override
+                protected void onSubmit(PluginHTTPRequest request) {
+                    
+                   String userName = nickTextField.getValue();
+                   
+                   User existingUser = 
Store.instance().getUserByName(userName);
+                   if (existingUser!=null) {
+                       nickTextField.showValidationMessage("There already 
exists an account with the nickname you chose. Please enter a unique name.");
+                       return;
+                   }
+                   
+                   user.setLastModified(System.currentTimeMillis());
+                   user.setName(userName);
+                   NewUserForm.this.hide();
+                   loginForm.show();
+                   loginForm.privateKeyField.setValue(user.getPrivateSSK());
+                   loginForm.publicKeyField.setValue(user.getPublicSSK());
+                   
+                   Channel channel = new Channel();
+                   channel.setSender(user.getPublicSSK());
+                   channel.setBasename(user.getName());
+                   channel.setLastInsertTime(System.currentTimeMillis());
+                   
+                   Store.instance().saveChannel(channel);
+                   Store.instance().saveUser(user);
+                }
+
+                @Override
+                protected void onInvalidSubmission(PluginHTTPRequest request) {
+                    // TODO Auto-generated method stub
+                    
+                }
+                
+            });
+            this.addComponent(submitButton);
+            
+            final SubmitButton cancelButton = new SubmitButton("Cancel");
+            cancelButton.setAction(new FormAction(this, false) {
+
+                @Override
+                protected void onSubmit(PluginHTTPRequest request) {
+                   NewUserForm.this.resetFormValues();
+                   NewUserForm.this.hide();
+                   loginForm.resetFormValues();
+                   loginForm.show();
+                }
+
+                @Override
+                protected void onInvalidSubmission(PluginHTTPRequest request) {
+                    this.onSubmit(request);
+                }
+                
+            });
+            this.addComponent(cancelButton);            
+        }
+    }
+    
+    
+    class LoginForm extends Form {
+        private LabeledTextField publicKeyField;
+
+        private LabeledTextField privateKeyField;
+
+        private MessageArea message;
+
+        public void constructComponents() {
+            Heading heading = new Heading(2, "Login");
+            this.addComponent(heading);
+
+            message = new MessageArea();
+            this.addComponent(message);
+
+            publicKeyField = new LabeledTextField("publicKey", "Please enter 
your public key...");
+            publicKeyField.setRequired(true);
+            publicKeyField.setValidation("This is not a valid freenet SSK", 
RegExes.SSK_KEY);
+            this.addComponent(publicKeyField);
+
+            privateKeyField = new LabeledTextField("privateKey", "...and your 
private key");
+            privateKeyField.setRequired(true);
+            privateKeyField.setValidation("This is not a valid freenet SSK", 
RegExes.SSK_KEY);
+            this.addComponent(privateKeyField);
+
+            SubmitButton loginButton = new SubmitButton("Login");
+            loginButton.setAction(new FormAction(this) {
+
+                @Override
+                protected void onSubmit(PluginHTTPRequest request) {
+                    try {
+                        
LoginCredentials.instance().login(publicKeyField.getValue(), 
privateKeyField.getValue());
+                        LoginForm.this.hide();
+                        resetLoginForm();
+                        addBookmarkForm.show();
+                    } catch (InvalidLoginException e) {
+                        privateKeyField.setValue("");
+                        message.show("Invalid Login", e.getMessages());
+                    }
+                }
+
+                @Override
+                protected void onInvalidSubmission(PluginHTTPRequest request) {
+                    LoginCredentials.instance().logout();
+                    resetLoginForm();
+                }
+
+            });
+            this.addComponent(loginButton);
+
+            resetLoginForm();
+        }
+
+        private void resetLoginForm() {
+            publicKeyField.setValue("");
+            privateKeyField.setValue("");
+            message
+                    .show(
+                            "How to Login:",
+                            "You have to enter a valid Freenet SSK keypair 
here. Please beware that currently the keypair is visible in " +
+                            "the text fields, as well as the location bar. 
Don't use a keypair you need to keep secret for this right now. ");
+        }
+    }
+
+    private Bookmark createBookmark(String key) throws KeyNotFoundException {
+        try {
+            HighLevelSimpleClient freenetClient = 
this.bookmarkPlugin.obtainClient();
+            freenetClient.getFetcherContext().localRequestOnly = true;
+            FreenetURI uri = new FreenetURI(key);
+            FetchResult fetchResult = freenetClient.fetch(uri);
+
+            Bookmark bookmark = new Bookmark();
+            
bookmark.setSender(LoginCredentials.instance().getCurrentUser().getPublicSSK());
+            bookmark.setContentType(fetchResult.getMimeType());
+            bookmark.setSize(fetchResult.size());
+            bookmark.setLastModified(System.currentTimeMillis());
+            bookmark.setUri(key);
+            return bookmark;
+        } catch (Exception e) {
+            throw new KeyNotFoundException(uriTextField.getValue());
+        }
+    }
+
+    public void onLogout() {
+        this.addBookmarkForm.hide();
+        this.loginForm.show();
+        this.editor.setBookmark(null);
+        this.newUserForm.hide();
+
+    }
+}
\ No newline at end of file

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/ui/BookmarksPage.java
===================================================================
--- 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/ui/BookmarksPage.java
  2006-03-07 18:55:50 UTC (rev 8182)
+++ 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/ui/BookmarksPage.java
  2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,41 @@
+/**
+ * 
+ */
+package falimat.freenet.bookmarkplugin.ui;
+
+import falimat.freenet.bookmarkplugin.components.BookmarkList;
+import falimat.freenet.bookmarkplugin.components.BookmarkSearchForm;
+import falimat.freenet.bookmarkplugin.storage.Store;
+import falimat.freenet.webplugin.HtmlPage;
+import falimat.freenet.webplugin.HtmlWriter;
+
+public class BookmarksPage extends HtmlPage {
+
+    
+    BookmarkSearchForm searchForm;
+    
+    BookmarkList list;
+
+    public BookmarksPage(String name) {
+        super(name);
+    }
+
+    @Override
+    public void constructComponents() {
+        
+        this.searchForm = new BookmarkSearchForm();
+        this.addComponent(this.searchForm);
+        
+        this.list = new BookmarkList();
+        this.addComponent(this.list);
+        
+        this.searchForm.setResultList(this.list);
+        
+        this.list.setBookmarks(Store.instance().getAllBookmarks());            
+    }
+
+    @Override
+    public void renderHtml(HtmlWriter out) {
+        super.renderHtml(out);
+    }
+}
\ No newline at end of file

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/ui/ChannelsPage.java
===================================================================
--- 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/ui/ChannelsPage.java
   2006-03-07 18:55:50 UTC (rev 8182)
+++ 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/ui/ChannelsPage.java
   2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,197 @@
+/**
+ * 
+ */
+package falimat.freenet.bookmarkplugin.ui;
+
+import java.util.List;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.time.DateUtils;
+
+import falimat.freenet.bookmarkplugin.LoginCredentials;
+import falimat.freenet.bookmarkplugin.model.Channel;
+import falimat.freenet.bookmarkplugin.model.Slot;
+import falimat.freenet.bookmarkplugin.model.User;
+import falimat.freenet.bookmarkplugin.storage.Store;
+import falimat.freenet.network.RegExes;
+import falimat.freenet.webplugin.FormAction;
+import falimat.freenet.webplugin.HtmlPage;
+import falimat.freenet.webplugin.components.BeanTable;
+import falimat.freenet.webplugin.components.BeanTableDataSource;
+import falimat.freenet.webplugin.components.Form;
+import falimat.freenet.webplugin.components.Heading;
+import falimat.freenet.webplugin.components.LabeledTextField;
+import falimat.freenet.webplugin.components.SubmitButton;
+import freenet.pluginmanager.PluginHTTPRequest;
+
+public class ChannelsPage extends HtmlPage {
+    final BeanTable channelsTable = new BeanTable();
+
+    final BeanTable slotsTable = new BeanTable();
+    
+    final BeanTable toBeFetchedTable = new BeanTable();
+
+    final Form form = new Form();
+
+    final LabeledTextField addChannelUri = new LabeledTextField("uri", "Add 
Channel with URI");
+
+    final SubmitButton addChannelButton = new SubmitButton("add");
+
+    final Form channelSettingsForm = new Form();
+    
+    public ChannelsPage(String name) {
+        super(name);
+
+    }
+
+    @Override
+    public void constructComponents() {
+
+        this.addChannelUri.setRequired(true);
+        this.addChannelUri.setValidation("URIs of channels must be formatted 
like SSK at abc.../basename-1",
+                RegExes.CHANNEL_URI);
+
+        this.addChannelButton.setAction(new FormAction(this.form) {
+
+            @Override
+            protected void onSubmit(PluginHTTPRequest request) {
+                try {
+                    String channelUri = addChannelUri.getValue();
+                    Channel channel = new Channel();
+                    String ssk = StringUtils.substringBefore(channelUri, "/");
+                    String baseName = StringUtils.substringBetween(channelUri, 
"/", "-");
+                    int index = 
Integer.parseInt(StringUtils.substringAfterLast(channelUri, "-"));
+                    channel.setSender(ssk);
+                    channel.setBasename(baseName);
+                    channel.setLastSlot(index);
+
+                    Store.instance().saveChannel(channel);
+                    
+                    for (int i=0; i<=index; i++) {
+                        Slot slot = new Slot(channel, i);
+                        if 
(!Store.instance().slotIsAvailable(slot.getChannel(), i)) {
+                            slot.setRequestTime(System.currentTimeMillis());
+                            Store.instance().saveSlot(slot);
+                        }
+                    }
+                    
+                    form.resetFormValues();
+                } catch (RuntimeException e) {
+                    addChannelUri.showValidationMessage("This channel URI 
could not be parsed. <!--\n\n"
+                            + HtmlPage.getStacktrace(e) + "-->");
+                }
+
+            }
+
+            @Override
+            protected void onInvalidSubmission(PluginHTTPRequest request) {
+                // TODO Auto-generated method stub
+
+            }
+
+        });
+
+        this.form.addComponent(this.addChannelUri);
+        this.form.addComponent(this.addChannelButton);
+        super.addComponent(this.form);
+
+        super.addComponent(new Heading(2, "All known channels"));
+        channelsTable.addColumn("sender", BeanTable.PUBLIC_SSK_REND);
+        channelsTable.addColumn("basename");
+        channelsTable.addColumn("lastSlot");
+        channelsTable.addColumn("lastInsertTime", 
BeanTable.DATE_TIME_RENDERER);
+        channelsTable.addColumn("insertInterval", BeanTable.DURATION_RENDERER);
+
+        channelsTable.setDataProvider(new BeanTableDataSource() {
+
+            public Object[] getRows() {
+                return Store.instance().getAllChannels().toArray();
+            }
+
+        });
+        super.addComponent(channelsTable);
+
+        super.addComponent(new Heading(2, "Slots to be fetched"));
+        toBeFetchedTable.addColumn("uri", BeanTable.FREENET_LINK_RENDERER);
+        toBeFetchedTable.addColumn("expectedTime", 
BeanTable.DATE_TIME_RENDERER);
+        toBeFetchedTable.addColumn("available");
+        toBeFetchedTable.addColumn("failureCount");
+        toBeFetchedTable.addColumn("requestTime", 
BeanTable.DATE_TIME_RENDERER);
+        
+
+        toBeFetchedTable.setDataProvider(new BeanTableDataSource() {
+
+            public Object[] getRows() {
+                User currentUser = 
LoginCredentials.instance().getCurrentUser();
+                List<Slot> slots = 
Store.instance().getSlotsToBeFetched(currentUser);
+                if (slots==null) {
+                    return new Object[0];
+                } else {
+                    return slots.toArray();
+                }
+            }
+
+        });
+        super.addComponent(toBeFetchedTable);
+        
+        super.addComponent(new Heading(2, "All Slots"));
+        slotsTable.addColumn("uri", BeanTable.FREENET_LINK_RENDERER);
+        slotsTable.addColumn("expectedTime", BeanTable.DATE_TIME_RENDERER);
+        slotsTable.addColumn("available");
+        slotsTable.addColumn("failureCount");
+        slotsTable.addColumn("requestTime", BeanTable.DATE_TIME_RENDERER);
+
+        slotsTable.setDataProvider(new BeanTableDataSource() {
+
+            public Object[] getRows() {
+                return Store.instance().getAllSlots().toArray();
+            }
+
+        });
+        super.addComponent(slotsTable);
+        
+        
+        this.channelSettingsForm.addComponent(new Heading(2, "Publishing 
preferences"));
+        
+        final LabeledTextField insertIntervalField = new 
LabeledTextField("intervalMinutes", "How many minutes should pass between two 
inserts on your channel?");
+        insertIntervalField.setRequired(true);
+        insertIntervalField.setValidation("the number of minutes must be 
entered as integer", RegExes.INTEGER);
+        this.channelSettingsForm.addComponent(insertIntervalField);
+        
+        final SubmitButton submitButton = new SubmitButton("Apply Changes");
+        submitButton.setAction(new FormAction(this.channelSettingsForm) {
+
+            @Override
+            protected void onSubmit(PluginHTTPRequest request) {
+                User user = LoginCredentials.instance().getCurrentUser();
+                if (user==null) {
+                    insertIntervalField.showValidationMessage("You must be 
logged in to change publish preferences");
+                    return;
+                }
+                Channel channel = 
Store.instance().getChannelBySender(user.getPublicSSK());
+                if (channel==null) {
+                    insertIntervalField.showValidationMessage("There doesn't 
exist a channel for the loged in user");
+                    return;
+                }
+                int insertIntervalMinutes = 
request.getIntParam(insertIntervalField.getName(), channel.getInsertInterval());
+                channel.setInsertInterval((int)DateUtils.MILLIS_PER_MINUTE * 
insertIntervalMinutes);
+                channel.setLastInsertTime(System.currentTimeMillis() - 
channel.getInsertInterval());
+                channel.setLastModified(System.currentTimeMillis());
+                channel.setPublished(false);
+                Store.instance().saveChannel(channel);
+            }
+
+            @Override
+            protected void onInvalidSubmission(PluginHTTPRequest request) {
+                // TODO Auto-generated method stub
+                
+            }
+            
+        });
+        this.channelSettingsForm.addComponent(submitButton);
+        
+        super.addComponent(this.channelSettingsForm);
+
+    }
+
+}
\ No newline at end of file

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/ui/HomePage.java
===================================================================
--- 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/ui/HomePage.java   
    2006-03-07 18:55:50 UTC (rev 8182)
+++ 
trunk/apps/bookmarkplugin/src/falimat/freenet/bookmarkplugin/ui/HomePage.java   
    2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,17 @@
+/**
+ * 
+ */
+package falimat.freenet.bookmarkplugin.ui;
+
+import falimat.freenet.webplugin.HtmlPage;
+
+public class HomePage extends HtmlPage {
+
+    public HomePage(String name) {
+        super(name);
+    }
+    @Override
+    public void constructComponents() {
+
+    }        
+}
\ No newline at end of file

Added: trunk/apps/bookmarkplugin/src/falimat/freenet/crypt/CryptoUtil.java
===================================================================
--- trunk/apps/bookmarkplugin/src/falimat/freenet/crypt/CryptoUtil.java 
2006-03-07 18:55:50 UTC (rev 8182)
+++ trunk/apps/bookmarkplugin/src/falimat/freenet/crypt/CryptoUtil.java 
2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,450 @@
+package falimat.freenet.crypt;
+
+import java.math.BigInteger;
+import java.net.MalformedURLException;
+import java.security.MessageDigest;
+import java.util.Arrays;
+
+import net.i2p.util.NativeBigInteger;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.dom4j.Element;
+
+import xomat.util.ParamRuntimeException;
+import xomat.util.xml.XmlUtils;
+import falimat.freenet.bookmarkplugin.model.User;
+import freenet.crypt.BlockCipher;
+import freenet.crypt.DSA;
+import freenet.crypt.DSAPrivateKey;
+import freenet.crypt.DSAPublicKey;
+import freenet.crypt.DSASignature;
+import freenet.crypt.DiffieHellmanContext;
+import freenet.crypt.Digest;
+import freenet.crypt.Global;
+import freenet.crypt.RandomSource;
+import freenet.crypt.SHA256;
+import freenet.crypt.UnsupportedCipherException;
+import freenet.crypt.Yarrow;
+import freenet.crypt.ciphers.Rijndael;
+import freenet.keys.FreenetURI;
+import freenet.keys.InsertableClientSSK;
+import freenet.support.Base64;
+import freenet.support.IllegalBase64Exception;
+
+public class CryptoUtil {
+
+    private static final Log log = LogFactory.getLog(CryptoUtil.class);
+
+    private static boolean verifyCreatedSignatures = true;
+
+    public static DSAPublicKey getDSAPublicKey(byte[] yAsBytes) {
+        BigInteger y = new NativeBigInteger(1, yAsBytes);
+        DSAPublicKey pubKey = new DSAPublicKey(Global.DSAgroupBigA, y);
+        return pubKey;
+    }
+
+    public static DSAPrivateKey getDSAPrivateKey(byte[] xAsBytes) {
+        BigInteger x = new NativeBigInteger(1, xAsBytes);
+        DSAPrivateKey key = new DSAPrivateKey(x);
+        return key;
+    }
+
+    public static boolean verifySignature(Element element, String 
signatureString, byte[] dsaPublicKey) {
+        try {
+            // recreate public key and parse signature
+            DSAPublicKey pubKey = getDSAPublicKey(dsaPublicKey);
+            DSASignature signature = parseSignatureString(signatureString);
+
+            // calculate a SHA256 digest for the given element
+            BigInteger elementDigest = calculateSHA256Digest(element);
+
+            // verify the signature of that digest against the public key
+            boolean verified = DSA.verify(pubKey, signature, elementDigest);
+            return verified;
+        } catch (Exception e) {
+            String msg = "Failed to verify signature {0} for element {1}";
+            throw new ParamRuntimeException(msg, signatureString, 
XmlUtils.toString(element), e);
+        }
+    }
+
+    protected static InsertableClientSSK createInsertableClientSSK(String 
privateSSK) throws MalformedURLException {
+        
+        try {
+            // append arbitrary document name if privateSSK so that it can be
+            // passed to constructor FreenetURI(String)
+            privateSSK = convertSSKString(privateSSK, true, true);
+    
+            // create InsertableClientSSK to easily get the parsed private key
+            // and public key
+            InsertableClientSSK clientSSK = InsertableClientSSK.create((new 
FreenetURI(privateSSK)));
+    
+            return clientSSK;
+        } catch (RuntimeException e) {
+            String msg = "The private ssk does not seem to be a valid freenet 
insert URI";
+            throw new ParamRuntimeException(msg);
+        }
+    }
+
+    public static String calculateSignature(Element element, byte[] 
dsaPrivateKey, byte[] dsaPublicKey) {
+        try {
+
+            // calculate a SHA256 digest for the given element
+            BigInteger elementDigest = calculateSHA256Digest(element);
+
+            DSAPrivateKey privateKey = getDSAPrivateKey(dsaPrivateKey);
+            // sign this digest with the private key and convert the signature
+            // to string
+            DSASignature signature = DSA.sign(Global.DSAgroupBigA, privateKey, 
elementDigest, new Yarrow());
+            String signatureString = createSignatureString(signature);
+
+            // for debugging: make sure the signature was correctly calculated
+            if (verifyCreatedSignatures) {
+                if (!verifySignature(element, signatureString, dsaPublicKey)) {
+                    String msg = "The signature {0} calculated for element {1} 
falied to be verified";
+                    throw new ParamRuntimeException(msg, signatureString, 
XmlUtils.toString(element));
+                }
+            }
+
+            return signatureString;
+        } catch (Exception e) {
+            String msg = "Failed to sign element {0}";
+            throw new ParamRuntimeException(msg, XmlUtils.toString(element), 
e);
+        }
+    }
+
+    public static boolean verifyPublicKey(String publicSSK, byte[] yAsBytes) {
+        try {
+
+            DSAPublicKey publicKey = getDSAPublicKey(yAsBytes);
+
+            // extract the expected public key hash from the publicSSK
+            publicSSK = convertSSKString(publicSSK, false, false);
+            String publicKeyHashString = publicSSK.substring(0, 
publicSSK.indexOf(','));
+            // decode the public key hash
+            byte[] expectedHash = Base64.decode(publicKeyHashString);
+
+            // calculate the real has of the public key
+            MessageDigest md = MessageDigest.getInstance("SHA-256");
+            md.update(publicKey.asBytes());
+            byte[] realHash = md.digest();
+
+            // verify that is the same as the one passed
+            return (Arrays.equals(expectedHash, realHash));
+        } catch (Exception e) {
+            String msg = "Failed to verify hash {0} of public key {1}";
+            throw new ParamRuntimeException(msg, publicSSK, 
encodeBase64(yAsBytes), e);
+        }
+    }
+
+    public static BigInteger calculateSHA256Digest(Element element) {
+        // TODO: Do something more standardized to serialize XML (maybe use
+        // DOMHASH)
+        byte[] bytes = XmlUtils.elementAsByteArray(element);
+        Digest sha1 = new SHA256();
+        sha1.update(bytes);
+        byte[] hash = sha1.digest();
+        return new BigInteger(1, hash);
+    }
+
+    private static DSASignature parseSignatureString(String signatureString) 
throws IllegalBase64Exception {
+        String rString = signatureString.substring(0, 
signatureString.indexOf(','));
+        String sString = 
signatureString.substring(signatureString.indexOf(',') + 1);
+        BigInteger rBigInt = new BigInteger(1, Base64.decode(rString));
+        BigInteger sBigInt = new BigInteger(1, Base64.decode(sString));
+        DSASignature signature = new DSASignature(rBigInt, sBigInt);
+        return signature;
+    }
+
+    private static String createSignatureString(DSASignature signature) {
+        String r = Base64.encode(signature.getR().toByteArray());
+        String s = Base64.encode(signature.getS().toByteArray());
+        return r + "," + s;
+    }
+
+    public static String convertSSKString(String sskString, boolean 
shouldIncludeKeytype, boolean mustHaveDocname) {
+        // strip a leading "freenet:" prefix
+        if (sskString.startsWith("freenet:")) {
+            sskString = sskString.substring(sskString.indexOf(":") + 1);
+        }
+        // append arbitrary document name if the sskString doesn't have it
+        if (mustHaveDocname) {
+            if (!sskString.contains("/")) {
+                sskString = sskString + "/";
+            }
+            if (sskString.endsWith("/")) {
+                sskString += "0";
+            }
+        } else {
+            if (sskString.contains("/")) {
+                sskString = sskString.substring(0, sskString.indexOf("/"));
+            }
+        }
+        // strip or insert SSK@ prefix
+        if (shouldIncludeKeytype) {
+            if (!sskString.startsWith("SSK@")) {
+                sskString = "SSK@" + sskString;
+            }
+        } else {
+            if (sskString.startsWith("SSK@")) {
+                sskString = sskString.substring(sskString.indexOf("@") + 1);
+            }
+        }
+        return sskString;
+    }
+
+    public static byte[] calculateDHExponential(byte[] dsaPrivateKey) {
+        try {
+            DiffieHellmanContext dhContext = getContext(dsaPrivateKey);
+
+            return dhContext.getOurExponential().toByteArray();
+        } catch (Exception e) {
+            String msg = "Failed to create DiffieHellman exponential from 
private key {0}";
+            throw new ParamRuntimeException(msg, encodeBase64(dsaPrivateKey), 
e);
+        }
+    }
+
+    private static DiffieHellmanContext getContext(byte[] dsaPrivateKey) {
+        try {
+            BigInteger x = new BigInteger(1, dsaPrivateKey);
+            BigInteger X = Global.DHgroupA.g.modPow(x, Global.DHgroupA.p);
+
+            return new DiffieHellmanContext(new NativeBigInteger(x), new 
NativeBigInteger(X), Global.DHgroupA);
+        } catch (Exception e) {
+            String msg = "Failed to create DiffieHellman exponential from 
private key {0}";
+            throw new ParamRuntimeException(msg, Base64.encode(dsaPrivateKey), 
e);
+        }
+    }
+
+    public static String encryptSessionKey(String rijndaelKeyString, byte[] 
senderExponent, byte[] recipientExponential) {
+        try {
+            DiffieHellmanContext aliceContext = getContext(senderExponent);
+            NativeBigInteger Y = new NativeBigInteger(1, recipientExponential);
+            aliceContext.setOtherSideExponential(Y);
+            byte[] aliceKey = aliceContext.getKey();
+            if (log.isDebugEnabled()) {
+                log.debug("aliceKey has length of " + aliceKey.length);
+            }
+
+            byte[] secretSharedKey = Base64.decode(rijndaelKeyString);
+            if (log.isDebugEnabled()) {
+                log.debug("secretSharedKey has length of " + 
secretSharedKey.length);
+            }
+
+            BlockCipher cipher = new Rijndael(aliceKey.length * 8);
+            byte[] encodedSharedKey = encipherBytes(aliceKey, secretSharedKey, 
cipher);
+            if (log.isDebugEnabled()) {
+                log.debug("encodedSharedKey has length of " + 
encodedSharedKey.length);
+            }
+            
+            String encodedKeyString = Base64.encode(encodedSharedKey);
+            return encodedKeyString;
+        } catch (Exception e) {
+            String msg = "Failed to encrypt shared rijndael key {0} for 
recpient {1}";
+            throw new ParamRuntimeException(msg, rijndaelKeyString, 
recipientExponential, e);
+        }
+    }
+
+    public static String decryptSessionKey(String encryptedKeyString, byte[] 
recipientExponent, byte[] senderExponential) {
+        try {
+            DiffieHellmanContext myContext = getContext(recipientExponent);
+            NativeBigInteger Y = new NativeBigInteger(1, senderExponential);
+            myContext.setOtherSideExponential(Y);
+            byte[] bobKey = myContext.getKey();
+            if (log.isDebugEnabled()) {
+                log.debug("bobKey has length of " + bobKey.length);
+            }
+            
+            byte[] encryptedSharedKey = Base64.decode(encryptedKeyString);
+            if (log.isDebugEnabled()) {
+                log.debug("encryptedSharedKey has length of " + 
encryptedSharedKey.length);
+            }
+            
+            BlockCipher cipher = new Rijndael(bobKey.length * 8);
+            byte[] decryptedSharedKey = decipherBytes(bobKey, 
encryptedSharedKey, cipher, false);
+            if (log.isDebugEnabled()) {
+                log.debug("decryptedSharedKey has length of " + 
decryptedSharedKey.length);
+            }
+            
+            String decryptedKeyString = Base64.encode(decryptedSharedKey);
+            return decryptedKeyString;
+
+        } catch (Exception e) {
+            String msg = "Failed to encrypt shared rijndael key {0} from 
sender {1}";
+            throw new ParamRuntimeException(msg, encryptedKeyString, 
senderExponential, e);
+        }
+    }
+
+    public static void encrypt(Element toBeEncrypted, String 
rijndaelKeyString) {
+        try {
+
+            // decode rjndael key and serialize xml element
+            byte[] keyBytes = Base64.decode(rijndaelKeyString);
+            byte[] contentBytes = XmlUtils.elementAsByteArray(toBeEncrypted);
+
+            Rijndael rijndael = new Rijndael(keyBytes.length * 8);
+            rijndael.initialize(keyBytes);
+
+            // encipher the byte array
+            byte[] encryptedBytes = encipherBytes(keyBytes, contentBytes, 
rijndael);
+
+            // convert encrypted byte array to string
+            String encodedString = Base64.encode(encryptedBytes);
+
+            // replace content of element with encrypted string
+            toBeEncrypted.clearContent();
+            toBeEncrypted.attributes().clear();
+            toBeEncrypted.setText(encodedString);
+        } catch (Exception e) {
+            String msg = "Failed to encrypt element {0} with key {1}";
+            throw new ParamRuntimeException(msg, 
XmlUtils.toString(toBeEncrypted), rijndaelKeyString, e);
+        }
+    }
+
+    public static void decrypt(Element encrypted, String rijndaelKeyString) {
+        try {
+            // decode element content and rijndael key
+            byte[] encryptedBytes = Base64.decode(encrypted.getText());
+            byte[] keyBytes = Base64.decode(rijndaelKeyString);
+
+            // decipher the bytes
+            Rijndael rijndael = new Rijndael(keyBytes.length * 8);
+            rijndael.initialize(keyBytes);
+            byte[] contentBytes = decipherBytes(keyBytes, encryptedBytes, 
rijndael, true);
+
+            // create xml element from deciphered bytes
+            Element decrypted = XmlUtils.elementFromByteArray(contentBytes);
+
+            // replace content in encrypted element with the decrypted content
+            encrypted.clearContent();
+            encrypted.setAttributes(decrypted.attributes());
+            encrypted.setContent(decrypted.content());
+
+        } catch (Exception e) {
+            String msg = "Failed to decrypt element from string {0}";
+            throw new ParamRuntimeException(msg, encrypted.getText(), e);
+        }
+    }
+
+    static byte[] encipherBytes(byte[] keyBytes, byte[] contentBytes, 
BlockCipher rijndael)
+            throws UnsupportedCipherException {
+
+        rijndael.initialize(keyBytes);
+        byte[] paddedBytes = padByteArray(contentBytes, 
rijndael.getBlockSize());
+        byte[] encryptedBytes = new byte[paddedBytes.length];
+
+        int blockLength = rijndael.getBlockSize() / 8;
+        byte[] plainBlock = new byte[blockLength];
+        byte[] cipherBlock = new byte[blockLength];
+        for (int i = 0; i < encryptedBytes.length / blockLength; i++) {
+            System.arraycopy(paddedBytes, i * blockLength, plainBlock, 0, 
blockLength);
+            rijndael.encipher(plainBlock, cipherBlock);
+            System.arraycopy(cipherBlock, 0, encryptedBytes, i * blockLength, 
blockLength);
+        }
+        return encryptedBytes;
+    }
+
+    static byte[] decipherBytes(byte[] keyBytes, byte[] encryptedBytes, 
BlockCipher blockCipher, boolean removePadding)
+            throws UnsupportedCipherException {
+
+        blockCipher.initialize(keyBytes);
+        byte[] decipheredBytes = new byte[encryptedBytes.length];
+
+        int blockLength = blockCipher.getBlockSize() / 8;
+        byte[] plainBlock = new byte[blockLength];
+        byte[] cipherBlock = new byte[blockLength];
+        for (int i = 0; i < encryptedBytes.length / blockLength; i++) {
+            System.arraycopy(encryptedBytes, i * blockLength, cipherBlock, 0, 
blockLength);
+            blockCipher.decipher(cipherBlock, plainBlock);
+            System.arraycopy(plainBlock, 0, decipheredBytes, i * blockLength, 
blockLength);
+        }
+
+        if (removePadding) {
+            return removePadding(decipheredBytes, blockCipher.getBlockSize());
+        } else {
+            return decipheredBytes;
+        }
+    }
+
+    public static byte[] padByteArray(byte[] bytes, int blockSize) {
+        int additionalBytes = bytes.length % (blockSize / 8);
+        byte[] out;
+        if (additionalBytes == 0) {
+            out = new byte[bytes.length];
+        } else {
+            out = new byte[bytes.length + (blockSize / 8 - additionalBytes)];
+        }
+        System.arraycopy(bytes, 0, out, 0, bytes.length);
+        if (additionalBytes > 0) {
+            Arrays.fill(out, bytes.length, out.length, (byte) 0);
+        }
+        if (log.isDebugEnabled()) {
+            log.debug("padded '" + new String(bytes) + "' to '" + new 
String(out) + "' with length " + out.length);
+        }
+        return out;
+    }
+
+    public static byte[] removePadding(byte[] bytes, int blockSize) {
+
+        int firstPossiblePadByte = bytes.length - blockSize / 8;
+        int firstPadByte = 0;
+        for (int i = firstPossiblePadByte; i < bytes.length; i++) {
+            if (bytes[i] == (byte) 0) {
+                firstPadByte = i;
+                break;
+            }
+        }
+        if (firstPadByte == 0) {
+            firstPadByte = bytes.length;
+        }
+        byte[] out = new byte[firstPadByte];
+        System.arraycopy(bytes, 0, out, 0, firstPadByte);
+        if (log.isDebugEnabled()) {
+            log.debug("unpadded '" + new String(bytes) + "' to '" + new 
String(out) + "'");
+        }
+        return out;
+    }
+
+    public final static String randomRijndaelKeystring(RandomSource random) {
+        byte[] randomBytes = new byte[32];
+        random.nextBytes(randomBytes);
+        String rijndaelKeyString = Base64.encode(randomBytes);
+        return rijndaelKeyString;
+    }
+
+    public static String encodeBase64(byte[] bytes) {
+        return Base64.encode(bytes);
+    }
+
+    public static void calculateKeys(String privateSSK, User user) throws 
MalformedURLException {
+        InsertableClientSSK clientSSK = createInsertableClientSSK(privateSSK);
+
+        DSAPrivateKey privateKey = clientSSK.privKey;
+        DSAPublicKey publicKey = clientSSK.getPubKey();
+
+        String publicSSK = convertSSKString(clientSSK.getURI().toString(), 
true, false);
+        privateSSK = convertSSKString(clientSSK.getInsertURI().toString(), 
true, false);
+        byte[] dsaPrivateKey = privateKey.getX().toByteArray();
+        byte[] dsaPublicKey = publicKey.getY().toByteArray();
+        byte[] dhPublicKey = CryptoUtil.calculateDHExponential(dsaPrivateKey);
+
+        user.calculateKeys(publicSSK, dsaPrivateKey, dsaPublicKey, 
dhPublicKey, privateSSK);
+    }
+
+    public static byte[] decodeBase64(String string) throws 
IllegalBase64Exception {
+        return Base64.decode(string);
+    }
+
+    public static boolean verifyKeypair(String publicSSK, String privateSSK) {
+        try {
+            User user = new User();
+            calculateKeys(privateSSK, user);
+            return (user.getPublicSSK().equals(publicSSK));
+        } catch (MalformedURLException e) {
+            return false;
+        }
+    }
+
+
+
+}

Added: trunk/apps/bookmarkplugin/src/falimat/freenet/crypt/XmlCryptoTest.java
===================================================================
--- trunk/apps/bookmarkplugin/src/falimat/freenet/crypt/XmlCryptoTest.java      
2006-03-07 18:55:50 UTC (rev 8182)
+++ trunk/apps/bookmarkplugin/src/falimat/freenet/crypt/XmlCryptoTest.java      
2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,234 @@
+package falimat.freenet.crypt;
+
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.LinkedList;
+
+import junit.framework.TestCase;
+
+import net.i2p.util.NativeBigInteger;
+
+import org.dom4j.Document;
+import org.dom4j.DocumentFactory;
+import org.dom4j.Element;
+
+import xomat.util.xml.XmlUtils;
+import falimat.freenet.bookmarkplugin.components.BookmarkEditor;
+import falimat.freenet.bookmarkplugin.model.AbstractSendable;
+import falimat.freenet.bookmarkplugin.model.Bookmark;
+import falimat.freenet.bookmarkplugin.model.User;
+import falimat.freenet.network.SlotReader;
+import falimat.freenet.network.SlotWriter;
+import falimat.freenet.network.XmlSlotReader;
+import falimat.freenet.network.XmlSlotWriter;
+import freenet.crypt.BlockCipher;
+import freenet.crypt.DSA;
+import freenet.crypt.DSAPrivateKey;
+import freenet.crypt.DSAPublicKey;
+import freenet.crypt.DSASignature;
+import freenet.crypt.DiffieHellman;
+import freenet.crypt.DiffieHellmanContext;
+import freenet.crypt.Global;
+import freenet.crypt.RandomSource;
+import freenet.crypt.UnsupportedCipherException;
+import freenet.crypt.Yarrow;
+import freenet.crypt.ciphers.Rijndael;
+import freenet.keys.InsertableClientSSK;
+import freenet.support.Base64;
+import freenet.support.IllegalBase64Exception;
+
+public class XmlCryptoTest extends TestCase {
+
+    private static final int TEST_RUNS = 10;
+
+    private static RandomSource random = new Yarrow();
+
+    public void testDetail() throws Exception {
+
+        for (int i = 0; i < TEST_RUNS; i++) {
+            Element element = 
DocumentFactory.getInstance().createElement("test");
+
+            InsertableClientSSK clientSSK = 
InsertableClientSSK.createRandom(random);
+
+            BigInteger m = CryptoUtil.calculateSHA256Digest(element);
+
+            DSAPrivateKey privateKey = clientSSK.privKey;
+            assertTrue(Arrays.equals(privateKey.asBytes(), 
privateKey.asBytes()));
+
+            DSAPublicKey publicKey = clientSSK.getPubKey();
+            
assertTrue(CryptoUtil.verifyPublicKey(clientSSK.getURI().toString(), 
publicKey.getY().toByteArray()));
+
+            DSASignature signature = DSA.sign(Global.DSAgroupBigA, privateKey, 
m, random);
+            assertTrue(DSA.verify(publicKey, signature, m));
+
+            byte[] privateKeyBytes = privateKey.getX().toByteArray();
+
+            DSAPrivateKey newKey = new DSAPrivateKey(new NativeBigInteger(1, 
privateKeyBytes));
+            assertTrue(Arrays.equals(privateKey.asBytes(), newKey.asBytes()));
+
+            byte[] publicKeyBytes = publicKey.getY().toByteArray();
+            DSAPublicKey newPublic = new DSAPublicKey(Global.DSAgroupBigA, new 
NativeBigInteger(1, publicKeyBytes));
+            assertTrue(Arrays.equals(publicKey.asBytes(), 
newPublic.asBytes()));
+
+            
assertTrue(CryptoUtil.verifyPublicKey(clientSSK.getURI().toString(), 
publicKeyBytes));
+
+            byte[] aotherBeytes = publicKey.asBytes();
+            DSAPublicKey otherPublic = new DSAPublicKey(aotherBeytes);
+            assertTrue(Arrays.equals(publicKey.asBytes(), 
otherPublic.asBytes()));
+
+            assertTrue(DSA.verify(newPublic, signature, m));
+        }
+
+    }
+
+    public void testSignature() throws Exception {
+
+        for (int i = 0; i < TEST_RUNS; i++) {
+            User alice = new User("alice", random);
+
+            Document doc = XmlUtils.createEmptyDocument("root");
+            Element root = doc.getRootElement();
+            Element abc = root.addElement("abcd");
+            Element def = root.addElement("def");
+
+            // create random SSK keypair
+            InsertableClientSSK clientSSK = 
InsertableClientSSK.createRandom(random);
+
+            // calculate and verify string repersentation of public key
+            assertTrue(CryptoUtil.verifyPublicKey(alice.getPublicSSK(), 
alice.getDsaPublicKey()));
+
+            // calculate and verify signature
+            String signatureString = CryptoUtil.calculateSignature(root, 
alice.getDsaPrivateKey(), alice
+                    .getDsaPublicKey());
+            assertTrue(CryptoUtil.verifySignature(root, signatureString, 
alice.getDsaPublicKey()));
+
+            // check that altering the element will make verification fail
+            abc.addAttribute("test", "t");
+            assertFalse(CryptoUtil.verifySignature(root, signatureString, 
alice.getDsaPublicKey()));
+        }
+    }
+
+    public void testEncrypt() throws Exception {
+        for (int i = 0; i < TEST_RUNS; i++) {
+            Document doc = XmlUtils.createEmptyDocument("root");
+            Element root = doc.getRootElement();
+            Element unencrypted = root.addElement("unencrypted");
+            unencrypted.setText("This text is not encrypted");
+            Element encrypted = root.addElement("encrypted");
+            encrypted.setText("This text is encrypted");
+            encrypted.addAttribute("attr", "This attribute is encrypted!!!");
+
+            String originalXmlString = XmlUtils.toString(root);
+
+            // calculate random encryption key
+            String rijndaelKeyString = 
CryptoUtil.randomRijndaelKeystring(random);
+
+            // encrypt the element
+            CryptoUtil.encrypt(encrypted, rijndaelKeyString);
+
+            // decrypt the element
+            CryptoUtil.decrypt(encrypted, rijndaelKeyString);
+
+            String decryptedXmlString = XmlUtils.toString(root);
+
+            // compare that the element is equal by string comparison
+            assertEquals(originalXmlString, decryptedXmlString);
+
+        }
+    }
+
+    public void testKeyExchange() throws Exception {
+
+        for (int i = 0; i < TEST_RUNS; i++) {
+
+            User alice = new User("alice", random);
+            User bob = new User("bob", random);
+
+            String rijndaelKeyString = 
CryptoUtil.randomRijndaelKeystring(random);
+
+            String encryptedKeyString = 
CryptoUtil.encryptSessionKey(rijndaelKeyString, alice.getDhPrivateKey(), bob
+                    .getDhPublicKey());
+
+            String decryptedKeyString = 
CryptoUtil.decryptSessionKey(encryptedKeyString, bob.getDhPrivateKey(), alice
+                    .getDhPublicKey());
+
+            if (!rijndaelKeyString.equals(decryptedKeyString)) {
+                System.err.println("alice=" + alice.getPrivateSSK());
+                System.err.println("bob=" + bob.getPrivateSSK());
+                System.err.println("rijndaelKey=" + rijndaelKeyString);
+                System.err.println("encryptedKey=" + encryptedKeyString);
+                System.err.println("decryptedKey=" + decryptedKeyString);
+                throw new RuntimeException();
+            }
+        }
+    }
+
+    public void testMessageSigning() throws Exception {
+        for (int i = 0; i < TEST_RUNS; i++) {
+            InsertableClientSSK aliceSSK = 
InsertableClientSSK.createRandom(random);
+            User alice = new User("alice", aliceSSK.getInsertURI().toString());
+
+            Bookmark bookmark = new Bookmark();
+            bookmark.setUri("KSK at gpl.txt");
+            bookmark.setTitle("The GNU General Public License");
+            bookmark.setSender(alice.getPublicSSK());
+            bookmark.setDescription("This is a copy of the gnu public 
license");
+            bookmark.setSize(21001);
+            bookmark.setContentType("text/plain");
+            bookmark.setTags(BookmarkEditor.tagsAsSet("legal software 
freedom"));
+            bookmark.setLastModified(System.currentTimeMillis());
+
+            SlotWriter writer = new XmlSlotWriter();
+            writer.setKeypair(alice);
+
+            java.util.List<AbstractSendable> bookmarks = new 
LinkedList<AbstractSendable>();
+            bookmarks.add(bookmark);
+ 
+            writer.writeObjects(bookmarks);
+
+            byte[] slotBytes = writer.toByteArray();
+            System.out.print(new String(slotBytes, XmlUtils.UTF_8));
+
+            InsertableClientSSK bobSSK = 
InsertableClientSSK.createRandom(random);
+            String bobPublicSSK = 
CryptoUtil.convertSSKString(bobSSK.getURI().toString(), true, false);
+            String bobPrivateSSK = 
CryptoUtil.convertSSKString(bobSSK.getInsertURI().toString(), true, false);
+
+            SlotReader reader = new XmlSlotReader();
+            reader.setKeypair(bobPrivateSSK, bobPublicSSK);
+            reader.readMessages(slotBytes);
+
+            java.util.List<Bookmark> retrievedBookmarks = 
reader.getBookmarks();
+
+        }
+    }
+
+    public void testDebugHKeyExchange() throws IllegalBase64Exception, 
UnsupportedCipherException {
+        DiffieHellman.init(random);
+        
+        for (int i = 0; i < TEST_RUNS; i++) {
+
+            DiffieHellmanContext aliceContext = 
DiffieHellman.generateContext();
+            DiffieHellmanContext bobContext = DiffieHellman.generateContext();
+
+            
aliceContext.setOtherSideExponential(bobContext.getOurExponential());
+            
bobContext.setOtherSideExponential(aliceContext.getOurExponential());
+
+            byte[] aliceKey = aliceContext.getKey();
+            byte[] bobKey = bobContext.getKey();
+
+            assertTrue(Arrays.equals(aliceKey, bobKey));
+
+            String rijndaelKeyString = 
CryptoUtil.randomRijndaelKeystring(random);
+
+            byte[] secretSharedKey = Base64.decode(rijndaelKeyString);
+            Rijndael rijndael = new Rijndael(aliceKey.length * 8);
+            byte[] encodedSharedKey = CryptoUtil.encipherBytes(aliceKey, 
secretSharedKey, rijndael);
+
+            BlockCipher bobCipher = new Rijndael(bobKey.length * 8);
+            byte[] decryptedSharedKey = CryptoUtil.decipherBytes(bobKey, 
encodedSharedKey, bobCipher, false);
+            String deryptedKeyString = Base64.encode(decryptedSharedKey);
+
+            assertEquals(rijndaelKeyString, deryptedKeyString);
+        }
+    }
+}

Added: trunk/apps/bookmarkplugin/src/falimat/freenet/network/RegExes.java
===================================================================
--- trunk/apps/bookmarkplugin/src/falimat/freenet/network/RegExes.java  
2006-03-07 18:55:50 UTC (rev 8182)
+++ trunk/apps/bookmarkplugin/src/falimat/freenet/network/RegExes.java  
2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,22 @@
+package falimat.freenet.network;
+
+import java.util.regex.Pattern;
+
+public class RegExes {
+    private final static Pattern FREENET_KEY = 
Pattern.compile("(?:CHK|SSK|KSK)@.+");
+
+    public final static Pattern FPROXY_URI = Pattern.compile("^https?\\:.*/" + 
FREENET_KEY + "$");
+
+    public final static Pattern FREENET_URI = Pattern.compile("^(freenet\\:)?" 
+ FREENET_KEY + "$");
+
+    public final static Pattern TAG = Pattern.compile("[a-z]+");
+
+    public final static Pattern TAGS = Pattern.compile("^(?:" + TAG + 
"[\\s,]*)*" + TAG + "$");
+
+    public static final Pattern SSK_KEY = Pattern.compile("SSK at .+");
+
+    public static final Pattern CHANNEL_URI = 
Pattern.compile(SSK_KEY+"/.+-[0-9]+");
+
+    public static final Pattern INTEGER = Pattern.compile("[0-9]+");
+
+}

Added: trunk/apps/bookmarkplugin/src/falimat/freenet/network/SlotReader.java
===================================================================
--- trunk/apps/bookmarkplugin/src/falimat/freenet/network/SlotReader.java       
2006-03-07 18:55:50 UTC (rev 8182)
+++ trunk/apps/bookmarkplugin/src/falimat/freenet/network/SlotReader.java       
2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,17 @@
+package falimat.freenet.network;
+
+import java.util.List;
+
+import xomat.util.ParamException;
+
+import falimat.freenet.bookmarkplugin.model.Bookmark;
+
+public interface SlotReader {
+
+    void setKeypair(String bobPrivateSSK, String bobPublicSSK);
+
+    void readMessages(byte[] slotBytes) throws ParamException;
+
+    List<Bookmark> getBookmarks();
+
+}

Added: trunk/apps/bookmarkplugin/src/falimat/freenet/network/SlotWriter.java
===================================================================
--- trunk/apps/bookmarkplugin/src/falimat/freenet/network/SlotWriter.java       
2006-03-07 18:55:50 UTC (rev 8182)
+++ trunk/apps/bookmarkplugin/src/falimat/freenet/network/SlotWriter.java       
2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,23 @@
+package falimat.freenet.network;
+
+import java.util.List;
+
+import falimat.freenet.bookmarkplugin.model.AbstractSendable;
+import falimat.freenet.bookmarkplugin.model.Slot;
+import falimat.freenet.bookmarkplugin.model.User;
+
+public interface SlotWriter {
+
+    String getContentType();
+    
+    void setKeypair(User inserter);
+    
+    void  writeObjects(List<AbstractSendable> sendables);
+
+    byte[] toByteArray();
+
+    void setSlot(Slot slot);
+
+    String getInsertUri();
+    
+}

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/network/XmlMessageCodec.java
===================================================================
--- trunk/apps/bookmarkplugin/src/falimat/freenet/network/XmlMessageCodec.java  
2006-03-07 18:55:50 UTC (rev 8182)
+++ trunk/apps/bookmarkplugin/src/falimat/freenet/network/XmlMessageCodec.java  
2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,174 @@
+package falimat.freenet.network;
+
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.dom4j.Element;
+
+import falimat.freenet.bookmarkplugin.model.User;
+import falimat.freenet.crypt.CryptoUtil;
+
+public class XmlMessageCodec {
+
+    private final static String ELEMENT_PLAIN = "plain";
+
+    private final static String ELEMENT_CIPHERED = "ciphered";
+
+    private final static String ELEMENT_SENDER = "sender";
+
+    private final static String ELEMENT_RECIPIENT = "recipient";
+
+    private final static String ATTR_SIGNATURE = "signature";
+
+    private final static String ATTR_SSK = "ssk";
+
+    private Element xmlElement;
+
+    private Element cipheredElement;
+
+    private Element plainElement;
+
+    private String messageType;
+
+    private String senderSSK;
+
+    private Set<String> recipients = new TreeSet<String>();
+
+    private boolean hiddenRecipients;
+
+    private boolean hiddenSender;
+
+    private boolean hiddenContent;
+
+    protected XmlMessageCodec(String type, Element slotElement) {
+        this.messageType = type;
+        this.xmlElement = slotElement.addElement(this.messageType);
+    }
+
+    protected XmlMessageCodec(Element messageElement) {
+        this.messageType = messageElement.getName();
+        this.xmlElement = messageElement;
+
+        this.plainElement = this.xmlElement.element(ELEMENT_PLAIN);
+        this.cipheredElement = this.xmlElement.element(ELEMENT_CIPHERED);
+
+        this.senderSSK = 
this.plainElement.element(ELEMENT_SENDER).attributeValue(ATTR_SSK);
+    }
+    
+    static XmlMessageCodec forType(Class clazz, Element slotElement) {
+        String className = 
clazz.getName().substring(clazz.getName().lastIndexOf('.')+1).toLowerCase();
+        XmlMessageCodec codec = new XmlMessageCodec(className, slotElement);
+        return codec;
+    }
+    protected void enableEncryption(boolean sender, boolean recipients, 
boolean content) {
+        this.hiddenSender = sender;
+        this.hiddenRecipients = recipients;
+        this.hiddenContent = content;
+    }
+
+    public void setSender(String senderSSK) {
+        this.senderSSK = senderSSK;
+        Element senderElement;
+        if (this.hiddenSender) {
+            senderElement = 
this.getCipheredElement().addElement(ELEMENT_SENDER);
+
+        } else {
+            senderElement = this.getPlainElement().addElement(ELEMENT_SENDER);
+        }
+
+        senderElement.addAttribute(ATTR_SSK, senderSSK);
+    }
+
+    public String getSender() {
+        return this.senderSSK;
+    }
+
+    protected Element getPlainElement() {
+        if (this.plainElement == null) {
+            this.plainElement = this.xmlElement.addElement(ELEMENT_PLAIN);
+        }
+        return this.plainElement;
+    }
+
+    protected Element getCipheredElement() {
+        if (this.cipheredElement == null) {
+            this.cipheredElement = 
this.xmlElement.addElement(ELEMENT_CIPHERED);
+        }
+        return this.cipheredElement;
+    }
+
+    public void writeElement(String name, long longValue) {
+        this.writeElement(name, Long.toString(longValue));
+    }
+
+    public void writeElement(String name, int intValue) {
+        this.writeElement(name, Integer.toString(intValue));
+    }
+
+    public void writeElement(String name, String textValue) {
+        if (textValue == null) {
+            return;
+        }
+        Element element;
+        if (this.hiddenContent) {
+            element = this.getCipheredElement().addElement(name);
+        } else {
+            element = this.getPlainElement().addElement(name);
+        }
+        element.setText(textValue);
+        return;
+    }
+
+    public void sign(User sender) {
+        String signature = CryptoUtil.calculateSignature(this.xmlElement, 
sender.getDsaPrivateKey(), sender.getDsaPublicKey());
+        this.xmlElement.addAttribute(ATTR_SIGNATURE, signature);
+    }
+
+    public boolean verify(User sender) {
+        String signature = this.xmlElement.attributeValue(ATTR_SIGNATURE);
+        this.xmlElement.addAttribute(ATTR_SIGNATURE, null);
+        boolean verified = CryptoUtil.verifySignature(this.xmlElement, 
signature, sender.getDsaPublicKey());
+        this.xmlElement.addAttribute(ATTR_SIGNATURE, signature);
+        return verified;
+    }
+
+    public String getElementText(String name) {
+        if (this.hiddenContent) {
+            // TODO: implement hidden content
+            throw new RuntimeException("hidden content not implemented");
+        } else {
+            return this.plainElement.elementText(name);
+        }
+    }
+
+    public int getElementInt(String name, int defaultValue) {
+        String string = this.getElementText(name);
+        try {
+            return Integer.parseInt(string);
+        } catch (Exception e) {
+            return defaultValue;
+        }
+    }
+
+    public long getElementLong(String name, long defaultValue) {
+        String string = this.getElementText(name);
+        try {
+            return Long.parseLong(string);
+        } catch (Exception e) {
+            return defaultValue;
+        }
+    }
+
+    public Set<String> getElementTextSet(String name) {
+        Set<String> out = new TreeSet<String>();
+        List<Element> elements = this.plainElement.elements(name);
+        if (elements != null) {
+            for (Element element : elements) {
+                out.add(element.getText());
+            }
+        }
+        return out;
+    }
+
+}

Added: trunk/apps/bookmarkplugin/src/falimat/freenet/network/XmlSlotReader.java
===================================================================
--- trunk/apps/bookmarkplugin/src/falimat/freenet/network/XmlSlotReader.java    
2006-03-07 18:55:50 UTC (rev 8182)
+++ trunk/apps/bookmarkplugin/src/falimat/freenet/network/XmlSlotReader.java    
2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,217 @@
+package falimat.freenet.network;
+
+import java.io.ByteArrayInputStream;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.dom4j.Document;
+import org.dom4j.Element;
+
+import xomat.util.ParamException;
+import xomat.util.ParamRuntimeException;
+import xomat.util.xml.ValidationException;
+import xomat.util.xml.XmlUtils;
+import falimat.freenet.bookmarkplugin.model.Bookmark;
+import falimat.freenet.bookmarkplugin.model.Channel;
+import falimat.freenet.bookmarkplugin.model.Ping;
+import falimat.freenet.bookmarkplugin.model.Pong;
+import falimat.freenet.bookmarkplugin.model.User;
+import falimat.freenet.crypt.CryptoUtil;
+
+public class XmlSlotReader implements SlotReader {
+
+    private String privateSSK;
+
+    private String publicSSK;
+
+    private Document slotDocument;
+
+    private Element slotElement;
+
+    private Map<String, User> sskUserMap = new TreeMap<String, User>();
+
+    private Map<String, String> sskDhMap = new TreeMap<String, String>();
+
+    private String contentType = XmlSlotWriter.CONTENT_TYPE;
+
+    public void setKeypair(String privateSSK, String publicSSK) {
+        this.privateSSK = privateSSK;
+        this.publicSSK = publicSSK;
+    }
+
+    public List<User> getUsers() {
+        return new LinkedList<User>(this.sskUserMap.values());
+    }
+
+    public void readMessages(byte[] slotBytes) throws ParamException {
+        try {
+            ByteArrayInputStream bais = new ByteArrayInputStream(slotBytes);
+            this.slotDocument = XmlUtils.readDocument(bais, false);
+            this.slotElement = this.slotDocument.getRootElement();
+
+            List<Element> userElements = 
this.slotElement.elements(XmlSlotWriter.ELEMENT_USER);
+            for (Element userElement : userElements) {
+
+                try {
+
+                    String ssk = 
userElement.attributeValue(XmlSlotWriter.PUBLIC_SSK);
+                    byte[] dsa = 
CryptoUtil.decodeBase64(userElement.attributeValue(XmlSlotWriter.PUBLIC_DSA));
+
+                    // byte[] dh =
+                    // 
CryptoUtil.decodeBase64(userElement.attributeValue(XmlSlotWriter.PUBLIC_DH));
+                    String name = 
userElement.attributeValue(XmlSlotWriter.ATTR_NAME);
+
+                    if (!CryptoUtil.verifyPublicKey(ssk, dsa)) {
+                        String msg = "Failed to verify public dsa key in key 
element {0}";
+                        throw new ParamException(msg, 
XmlUtils.toString(userElement));
+                    }
+
+                    String expectedSignature = 
userElement.attributeValue(XmlSlotWriter.ATTR_SIGNATURE);
+                    userElement.addAttribute(XmlSlotWriter.ATTR_SIGNATURE, 
null);
+                    if (!CryptoUtil.verifySignature(userElement, 
expectedSignature, dsa)) {
+                        String msg = "Failed to verify signature of key 
element {0}";
+                        throw new ParamException(msg, 
XmlUtils.toString(userElement));
+                    }
+
+                    User user = new User();
+                    user.setName(name);
+                    user.setPublicSSK(ssk);
+                    user.setDsaPublicKey(dsa);
+                    // user.setDhPublicKey(dh);
+
+                    this.sskUserMap.put(ssk, user);
+                } catch (Exception e) {
+                    String msg = "Failed to parse user data from element {0}";
+                    throw new ParamRuntimeException(msg, 
XmlUtils.toString(userElement), e);
+                }
+            }
+
+        } catch (ValidationException e) {
+            String msg = "XmlSlotReader failed to validate document";
+            throw new ParamException(msg, e);
+        }
+    }
+
+    public List<Channel> getChannels() {
+        List<Channel> channels = new LinkedList<Channel>();
+
+        List<Element> channelElement = 
this.slotElement.elements(XmlSlotWriter.TYPE_CHANNEL);
+        for (Element cElement : channelElement) {
+            XmlMessageCodec reader = new XmlMessageCodec(cElement);
+            String publicSSk = reader.getSender();
+            User sender = this.sskUserMap.get(publicSSk);
+            if (sender == null) {
+                String msg = "No user data found for verifying signature of 
message {0}";
+                throw new ParamRuntimeException(msg, 
XmlUtils.toString(cElement));
+            }
+            if (!reader.verify(sender)) {
+                String msg = "Failed to verify signature of message {0}";
+                throw new ParamRuntimeException(msg, 
XmlUtils.toString(cElement));
+            }
+            Channel c = new Channel();
+            c.setSender(sender.getPublicSSK());
+            
c.setBasename(reader.getElementText(XmlSlotWriter.ELEMENT_BASENAME));
+            
c.setLastSlot(reader.getElementInt(XmlSlotWriter.ELEMENT_LAST_SLOT, -1));
+            
c.setLastInsertTime(reader.getElementLong(XmlSlotWriter.ELEMENT_LAST_INSERT_TIME,
 -1));
+            
c.setInsertInterval(reader.getElementInt(XmlSlotWriter.ELEMENT_INSERT_INTERVAL, 
-1));
+            
c.setLastModified(reader.getElementLong(XmlSlotWriter.ELEMENT_LAST_MODIFIED, 
-1));
+            channels.add(c);
+        }
+        return channels;
+    }
+
+    public List<Bookmark> getBookmarks() {
+        List<Bookmark> bookmarks = new LinkedList<Bookmark>();
+
+        List<Element> bookmarkElements = 
this.slotElement.elements(XmlSlotWriter.TYPE_BOOKMARK);
+        for (Element bElement : bookmarkElements) {
+            XmlMessageCodec reader = new XmlMessageCodec(bElement);
+            String publicSSk = reader.getSender();
+            User sender = this.sskUserMap.get(publicSSk);
+            if (sender == null) {
+                String msg = "No user data found for verifying signature of 
message {0}";
+                throw new ParamRuntimeException(msg, 
XmlUtils.toString(bElement));
+            }
+            if (!reader.verify(sender)) {
+                String msg = "Failed to verify signature of message {0}";
+                throw new ParamRuntimeException(msg, 
XmlUtils.toString(bElement));
+            }
+
+            Bookmark b = new Bookmark();
+            b.setSender(sender.getPublicSSK());
+            b.setUri(reader.getElementText("uri"));
+            b.setTitle(reader.getElementText("title"));
+            b.setDescription(reader.getElementText("description"));
+            b.setRating(reader.getElementInt("rating", -1));
+            b.setLastModified(reader.getElementLong("lastModified", 0));
+            b.setSize(reader.getElementLong("size", 0));
+            b.setContentType(reader.getElementText("contentType"));
+            b.setTags(reader.getElementTextSet("tag"));
+
+            bookmarks.add(b);
+        }
+        return bookmarks;
+    }
+
+    public List<Ping> getPings() {
+        List<Ping> pings = new LinkedList<Ping>();
+
+        List<Element> pingElements = 
this.slotElement.elements(XmlSlotWriter.TYPE_PING);
+        for (Element pElement : pingElements) {
+            XmlMessageCodec reader = new XmlMessageCodec(pElement);
+            String publicSSk = reader.getSender();
+            User sender = this.sskUserMap.get(publicSSk);
+            if (sender == null) {
+                String msg = "No user data found for verifying signature of 
message {0}";
+                throw new ParamRuntimeException(msg, 
XmlUtils.toString(pElement));
+            }
+            if (!reader.verify(sender)) {
+                String msg = "Failed to verify signature of message {0}";
+                throw new ParamRuntimeException(msg, 
XmlUtils.toString(pElement));
+            }
+
+            // TODO: parse recipients
+            Ping p = new Ping();
+            p.setSender(sender.getPublicSSK());
+            
p.setInsertTime(reader.getElementInt(XmlSlotWriter.ELEMENT_INSERT_TIME, -1));
+            pings.add(p);
+        }
+        return pings;
+    }
+
+    public List<Pong> getPongs() {
+        List<Pong> pongs = new LinkedList<Pong>();
+
+        List<Element> pongElements = 
this.slotElement.elements(XmlSlotWriter.TYPE_PONG);
+        for (Element pElement : pongElements) {
+            XmlMessageCodec reader = new XmlMessageCodec(pElement);
+            String publicSSk = reader.getSender();
+            User sender = this.sskUserMap.get(publicSSk);
+            if (sender == null) {
+                String msg = "No user data found for verifying signature of 
message {0}";
+                throw new ParamRuntimeException(msg, 
XmlUtils.toString(pElement));
+            }
+            if (!reader.verify(sender)) {
+                String msg = "Failed to verify signature of message {0}";
+                throw new ParamRuntimeException(msg, 
XmlUtils.toString(pElement));
+            }
+            // TODO: parse recipients
+
+            Pong p = new Pong();
+            p.setSender(sender.getPublicSSK());
+            
p.setInsertTime(reader.getElementInt(XmlSlotWriter.ELEMENT_INSERT_TIME, -1));
+            p.setPingId(reader.getElementText(XmlSlotWriter.ELEMENT_PING_ID));
+            
p.setPingInsertTime(reader.getElementInt(XmlSlotWriter.ELEMENT_PING_INSERT_TIME,
 -1));
+            
p.setPingFetchTime(reader.getElementInt(XmlSlotWriter.ELEMENT_PING_FETCH_TIME, 
-1));
+            pongs.add(p);
+        }
+        return pongs;
+    }
+
+    public String getContentType() {
+        return this.contentType;
+    }
+
+}

Added: trunk/apps/bookmarkplugin/src/falimat/freenet/network/XmlSlotWriter.java
===================================================================
--- trunk/apps/bookmarkplugin/src/falimat/freenet/network/XmlSlotWriter.java    
2006-03-07 18:55:50 UTC (rev 8182)
+++ trunk/apps/bookmarkplugin/src/falimat/freenet/network/XmlSlotWriter.java    
2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,179 @@
+package falimat.freenet.network;
+
+import java.io.ByteArrayOutputStream;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.dom4j.Document;
+import org.dom4j.DocumentFactory;
+import org.dom4j.Element;
+
+import xomat.util.xml.XmlUtils;
+import falimat.freenet.bookmarkplugin.model.AbstractSendable;
+import falimat.freenet.bookmarkplugin.model.Bookmark;
+import falimat.freenet.bookmarkplugin.model.Channel;
+import falimat.freenet.bookmarkplugin.model.Ping;
+import falimat.freenet.bookmarkplugin.model.Pong;
+import falimat.freenet.bookmarkplugin.model.Slot;
+import falimat.freenet.bookmarkplugin.model.User;
+import falimat.freenet.crypt.CryptoUtil;
+
+public class XmlSlotWriter implements SlotWriter {
+
+    static final String ELEMENT_PING_ID = "pingId";
+
+    static final String ELEMENT_PING_FETCH_TIME = "pingFetchTime";
+
+    static final String ELEMENT_PING_INSERT_TIME = "pingInsertTime";
+
+    static final String ELEMENT_INSERT_TIME = "insertTime";
+
+    private static final String ELEMENT_CONTENT_TYPE = "contentType";
+
+    static final String ELEMENT_SIZE = "size";
+
+    static final String ELEMENT_LAST_MODIFIED = "lastModified";
+
+    static final String ELEMENT_RATING = "rating";
+
+    static final String ELEMENT_DESCRIPTION = "description";
+
+    static final String ELEMENT_TITLE = "title";
+
+    static final String ELEMENT_URI = "uri";
+
+    public static final String ATTR_SIGNATURE = "signature";
+
+    public static final String ELEMENT_KEYS = "keys";
+
+    public static final String PUBLIC_DH = "public-dh";
+
+    public static final String PUBLIC_DSA = "public-dsa";
+
+    public static final String PUBLIC_SSK = "public-ssk";
+
+    private final static Log log = LogFactory.getLog(XmlSlotWriter.class);
+
+    public final static String CONTENT_TYPE = 
"application/xml;falimat-version=0.1";
+
+    final static String TYPE_BOOKMARK = "bookmark";
+
+    public static final String ELEMENT_USER = "user";
+
+    public static final String ATTR_NAME = "name";
+
+    static final String TYPE_PING = "ping";
+
+    static final String TYPE_PONG = "pong";
+
+    public static final String ELEMENT_BASENAME = "basename";
+
+    public static final String ELEMENT_LAST_SLOT = "lastSlot";
+
+    public static final String ELEMENT_LAST_INSERT_TIME = "lastInsertTime";
+
+    public static final String ELEMENT_INSERT_INTERVAL = "insertInterval";
+
+    public static final String TYPE_CHANNEL = "channel";
+
+    private User inserter;
+
+    private Element slotElement;
+
+    private Document slotDocument;
+
+    private Slot slot;
+
+    public XmlSlotWriter() {
+        this.slotDocument = DocumentFactory.getInstance().createDocument();
+        this.slotElement = DocumentFactory.getInstance().createElement("slot");
+        this.slotDocument.setRootElement(this.slotElement);
+    }
+
+    public void setSlot(Slot slot) {
+        this.slot = slot;
+        this.slotElement.addAttribute(ELEMENT_URI, slot.getUri());
+        this.slotElement.addAttribute("insert-time", 
Long.toString(slot.getRequestTime()));
+    }
+
+    public String getInsertUri() {
+        String publicUri = slot.getUri();
+        return inserter.getPrivateSSK() + 
publicUri.substring(publicUri.indexOf('/'));
+    }
+
+    public String getContentType() {
+        return CONTENT_TYPE;
+    }
+
+    public void setKeypair(User inserter) {
+
+        this.inserter = inserter;
+
+        Element userElement = this.slotElement.addElement(ELEMENT_USER);
+        userElement.addAttribute(PUBLIC_SSK, inserter.getPublicSSK());
+        userElement.addAttribute(PUBLIC_DSA, 
CryptoUtil.encodeBase64(inserter.getDsaPublicKey()));
+//        userElement.addAttribute(PUBLIC_DH, 
CryptoUtil.encodeBase64(inserter.getDhPublicKey()));
+        userElement.addAttribute(ATTR_NAME, inserter.getName());
+
+        String signature = CryptoUtil.calculateSignature(userElement, 
inserter.getDsaPrivateKey(), inserter
+                .getDsaPublicKey());
+        userElement.addAttribute(ATTR_SIGNATURE, signature);
+    }
+
+    public void writeObjects(List<AbstractSendable> sendables) {
+        for (AbstractSendable object : sendables) {
+            if (!object.getSender().equals(inserter.getPublicSSK())) {
+                log.warn("Won't publish message from sender " + 
object.getSender()
+                        + " because forwarding of bomessageokmarks is not yet 
implemented and inserter ssk is "
+                        + inserter.getPublicSSK());
+                continue;
+
+            }
+            XmlMessageCodec codec = XmlMessageCodec.forType(object.getClass(), 
this.slotElement);
+            codec.setSender(inserter.getPublicSSK());
+
+            if (object instanceof Bookmark) {
+                Bookmark b = (Bookmark) object;
+                codec.writeElement(ELEMENT_URI, b.getUri());
+                codec.writeElement(ELEMENT_TITLE, b.getTitle());
+                codec.writeElement(ELEMENT_DESCRIPTION, b.getDescription());
+                codec.writeElement(ELEMENT_RATING, b.getRating());
+                codec.writeElement(ELEMENT_LAST_MODIFIED, b.getLastModified());
+                codec.writeElement(ELEMENT_SIZE, b.getSize());
+                codec.writeElement(ELEMENT_CONTENT_TYPE, b.getContentType());
+
+                for (String tag : b.getTags()) {
+                    codec.writeElement("tag", tag);
+                }
+            } else if (object instanceof Pong) {
+                Pong pong = (Pong) object;
+                codec.writeElement(ELEMENT_INSERT_TIME, pong.getInsertTime());
+                codec.writeElement(ELEMENT_PING_INSERT_TIME, 
pong.getPingInsertTime());
+                codec.writeElement(ELEMENT_PING_FETCH_TIME, 
pong.getPingFetchTime());
+                codec.writeElement(ELEMENT_PING_ID, pong.getPingId());
+            } else if (object instanceof Ping) {
+                Ping p = (Ping) object;
+                codec.writeElement(ELEMENT_INSERT_TIME, p.getInsertTime());
+            } else if (object instanceof Channel) {
+                Channel channel = (Channel) object;
+                codec.writeElement(ELEMENT_BASENAME, channel.getBasename());
+                codec.writeElement(ELEMENT_LAST_SLOT, channel.getLastSlot());
+                codec.writeElement(ELEMENT_LAST_INSERT_TIME, 
channel.getLastInsertTime());
+                codec.writeElement(ELEMENT_INSERT_INTERVAL, 
channel.getInsertInterval());
+                codec.writeElement(ELEMENT_LAST_MODIFIED, 
channel.getLastModified());
+
+            }
+
+            codec.sign(inserter);
+        }
+    }
+
+    public byte[] toByteArray() {
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        XmlUtils.printDocument(this.slotDocument, baos, false, false);
+        return baos.toByteArray();
+    }
+
+}

Added: trunk/apps/bookmarkplugin/src/falimat/freenet/tests/RegexTest.java
===================================================================
--- trunk/apps/bookmarkplugin/src/falimat/freenet/tests/RegexTest.java  
2006-03-07 18:55:50 UTC (rev 8182)
+++ trunk/apps/bookmarkplugin/src/falimat/freenet/tests/RegexTest.java  
2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,75 @@
+package falimat.freenet.tests;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import falimat.freenet.network.RegExes;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.TestCase;
+
+public class RegexTest extends TestCase {
+
+    public void testFreenetUri() {
+        Pattern pattern = RegExes.FREENET_URI;
+
+        assertMatches(pattern, "KSK at test.txt");
+        assertMatches(pattern, "freenet:KSK at test.txt");
+
+        assertMatches(pattern,
+                "SSK at 
uw8TPvhHbWe7GBH-PKMXy9A41w2hdMQZSY-RRrDBaiQ,AQnmRFKMOHx2uRulAfvjThGWWi~MbT521w1rmNuLVgk,AQABAAE/hack-a-bike///");
+        assertMatches(
+                pattern,
+                "freenet:SSK at 
uw8TPvhHbWe7GBH-PKMXy9A41w2hdMQZSY-RRrDBaiQ,AQnmRFKMOHx2uRulAfvjThGWWi~MbT521w1rmNuLVgk,AQABAAE/hack-a-bike///");
+
+        assertMatches(pattern,
+                "CHK at 
INMq6ryw8nYcxwMMSdKd~pSMI26rlVWFbFFD~ikFv1c,-jzPOS4lNJl62IY2QjHzfGMo0gOgqCU28A1AF0a48Ps,AAEC--8");
+        assertMatches(pattern,
+                "freenet:CHK at 
INMq6ryw8nYcxwMMSdKd~pSMI26rlVWFbFFD~ikFv1c,-jzPOS4lNJl62IY2QjHzfGMo0gOgqCU28A1AF0a48Ps,AAEC--8");
+
+        assertNotMatches(pattern, "http://www.google.com";);
+        assertNotMatches(pattern, "GSG at 9");
+    }
+
+    public void testFproxyUri() {
+        Pattern pattern = RegExes.FPROXY_URI;
+
+        assertMatches(pattern, "http://localhost:8888/KSK at test.txt");
+        assertMatches(
+                pattern,
+                "http://192.168.0.1/CHK at 
INMq6ryw8nYcxwMMSdKd~pSMI26rlVWFbFFD~ikFv1c,-jzPOS4lNJl62IY2QjHzfGMo0gOgqCU28A1AF0a48Ps,AAEC--8");
+        assertMatches(
+                pattern,
+                "https://192.168.0.1/SSK at 
uw8TPvhHbWe7GBH-PKMXy9A41w2hdMQZSY-RRrDBaiQ,AQnmRFKMOHx2uRulAfvjThGWWi~MbT521w1rmNuLVgk,AQABAAE/hack-a-bike///");
+
+        assertNotMatches(pattern, "http://www.google.com";);
+        assertNotMatches(pattern, "http://192.168.0.1/KSK@";);
+    }
+    
+    public void testTags() {
+        Pattern pattern = RegExes.TAGS;
+        assertMatches(pattern, "test");
+        assertMatches(pattern, "abc def");
+        assertMatches(pattern, "abc def ghi");
+        assertMatches(pattern, "abc, def");
+        assertMatches(pattern, "abc, def,ghi");
+        
+        assertNotMatches(pattern, "L????.-");
+        assertNotMatches(pattern, " ");
+    }
+
+    private void assertMatches(Pattern pattern, String string) {
+        Matcher matcher = pattern.matcher(string);
+        if (!matcher.matches()) {
+            throw new AssertionFailedError("String '" + string + "' does not 
match pattern '" + pattern.pattern() + "'");
+        }
+    }
+
+    private void assertNotMatches(Pattern pattern, String string) {
+        Matcher matcher = pattern.matcher(string);
+        if (matcher.matches()) {
+            throw new AssertionFailedError("String '" + string + "' 
unexpectedly matches pattern '" + pattern.pattern()
+                    + "' but it shouldn't");
+        }
+    }
+}

Added: trunk/apps/bookmarkplugin/src/falimat/freenet/tests/StoreTest.java
===================================================================
--- trunk/apps/bookmarkplugin/src/falimat/freenet/tests/StoreTest.java  
2006-03-07 18:55:50 UTC (rev 8182)
+++ trunk/apps/bookmarkplugin/src/falimat/freenet/tests/StoreTest.java  
2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,50 @@
+package falimat.freenet.tests;
+
+import java.util.List;
+
+import falimat.beanstore.BeanStore;
+import falimat.beanstore.GenericQuery;
+import falimat.beanstore.StoreQuery;
+import falimat.beanstore.test.BeanStoreTestBase;
+import falimat.freenet.bookmarkplugin.model.Bookmark;
+import falimat.freenet.bookmarkplugin.model.User;
+import freenet.crypt.Yarrow;
+
+public class StoreTest extends BeanStoreTestBase {
+    public void testQueryUnpublished() {
+
+        User user = new User("alice", new Yarrow());
+
+        BeanStore<Bookmark> beanStore = this.factory.getBeanStore("bookmarks", 
Bookmark.class);
+
+        Bookmark b1 = new Bookmark();
+        b1.setPublished(true);
+        b1.setUri("KSK at test.txt");
+        b1.setSender(user.getPublicSSK());
+
+        Bookmark b2 = new Bookmark();
+        b2.setPublished(false);
+        b2.setUri("KSK at gpl.txt");
+        b2.setSender(user.getPublicSSK());
+
+        beanStore.put(b1.getId(), b1);
+        beanStore.put(b2.getId(), b2);
+
+        GenericQuery query = StoreQuery.is("sender", user.getPublicSSK());
+        List<Bookmark> results = beanStore.executeQuery(query);
+        List<Bookmark> expected = beanStore.arrayToList(b1, b2);
+        assertEquals(expected.size(), results.size());
+
+        query = StoreQuery.is("sender", 
user.getPublicSSK()).andIs("published", true);
+        results = beanStore.executeQuery(query);
+        expected = beanStore.arrayToList(b1);
+        assertEquals(expected.size(), results.size());
+        assertEquals(b1.getId(), expected.get(0).getId());
+        
+        query = StoreQuery.is("sender", 
user.getPublicSSK()).andIs("published", false);
+        results = beanStore.executeQuery(query);
+        expected = beanStore.arrayToList(b2);
+        assertEquals(expected.size(), results.size());
+        assertEquals(b2.getId(), expected.get(0).getId());        
+    }
+}

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/AbstractHtmlComponent.java
===================================================================
--- 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/AbstractHtmlComponent.java
  2006-03-07 18:55:50 UTC (rev 8182)
+++ 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/AbstractHtmlComponent.java
  2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,7 @@
+package falimat.freenet.webplugin;
+
+
+public abstract class AbstractHtmlComponent implements HtmlComponent {
+
+
+}

Added: trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/Action.java
===================================================================
--- trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/Action.java 
2006-03-07 18:55:50 UTC (rev 8182)
+++ trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/Action.java 
2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,9 @@
+package falimat.freenet.webplugin;
+
+import freenet.pluginmanager.PluginHTTPRequest;
+
+
+public interface Action {
+    String getId();
+    void execute(PluginHTTPRequest request);
+}

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/ActionComponent.java
===================================================================
--- 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/ActionComponent.java    
    2006-03-07 18:55:50 UTC (rev 8182)
+++ 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/ActionComponent.java    
    2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,7 @@
+package falimat.freenet.webplugin;
+
+public interface ActionComponent extends HtmlComponent {
+
+    Action getAction(String id);
+
+}

Added: trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/ErrorPage.java
===================================================================
--- trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/ErrorPage.java      
2006-03-07 18:55:50 UTC (rev 8182)
+++ trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/ErrorPage.java      
2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,14 @@
+package falimat.freenet.webplugin;
+
+import falimat.freenet.webplugin.components.MessageArea;
+
+public class ErrorPage extends HtmlPage {
+    public ErrorPage(String msg, Throwable t) {
+        super("Error Page"); // TODO: This is weird
+        super.setTitle("Error in freenet plugin: " + msg);
+        MessageArea messageArea = new MessageArea();
+        messageArea.setHeadline(msg);
+        messageArea.setMessage("<pre>" + HtmlPage.getStacktrace(t) + "</pre>");
+        super.addComponent(messageArea);
+    } 
+}

Added: trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/FormAction.java
===================================================================
--- trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/FormAction.java     
2006-03-07 18:55:50 UTC (rev 8182)
+++ trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/FormAction.java     
2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,33 @@
+package falimat.freenet.webplugin;
+
+import falimat.freenet.webplugin.components.Form;
+import freenet.pluginmanager.PluginHTTPRequest;
+
+public abstract class FormAction extends SimpleAction {
+
+    private Form form;
+    
+    private boolean validateBeforeExecution = true;
+
+    public FormAction(Form form) {
+        this.form = form;
+    }
+
+    public FormAction(Form form, boolean validateBeforeExecution) {
+        this.form = form;
+        this.validateBeforeExecution = validateBeforeExecution;   
+    }
+
+    @Override
+    public void execute(PluginHTTPRequest request) {
+        boolean validationSuccesfull = validateBeforeExecution ? 
this.form.validate(request) : true;
+        if (validationSuccesfull) {
+            this.onSubmit(request);
+        }
+    }
+    
+    protected abstract void onSubmit(PluginHTTPRequest request);
+    
+    protected abstract void onInvalidSubmission(PluginHTTPRequest request);
+
+}

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/FormComponent.java
===================================================================
--- trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/FormComponent.java  
2006-03-07 18:55:50 UTC (rev 8182)
+++ trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/FormComponent.java  
2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,11 @@
+package falimat.freenet.webplugin;
+
+import freenet.pluginmanager.PluginHTTPRequest;
+
+public interface FormComponent {
+    void setValueFromRequest(PluginHTTPRequest request);
+
+    boolean validate(PluginHTTPRequest request);
+
+    void reset();
+}

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/HtmlComponent.java
===================================================================
--- trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/HtmlComponent.java  
2006-03-07 18:55:50 UTC (rev 8182)
+++ trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/HtmlComponent.java  
2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,8 @@
+package falimat.freenet.webplugin;
+
+
+public interface HtmlComponent {
+
+    void renderHtml(HtmlWriter out, HtmlPage contextPage);
+
+}

Added: trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/HtmlPage.java
===================================================================
--- trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/HtmlPage.java       
2006-03-07 18:55:50 UTC (rev 8182)
+++ trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/HtmlPage.java       
2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,186 @@
+package falimat.freenet.webplugin;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+
+public class HtmlPage {
+
+    private final static String doctype = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD 
HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\";>";
+
+    private String title = "Untitled Freenet Plugin";
+
+    private final List<HtmlComponent> componentList = new 
ArrayList<HtmlComponent>();
+
+    private final Map<String, HtmlPage> subPagesMap = new HashMap<String, 
HtmlPage>();
+
+    private String path = "/";
+
+    private String name;
+
+    private int level = 0;
+
+    public HtmlPage(String name) {
+        this.name = name;
+        this.title = name;
+    }
+
+    public void constructComponents() {
+
+    }
+
+    public void renderHtml(HtmlWriter out) {
+        out.append(doctype + "\n");
+        out.append("<html>");
+        this.renderHeadHtml(out);
+        this.renderBodyHtml(out);
+        out.append("</html>");
+    }
+
+    public Action findAction(String actionId) {
+        for (Iterator iter = this.componentList.iterator(); iter.hasNext();) {
+            HtmlComponent component = (HtmlComponent) iter.next();
+            if (component instanceof ActionComponent) {
+                Action action = ((ActionComponent) 
component).getAction(actionId);
+                if (action != null) {
+                    return action;
+                }
+            }
+        }
+        return null;
+    }
+
+    public HtmlPage createSubPage(String name, String pathElement) {
+        if (pathElement.contains("/")) {
+            throw new IllegalArgumentException("the id of the subpage must not 
contain the /");
+        }
+        HtmlPage subpage = new HtmlPage(name);
+        this.addSubpage(subpage, pathElement);
+        return subpage;
+    }
+
+    public void addSubpage(HtmlPage page, String pathElement) {
+        page.setTitle(this.title + " - " + page.getName());
+        page.level = this.level + 1;
+        page.path = this.getPath() + pathElement + "/";
+        synchronized (this.subPagesMap) {
+            this.subPagesMap.put(pathElement, page);
+        }
+    }
+
+    public HtmlPage[] listSubpages() {
+        synchronized (this.subPagesMap) {
+            HtmlPage[] subpages = new HtmlPage[this.subPagesMap.size()];
+            this.subPagesMap.values().toArray(subpages);
+            return subpages;
+        }
+    }
+
+    private void renderHeadHtml(HtmlWriter out) {
+        out.append("<head>");
+        out.append("<title>" + this.title + "</title>");
+        out.writeCssLink("/falimat/freenet/webplugin/style.css");
+        out.append("</head>");
+    }
+
+    public void addComponent(HtmlComponent component) {
+        synchronized (this.componentList) {
+            this.componentList.add(component);
+        }
+    }
+
+    public void removeCompoment(HtmlComponent component) {
+        synchronized (this.componentList) {
+            this.componentList.remove(component);
+        }
+    }
+
+    public void addToAll(HtmlComponent component) {
+        this.addComponent(component);
+        HtmlPage[] subpages = this.listSubpages();
+        for (int i = 0; i < subpages.length; i++) {
+            subpages[i].addToAll(component);
+        }
+    }
+
+    private void renderBodyHtml(HtmlWriter out) {
+        out.append("<body>");
+
+        out.append("<h1>"+this.name+"</h1>");
+        
+        synchronized (this.componentList) {
+            for (int i = 0; i < componentList.size(); i++) {
+                HtmlComponent component = (HtmlComponent) componentList.get(i);
+                try {
+                    component.renderHtml(out, this);
+                } catch (Exception e) {
+                    e.printStackTrace();
+
+                    out.beginDiv("error");
+
+                    out.append("Failed to render HtmlComponent " + 
component.toString() + ": ");
+                    out.append("<pre>");
+                    out.writeStacktrace(e, false);
+                    out.append("</pre>)");
+
+                    out.endDiv();
+                }
+            }
+        }
+
+        out.append("</body>");
+    }
+
+    public static String getStacktrace(Throwable t) {
+        StringWriter stringWriter = new StringWriter();
+        PrintWriter printWriter = new PrintWriter(stringWriter);
+        t.printStackTrace(printWriter);
+        return stringWriter.toString();
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public HtmlPage getSubPage(String path) throws PageNotFoundException {
+        if (path == null || path.length() == 0) {
+            throw new PageNotFoundException(this, path);
+        }
+        synchronized (this.subPagesMap) {
+            int slashPosition = path.indexOf('/');
+            if (slashPosition > 0) {
+                String firstPathSegment = path.substring(0, slashPosition);
+                String restOfPath = path.substring(slashPosition + 1);
+                HtmlPage page = (HtmlPage) 
this.subPagesMap.get(firstPathSegment);
+                if (page == null) {
+                    throw new PageNotFoundException(this, path);
+                }
+                return page.getSubPage(restOfPath);
+            } else {
+                HtmlPage page = (HtmlPage) this.subPagesMap.get(path);
+                if (page == null) {
+                    throw new PageNotFoundException(this, path);
+                }
+                return page;
+            }
+        }
+    }
+
+    public String getPath() {
+        return this.path;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public int getLevel() {
+        return this.level;
+    }
+
+}

Added: trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/HtmlWriter.java
===================================================================
--- trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/HtmlWriter.java     
2006-03-07 18:55:50 UTC (rev 8182)
+++ trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/HtmlWriter.java     
2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,160 @@
+package falimat.freenet.webplugin;
+
+import java.io.PrintWriter;
+import java.io.Writer;
+
+public class HtmlWriter extends PrintWriter {
+
+    static final String ACTION = "action";
+
+    private HtmlPage pageBeingWritten;
+
+    public HtmlWriter(HtmlPage page, Writer out) {
+        super(out);
+        this.pageBeingWritten = page;
+    }
+
+    public void writeTooltipItem(String tooltip, String content, String clazz) 
{
+        this.write("<span title=\"" + tooltip + "\"");
+        if (clazz!=null && clazz.length()!=0) {
+            this.write(" class=\""+clazz+"\"");
+        }
+        this.write(">");
+        if (content!=null) {
+            this.write(content);
+        }
+        this.write("</span>");
+    }
+
+    public void writeActionLink(HtmlPage targetPage, String linkText, Action 
action, String tooltip) {
+
+        if (targetPage == null) {
+            String msg = "writeLink() can only be called after the component 
was added to a page";
+            throw new RuntimeException(msg);
+        }
+
+        StringBuffer relativeUrl = getRelativeUrl(targetPage);
+
+        if (action != null) {
+            relativeUrl.append("?" + ACTION + "=");
+            relativeUrl.append(action.getId());
+        }
+
+        this.writeLink(relativeUrl.toString(), linkText, tooltip);
+    }
+
+    public void writeCssLink(String absolutePath) {
+        String relativeUrl = getRelativeUrl(absolutePath).toString();
+        super.write("<link rel=\"stylesheet\" type=\"text/css\" href=\"");
+        super.write(relativeUrl);
+        super.write("\">");
+    }
+
+    // rel="stylesheet" type="text/css"
+    // 
href="http://www.spiegel.de/css/0,5459,PB64-dmVyPWxvdyZyZXY9MjAwNjAxMjcxNzU5MTUmc3R5bGU9,00.css";>
+    private StringBuffer getRelativeUrl(HtmlPage targetPage) {
+        return this.getRelativeUrl(targetPage.getPath());
+    }
+
+    private StringBuffer getRelativeUrl(String absoluteUrl) {
+        StringBuffer relativeUrl = new StringBuffer();
+        relativeUrl.append(".");
+        for (int i = 0; i < pageBeingWritten.getLevel(); i++) {
+            relativeUrl.append("/..");
+        }
+        relativeUrl.append(absoluteUrl);
+        return relativeUrl;
+    }
+
+    public void writeActionLink(HtmlPage targetPage, Action action) {
+        this.writeActionLink(targetPage, targetPage.getName(), action, null);
+    }
+
+    public void writeLink(HtmlPage targetPage) {
+        this.writeActionLink(targetPage, null);
+    }
+
+    public void writeLink(CharSequence href, String linktext, String tooltip) {
+        super.write("<a href=\"" + href + "\"");
+
+        if (tooltip != null) {
+            super.write(" title=\"" + tooltip + "\"");
+        }
+        super.write(">");
+
+        super.write(linktext);
+        super.write("</a>");
+    }
+
+    public void writeInput(String type, String name, String value) {
+        this.writeInput(type, name, null, value);
+    }
+
+    public void writeInput(String type, String name, String id, String value) {
+        super.write("<input type=\"" + type + "\" name=\"" + name + "\" 
class=\"" + type + "\"");
+        if (id != null && id.length() > 0) {
+            super.write(" id=\"" + id + "\"");
+        }
+        if (value != null && value.length() > 0) {
+            super.write(" value=\"" + value + "\"");
+        }
+        super.write(">");
+
+    }
+
+    public void writeLabel(String id, String label) {
+        super.write("<label for=\"" + id + "\">" + label + "</label>");
+    }
+
+    public void beginSpan(String clazz) {
+        this.beginTag("span", clazz);
+    }
+
+    public void endSpan() {
+        this.endTag("span");
+    }
+
+    public void beginDiv(String clazz) {
+        this.beginTag("div", clazz);
+    }
+
+    public void endDiv() {
+        this.endTag("div");
+    }
+
+    public void newLine() {
+        super.append("<br>");
+    }
+
+    public void beginTag(String name, String clazz) {
+        super.append("<" + name + " class=\"" + clazz + "\">");
+    }
+
+    public void endTag(String name) {
+        super.append("</" + name + ">");
+    }
+
+    public void writeStacktrace(Exception e, boolean wrapInComment) {
+        super.append("\n\n");
+        if (wrapInComment) {
+            super.append("<!--");
+        }
+        super.append(HtmlPage.getStacktrace(e));
+        if (wrapInComment) {
+            super.append("-->");
+        }
+        super.append("\n\n");
+    }
+
+    public void beginForm(HtmlPage targetPage) {
+        super.append("<form action=\"");
+        super.append(getRelativeUrl(targetPage));
+        super.append("\" method=\"get\"");
+        super.append(">");
+    }
+
+    public void endForm() {
+        this.endTag("form");
+    }
+
+}

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/KeyNotFoundException.java
===================================================================
--- 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/KeyNotFoundException.java
   2006-03-07 18:55:50 UTC (rev 8182)
+++ 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/KeyNotFoundException.java
   2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,12 @@
+/**
+ * 
+ */
+package falimat.freenet.webplugin;
+
+import xomat.util.ParamException;
+
+public class KeyNotFoundException extends ParamException {
+    public KeyNotFoundException(String uri) {
+        super("Failed to fetch freenet key  {0}", uri);
+    }
+}
\ No newline at end of file

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/PageNotFoundException.java
===================================================================
--- 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/PageNotFoundException.java
  2006-03-07 18:55:50 UTC (rev 8182)
+++ 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/PageNotFoundException.java
  2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,16 @@
+package falimat.freenet.webplugin;
+
+import xomat.util.ParamException;
+
+public class PageNotFoundException extends ParamException {
+
+    private static final long serialVersionUID = -8403407986983450467L;
+
+    public PageNotFoundException(HtmlPage page, String path) {
+        super("Page {0} has no subpage with path ''{1}''", page.getName(), 
path);
+    }
+
+    public PageNotFoundException(String msg, String param, Exception e) {
+        super(msg, param, e);
+    }
+}

Added: trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/SimpleAction.java
===================================================================
--- trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/SimpleAction.java   
2006-03-07 18:55:50 UTC (rev 8182)
+++ trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/SimpleAction.java   
2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,29 @@
+package falimat.freenet.webplugin;
+
+import freenet.pluginmanager.PluginHTTPRequest;
+
+
+public abstract class SimpleAction implements Action {
+
+    private static long idCounter = 0;
+
+    private String id = null;
+
+    protected SimpleAction() {
+
+    }
+
+    protected SimpleAction(String id) {
+        this.id = id;
+    }
+
+    public String getId() {
+        if (this.id == null) {
+            this.id = Long.toString(idCounter++);
+        }
+        return this.id;
+    }
+
+    public abstract void execute(PluginHTTPRequest request);
+
+}

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/StylesheetLoader.java
===================================================================
--- 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/StylesheetLoader.java   
    2006-03-07 18:55:50 UTC (rev 8182)
+++ 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/StylesheetLoader.java   
    2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,45 @@
+package falimat.freenet.webplugin;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.StringWriter;
+import java.text.MessageFormat;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+public  class StylesheetLoader {
+ 
+    private final static Log log = LogFactory.getLog(StylesheetLoader.class);
+    
+    private final static Map<String, String> cachedStylesheets = new 
HashMap<String, String>();
+    
+    public static synchronized String getStylesheet(String url) throws 
PageNotFoundException {
+        try {
+            url = "/"+url;
+            if (!cachedStylesheets.containsKey(url)) {
+                InputStream inputStream = 
StylesheetLoader.class.getResourceAsStream(url);
+                BufferedReader reader = new BufferedReader(new 
InputStreamReader(inputStream));
+                StringWriter stringWriter = new StringWriter();
+                while(true) {
+                    String nextLine = reader.readLine();
+                    if (nextLine==null) {
+                        break;
+                    }
+                    stringWriter.write(nextLine+"\n");
+                }
+                inputStream.close();
+                // cachedStylesheets.put(url, stringWriter.toString());
+                return stringWriter.toString();
+            }
+            return cachedStylesheets.get(url);
+        } catch (Exception e) {
+            String msg = "Couldn't find stylesheet at {0}";
+            log.warn(MessageFormat.format(msg, url), e);
+            throw new PageNotFoundException(msg, url, e);
+        }
+    }
+}

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/WebinterfacePlugin.java
===================================================================
--- 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/WebinterfacePlugin.java 
    2006-03-07 18:55:50 UTC (rev 8182)
+++ 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/WebinterfacePlugin.java 
    2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,101 @@
+package falimat.freenet.webplugin;
+
+import java.io.StringWriter;
+import java.net.URISyntaxException;
+
+import freenet.pluginmanager.FredPlugin;
+import freenet.pluginmanager.FredPluginHTTP;
+import freenet.pluginmanager.PluginHTTPException;
+import freenet.pluginmanager.PluginHTTPRequest;
+import freenet.pluginmanager.PluginRespirator;
+
+public abstract class WebinterfacePlugin implements FredPlugin, FredPluginHTTP 
{
+
+    private HtmlPage rootPage;
+
+    public String handleHTTPGet(String path) throws PluginHTTPException {
+        PluginHTTPRequest request;
+
+        try {
+            request = new PluginHTTPRequest(path);
+        } catch (URISyntaxException e) {
+            throw new PluginHTTPException(501, "text/plain", "ERROR", 
e.getMessage());
+        }
+
+        if (path.endsWith("css")) {
+            try {
+                throw new PluginHTTPException(200, "text/css", "CSS", 
StylesheetLoader.getStylesheet(path));
+            } catch (PageNotFoundException e) {
+                throw new PluginHTTPException(404, "text/plain", "NOTFOUND", 
e.toString());
+            }
+        }
+
+        // TODO: put this in PluginHTTPRequest ?
+        if (path.contains("?")) {
+            path = path.substring(0, path.indexOf("?"));
+        }
+        if (path.startsWith("/")) {
+            path = path.substring(1);
+        }
+        if (path.endsWith("/")) {
+            path = path.substring(0, path.length() - 1);
+        }
+
+        HtmlPage pageToBeRendered;
+        if (path.length() == 0) {
+            pageToBeRendered = this.rootPage;
+        } else {
+            try {
+                pageToBeRendered = this.rootPage.getSubPage(path);
+            } catch (PageNotFoundException e) {
+                pageToBeRendered = new ErrorPage("404 - Page Not Found: " + 
path, e);
+            }
+        }
+
+        if (request.isParameterSet(HtmlWriter.ACTION)) {
+            String actionId = request.getParam(HtmlWriter.ACTION);
+            Action action = pageToBeRendered.findAction(actionId);
+            if (action == null) {
+                throw new PluginHTTPException(400, "text/plain", "BADR", "Bad 
Request: no action found with id "
+                        + actionId);
+            }
+            try {
+                action.execute(request);
+            } catch (RuntimeException e) {
+                pageToBeRendered = new ErrorPage("501 - Unexpected Excepton: 
", e);
+            }
+        }
+
+        return renderPage(pageToBeRendered);
+    }
+
+    private String renderPage(HtmlPage pageToBeRendered) {
+        StringWriter stringWriter = new StringWriter();
+        HtmlWriter htmlWriter = new HtmlWriter(pageToBeRendered, stringWriter);
+        pageToBeRendered.renderHtml(htmlWriter);
+        return stringWriter.toString();
+    }
+
+    public String handleHTTPPut(String path) throws PluginHTTPException {
+        return this.renderPage(new ErrorPage("HTTP PUT is not supported by 
this plugin.", null));
+    }
+
+    public String handleHTTPPost(String path) throws PluginHTTPException {
+        return this.renderPage(new ErrorPage("HTTP POST is not supported by 
this plugin.", null));
+    }
+
+    public abstract void runPlugin(PluginRespirator pr);
+
+    public abstract void terminate();
+
+    protected HtmlPage getRootPage() {
+        if (this.rootPage == null) {
+            this.rootPage = new HtmlPage("Freenet Plugin");
+        }
+        return this.rootPage;
+    }
+
+    public void setRootPage(HtmlPage page) {
+        this.rootPage = page;
+    }
+}

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/AbstractFormComponent.java
===================================================================
--- 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/AbstractFormComponent.java
       2006-03-07 18:55:50 UTC (rev 8182)
+++ 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/AbstractFormComponent.java
       2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,95 @@
+package falimat.freenet.webplugin.components;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import falimat.freenet.webplugin.AbstractHtmlComponent;
+import falimat.freenet.webplugin.HtmlPage;
+import falimat.freenet.webplugin.HtmlWriter;
+import freenet.pluginmanager.PluginHTTPRequest;
+
+public abstract class AbstractFormComponent extends AbstractHtmlComponent {
+
+    protected String name;
+
+    private String defaultValue;
+
+    protected String value = null;
+
+    private Map<String, Pattern[]> validationPatterns = new HashMap<String, 
Pattern[]>();
+
+    private boolean required = false;
+
+    protected String validationMessage;
+
+    protected String label;
+
+    public abstract void renderHtml(HtmlWriter out, HtmlPage contextPage);
+
+    public String getName() {
+        return this.name;
+    }
+
+    public String getValue() {
+        return this.value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    public void setValueFromRequest(PluginHTTPRequest request) {
+        this.value = request.getParam(this.name).trim();
+    }
+
+    public boolean validate(PluginHTTPRequest request) {
+        String newValue = this.value;
+        if (newValue.length() == 0) {
+            if (this.required) {
+                this.validationMessage = "^ You have to enter a value here.";
+                return false;
+            } else {
+                this.validationMessage = null;
+                return true;
+            }
+        }
+        for (String msg : this.validationPatterns.keySet()) {
+            Pattern[] patterns = this.validationPatterns.get(msg);
+            boolean foundMatch = false;
+            for (Pattern pattern : patterns) {
+                Matcher matcher = pattern.matcher(newValue);
+                if (matcher.matches()) {
+                    foundMatch = true;
+                    break;
+                }
+            }
+            if (!foundMatch) {
+                this.validationMessage = "^ "+msg;
+                return false;
+            }
+        }
+        this.validationMessage = null;
+        return true;
+
+    }
+
+    public void reset() {
+        this.value = this.defaultValue;
+        this.validationMessage = null;
+    }
+
+    public void setRequired(boolean required) {
+        this.required = required;
+    }
+
+    public void setValidation(String message, Pattern... patterns) {
+        this.validationPatterns.put(message, patterns);
+    }
+
+    public void showValidationMessage(String string) {
+        this.validationMessage = string;
+    }
+
+}

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/ActionButton.java
===================================================================
--- 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/ActionButton.java
        2006-03-07 18:55:50 UTC (rev 8182)
+++ 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/ActionButton.java
        2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,52 @@
+package falimat.freenet.webplugin.components;
+
+import falimat.freenet.webplugin.AbstractHtmlComponent;
+import falimat.freenet.webplugin.Action;
+import falimat.freenet.webplugin.ActionComponent;
+import falimat.freenet.webplugin.HtmlPage;
+import falimat.freenet.webplugin.HtmlWriter;
+
+public class ActionButton extends AbstractHtmlComponent implements 
ActionComponent {
+
+    private HtmlPage targetPage;
+
+    private Action action;
+
+    private String text;
+    
+    private String tooltip;
+
+    public ActionButton(String text) {
+        this.text = text;
+    }
+
+    public ActionButton(String text, String tooltip) {
+        this.text = text;
+        this.tooltip = tooltip;
+    }
+
+    public void renderHtml(HtmlWriter out, HtmlPage contextPage) {
+
+        if (this.targetPage == null) {
+            this.targetPage = contextPage;
+        }
+        out.beginDiv("button");
+
+        out.writeActionLink(targetPage, this.text, this.action, this.tooltip);
+
+        out.endDiv();
+    }
+
+    public void setAction(Action action) {
+        this.action = action;
+    }
+
+    public Action getAction(String id) {
+       if (this.action.getId().equals(id)) {
+           return this.action;
+       }
+       return null;
+    }
+
+
+}

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/BeanTable.java
===================================================================
--- 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/BeanTable.java
   2006-03-07 18:55:50 UTC (rev 8182)
+++ 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/BeanTable.java
   2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,218 @@
+package falimat.freenet.webplugin.components;
+
+import java.beans.BeanInfo;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Method;
+import java.text.DateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import falimat.freenet.webplugin.AbstractHtmlComponent;
+import falimat.freenet.webplugin.HtmlPage;
+import falimat.freenet.webplugin.HtmlWriter;
+
+public class BeanTable extends AbstractHtmlComponent {
+
+    public static final CellRenderer PUBLIC_SSK_REND = new CellRenderer() {
+
+        public void renderCellContent(Object value, HtmlWriter out, HtmlPage 
contextPage) {
+            String ssk = (String) value;
+            String croppedSSK = ssk.substring(0, 15) + 
"..."+ssk.substring(ssk.length()-15);
+            out.writeTooltipItem(ssk, croppedSSK, "ssk");
+        }
+
+    };
+
+    public static final CellRenderer FREENET_LINK_RENDERER = new 
CellRenderer() {
+
+        public void renderCellContent(Object value, HtmlWriter out, HtmlPage 
contextPage) {
+            String ssk = (String) value;
+            String croppedSSK = ssk.substring(0, 15) + 
"..."+ssk.substring(ssk.length()-15);
+            out.writeLink("/"+ssk, croppedSSK, "View the content of this slot 
as xml" );
+        }
+
+    };
+    
+    public static final CellRenderer DEFAULT_RENDERER = new CellRenderer() {
+
+        public void renderCellContent(Object value, HtmlWriter out, HtmlPage 
contextPage) {
+            out.write(value.toString());
+        }
+
+    };
+
+    public static final CellRenderer DATE_TIME_RENDERER = new CellRenderer() {
+
+        private final DateFormat dateFormat = 
DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
+
+        public void renderCellContent(Object value, HtmlWriter out, HtmlPage 
contextPage) {
+            Long millis = (Long)value;
+            if (millis<=0) {
+                out.write("-");
+                return;
+            }
+            Date date = new Date(millis);
+            synchronized (dateFormat) {
+                out.write(dateFormat.format(date));
+            }
+
+        }
+
+    };
+
+    public static final CellRenderer DURATION_RENDERER = new CellRenderer() {
+
+        public void renderCellContent(Object value, HtmlWriter out, HtmlPage 
contextPage) {
+            Integer millis = (Integer) value;
+            int minutes = millis.intValue() / 1000 / 60;
+            int hours = minutes / 60;
+            minutes = minutes - hours * 60;
+            if (hours > 0) {
+                out.write(hours + " h ");
+            }
+            if (minutes > 0) {
+                out.write(minutes + " min");
+            }
+
+        }
+
+    };
+
+    private List<String> propertyNameList = new LinkedList<String>();
+
+    private Map<String, CellRenderer> propertyCellRendererMap = new 
HashMap<String, CellRenderer>();
+
+    private List<Object> beanList = new LinkedList<Object>();
+
+    private BeanTableDataSource dataProvider;
+
+    public void addRow(Object bean) {
+        this.beanList.add(bean);
+    }
+
+    public void addColumn(String string) {
+        this.propertyNameList.add(string);
+    }
+
+    public void addColumn(String propertyName, CellRenderer cellRenderer) {
+        this.addColumn(propertyName);
+        this.propertyCellRendererMap.put(propertyName, cellRenderer);
+    }
+
+    interface CellRenderer {
+        void renderCellContent(Object value, HtmlWriter out, HtmlPage 
contextPage);
+    }
+
+    public void renderHtml(HtmlWriter out, HtmlPage contextPage) {
+
+        if (this.dataProvider != null) {
+            synchronized (this.beanList) {
+                this.beanList.clear();
+                Object[] newValues = this.dataProvider.getRows();
+                for (Object o : newValues) {
+                    this.beanList.add(o);
+                }
+            }
+        }
+
+        out.append("<table><thead>");
+        for (Iterator propertyNameIterator = this.propertyNameList.iterator(); 
propertyNameIterator.hasNext();) {
+            String propertyName = (String) propertyNameIterator.next();
+            out.append("<th>" + this.getColumnHeader(propertyName) + "</th>");
+        }
+        out.append("</tr></thead><tbody>");
+
+        for (Iterator objectIterator = this.beanList.iterator(); 
objectIterator.hasNext();) {
+            out.append("<tr>");
+            Object nextBean = (Object) objectIterator.next();
+
+            try {
+                Map getMethodMap = this.findAvailableGetters(nextBean);
+
+                for (Iterator propertyNameIterator = 
this.propertyNameList.iterator(); propertyNameIterator.hasNext();) {
+                    String propertyName = (String) propertyNameIterator.next();
+                    out.append("<td>");
+                    try {
+                        Method getter = (Method) 
getMethodMap.get(propertyName);
+                        if (getter == null) {
+                            String msg = "Bean of " + nextBean + " does not 
have a property named " + propertyName;
+                            throw new RuntimeException(msg);
+                        }
+                        Object value = getter.invoke(nextBean, (Object[])new 
String[0]);
+
+                        this.formatValue(propertyName, value, out, 
contextPage);
+
+                    } catch (Exception e) {
+                        out.append("<span class=\"error\">n/a</span>");
+                        out.writeStacktrace(e, true);
+                    }
+                    out.append("</td>");
+                }
+            } catch (Exception e) {
+                out.append("<td colspan=\"" + propertyNameList.size() + "\">");
+                out.append("<span class=\"error\">n/a</span>");
+                out.writeStacktrace(e, true);
+
+                out.append("</td>");
+            }
+            out.append("</tr>");
+        }
+
+        out.append("</tbody></table>");
+    }
+
+    private Map<String, Method> findAvailableGetters(Object bean) throws 
IntrospectionException {
+        // find all properties with introspection
+        BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass());
+        PropertyDescriptor[] properties = beanInfo.getPropertyDescriptors();
+
+        // gather all getters in a map with the property name as key
+        Map<String, Method> getMethodMap = new HashMap<String, Method>();
+        for (int i = 0; i < properties.length; i++) {
+            PropertyDescriptor descriptor = properties[i];
+            Method getter = descriptor.getReadMethod();
+            if (getter != null) {
+                getMethodMap.put(descriptor.getName(), getter);
+            }
+        }
+        return getMethodMap;
+    }
+
+    private void formatValue(String propertyName, Object value, HtmlWriter 
out, HtmlPage contextPage) {
+        // TODO implement custom formatting for columns
+        if (value == null) {
+            out.write("n/a");
+            return;
+        }
+        try {
+            CellRenderer renderer = 
this.propertyCellRendererMap.get(propertyName);
+            if (renderer == null) {
+                renderer = DEFAULT_RENDERER;
+            }
+            renderer.renderCellContent(value, out, contextPage);
+        } catch (Exception e) {
+            out.write("n/a");
+            out.writeStacktrace(e, true);
+        }
+    }
+
+    public String getColumnHeader(String propertyName) {
+        // TODO: maybe look up i18nized column header?
+        return propertyName;
+    }
+
+    protected BeanTableDataSource getDataProvider() {
+        return this.dataProvider;
+    }
+
+    public void setDataProvider(BeanTableDataSource dataProvider) {
+        this.dataProvider = dataProvider;
+    }
+
+}

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/BeanTableDataSource.java
===================================================================
--- 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/BeanTableDataSource.java
 2006-03-07 18:55:50 UTC (rev 8182)
+++ 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/BeanTableDataSource.java
 2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,8 @@
+/**
+ * 
+ */
+package falimat.freenet.webplugin.components;
+
+public interface BeanTableDataSource {
+    Object[] getRows();
+}
\ No newline at end of file

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/DefinitionList.java
===================================================================
--- 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/DefinitionList.java
      2006-03-07 18:55:50 UTC (rev 8182)
+++ 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/DefinitionList.java
      2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,40 @@
+package falimat.freenet.webplugin.components;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import falimat.freenet.webplugin.AbstractHtmlComponent;
+import falimat.freenet.webplugin.HtmlPage;
+import falimat.freenet.webplugin.HtmlWriter;
+
+public class DefinitionList extends AbstractHtmlComponent {
+
+    private Map<String, String> nameValueMap = new TreeMap<String, String>();
+    
+    private List<String> keyOrderList = new LinkedList<String>();
+
+    public void renderHtml(HtmlWriter out, HtmlPage contextPage) {
+        if (this.nameValueMap.isEmpty()) {
+            return;
+        }
+        out.write("<dl>");
+        for (String key : this.keyOrderList) {
+            out.write("<dt>" + key + "</dt>");
+            out.write("<dd>" + this.nameValueMap.get(key) + "</dd>");
+        }
+        out.write("</dl>");
+    }
+
+    public void addEntry(String title, String description) {
+        this.keyOrderList.add(title);
+        this.nameValueMap.put(title, description);
+    }
+
+    public void removeEntry(String title) {
+        this.keyOrderList.remove(title);
+        this.nameValueMap.remove(title);
+    }
+
+}

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/Form.java
===================================================================
--- 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/Form.java    
    2006-03-07 18:55:50 UTC (rev 8182)
+++ 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/Form.java    
    2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,100 @@
+package falimat.freenet.webplugin.components;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import falimat.freenet.webplugin.AbstractHtmlComponent;
+import falimat.freenet.webplugin.Action;
+import falimat.freenet.webplugin.ActionComponent;
+import falimat.freenet.webplugin.FormComponent;
+import falimat.freenet.webplugin.HtmlComponent;
+import falimat.freenet.webplugin.HtmlPage;
+import falimat.freenet.webplugin.HtmlWriter;
+import freenet.pluginmanager.PluginHTTPRequest;
+
+public class Form extends AbstractHtmlComponent implements ActionComponent {
+
+    private Action action;
+
+    private List<HtmlComponent> components = new LinkedList<HtmlComponent>();
+
+    private boolean hidden = false;
+    
+    public void addComponent(HtmlComponent component) {
+        this.components.add(component);
+    }
+
+    public void show() {
+        this.hidden = false;
+    }
+    
+    public void hide() {
+        this.hidden = true;
+    }
+    
+    public void renderHtml(HtmlWriter out, HtmlPage contextPage) {
+        
+        if (this.hidden) {
+            return;
+        }
+        out.beginForm(contextPage);
+
+        for (HtmlComponent component : this.components) {
+            component.renderHtml(out, contextPage);
+        }
+
+        out.endForm();
+    }
+
+    public Action getAction(String id) {
+
+        if (this.action != null && this.action.getId().equals(id)) {
+            return this.action;
+        }
+
+        for (HtmlComponent component : this.components) {
+            if (component instanceof ActionComponent) {
+                Action action = ((ActionComponent) component).getAction(id);
+                if (action != null) {
+                    return action;
+                }
+            }
+        }
+        return null;
+    }
+
+    public void setAction(Action action) {
+        this.action = action;
+    }
+
+    public boolean validate(PluginHTTPRequest request) {
+        for (HtmlComponent component : this.components) {
+            if (component instanceof FormComponent) {
+                ((FormComponent) component).setValueFromRequest(request);
+            }
+        }
+        boolean overallSuccess = true;
+        for (HtmlComponent component : this.components) {
+            if (component instanceof FormComponent) {
+                boolean success = ((FormComponent) 
component).validate(request);
+                if (!success) {
+                    overallSuccess = false;
+                }
+            }
+        }
+        return overallSuccess;
+    }
+
+    public void resetFormValues() {
+        for (HtmlComponent component : this.components) {
+            if (component instanceof FormComponent) {
+                ((FormComponent) component).reset();
+            }
+        }
+    }
+
+    public void removeAllComponents() {
+      this.components.clear();
+    }
+
+}

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/Heading.java
===================================================================
--- 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/Heading.java 
    2006-03-07 18:55:50 UTC (rev 8182)
+++ 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/Heading.java 
    2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,24 @@
+package falimat.freenet.webplugin.components;
+
+import falimat.freenet.webplugin.AbstractHtmlComponent;
+import falimat.freenet.webplugin.HtmlPage;
+import falimat.freenet.webplugin.HtmlWriter;
+
+public class Heading extends AbstractHtmlComponent {
+
+    private int level = 3;
+
+    private String content;
+
+    public Heading(int level, String content) {
+        this.level = level;
+        this.content = content;
+    }
+    
+    public void renderHtml(HtmlWriter out, HtmlPage contextPage) {
+        out.append("<h" + level + ">");
+        out.append(this.content);
+        out.append("</h" + level + ">");
+    }
+
+}

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/LabeledDropdown.java
===================================================================
--- 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/LabeledDropdown.java
     2006-03-07 18:55:50 UTC (rev 8182)
+++ 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/LabeledDropdown.java
     2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,73 @@
+package falimat.freenet.webplugin.components;
+
+import falimat.freenet.webplugin.FormComponent;
+import falimat.freenet.webplugin.HtmlPage;
+import falimat.freenet.webplugin.HtmlWriter;
+import freenet.pluginmanager.PluginHTTPRequest;
+
+public class LabeledDropdown extends AbstractFormComponent implements 
FormComponent {
+
+    private static int textFieldCount = 0;
+
+    private String id = "txtfld_" + textFieldCount++;
+
+    private String[] possibleValues;
+
+    private boolean allowEmptySelection;
+
+    public LabeledDropdown(String name, String label, boolean 
allowEmptySelection, String... possibleValues) {
+        this.name = name;
+        this.label = label;
+
+        this.allowEmptySelection = allowEmptySelection;
+        this.possibleValues = possibleValues;
+    }
+
+    @Override
+    public void setValueFromRequest(PluginHTTPRequest request) {
+        try {
+            int index = request.getIntParam(this.name, -1);
+            if (index >= 0) {
+                this.value = this.possibleValues[index];
+            } else {
+                this.value = "";
+            }
+        } catch (Exception e) {
+            this.value = "";
+        }
+    }
+
+    @Override
+    public void renderHtml(HtmlWriter out, HtmlPage contextPage) {
+        out.writeLabel(this.id, this.label);
+
+        out.write("<select name=\"" + this.name + "\" id=\"" + this.id + 
"\">");
+        if (this.allowEmptySelection) {
+            out.write("<option");
+
+            if (this.value == null || this.value.length() == 0) {
+                out.write(" selected=\"selected\"");
+            }
+            out.write("></option>");
+        }
+        for (int i = 0; i < possibleValues.length; i++) {
+            out.write("<option value=\"" + i + "\"");
+            if (possibleValues[i].equals(this.value)) {
+                out.write(" selected=\"selected\"");
+            }
+            out.write(">");
+            out.write(this.possibleValues[i]);
+            out.write("</option>");
+        }
+        out.write("</select>");
+
+        if (this.validationMessage != null) {
+
+            out.write("<span class=\"invalid\">");
+            out.write(this.validationMessage);
+            out.write("</span>");
+
+        }
+    }
+
+}

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/LabeledTextArea.java
===================================================================
--- 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/LabeledTextArea.java
     2006-03-07 18:55:50 UTC (rev 8182)
+++ 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/LabeledTextArea.java
     2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,36 @@
+package falimat.freenet.webplugin.components;
+
+import falimat.freenet.webplugin.FormComponent;
+import falimat.freenet.webplugin.HtmlPage;
+import falimat.freenet.webplugin.HtmlWriter;
+
+public class LabeledTextArea extends AbstractFormComponent implements 
FormComponent {
+
+    private static int textFieldCount = 0;
+
+    private String id = "txtarea_" + textFieldCount++;
+
+    public LabeledTextArea(String name, String label) {
+        this.name = name;
+        this.label = label; 
+    }
+
+    @Override
+    public void renderHtml(HtmlWriter out, HtmlPage contextPage) {
+        out.writeLabel(this.id, this.label);
+
+        out.write("<textarea name=\"" + this.name + "\" id=\"" + this.id + 
"\">");
+        if (this.value != null) {
+            out.write(this.value);
+        }
+        out.write("</textarea>");
+        if (this.validationMessage != null) {
+
+            out.write("<span class=\"invalid\">");
+            out.write(this.validationMessage);
+            out.write("</span>");
+
+        }
+    }
+
+}

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/LabeledTextField.java
===================================================================
--- 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/LabeledTextField.java
    2006-03-07 18:55:50 UTC (rev 8182)
+++ 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/LabeledTextField.java
    2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,30 @@
+package falimat.freenet.webplugin.components;
+
+import falimat.freenet.webplugin.FormComponent;
+import falimat.freenet.webplugin.HtmlPage;
+import falimat.freenet.webplugin.HtmlWriter;
+
+public class LabeledTextField extends AbstractFormComponent implements 
FormComponent {
+
+    private static int textFieldCount = 0;
+
+    private String id = "txtfld_" + textFieldCount++;
+
+    public LabeledTextField(String name, String label) {
+        this.name = name;
+        this.label = label;
+    }
+
+    @Override
+    public void renderHtml(HtmlWriter out, HtmlPage contextPage) {
+        out.writeLabel(this.id, this.label);
+
+        out.writeInput("text", this.name, this.id, this.value);
+        if (this.validationMessage != null) {
+            out.write("<span class=\"invalid\">");
+            out.write(this.validationMessage);
+            out.write("</span>");
+        }
+    }
+
+}

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/MessageArea.java
===================================================================
--- 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/MessageArea.java
 2006-03-07 18:55:50 UTC (rev 8182)
+++ 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/MessageArea.java
 2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,74 @@
+package falimat.freenet.webplugin.components;
+
+import falimat.freenet.webplugin.AbstractHtmlComponent;
+import falimat.freenet.webplugin.HtmlPage;
+import falimat.freenet.webplugin.HtmlWriter;
+
+public class MessageArea extends AbstractHtmlComponent {
+
+    private String headline;
+
+    private String message;
+
+    private Exception exception;
+
+    private boolean hidden;
+    
+    public void renderHtml(HtmlWriter out, HtmlPage contextPage) {
+
+        if (this.hidden) {
+            return;
+        }
+        out.beginDiv("message_area");
+
+        if (this.headline != null && this.headline.length() > 0) {
+            out.beginDiv("headline");
+            out.append(this.headline);
+            out.endDiv();
+        }
+
+        if (this.message != null && this.message.length() > 0) {
+            out.beginDiv("message");
+            out.append(message);
+            out.endDiv();
+        }
+
+        out.append("</div>");
+
+        if (this.exception != null) {
+            out.writeStacktrace(this.exception, true);
+            this.exception = null;
+        }
+    }
+
+    protected String getMessage() {
+        return this.message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    protected String getHeadline() {
+        return this.headline;
+    }
+
+    public void setHeadline(String headline) {
+        this.headline = headline;
+    }
+
+    public void showStackTrace(Exception e) {
+        this.exception = e;
+    }
+
+    public void show(String headline, String message) {
+        this.headline = headline;
+        this.message = message;
+        this.hidden = false;
+    }
+
+    public void hide() {
+        this.hidden = true;
+    }
+
+}

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/NavigationMenu.java
===================================================================
--- 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/NavigationMenu.java
      2006-03-07 18:55:50 UTC (rev 8182)
+++ 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/NavigationMenu.java
      2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,50 @@
+package falimat.freenet.webplugin.components;
+
+import falimat.freenet.webplugin.AbstractHtmlComponent;
+import falimat.freenet.webplugin.HtmlPage;
+import falimat.freenet.webplugin.HtmlWriter;
+
+public class NavigationMenu extends AbstractHtmlComponent  {
+
+    private final HtmlPage rootPage;
+    
+    public NavigationMenu(HtmlPage navigationRootPage) {
+        this.rootPage = navigationRootPage;
+    }
+    
+    public void renderHtml(HtmlWriter out, HtmlPage contextPage) {
+      out.beginDiv("nav");
+      
+      out.append("<div class=\"toplevel\">");
+      out.writeLink( rootPage);
+      out.append("</div>");
+
+      this.writeMenu(out, rootPage, contextPage);
+      
+      
+
+      out.endDiv();
+      
+    }
+    
+    private void writeMenu(HtmlWriter out, HtmlPage parentPage, HtmlPage 
contextPage) {
+        HtmlPage[] subPages = parentPage.listSubpages();
+        if (subPages.length==0) {
+            return;
+        }
+        
+        out.append("<ul>");
+        
+        for (int i=0; i<subPages.length; i++) {
+            out.append("<li>");
+            out.writeLink(subPages[i]);
+            out.append("</li>");
+        }
+        
+        out.append("</ul>");
+        
+    }
+    
+    
+
+}

Added: 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/SubmitButton.java
===================================================================
--- 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/SubmitButton.java
        2006-03-07 18:55:50 UTC (rev 8182)
+++ 
trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/components/SubmitButton.java
        2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,36 @@
+package falimat.freenet.webplugin.components;
+
+import falimat.freenet.webplugin.AbstractHtmlComponent;
+import falimat.freenet.webplugin.Action;
+import falimat.freenet.webplugin.ActionComponent;
+import falimat.freenet.webplugin.HtmlPage;
+import falimat.freenet.webplugin.HtmlWriter;
+
+public class SubmitButton extends AbstractHtmlComponent implements 
ActionComponent {
+
+    private String value;
+
+    private Action action;
+
+    private final String name = "action";
+
+    public SubmitButton(String value) {
+        this.value = value;
+    }
+
+    public void renderHtml(HtmlWriter out, HtmlPage contextPage) {
+       out.writeInput("submit", this.name, this.value);
+    }
+
+    public Action getAction(String id) {
+        if (value.equals(id)) {
+            return this.action;
+        }
+        return null;
+    }
+
+    public void setAction(Action action) {
+        this.action = action;
+    }
+
+}

Added: trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/style.css
===================================================================
--- trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/style.css   
2006-03-07 18:55:50 UTC (rev 8182)
+++ trunk/apps/bookmarkplugin/src/falimat/freenet/webplugin/style.css   
2006-03-07 20:25:13 UTC (rev 8183)
@@ -0,0 +1,222 @@
+body {
+       font-family: sans-serif;
+       background-color: white;
+       margin: 0px 0px 0px 9.5em;
+       padding: 0em;
+}
+
+.sidebar {
+       position: absolute;
+       top: 0px;
+       left: 0px;
+       padding: 0.5em;
+       width: 8em;
+       background-color: #222222;
+}
+
+h1 {
+       text-align: right;
+       margin: 0px 30px 0.1em 0px;
+       padding: 0px;
+       border-bottom: 2px solid black;
+}
+
+h2 {
+       border-bottom: 2px solid black;
+       margin: 0.2em 0em 0.5em 15px;
+       font-size: 1.2em;
+}
+
+.sidebar a {
+       color: white;
+}
+
+.nav {
+       font-size: 0.9em;
+       color: white;
+}
+
+.nav ul {
+       list-style-type: circle;
+       padding: 0em 0em 0em 0.8em;
+       margin: 0em;
+       list-style-position: outside;
+}
+
+ul.results {
+       padding: 0em 0em 0em 0.3em;
+       list-style-type: none;
+}
+
+ul.results li {
+       margin: 0em 0em 0.5em 0em;
+       padding: 0.2em;
+       border-bottom: 1px solid gray;
+}
+
+.teaser h3 {
+       font-size: 1em;
+       margin: 0px;
+       border-bottom: 1px solid black;
+}
+
+.teaser h4 {
+       font-size: 0.8em;
+       margin: 0em 0em 0em 1em;
+}
+
+.teaser .metadata {
+       font-family: Lucida Console, monospace;
+       font-size: 0.7em;
+}
+
+.metadata .rating {
+       display: none;
+       font-size: 1.3em;
+       font-weight: bold;
+}
+
+.metadata .tag {
+       font-weight: bold;
+       background-color: black;
+       color: white;
+       padding: 0em 0.2em 0em 0.2em;
+       margin-left: 0.3em;
+}
+
+.metadata .attributes {
+       float: right;
+       margin-left: 1em;
+       text-align: right;
+}
+
+p.description {
+       font-size: small;
+       margin-top: 0.2em;
+       margin-bottom: 0.2em;
+}
+
+.button {
+       margin-top: 0.2em;
+       padding: 0.1em;
+}
+
+.button a:link,.button a:visited,.button a:active,.button a:link {
+       padding: 0.1em;
+}
+
+.button a:hover {
+       
+}
+
+.nick {
+       font-weight: bold;
+       font-style: italic;
+       margin-left: 1em;
+       margin-right: 0.5em;
+}
+
+.freenet_link {
+       color: #aa0000;
+       font-size: smaller;
+       padding-bottom: 0.2em;
+}
+
+.freenet_link a {
+       text-decoration: none;
+       color: #aa0000;
+}
+
+.freenet_link a:visited {
+       color: #aa0000;
+}
+
+.freenet_link a:hover {
+       text-decoration: underline;
+}
+
+label {
+       display: block;
+       font-weight: bold;
+       font-size: 0.8em;
+}
+
+table {
+       border: 1px solid gray;
+
+}
+
+table td {
+       border: 1px solid #cccccc;
+       margin: 0px;
+}
+
+dl {
+       font-size: small;
+}
+
+dt {
+       font-weight: bold;
+}
+
+form {
+       border: 1px solid gray;
+       padding: 0.3em;
+}
+
+input,textarea,select {
+       display: block;
+       width: 100%;
+       font-family: Tahoma, sans-serif;
+}
+
+input.submit,.button a {
+       background-color: #555555;
+       color: white;
+       border: 2px solid gray;
+}
+
+input.submit:hover,.button a:hover {
+       background-color: #333333;
+       color: white;
+       border: 2px outset gray;
+}
+
+.invalid {
+       font-size: small;
+       color: #aa0000;
+}
+
+.message_area {
+       background-color: white;
+}
+
+.headline {
+       font-weight: bold;
+       border-bottom: 1px solid #aa0000;
+}
+
+.message {
+       
+}
+
+.login_message {
+       background-color: #222222;
+       color: white;
+       font-size: 0.8em;
+       position: fixed;
+       padding: 0.2em;
+       left: 0px;
+       bottom: 0px;
+       width: 11.05em;
+}
+
+.login_message .button {
+       position: absolute;
+       top: 0px;
+       right: 0px;
+}
+
+.login_message .button a {
+       
+}
\ No newline at end of file

Added: trunk/apps/bookmarkplugin/src/plugins/BookmarkPlugin.java
===================================================================
--- trunk/apps/bookmarkplugin/src/plugins/BookmarkPlugin.java   2006-03-07 
18:55:50 UTC (rev 8182)
+++ trunk/apps/bookmarkplugin/src/plugins/BookmarkPlugin.java   2006-03-07 
20:25:13 UTC (rev 8183)
@@ -0,0 +1,5 @@
+package plugins;
+// "Shortcut" to falimat.freenet.bookmarkplugin/BookmarkPlugin
+public class BookmarkPlugin extends 
falimat.freenet.bookmarkplugin.BookmarkPlugin {
+
+}


Reply via email to