Too many changes to list them all.
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Mon, 1 Nov 2010 20:33:47 +0000 (21:33 +0100)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Mon, 1 Nov 2010 20:33:47 +0000 (21:33 +0100)
Basically, the complete handling of Sones was rewritten to use the Web of Trust
backend.

27 files changed:
src/main/java/net/pterodactylus/sone/core/Core.java
src/main/java/net/pterodactylus/sone/core/SoneDownloader.java
src/main/java/net/pterodactylus/sone/core/SoneInserter.java
src/main/java/net/pterodactylus/sone/main/SonePlugin.java
src/main/java/net/pterodactylus/sone/web/AddSonePage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/CreateSonePage.java
src/main/java/net/pterodactylus/sone/web/ImportSonePage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/KnownSonesPage.java
src/main/java/net/pterodactylus/sone/web/LoadSonePage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/LoginPage.java
src/main/java/net/pterodactylus/sone/web/WebInterface.java
src/main/resources/i18n/sone.en.properties
src/main/resources/static/css/sone.css
src/main/resources/templates/addSone.html [deleted file]
src/main/resources/templates/backup.xml
src/main/resources/templates/createSone.html
src/main/resources/templates/deleteSone.html
src/main/resources/templates/importSone.html [deleted file]
src/main/resources/templates/include/createSone.html
src/main/resources/templates/include/head.html
src/main/resources/templates/include/importSone.html [deleted file]
src/main/resources/templates/include/loadSone.html [deleted file]
src/main/resources/templates/insert/sone.xml
src/main/resources/templates/knownSones.html
src/main/resources/templates/loadSone.html [deleted file]
src/main/resources/templates/login.html
src/main/resources/templates/wotPluginMissing.html [new file with mode: 0644]

index 1a1c6df..284c6fb 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * FreenetSone - Core.java - Copyright © 2010 David Roden
+ * Sone - Core.java - Copyright © 2010 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
 
 package net.pterodactylus.sone.core;
 
-import java.io.InputStream;
 import java.net.MalformedURLException;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.UUID;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import net.pterodactylus.sone.core.Options.DefaultOption;
 import net.pterodactylus.sone.core.Options.Option;
 import net.pterodactylus.sone.core.Options.OptionWatcher;
-import net.pterodactylus.sone.core.SoneException.Type;
 import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.Profile;
 import net.pterodactylus.sone.data.Reply;
 import net.pterodactylus.sone.data.Sone;
-import net.pterodactylus.sone.freenet.wot.WebOfTrustConnector;
+import net.pterodactylus.sone.freenet.wot.Identity;
+import net.pterodactylus.sone.freenet.wot.IdentityListener;
+import net.pterodactylus.sone.freenet.wot.IdentityManager;
+import net.pterodactylus.sone.freenet.wot.OwnIdentity;
 import net.pterodactylus.util.config.Configuration;
 import net.pterodactylus.util.config.ConfigurationException;
-import net.pterodactylus.util.filter.Filter;
-import net.pterodactylus.util.filter.Filters;
 import net.pterodactylus.util.logging.Logging;
-import net.pterodactylus.util.service.AbstractService;
-import freenet.client.FetchResult;
+import net.pterodactylus.util.number.Numbers;
 import freenet.keys.FreenetURI;
 
 /**
@@ -55,17 +49,7 @@ import freenet.keys.FreenetURI;
  *
  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
-public class Core extends AbstractService {
-
-       /** The default Sones. */
-       private static final Set<String> defaultSones = new HashSet<String>();
-
-       static {
-               /* Sone of Sone. */
-               defaultSones.add("USK@eRHt0ceFsHjRZ11j6dd68RSdIvfd8f9YjJLZ9lnhEyo,iJWjIWh6TkMZm1NY8qBranKTIuwsCPkVPG6T6c6ft-I,AQACAAE/Sone/4");
-               /* Sone of Bombe. */
-               defaultSones.add("USK@RuW~uAO35Ipne896-1OmaVJNPuYE4ZIB5oZ5ziaU57A,7rV3uiyztXBDt03DCoRiNwiGjgFCJuznM9Okc1opURU,AQACAAE/Sone/29");
-       }
+public class Core implements IdentityListener {
 
        /**
         * Enumeration for the possible states of a {@link Sone}.
@@ -94,42 +78,54 @@ public class Core extends AbstractService {
        private final Options options = new Options();
 
        /** The configuration. */
-       private Configuration configuration;
+       private final Configuration configuration;
 
-       /** Interface to freenet. */
-       private FreenetInterface freenetInterface;
+       /** The identity manager. */
+       private final IdentityManager identityManager;
 
-       /** The WoT connector. */
-       private WebOfTrustConnector webOfTrustConnector;
+       /** Interface to freenet. */
+       private final FreenetInterface freenetInterface;
 
        /** The Sone downloader. */
-       private SoneDownloader soneDownloader;
+       private final SoneDownloader soneDownloader;
 
-       /** The local Sones. */
-       private final Set<Sone> localSones = Collections.synchronizedSet(new HashSet<Sone>());
+       /** The Sones’ statuses. */
+       /* synchronize access on itself. */
+       private final Map<Sone, SoneStatus> soneStatuses = new HashMap<Sone, SoneStatus>();
 
        /** Sone inserters. */
-       private final Map<Sone, SoneInserter> soneInserters = Collections.synchronizedMap(new HashMap<Sone, SoneInserter>());
-
-       /** The Sones’ statuses. */
-       private final Map<Sone, SoneStatus> soneStatuses = Collections.synchronizedMap(new HashMap<Sone, SoneStatus>());
+       /* synchronize access on this on localSones. */
+       private final Map<Sone, SoneInserter> soneInserters = new HashMap<Sone, SoneInserter>();
 
-       /* various caches follow here. */
+       /** All local Sones. */
+       /* synchronize access on this on itself. */
+       private Map<String, Sone> localSones = new HashMap<String, Sone>();
 
-       /** Cache for all known Sones. */
-       private final Map<String, Sone> soneCache = Collections.synchronizedMap(new HashMap<String, Sone>());
+       /** All remote Sones. */
+       /* synchronize access on this on itself. */
+       private Map<String, Sone> remoteSones = new HashMap<String, Sone>();
 
-       /** Cache for all known posts. */
-       private final Map<String, Post> postCache = Collections.synchronizedMap(new HashMap<String, Post>());
+       /** All posts. */
+       private Map<String, Post> posts = new HashMap<String, Post>();
 
-       /** Cache for all known replies. */
-       private final Map<String, Reply> replyCache = Collections.synchronizedMap(new HashMap<String, Reply>());
+       /** All replies. */
+       private Map<String, Reply> replies = new HashMap<String, Reply>();
 
        /**
         * Creates a new core.
+        *
+        * @param configuration
+        *            The configuration of the core
+        * @param freenetInterface
+        *            The freenet interface
+        * @param identityManager
+        *            The identity manager
         */
-       public Core() {
-               super("Sone Core", false);
+       public Core(Configuration configuration, FreenetInterface freenetInterface, IdentityManager identityManager) {
+               this.configuration = configuration;
+               this.freenetInterface = freenetInterface;
+               this.identityManager = identityManager;
+               this.soneDownloader = new SoneDownloader(this, freenetInterface);
        }
 
        //
@@ -137,190 +133,234 @@ public class Core extends AbstractService {
        //
 
        /**
-        * Returns the options of the Sone plugin.
+        * Returns the options used by the core.
         *
-        * @return The options of the Sone plugin
+        * @return The options of the core
         */
        public Options getOptions() {
                return options;
        }
 
        /**
-        * Sets the configuration of the core.
+        * Returns the identity manager used by the core.
         *
-        * @param configuration
-        *            The configuration of the core
-        * @return This core (for method chaining)
+        * @return The identity manager
         */
-       public Core configuration(Configuration configuration) {
-               this.configuration = configuration;
-               return this;
+       public IdentityManager getIdentityManager() {
+               return identityManager;
        }
 
        /**
-        * Sets the Freenet interface to use.
+        * Returns the status of the given Sone.
         *
-        * @param freenetInterface
-        *            The Freenet interface to use
-        * @return This core (for method chaining)
+        * @param sone
+        *            The Sone to get the status for
+        * @return The status of the Sone
         */
-       public Core freenetInterface(FreenetInterface freenetInterface) {
-               this.freenetInterface = freenetInterface;
-               soneDownloader = new SoneDownloader(this, freenetInterface);
-               soneDownloader.start();
-               return this;
+       public SoneStatus getSoneStatus(Sone sone) {
+               synchronized (soneStatuses) {
+                       return soneStatuses.get(sone);
+               }
        }
 
        /**
-        * Returns the Web of Trust connector.
+        * Sets the status of the given Sone.
         *
-        * @return The Web of Trust connector
+        * @param sone
+        *            The Sone to set the status of
+        * @param soneStatus
+        *            The status to set
         */
-       public WebOfTrustConnector getWebOfTrustConnector() {
-               return webOfTrustConnector;
+       public void setSoneStatus(Sone sone, SoneStatus soneStatus) {
+               synchronized (soneStatuses) {
+                       soneStatuses.put(sone, soneStatus);
+               }
        }
 
        /**
-        * Sets the Web of Trust connector.
+        * Returns all Sones, remote and local.
         *
-        * @param webOfTrustConnector
-        *            The Web of Trust connector
-        * @return This core (for method chaining)
+        * @return All Sones
         */
-       public Core setWebOfTrustConnector(WebOfTrustConnector webOfTrustConnector) {
-               this.webOfTrustConnector = webOfTrustConnector;
-               return this;
+       public Set<Sone> getSones() {
+               Set<Sone> allSones = new HashSet<Sone>();
+               allSones.addAll(getLocalSones());
+               allSones.addAll(getRemoteSones());
+               return allSones;
        }
 
        /**
-        * Returns the local Sones.
+        * Returns the Sone with the given ID, regardless whether it’s local or
+        * remote.
         *
-        * @return The local Sones
+        * @param id
+        *            The ID of the Sone to get
+        * @return The Sone with the given ID, or {@code null} if there is no such
+        *         Sone
+        */
+       public Sone getSone(String id) {
+               Sone sone = getRemoteSone(id);
+               if (sone != null) {
+                       return sone;
+               }
+               sone = getLocalSone(id);
+               return sone;
+       }
+
+       /**
+        * Returns whether the given Sone is a local Sone.
+        *
+        * @param sone
+        *            The Sone to check for its locality
+        * @return {@code true} if the given Sone is local, {@code false} otherwise
         */
-       public Set<Sone> getSones() {
-               return Collections.unmodifiableSet(localSones);
+       public boolean isLocalSone(Sone sone) {
+               synchronized (localSones) {
+                       return localSones.containsKey(sone.getId());
+               }
        }
 
        /**
-        * Returns the Sone with the given ID, or an empty Sone that has been
-        * initialized with the given ID.
+        * Returns all local Sones.
         *
-        * @param soneId
-        *            The ID of the Sone
-        * @return The Sone
+        * @return All local Sones
         */
-       public Sone getSone(String soneId) {
-               if (!soneCache.containsKey(soneId)) {
-                       Sone sone = new Sone(soneId);
-                       soneCache.put(soneId, sone);
-                       setSoneStatus(sone, SoneStatus.unknown);
+       public Set<Sone> getLocalSones() {
+               synchronized (localSones) {
+                       return new HashSet<Sone>(localSones.values());
                }
-               return soneCache.get(soneId);
        }
 
        /**
-        * Returns all known sones.
+        * Returns the local Sone with the given ID.
         *
-        * @return All known sones
+        * @param id
+        *            The ID of the Sone to get
+        * @return The Sone, or {@code null} if there is no Sone with the given ID
         */
-       public Collection<Sone> getKnownSones() {
-               return Collections.unmodifiableCollection(soneCache.values());
+       public Sone getLocalSone(String id) {
+               synchronized (localSones) {
+                       return localSones.get(id);
+               }
        }
 
        /**
-        * Gets all known Sones that are not local Sones.
+        * Returns all remote Sones.
         *
         * @return All remote Sones
         */
-       public Collection<Sone> getRemoteSones() {
-               return Collections.unmodifiableCollection(getKnownSones());
+       public Set<Sone> getRemoteSones() {
+               synchronized (remoteSones) {
+                       return new HashSet<Sone>(remoteSones.values());
+               }
        }
 
        /**
-        * Returns the status of the given Sone.
+        * Returns the remote Sone with the given ID.
         *
-        * @param sone
-        *            The Sone to get the status for
-        * @return The status of the Sone
+        * @param id
+        *            The ID of the remote Sone to get
+        * @return The Sone, or {@code null} if there is no such Sone
         */
-       public SoneStatus getSoneStatus(Sone sone) {
-               return soneStatuses.get(sone);
+       public Sone getRemoteSone(String id) {
+               synchronized (remoteSones) {
+                       return remoteSones.get(id);
+               }
        }
 
        /**
-        * Sets the status of the Sone.
+        * Returns whether the given Sone is a remote Sone.
         *
         * @param sone
-        *            The Sone to set the status for
-        * @param soneStatus
-        *            The status of the Sone
+        *            The Sone to check
+        * @return {@code true} if the given Sone is a remote Sone, {@code false}
+        *         otherwise
         */
-       public void setSoneStatus(Sone sone, SoneStatus soneStatus) {
-               soneStatuses.put(sone, soneStatus);
+       public boolean isRemoteSone(Sone sone) {
+               synchronized (remoteSones) {
+                       return remoteSones.containsKey(sone.getId());
+               }
        }
 
        /**
-        * Creates a new post and adds it to the given Sone.
+        * Returns the post with the given ID.
         *
-        * @param sone
-        *            The sone that creates the post
-        * @param text
-        *            The text of the post
-        * @return The created post
+        * @param postId
+        *            The ID of the post to get
+        * @return The post, or {@code null} if there is no such post
         */
-       public Post createPost(Sone sone, String text) {
-               return createPost(sone, System.currentTimeMillis(), text);
+       public Post getPost(String postId) {
+               synchronized (posts) {
+                       return posts.get(postId);
+               }
        }
 
        /**
-        * Creates a new post and adds it to the given Sone.
+        * Returns the reply with the given ID.
         *
-        * @param sone
-        *            The Sone that creates the post
-        * @param time
-        *            The time of the post
-        * @param text
-        *            The text of the post
-        * @return The created post
+        * @param replyId
+        *            The ID of the reply to get
+        * @return The reply, or {@code null} if there is no such reply
         */
-       public Post createPost(Sone sone, long time, String text) {
-               Post post = getPost(UUID.randomUUID().toString()).setSone(sone).setTime(time).setText(text);
-               sone.addPost(post);
-               return post;
+       public Reply getReply(String replyId) {
+               synchronized (replies) {
+                       return replies.get(replyId);
+               }
        }
 
        /**
-        * Creates a reply.
+        * Returns all replies for the given post, order ascending by time.
         *
-        * @param sone
-        *            The Sone that posts the reply
         * @param post
-        *            The post the reply refers to
-        * @param text
-        *            The text of the reply
-        * @return The created reply
+        *            The post to get all replies for
+        * @return All replies for the given post
         */
-       public Reply createReply(Sone sone, Post post, String text) {
-               return createReply(sone, post, System.currentTimeMillis(), text);
+       public List<Reply> getReplies(Post post) {
+               Set<Sone> sones = getSones();
+               List<Reply> replies = new ArrayList<Reply>();
+               for (Sone sone : sones) {
+                       for (Reply reply : sone.getReplies()) {
+                               if (reply.getPost().equals(post)) {
+                                       replies.add(reply);
+                               }
+                       }
+               }
+               Collections.sort(replies, Reply.TIME_COMPARATOR);
+               return replies;
        }
 
        /**
-        * Creates a reply.
+        * Returns all Sones that have liked the given post.
         *
-        * @param sone
-        *            The Sone that posts the reply
         * @param post
-        *            The post the reply refers to
-        * @param time
-        *            The time of the post
-        * @param text
-        *            The text of the reply
-        * @return The created reply
-        */
-       public Reply createReply(Sone sone, Post post, long time, String text) {
-               Reply reply = getReply(UUID.randomUUID().toString()).setSone(sone).setPost(post).setTime(time).setText(text);
-               sone.addReply(reply);
-               return reply;
+        *            The post to get the liking Sones for
+        * @return The Sones that like the given post
+        */
+       public Set<Sone> getLikes(Post post) {
+               Set<Sone> sones = new HashSet<Sone>();
+               for (Sone sone : getSones()) {
+                       if (sone.getLikedPostIds().contains(post.getId())) {
+                               sones.add(sone);
+                       }
+               }
+               return sones;
+       }
+
+       /**
+        * Returns all Sones that have liked the given reply.
+        *
+        * @param reply
+        *            The reply to get the liking Sones for
+        * @return The Sones that like the given reply
+        */
+       public Set<Sone> getLikes(Reply reply) {
+               Set<Sone> sones = new HashSet<Sone>();
+               for (Sone sone : getSones()) {
+                       if (sone.getLikedPostIds().contains(reply.getId())) {
+                               sones.add(sone);
+                       }
+               }
+               return sones;
        }
 
        //
@@ -328,334 +368,306 @@ public class Core extends AbstractService {
        //
 
        /**
-        * Adds a Sone to watch for updates. The Sone needs to be completely
-        * initialized.
+        * Adds a local Sone from the given ID which has to be the ID of an own
+        * identity.
         *
-        * @param sone
-        *            The Sone to watch for updates
-        */
-       public void addSone(Sone sone) {
-               soneCache.put(sone.getId(), sone);
-               if (!localSones.contains(sone)) {
-                       soneDownloader.addSone(sone);
+        * @param id
+        *            The ID of an own identity to add a Sone for
+        * @return The added (or already existing) Sone
+        */
+       public Sone addLocalSone(String id) {
+               synchronized (localSones) {
+                       if (localSones.containsKey(id)) {
+                               logger.log(Level.FINE, "Tried to add known local Sone: %s", id);
+                               return localSones.get(id);
+                       }
+                       OwnIdentity ownIdentity = identityManager.getOwnIdentity(id);
+                       if (ownIdentity == null) {
+                               logger.log(Level.INFO, "Invalid Sone ID: %s", id);
+                               return null;
+                       }
+                       return addLocalSone(ownIdentity);
                }
        }
 
        /**
-        * Adds the given Sone.
+        * Adds a local Sone from the given own identity.
         *
-        * @param sone
-        *            The Sone to add
+        * @param ownIdentity
+        *            The own identity to create a Sone from
+        * @return The added (or already existing) Sone
         */
-       public void addLocalSone(Sone sone) {
-               if (localSones.add(sone)) {
-                       setSoneStatus(sone, SoneStatus.idle);
+       public Sone addLocalSone(OwnIdentity ownIdentity) {
+               if (ownIdentity == null) {
+                       logger.log(Level.WARNING, "Given OwnIdentity is null!");
+                       return null;
+               }
+               synchronized (localSones) {
+                       if (localSones.containsKey(ownIdentity.getId())) {
+                               logger.log(Level.FINE, "Tried to add known local Sone: %s", ownIdentity);
+                               return localSones.get(ownIdentity.getId());
+                       }
+                       String latestEdition = ownIdentity.getProperty("Sone.LatestEdition");
+                       Sone sone = new Sone(ownIdentity).setInsertUri(getSoneUri(ownIdentity.getInsertUri(), latestEdition)).setRequestUri(getSoneUri(ownIdentity.getRequestUri(), latestEdition));
+                       /* TODO - load posts ’n stuff */
+                       localSones.put(ownIdentity.getId(), sone);
                        SoneInserter soneInserter = new SoneInserter(this, freenetInterface, sone);
-                       soneInserter.start();
                        soneInserters.put(sone, soneInserter);
+                       soneInserter.start();
+                       setSoneStatus(sone, SoneStatus.idle);
+                       return sone;
                }
        }
 
        /**
-        * Creates a new Sone at a random location.
+        * Creates a new Sone for the given own identity.
         *
-        * @param name
-        *            The name of the Sone
+        * @param ownIdentity
+        *            The own identity to create a Sone for
         * @return The created Sone
-        * @throws SoneException
-        *             if a Sone error occurs
         */
-       public Sone createSone(String name) throws SoneException {
-               return createSone(name, "Sone", null, null);
-       }
-
-       /**
-        * Creates a new Sone at the given location. If one of {@code requestUri} or
-        * {@code insertUrI} is {@code null}, the Sone is created at a random
-        * location.
-        *
-        * @param name
-        *            The name of the Sone
-        * @param documentName
-        *            The document name in the SSK
-        * @param requestUri
-        *            The request URI of the Sone, or {@link NullPointerException}
-        *            to create a Sone at a random location
-        * @param insertUri
-        *            The insert URI of the Sone, or {@code null} to create a Sone
-        *            at a random location
-        * @return The created Sone
-        * @throws SoneException
-        *             if a Sone error occurs
-        */
-       public Sone createSone(String name, String documentName, String requestUri, String insertUri) throws SoneException {
-               if ((name == null) || (name.trim().length() == 0)) {
-                       throw new SoneException(Type.INVALID_SONE_NAME);
-               }
-               String finalRequestUri;
-               String finalInsertUri;
-               if ((requestUri == null) || (insertUri == null)) {
-                       String[] keyPair = freenetInterface.generateKeyPair();
-                       finalRequestUri = keyPair[0];
-                       finalInsertUri = keyPair[1];
-               } else {
-                       finalRequestUri = requestUri;
-                       finalInsertUri = insertUri;
-               }
-               Sone sone;
-               try {
-                       logger.log(Level.FINEST, "Creating new Sone “%s” at %s (%s)…", new Object[] { name, finalRequestUri, finalInsertUri });
-                       sone = getSone(UUID.randomUUID().toString()).setName(name).setRequestUri(new FreenetURI(finalRequestUri).setKeyType("USK").setDocName(documentName)).setInsertUri(new FreenetURI(finalInsertUri).setKeyType("USK").setDocName(documentName));
-                       sone.setProfile(new Profile());
-                       /* set modification counter to 1 so it is inserted immediately. */
-                       sone.setModificationCounter(1);
-                       addLocalSone(sone);
-               } catch (MalformedURLException mue1) {
-                       throw new SoneException(Type.INVALID_URI);
+       public Sone createSone(OwnIdentity ownIdentity) {
+               identityManager.addContext(ownIdentity, "Sone");
+               Sone sone = addLocalSone(ownIdentity);
+               synchronized (sone) {
+                       /* mark as modified so that it gets inserted immediately. */
+                       sone.setModificationCounter(sone.getModificationCounter() + 1);
                }
                return sone;
        }
 
        /**
-        * Loads the Sone from the given request URI. The fetching of the data is
-        * performed in a new thread so this method returns immediately.
+        * Adds the Sone of the given identity.
         *
-        * @param requestUri
-        *            The request URI to load the Sone from
+        * @param identity
+        *            The identity whose Sone to add
+        * @return The added or already existing Sone
         */
-       public void loadSone(final String requestUri) {
-               loadSone(requestUri, null);
+       public Sone addRemoteSone(Identity identity) {
+               if (identity == null) {
+                       logger.log(Level.WARNING, "Given Identity is null!");
+                       return null;
+               }
+               synchronized (remoteSones) {
+                       if (remoteSones.containsKey(identity.getId())) {
+                               logger.log(Level.FINE, "Identity already exists: %s", identity);
+                               return remoteSones.get(identity.getId());
+                       }
+                       Sone sone = new Sone(identity);
+                       sone.setRequestUri(getSoneUri(identity.getRequestUri(), identity.getProperty("Sone.LatestEdition")));
+                       remoteSones.put(identity.getId(), sone);
+                       soneDownloader.addSone(sone);
+                       setSoneStatus(sone, SoneStatus.idle);
+                       return sone;
+               }
        }
 
        /**
-        * Loads the Sone from the given request URI. The fetching of the data is
-        * performed in a new thread so this method returns immediately. If
-        * {@code insertUri} is not {@code null} the loaded Sone is converted into a
-        * local Sone and available using as any other local Sone.
+        * Updates the stores Sone with the given Sone.
         *
-        * @param requestUri
-        *            The request URI to load the Sone from
-        * @param insertUri
-        *            The insert URI of the Sone
-        */
-       public void loadSone(final String requestUri, final String insertUri) {
-               new Thread(new Runnable() {
-
-                       @Override
-                       @SuppressWarnings("synthetic-access")
-                       public void run() {
-                               try {
-                                       FreenetURI realRequestUri = new FreenetURI(requestUri).setMetaString(new String[] { "sone.xml" });
-                                       FetchResult fetchResult = freenetInterface.fetchUri(realRequestUri);
-                                       if (fetchResult == null) {
-                                               return;
-                                       }
-                                       Sone parsedSone = soneDownloader.parseSone(null, fetchResult, realRequestUri);
-                                       if (parsedSone != null) {
-                                               if (insertUri != null) {
-                                                       parsedSone.setInsertUri(new FreenetURI(insertUri));
-                                                       addLocalSone(parsedSone);
-                                               } else {
-                                                       addSone(parsedSone);
-                                               }
-                                               setSoneStatus(parsedSone, SoneStatus.idle);
-                                       }
-                               } catch (MalformedURLException mue1) {
-                                       logger.log(Level.INFO, "Could not create URI from “" + requestUri + "”.", mue1);
+        * @param sone
+        *            The updated Sone
+        */
+       public void updateSone(Sone sone) {
+               if (isRemoteSone(sone)) {
+                       Sone storedSone = getRemoteSone(sone.getId());
+                       if (!(sone.getTime() > storedSone.getTime())) {
+                               logger.log(Level.FINE, "Downloaded Sone %s is not newer than stored Sone %s.", new Object[] { sone, storedSone });
+                               return;
+                       }
+                       synchronized (posts) {
+                               for (Post post : storedSone.getPosts()) {
+                                       posts.remove(post.getId());
                                }
+                               for (Post post : sone.getPosts()) {
+                                       posts.put(post.getId(), post);
+                               }
+                       }
+                       synchronized (replies) {
+                               for (Reply reply : storedSone.getReplies()) {
+                                       replies.remove(reply.getId());
+                               }
+                               for (Reply reply : sone.getReplies()) {
+                                       replies.put(reply.getId(), reply);
+                               }
+                       }
+                       synchronized (storedSone) {
+                               storedSone.setTime(sone.getTime());
+                               storedSone.setProfile(sone.getProfile());
+                               storedSone.setPosts(sone.getPosts());
+                               storedSone.setReplies(sone.getReplies());
+                               storedSone.setLikePostIds(sone.getLikedPostIds());
+                               storedSone.setLikeReplyIds(sone.getLikedReplyIds());
+                               storedSone.updateUris(sone.getRequestUri().getEdition());
                        }
-               }, "Sone Downloader").start();
+                       saveSone(storedSone);
+               }
        }
 
        /**
-        * Loads a Sone from an input stream.
+        * Deletes the given Sone. This will remove the Sone from the
+        * {@link #getLocalSone(String) local Sones}, stops its {@link SoneInserter}
+        * and remove the context from its identity.
         *
-        * @param soneInputStream
-        *            The input stream to load the Sone from
-        * @return The parsed Sone, or {@code null} if the Sone could not be parsed
+        * @param sone
+        *            The Sone to delete
         */
-       public Sone loadSone(InputStream soneInputStream) {
-               Sone parsedSone = soneDownloader.parseSone(soneInputStream);
-               if (parsedSone == null) {
-                       return null;
+       public void deleteSone(Sone sone) {
+               if (!(sone.getIdentity() instanceof OwnIdentity)) {
+                       logger.log(Level.WARNING, "Tried to delete Sone of non-own identity: %s", sone);
+                       return;
                }
-               if (parsedSone.getInsertUri() != null) {
-                       addLocalSone(parsedSone);
-               } else {
-                       addSone(parsedSone);
+               synchronized (localSones) {
+                       if (!localSones.containsKey(sone.getId())) {
+                               logger.log(Level.WARNING, "Tried to delete non-local Sone: %s", sone);
+                               return;
+                       }
+                       localSones.remove(sone.getId());
+                       soneInserters.remove(sone.getId()).stop();
                }
-               return parsedSone;
+               identityManager.removeContext((OwnIdentity) sone.getIdentity(), "Sone");
        }
 
        /**
-        * Loads and updates the given Sone.
+        * Saves the given Sone. This will persist all local settings for the given
+        * Sone, such as the friends list and similar, private options.
         *
         * @param sone
-        *            The Sone to load
+        *            The Sone to save
         */
-       public void loadSone(final Sone sone) {
-               new Thread(new Runnable() {
-
-                       @Override
-                       @SuppressWarnings("synthetic-access")
-                       public void run() {
-                               FreenetURI realRequestUri = sone.getRequestUri().setMetaString(new String[] { "sone.xml" });
-                               setSoneStatus(sone, SoneStatus.downloading);
-                               try {
-                                       FetchResult fetchResult = freenetInterface.fetchUri(realRequestUri);
-                                       if (fetchResult == null) {
-                                               /* TODO - mark Sone as bad. */
-                                               return;
-                                       }
-                                       Sone parsedSone = soneDownloader.parseSone(sone, fetchResult, realRequestUri);
-                                       if (parsedSone != null) {
-                                               addSone(parsedSone);
-                                       }
-                               } finally {
-                                       setSoneStatus(sone, (sone.getTime() == 0) ? SoneStatus.unknown : SoneStatus.idle);
-                               }
-                       }
-               }, "Sone Downloader").start();
+       public void saveSone(Sone sone) {
+               if (!isLocalSone(sone)) {
+                       logger.log(Level.FINE, "Tried to save non-local Sone: %s", sone);
+               }
+               /* TODO - implement saving. */
        }
 
        /**
-        * Deletes the given Sone from this plugin instance.
+        * Creates a new post.
         *
         * @param sone
-        *            The sone to delete
+        *            The Sone that creates the post
+        * @param text
+        *            The text of the post
         */
-       public void deleteSone(Sone sone) {
-               SoneInserter soneInserter = soneInserters.remove(sone);
-               soneInserter.stop();
-               localSones.remove(sone);
-               soneStatuses.remove(sone);
-               soneCache.remove(sone.getId());
+       public void createPost(Sone sone, String text) {
+               createPost(sone, System.currentTimeMillis(), text);
        }
 
        /**
-        * Returns the post with the given ID. If no post exists yet with the given
-        * ID, a new post is returned.
+        * Creates a new post.
         *
-        * @param postId
-        *            The ID of the post
-        * @return The post
+        * @param sone
+        *            The Sone that creates the post
+        * @param time
+        *            The time of the post
+        * @param text
+        *            The text of the post
         */
-       public Post getPost(String postId) {
-               if (!postCache.containsKey(postId)) {
-                       postCache.put(postId, new Post(postId));
+       public void createPost(Sone sone, long time, String text) {
+               if (!isLocalSone(sone)) {
+                       logger.log(Level.FINE, "Tried to create post for non-local Sone: %s", sone);
+                       return;
                }
-               return postCache.get(postId);
-       }
-
-       /**
-        * Returns the reply with the given ID. If no reply exists yet with the
-        * given ID, a new reply is returned.
-        *
-        * @param replyId
-        *            The ID of the reply
-        * @return The reply
-        */
-       public Reply getReply(String replyId) {
-               if (!replyCache.containsKey(replyId)) {
-                       replyCache.put(replyId, new Reply(replyId));
+               Post post = new Post(sone, time, text);
+               synchronized (posts) {
+                       posts.put(post.getId(), post);
                }
-               return replyCache.get(replyId);
+               sone.addPost(post);
+               saveSone(sone);
        }
 
        /**
-        * Gets all replies to the given post, sorted by date, oldest first.
+        * Deletes the given post.
         *
         * @param post
-        *            The post the replies refer to
-        * @return The sorted list of replies for the post
+        *            The post to delete
         */
-       public List<Reply> getReplies(Post post) {
-               List<Reply> replies = new ArrayList<Reply>();
-               for (Reply reply : replyCache.values()) {
-                       if (reply.getPost().equals(post)) {
-                               replies.add(reply);
-                       }
+       public void deletePost(Post post) {
+               if (!isLocalSone(post.getSone())) {
+                       logger.log(Level.WARNING, "Tried to delete post of non-local Sone: %s", post.getSone());
+                       return;
                }
-               Collections.sort(replies, new Comparator<Reply>() {
-
-                       /**
-                        * {@inheritDoc}
-                        */
-                       @Override
-                       public int compare(Reply leftReply, Reply rightReply) {
-                               return (int) Math.max(Integer.MIN_VALUE, Math.min(Integer.MAX_VALUE, leftReply.getTime() - rightReply.getTime()));
-                       }
-               });
-               return replies;
+               post.getSone().removePost(post);
+               synchronized (posts) {
+                       posts.remove(post.getId());
+               }
+               saveSone(post.getSone());
        }
 
        /**
-        * Gets all Sones that like the given post.
+        * Creates a new reply.
         *
+        * @param sone
+        *            The Sone that creates the reply
         * @param post
-        *            The post to check for
-        * @return All Sones that like the post
+        *            The post that this reply refers to
+        * @param text
+        *            The text of the reply
         */
-       public Collection<Sone> getLikes(final Post post) {
-               return Filters.filteredCollection(getKnownSones(), new Filter<Sone>() {
-
-                       @Override
-                       public boolean filterObject(Sone sone) {
-                               return sone.isLikedPostId(post.getId());
-                       }
-               });
+       public void createReply(Sone sone, Post post, String text) {
+               createReply(sone, post, System.currentTimeMillis(), text);
        }
 
        /**
-        * Gets all Sones that like the given reply.
+        * Creates a new reply.
         *
-        * @param reply
-        *            The reply to check for
-        * @return All Sones that like the reply
+        * @param sone
+        *            The Sone that creates the reply
+        * @param post
+        *            The post that this reply refers to
+        * @param time
+        *            The time of the reply
+        * @param text
+        *            The text of the reply
         */
-       public Collection<Sone> getLikes(final Reply reply) {
-               return Filters.filteredCollection(getKnownSones(), new Filter<Sone>() {
-
-                       @Override
-                       public boolean filterObject(Sone sone) {
-                               return sone.isLikedReplyId(reply.getId());
-                       }
-               });
+       public void createReply(Sone sone, Post post, long time, String text) {
+               if (!isLocalSone(sone)) {
+                       logger.log(Level.FINE, "Tried to create reply for non-local Sone: %s", sone);
+                       return;
+               }
+               Reply reply = new Reply(sone, post, System.currentTimeMillis(), text);
+               synchronized (replies) {
+                       replies.put(reply.getId(), reply);
+               }
+               sone.addReply(reply);
+               saveSone(sone);
        }
 
        /**
-        * Deletes the given reply. It is removed from its Sone and from the reply
-        * cache.
+        * Deletes the given reply.
         *
         * @param reply
-        *            The reply to remove
+        *            The reply to delete
         */
        public void deleteReply(Reply reply) {
-               reply.getSone().removeReply(reply);
-               replyCache.remove(reply.getId());
+               Sone sone = reply.getSone();
+               if (!isLocalSone(sone)) {
+                       logger.log(Level.FINE, "Tried to delete non-local reply: %s", reply);
+                       return;
+               }
+               synchronized (replies) {
+                       replies.remove(reply.getId());
+               }
+               sone.removeReply(reply);
+               saveSone(sone);
        }
 
-       //
-       // SERVICE METHODS
-       //
-
        /**
-        * {@inheritDoc}
+        * Starts the core.
         */
-       @Override
-       protected void serviceStart() {
+       public void start() {
                loadConfiguration();
        }
 
        /**
-        * {@inheritDoc}
+        * Stops the core.
         */
-       @Override
-       protected void serviceStop() {
-               soneDownloader.stop();
-               /* stop all Sone inserters. */
-               for (SoneInserter soneInserter : soneInserters.values()) {
-                       soneInserter.stop();
+       public void stop() {
+               synchronized (localSones) {
+                       for (SoneInserter soneInserter : soneInserters.values()) {
+                               soneInserter.stop();
+                       }
                }
                saveConfiguration();
        }
@@ -665,32 +677,11 @@ public class Core extends AbstractService {
        //
 
        /**
-        * Adds some default Sones.
-        */
-       private void addDefaultSones() {
-               for (String soneUri : defaultSones) {
-                       loadSone(soneUri);
-               }
-       }
-
-       /**
         * Loads the configuration.
         */
        @SuppressWarnings("unchecked")
        private void loadConfiguration() {
-               logger.entering(Core.class.getName(), "loadConfiguration()");
-
-               boolean firstStart = configuration.getBooleanValue("FirstStart").getValue(true);
-               if (firstStart) {
-                       logger.log(Level.INFO, "First start of Sone, adding a couple of default Sones…");
-                       addDefaultSones();
-                       try {
-                               configuration.getBooleanValue("FirstStart").setValue(false);
-                       } catch (ConfigurationException ce1) {
-                               logger.log(Level.WARNING, "Could not clear “first start” flag!");
-                       }
-               }
-
+               /* create options. */
                options.addIntegerOption("InsertionDelay", new DefaultOption<Integer>(60, new OptionWatcher<Integer>() {
 
                        @Override
@@ -699,14 +690,10 @@ public class Core extends AbstractService {
                        }
 
                }));
-
                options.addBooleanOption("ClearOnNextRestart", new DefaultOption<Boolean>(false));
                options.addBooleanOption("ReallyClearOnNextRestart", new DefaultOption<Boolean>(false));
 
-               if (firstStart) {
-                       return;
-               }
-
+               /* read options from configuration. */
                options.getBooleanOption("ClearOnNextRestart").set(configuration.getBooleanValue("Option/ClearOnNextRestart").getValue(null));
                options.getBooleanOption("ReallyClearOnNextRestart").set(configuration.getBooleanValue("Option/ReallyClearOnNextRestart").getValue(null));
                boolean clearConfiguration = options.getBooleanOption("ClearOnNextRestart").get() && options.getBooleanOption("ReallyClearOnNextRestart").get();
@@ -714,203 +701,92 @@ public class Core extends AbstractService {
                options.getBooleanOption("ReallyClearOnNextRestart").set(null);
                if (clearConfiguration) {
                        /* stop loading the configuration. */
-                       addDefaultSones();
                        return;
                }
 
                options.getIntegerOption("InsertionDelay").set(configuration.getIntValue("Option/InsertionDelay").getValue(null));
 
-               /* parse local Sones. */
-               logger.log(Level.INFO, "Loading Sones…");
-               int soneId = 0;
-               do {
-                       String sonePrefix = "Sone/Sone." + soneId++;
-                       String id = configuration.getStringValue(sonePrefix + "/ID").getValue(null);
-                       if (id == null) {
-                               break;
-                       }
-                       String name = configuration.getStringValue(sonePrefix + "/Name").getValue(null);
-                       long time = configuration.getLongValue(sonePrefix + "/Time").getValue((long) 0);
-                       String insertUri = configuration.getStringValue(sonePrefix + "/InsertURI").getValue(null);
-                       String requestUri = configuration.getStringValue(sonePrefix + "/RequestURI").getValue(null);
-                       long modificationCounter = configuration.getLongValue(sonePrefix + "/ModificationCounter").getValue((long) 0);
-                       String firstName = configuration.getStringValue(sonePrefix + "/Profile/FirstName").getValue(null);
-                       String middleName = configuration.getStringValue(sonePrefix + "/Profile/MiddleName").getValue(null);
-                       String lastName = configuration.getStringValue(sonePrefix + "/Profile/LastName").getValue(null);
-                       Integer birthDay = configuration.getIntValue(sonePrefix + "/Profile/BirthDay").getValue(null);
-                       Integer birthMonth = configuration.getIntValue(sonePrefix + "/Profile/BirthMonth").getValue(null);
-                       Integer birthYear = configuration.getIntValue(sonePrefix + "/Profile/BirthYear").getValue(null);
-                       try {
-                               Profile profile = new Profile();
-                               profile.setFirstName(firstName);
-                               profile.setMiddleName(middleName);
-                               profile.setLastName(lastName);
-                               profile.setBirthDay(birthDay).setBirthMonth(birthMonth).setBirthYear(birthYear);
-                               Sone sone = getSone(id).setName(name).setTime(time).setRequestUri(new FreenetURI(requestUri)).setInsertUri(new FreenetURI(insertUri));
-                               sone.setProfile(profile);
-                               int postId = 0;
-                               do {
-                                       String postPrefix = sonePrefix + "/Post." + postId++;
-                                       id = configuration.getStringValue(postPrefix + "/ID").getValue(null);
-                                       if (id == null) {
-                                               break;
-                                       }
-                                       time = configuration.getLongValue(postPrefix + "/Time").getValue((long) 0);
-                                       String text = configuration.getStringValue(postPrefix + "/Text").getValue(null);
-                                       Post post = getPost(id).setSone(sone).setTime(time).setText(text);
-                                       sone.addPost(post);
-                               } while (true);
-                               int replyCounter = 0;
-                               do {
-                                       String replyPrefix = sonePrefix + "/Reply." + replyCounter++;
-                                       String replyId = configuration.getStringValue(replyPrefix + "/ID").getValue(null);
-                                       if (replyId == null) {
-                                               break;
-                                       }
-                                       Post replyPost = getPost(configuration.getStringValue(replyPrefix + "/Post").getValue(null));
-                                       long replyTime = configuration.getLongValue(replyPrefix + "/Time").getValue(null);
-                                       String replyText = configuration.getStringValue(replyPrefix + "/Text").getValue(null);
-                                       Reply reply = getReply(replyId).setSone(sone).setPost(replyPost).setTime(replyTime).setText(replyText);
-                                       sone.addReply(reply);
-                               } while (true);
-
-                               /* load friends. */
-                               int friendCounter = 0;
-                               while (true) {
-                                       String friendPrefix = sonePrefix + "/Friend." + friendCounter++;
-                                       String friendId = configuration.getStringValue(friendPrefix + "/ID").getValue(null);
-                                       if (friendId == null) {
-                                               break;
-                                       }
-                                       Sone friendSone = getSone(friendId);
-                                       String friendKey = configuration.getStringValue(friendPrefix + "/Key").getValue(null);
-                                       String friendName = configuration.getStringValue(friendPrefix + "/Name").getValue(null);
-                                       friendSone.setRequestUri(new FreenetURI(friendKey)).setName(friendName);
-                                       sone.addFriend(friendSone);
-                               }
-
-                               /* load liked post IDs. */
-                               int likedPostIdCounter = 0;
-                               while (true) {
-                                       String likedPostIdPrefix = sonePrefix + "/LikedPostId." + likedPostIdCounter++;
-                                       String likedPostId = configuration.getStringValue(likedPostIdPrefix + "/ID").getValue(null);
-                                       if (likedPostId == null) {
-                                               break;
-                                       }
-                                       sone.addLikedPostId(likedPostId);
-                               }
-
-                               /* load liked reply IDs. */
-                               int likedReplyIdCounter = 0;
-                               while (true) {
-                                       String likedReplyIdPrefix = sonePrefix + "/LikedReplyId." + likedReplyIdCounter++;
-                                       String likedReplyId = configuration.getStringValue(likedReplyIdPrefix + "/ID").getValue(null);
-                                       if (likedReplyId == null) {
-                                               break;
-                                       }
-                                       sone.addLikedReplyId(likedReplyId);
-                               }
-
-                               sone.setModificationCounter(modificationCounter);
-                               addLocalSone(sone);
-                       } catch (MalformedURLException mue1) {
-                               logger.log(Level.WARNING, "Could not create Sone from requestUri (“" + requestUri + "”) and insertUri (“" + insertUri + "”)!", mue1);
-                       }
-               } while (true);
-               logger.log(Level.INFO, "Loaded %d Sones.", getSones().size());
-
-               /* load all remote Sones. */
-               for (Sone remoteSone : getRemoteSones()) {
-                       loadSone(remoteSone);
-               }
-
-               logger.exiting(Core.class.getName(), "loadConfiguration()");
        }
 
        /**
-        * Saves the configuraiton.
+        * Saves the current options.
         */
        private void saveConfiguration() {
-               Set<Sone> sones = getSones();
-               logger.log(Level.INFO, "Storing %d Sones…", sones.size());
-
+               /* store the options first. */
                try {
-                       /* store the options first. */
                        configuration.getIntValue("Option/InsertionDelay").setValue(options.getIntegerOption("InsertionDelay").getReal());
                        configuration.getBooleanValue("Option/ClearOnNextRestart").setValue(options.getBooleanOption("ClearOnNextRestart").getReal());
                        configuration.getBooleanValue("Option/ReallyClearOnNextRestart").setValue(options.getBooleanOption("ReallyClearOnNextRestart").getReal());
+               } catch (ConfigurationException ce1) {
+                       logger.log(Level.SEVERE, "Could not store configuration!", ce1);
+               }
+       }
 
-                       /* store all Sones. */
-                       int soneId = 0;
-                       for (Sone sone : localSones) {
-                               String sonePrefix = "Sone/Sone." + soneId++;
-                               configuration.getStringValue(sonePrefix + "/ID").setValue(sone.getId());
-                               configuration.getStringValue(sonePrefix + "/Name").setValue(sone.getName());
-                               configuration.getLongValue(sonePrefix + "/Time").setValue(sone.getTime());
-                               configuration.getStringValue(sonePrefix + "/RequestURI").setValue(sone.getRequestUri().toString());
-                               configuration.getStringValue(sonePrefix + "/InsertURI").setValue(sone.getInsertUri().toString());
-                               configuration.getLongValue(sonePrefix + "/ModificationCounter").setValue(sone.getModificationCounter());
-                               Profile profile = sone.getProfile();
-                               configuration.getStringValue(sonePrefix + "/Profile/FirstName").setValue(profile.getFirstName());
-                               configuration.getStringValue(sonePrefix + "/Profile/MiddleName").setValue(profile.getMiddleName());
-                               configuration.getStringValue(sonePrefix + "/Profile/LastName").setValue(profile.getLastName());
-                               configuration.getIntValue(sonePrefix + "/Profile/BirthDay").setValue(profile.getBirthDay());
-                               configuration.getIntValue(sonePrefix + "/Profile/BirthMonth").setValue(profile.getBirthMonth());
-                               configuration.getIntValue(sonePrefix + "/Profile/BirthYear").setValue(profile.getBirthYear());
-                               int postId = 0;
-                               for (Post post : sone.getPosts()) {
-                                       String postPrefix = sonePrefix + "/Post." + postId++;
-                                       configuration.getStringValue(postPrefix + "/ID").setValue(post.getId());
-                                       configuration.getLongValue(postPrefix + "/Time").setValue(post.getTime());
-                                       configuration.getStringValue(postPrefix + "/Text").setValue(post.getText());
-                               }
-                               /* write null ID as terminator. */
-                               configuration.getStringValue(sonePrefix + "/Post." + postId + "/ID").setValue(null);
+       /**
+        * Generate a Sone URI from the given URI and latest edition.
+        *
+        * @param uriString
+        *            The URI to derive the Sone URI from
+        * @param latestEditionString
+        *            The latest edition as a {@link String}, or {@code null}
+        * @return The derived URI
+        */
+       private FreenetURI getSoneUri(String uriString, String latestEditionString) {
+               try {
+                       FreenetURI uri = new FreenetURI(uriString).setDocName("Sone").setMetaString(new String[0]).setSuggestedEdition(Numbers.safeParseLong(latestEditionString, (long) 0));
+                       return uri;
+               } catch (MalformedURLException mue1) {
+                       logger.log(Level.WARNING, "Could not create Sone URI from URI: " + uriString, mue1);
+                       return null;
+               }
+       }
 
-                               int replyId = 0;
-                               for (Reply reply : sone.getReplies()) {
-                                       String replyPrefix = sonePrefix + "/Reply." + replyId++;
-                                       configuration.getStringValue(replyPrefix + "/ID").setValue(reply.getId());
-                                       configuration.getStringValue(replyPrefix + "/Post").setValue(reply.getPost().getId());
-                                       configuration.getLongValue(replyPrefix + "/Time").setValue(reply.getTime());
-                                       configuration.getStringValue(replyPrefix + "/Text").setValue(reply.getText());
-                               }
-                               /* write null ID as terminator. */
-                               configuration.getStringValue(sonePrefix + "/Reply." + replyId + "/ID").setValue(null);
-
-                               int friendId = 0;
-                               for (Sone friend : sone.getFriends()) {
-                                       String friendPrefix = sonePrefix + "/Friend." + friendId++;
-                                       configuration.getStringValue(friendPrefix + "/ID").setValue(friend.getId());
-                                       configuration.getStringValue(friendPrefix + "/Key").setValue(friend.getRequestUri().toString());
-                                       configuration.getStringValue(friendPrefix + "/Name").setValue(friend.getName());
-                               }
-                               /* write null ID as terminator. */
-                               configuration.getStringValue(sonePrefix + "/Friend." + friendId + "/ID").setValue(null);
-
-                               /* write all liked posts. */
-                               int likedPostIdCounter = 0;
-                               for (String soneLikedPostId : sone.getLikedPostIds()) {
-                                       String likedPostIdPrefix = sonePrefix + "/LikedPostId." + likedPostIdCounter++;
-                                       configuration.getStringValue(likedPostIdPrefix + "/ID").setValue(soneLikedPostId);
-                               }
-                               configuration.getStringValue(sonePrefix + "/LikedPostId." + likedPostIdCounter + "/ID").setValue(null);
+       //
+       // INTERFACE IdentityListener
+       //
 
-                               /* write all liked replies. */
-                               int likedReplyIdCounter = 0;
-                               for (String soneLikedReplyId : sone.getLikedReplyIds()) {
-                                       String likedReplyIdPrefix = sonePrefix + "/LikedReplyId." + likedReplyIdCounter++;
-                                       configuration.getStringValue(likedReplyIdPrefix + "/ID").setValue(soneLikedReplyId);
-                               }
-                               configuration.getStringValue(sonePrefix + "/LikedReplyId." + likedReplyIdCounter + "/ID").setValue(null);
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void ownIdentityAdded(OwnIdentity ownIdentity) {
+               logger.log(Level.FINEST, "Adding OwnIdentity: " + ownIdentity);
+               if (ownIdentity.hasContext("Sone")) {
+                       addLocalSone(ownIdentity);
+               }
+       }
 
-                       }
-                       /* write null ID as terminator. */
-                       configuration.getStringValue("Sone/Sone." + soneId + "/ID").setValue(null);
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void ownIdentityRemoved(OwnIdentity ownIdentity) {
+               /* TODO */
+       }
 
-               } catch (ConfigurationException ce1) {
-                       logger.log(Level.WARNING, "Could not store configuration!", ce1);
-               }
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void identityAdded(Identity identity) {
+               logger.log(Level.FINEST, "Adding Identity: " + identity);
+               addRemoteSone(identity);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void identityUpdated(Identity identity) {
+               /* TODO */
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void identityRemoved(Identity identity) {
+               /* TODO */
        }
 
 }
index 94f2b63..8a27979 100644 (file)
@@ -127,7 +127,7 @@ public class SoneDownloader extends AbstractService {
                        logger.log(Level.FINEST, "Got %d bytes back.", fetchResult.size());
                        Sone parsedSone = parseSone(sone, fetchResult, requestUri);
                        if (parsedSone != null) {
-                               core.addSone(parsedSone);
+                               core.updateSone(parsedSone);
                        }
                } finally {
                        core.setSoneStatus(sone, (sone.getTime() == 0) ? SoneStatus.unknown : SoneStatus.idle);
@@ -166,29 +166,17 @@ public class SoneDownloader extends AbstractService {
        }
 
        /**
-        * Parses a Sone from the given input stream.
-        *
-        * @param soneInputStream
-        *            The input stream to parse the Sone from
-        * @return The parsed Sone
-        */
-       public Sone parseSone(InputStream soneInputStream) {
-               return parseSone(null, soneInputStream);
-       }
-
-       /**
         * Parses a Sone from the given input stream and updates the given Sone, or
         * creates a new Sone.
         *
-        * @param originalSone
-        *            The Sone to update (may be {@code null})
+        * @param sone
+        *            The Sone to update
         * @param soneInputStream
         *            The input stream to parse the Sone from
         * @return The parsed Sone
         */
-       public Sone parseSone(Sone originalSone, InputStream soneInputStream) {
+       public Sone parseSone(Sone sone, InputStream soneInputStream) {
                /* TODO - impose a size limit? */
-               Sone sone;
 
                Document document;
                /* XML parsing is not thread-safe. */
@@ -197,7 +185,7 @@ public class SoneDownloader extends AbstractService {
                }
                if (document == null) {
                        /* TODO - mark Sone as bad. */
-                       logger.log(Level.WARNING, "Could not parse XML for Sone %s!", new Object[] { originalSone });
+                       logger.log(Level.WARNING, "Could not parse XML for Sone %s!", new Object[] { sone });
                        return null;
                }
                SimpleXML soneXml;
@@ -205,32 +193,10 @@ public class SoneDownloader extends AbstractService {
                        soneXml = SimpleXML.fromDocument(document);
                } catch (NullPointerException npe1) {
                        /* for some reason, invalid XML can cause NPEs. */
-                       logger.log(Level.WARNING, "XML for Sone " + originalSone + " can not be parsed!", npe1);
-                       return null;
-               }
-
-               /* check ID. */
-               String soneId = soneXml.getValue("id", null);
-               if ((originalSone != null) && !originalSone.getId().equals(soneId)) {
-                       /* TODO - mark Sone as bad. */
-                       logger.log(Level.WARNING, "Downloaded ID for Sone %s (%s) does not match known ID (%s)!", new Object[] { originalSone, originalSone.getId(), soneId });
+                       logger.log(Level.WARNING, "XML for Sone " + sone + " can not be parsed!", npe1);
                        return null;
                }
 
-               /* load Sone from core. */
-               sone = originalSone;
-               if (sone == null) {
-                       sone = core.getSone(soneId);
-               }
-
-               String soneName = soneXml.getValue("name", null);
-               if (soneName == null) {
-                       /* TODO - mark Sone as bad. */
-                       logger.log(Level.WARNING, "Downloaded name for Sone %s was null!", new Object[] { sone });
-                       return null;
-               }
-               sone.setName(soneName);
-
                String soneTime = soneXml.getValue("time", null);
                if (soneTime == null) {
                        /* TODO - mark Sone as bad. */
@@ -364,32 +330,6 @@ public class SoneDownloader extends AbstractService {
                        }
                }
 
-               /* parse known Sones. */
-               SimpleXML knownSonesXml = soneXml.getNode("known-sones");
-               Set<Sone> knownSones = new HashSet<Sone>();
-               if (knownSonesXml == null) {
-                       /* TODO - mark Sone as bad. */
-                       logger.log(Level.WARNING, "Downloaded Sone %s has no known Sones!", new Object[] { sone });
-               } else {
-                       for (SimpleXML knownSoneXml : knownSonesXml.getNodes("known-sone")) {
-                               String knownSoneId = knownSoneXml.getValue("sone-id", null);
-                               String knownSoneKey = knownSoneXml.getValue("sone-key", null);
-                               String knownSoneName = knownSoneXml.getValue("sone-name", null);
-                               if ((knownSoneId == null) || (knownSoneKey == null) || (knownSoneName == null)) {
-                                       /* TODO - mark Sone as bad. */
-                                       logger.log(Level.WARNING, "Downloaded known Sone for Sone %s with missing data! ID: %s, Key: %s, Name: %s", new Object[] { sone, knownSoneId, knownSoneKey, knownSoneName });
-                                       return null;
-                               }
-                               try {
-                                       knownSones.add(core.getSone(knownSoneId).setRequestUri(new FreenetURI(knownSoneKey)).setName(knownSoneName));
-                               } catch (MalformedURLException mue1) {
-                                       /* TODO - mark Sone as bad. */
-                                       logger.log(Level.WARNING, "Downloaded known Sone for Sone %s with invalid key: %s", new Object[] { sone, knownSoneKey });
-                                       return null;
-                               }
-                       }
-               }
-
                /* okay, apparently everything was parsed correctly. Now import. */
                /* atomic setter operation on the Sone. */
                synchronized (sone) {
@@ -400,10 +340,6 @@ public class SoneDownloader extends AbstractService {
                        sone.setModificationCounter(0);
                }
 
-               /* add all known Sones to core for downloading. */
-               for (Sone knownSone : knownSones) {
-                       core.addSone(knownSone);
-               }
                return sone;
        }
 
index 1136973..2a1f1fb 100644 (file)
@@ -21,7 +21,6 @@ import java.io.InputStreamReader;
 import java.io.StringWriter;
 import java.nio.charset.Charset;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
@@ -33,8 +32,7 @@ import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.Reply;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.freenet.StringBucket;
-import net.pterodactylus.util.filter.Filter;
-import net.pterodactylus.util.filter.Filters;
+import net.pterodactylus.sone.freenet.wot.OwnIdentity;
 import net.pterodactylus.util.io.Closer;
 import net.pterodactylus.util.logging.Logging;
 import net.pterodactylus.util.service.AbstractService;
@@ -147,6 +145,8 @@ public class SoneInserter extends AbstractService {
                                        core.setSoneStatus(sone, SoneStatus.inserting);
                                        FreenetURI finalUri = freenetInterface.insertDirectory(insertInformation.getInsertUri().setKeyType("USK").setSuggestedEdition(0), insertInformation.generateManifestEntries(), "index.html");
                                        sone.updateUris(finalUri.getEdition());
+                                       /* TODO - better encapsulation? */
+                                       core.getIdentityManager().setProperty((OwnIdentity) sone.getIdentity(), "Sone.LatestEdition", String.valueOf(finalUri.getEdition()));
                                        success = true;
                                        logger.log(Level.INFO, "Inserted Sone “%s” at %s.", new Object[] { sone.getName(), finalUri });
                                } catch (SoneException se1) {
@@ -265,19 +265,8 @@ public class SoneInserter extends AbstractService {
                        } finally {
                                Closer.close(templateInputStreamReader);
                        }
-                       Collection<Sone> knownSones = Filters.filteredCollection(core.getKnownSones(), new Filter<Sone>() {
-
-                               /**
-                                * {@inheritDoc}
-                                */
-                               @Override
-                               public boolean filterObject(Sone object) {
-                                       return !object.getId().equals(soneProperties.get("id"));
-                               }
-                       });
 
                        template.set("currentSone", soneProperties);
-                       template.set("knownSones", knownSones);
                        StringWriter writer = new StringWriter();
                        StringBucket bucket = null;
                        try {
index e156967..f1b9ff3 100644 (file)
@@ -20,12 +20,12 @@ package net.pterodactylus.sone.main;
 import java.io.File;
 import java.util.Collections;
 import java.util.logging.Level;
-import java.util.logging.LogRecord;
 import java.util.logging.Logger;
 
 import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.core.FreenetInterface;
 import net.pterodactylus.sone.freenet.PluginStoreConfigurationBackend;
+import net.pterodactylus.sone.freenet.wot.IdentityManager;
 import net.pterodactylus.sone.freenet.wot.PluginConnector;
 import net.pterodactylus.sone.freenet.wot.WebOfTrustConnector;
 import net.pterodactylus.sone.web.WebInterface;
@@ -34,7 +34,6 @@ import net.pterodactylus.util.config.ConfigurationException;
 import net.pterodactylus.util.config.MapConfigurationBackend;
 import net.pterodactylus.util.config.XMLConfigurationBackend;
 import net.pterodactylus.util.logging.Logging;
-import net.pterodactylus.util.logging.LoggingListener;
 import net.pterodactylus.util.version.Version;
 import freenet.client.async.DatabaseDisabledException;
 import freenet.l10n.BaseL10n.LANGUAGE;
@@ -58,30 +57,34 @@ public class SonePlugin implements FredPlugin, FredPluginL10n, FredPluginBaseL10
        static {
                /* initialize logging. */
                Logging.setup("sone");
-               Logging.addLoggingListener(new LoggingListener() {
-
-                       @Override
-                       public void logged(LogRecord logRecord) {
-                               Class<?> loggerClass = Logging.getLoggerClass(logRecord.getLoggerName());
-                               int recordLevel = logRecord.getLevel().intValue();
-                               if (recordLevel < Level.FINE.intValue()) {
-                                       freenet.support.Logger.debug(loggerClass, String.format(logRecord.getMessage(), logRecord.getParameters()), logRecord.getThrown());
-                               } else if (recordLevel < Level.INFO.intValue()) {
-                                       freenet.support.Logger.minor(loggerClass, String.format(logRecord.getMessage(), logRecord.getParameters()), logRecord.getThrown());
-                               } else if (recordLevel < Level.WARNING.intValue()) {
-                                       freenet.support.Logger.normal(loggerClass, String.format(logRecord.getMessage(), logRecord.getParameters()), logRecord.getThrown());
-                               } else if (recordLevel < Level.SEVERE.intValue()) {
-                                       freenet.support.Logger.warning(loggerClass, String.format(logRecord.getMessage(), logRecord.getParameters()), logRecord.getThrown());
-                               } else {
-                                       freenet.support.Logger.error(loggerClass, String.format(logRecord.getMessage(), logRecord.getParameters()), logRecord.getThrown());
-                               }
-                       }
-
-               });
+               Logging.setupConsoleLogging();
+               /*
+                * Logging.addLoggingListener(new LoggingListener() {
+                * @Override public void logged(LogRecord logRecord) { Class<?>
+                * loggerClass = Logging.getLoggerClass(logRecord.getLoggerName()); int
+                * recordLevel = logRecord.getLevel().intValue(); if (recordLevel <
+                * Level.FINE.intValue()) { freenet.support.Logger.debug(loggerClass,
+                * String.format(logRecord.getMessage(), logRecord.getParameters()),
+                * logRecord.getThrown()); } else if (recordLevel <
+                * Level.INFO.intValue()) { freenet.support.Logger.minor(loggerClass,
+                * String.format(logRecord.getMessage(), logRecord.getParameters()),
+                * logRecord.getThrown()); } else if (recordLevel <
+                * Level.WARNING.intValue()) {
+                * freenet.support.Logger.normal(loggerClass,
+                * String.format(logRecord.getMessage(), logRecord.getParameters()),
+                * logRecord.getThrown()); } else if (recordLevel <
+                * Level.SEVERE.intValue()) {
+                * freenet.support.Logger.warning(loggerClass,
+                * String.format(logRecord.getMessage(), logRecord.getParameters()),
+                * logRecord.getThrown()); } else {
+                * freenet.support.Logger.error(loggerClass,
+                * String.format(logRecord.getMessage(), logRecord.getParameters()),
+                * logRecord.getThrown()); } } });
+                */
        }
 
        /** The version. */
-       public static final Version VERSION = new Version("RC3", 0, 1);
+       public static final Version VERSION = new Version("RC1", 0, 2);
 
        /** The logger. */
        private static final Logger logger = Logging.getLogger(SonePlugin.class);
@@ -101,6 +104,9 @@ public class SonePlugin implements FredPlugin, FredPluginL10n, FredPluginBaseL10
        /** The plugin store. */
        private PluginStore pluginStore;
 
+       /** The identity manager. */
+       private IdentityManager identityManager;
+
        //
        // ACCESSORS
        //
@@ -163,21 +169,24 @@ public class SonePlugin implements FredPlugin, FredPluginL10n, FredPluginBaseL10
                /* create web of trust connector. */
                PluginConnector pluginConnector = new PluginConnector(pluginRespirator);
                WebOfTrustConnector webOfTrustConnector = new WebOfTrustConnector(pluginConnector);
+               identityManager = new IdentityManager(webOfTrustConnector);
+               identityManager.setContext("Sone");
 
                /* create the web interface. */
                webInterface = new WebInterface(this);
 
                /* create core. */
-               core = new Core();
-               core.configuration(configuration);
-               core.freenetInterface(freenetInterface);
-               core.setWebOfTrustConnector(webOfTrustConnector);
+               core = new Core(configuration, freenetInterface, identityManager);
+
+               /* create the identity manager. */
+               identityManager.addIdentityListener(core);
 
                /* start core! */
                boolean startupFailed = true;
                try {
                        core.start();
                        webInterface.start();
+                       identityManager.start();
                        startupFailed = false;
                } finally {
                        if (startupFailed) {
@@ -203,6 +212,9 @@ public class SonePlugin implements FredPlugin, FredPluginL10n, FredPluginBaseL10
                        /* stop the core. */
                        core.stop();
 
+                       /* stop the identity manager. */
+                       identityManager.stop();
+
                        /* TODO wait for core to stop? */
                        try {
                                pluginRespirator.putStore(pluginStore);
diff --git a/src/main/java/net/pterodactylus/sone/web/AddSonePage.java b/src/main/java/net/pterodactylus/sone/web/AddSonePage.java
deleted file mode 100644 (file)
index fe3ce1c..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Sone - AddSonePage.java - Copyright © 2010 David Roden
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.web;
-
-import net.pterodactylus.sone.web.page.Page.Request.Method;
-import net.pterodactylus.util.template.Template;
-
-/**
- * This page lets the user add a Sone by URI.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class AddSonePage extends SoneTemplatePage {
-
-       /**
-        * Creates a new “add Sone” page.
-        *
-        * @param template
-        *            The template to render
-        * @param webInterface
-        *            The Sone web interface
-        */
-       public AddSonePage(Template template, WebInterface webInterface) {
-               super("addSone.html", template, "Page.AddSone.Title", webInterface, false);
-       }
-
-       //
-       // TEMPLATEPAGE METHODS
-       //
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       protected void processTemplate(Request request, Template template) throws RedirectException {
-               super.processTemplate(request, template);
-               if (request.getMethod() == Method.POST) {
-                       String soneKey = request.getHttpRequest().getPartAsStringFailsafe("request-uri", 256);
-                       String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 64);
-                       webInterface.core().loadSone(soneKey);
-                       throw new RedirectException(returnPage);
-               }
-       }
-
-}
index dfd3760..d55624f 100644 (file)
 
 package net.pterodactylus.sone.web;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import net.pterodactylus.sone.core.SoneException;
-import net.pterodactylus.sone.core.SoneException.Type;
+import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.freenet.wot.OwnIdentity;
 import net.pterodactylus.sone.web.page.Page.Request.Method;
 import net.pterodactylus.util.logging.Logging;
 import net.pterodactylus.util.template.Template;
@@ -51,6 +56,36 @@ public class CreateSonePage extends SoneTemplatePage {
        }
 
        //
+       // STATIC ACCESSORS
+       //
+
+       /**
+        * Returns a sorted list of all own identities that do not have the “Sone”
+        * context.
+        *
+        * @param core
+        *            The core
+        * @return The list of own identities without the “Sone” context
+        */
+       public static List<OwnIdentity> getOwnIdentitiesWithoutSone(Core core) {
+               List<OwnIdentity> identitiesWithoutSone = new ArrayList<OwnIdentity>();
+               Set<OwnIdentity> allOwnIdentity = core.getIdentityManager().getAllOwnIdentities();
+               for (OwnIdentity ownIdentity : allOwnIdentity) {
+                       if (!ownIdentity.hasContext("Sone")) {
+                               identitiesWithoutSone.add(ownIdentity);
+                       }
+               }
+               Collections.sort(identitiesWithoutSone, new Comparator<OwnIdentity>() {
+
+                       @Override
+                       public int compare(OwnIdentity leftIdentity, OwnIdentity rightIdentity) {
+                               return (leftIdentity.getNickname() + "@" + leftIdentity.getId()).compareToIgnoreCase(rightIdentity.getNickname() + "@" + rightIdentity.getId());
+                       }
+               });
+               return identitiesWithoutSone;
+       }
+
+       //
        // TEMPLATEPAGE METHODS
        //
 
@@ -60,34 +95,33 @@ public class CreateSonePage extends SoneTemplatePage {
        @Override
        protected void processTemplate(Request request, Template template) throws RedirectException {
                super.processTemplate(request, template);
-               String name = "";
-               String requestUri = null;
-               String insertUri = null;
+               List<OwnIdentity> ownIdentitiesWithoutSone = getOwnIdentitiesWithoutSone(webInterface.core());
+               template.set("identitiesWithoutSone", ownIdentitiesWithoutSone);
                if (request.getMethod() == Method.POST) {
-                       name = request.getHttpRequest().getPartAsStringFailsafe("name", 100);
-                       if (request.getHttpRequest().isPartSet("create-from-uri")) {
-                               requestUri = request.getHttpRequest().getPartAsStringFailsafe("request-uri", 256);
-                               insertUri = request.getHttpRequest().getPartAsStringFailsafe("insert-uri", 256);
-                       }
-                       try {
-                               /* create Sone. */
-                               Sone sone = webInterface.core().createSone(name, "Sone", requestUri, insertUri);
-
-                               /* log in the new Sone. */
-                               setCurrentSone(request.getToadletContext(), sone);
-                               throw new RedirectException("index.html");
-                       } catch (SoneException se1) {
-                               logger.log(Level.FINE, "Could not create Sone “%s” at (“%s”, “%s”), %s!", new Object[] { name, requestUri, insertUri, se1.getType() });
-                               if (se1.getType() == Type.INVALID_SONE_NAME) {
-                                       template.set("errorName", true);
-                               } else if (se1.getType() == Type.INVALID_URI) {
-                                       template.set("errorUri", true);
+                       String id = request.getHttpRequest().getPartAsStringFailsafe("identity", 44);
+                       OwnIdentity selectedIdentity = null;
+                       for (OwnIdentity ownIdentity : ownIdentitiesWithoutSone) {
+                               if (ownIdentity.getId().equals(id)) {
+                                       selectedIdentity = ownIdentity;
+                                       break;
                                }
                        }
+                       if (selectedIdentity == null) {
+                               template.set("errorNoIdentity", true);
+                               return;
+                       }
+                       /* create Sone. */
+                       webInterface.core().getIdentityManager().addContext(selectedIdentity, "Sone");
+                       Sone sone = webInterface.core().createSone(selectedIdentity);
+                       if (sone == null) {
+                               logger.log(Level.SEVERE, "Could not create Sone for OwnIdentity: %s", selectedIdentity);
+                               /* TODO - go somewhere else */
+                       }
+
+                       /* log in the new Sone. */
+                       setCurrentSone(request.getToadletContext(), sone);
+                       throw new RedirectException("index.html");
                }
-               template.set("name", name);
-               template.set("requestUri", requestUri);
-               template.set("insertUri", insertUri);
        }
 
        /**
diff --git a/src/main/java/net/pterodactylus/sone/web/ImportSonePage.java b/src/main/java/net/pterodactylus/sone/web/ImportSonePage.java
deleted file mode 100644 (file)
index 413ed80..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Sone - ImportSonePage.java - Copyright © 2010 David Roden
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.web;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import net.pterodactylus.sone.data.Sone;
-import net.pterodactylus.sone.web.page.Page.Request.Method;
-import net.pterodactylus.util.logging.Logging;
-import net.pterodactylus.util.template.Template;
-import freenet.support.api.Bucket;
-import freenet.support.io.Closer;
-
-/**
- * The “import Sone” page lets the user import a Sone from a previous backup.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class ImportSonePage extends SoneTemplatePage {
-
-       /** The logger. */
-       private static final Logger logger = Logging.getLogger(ImportSonePage.class);
-
-       /**
-        * Creates a new “import Sone” page.
-        *
-        * @param template
-        *            The template to render
-        * @param webInterface
-        *            The Sone web interface
-        */
-       public ImportSonePage(Template template, WebInterface webInterface) {
-               super("importSone.html", template, "Page.ImportSone.Title", webInterface, false);
-       }
-
-       //
-       // TEMPLATEPAGE METHODS
-       //
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       protected void processTemplate(net.pterodactylus.sone.web.page.Page.Request request, Template template) throws RedirectException {
-               super.processTemplate(request, template);
-               template.set("errorParsingSone", false);
-               if (request.getMethod() == Method.POST) {
-                       Bucket soneBucket = request.getHttpRequest().getPart("sone-file");
-                       InputStream soneInputStream = null;
-                       try {
-                               soneInputStream = soneBucket.getInputStream();
-                               Sone sone = webInterface.core().loadSone(soneInputStream);
-                               if (sone != null) {
-                                       throw new RedirectException("viewSone.html?sone=" + sone.getId());
-                               }
-                       } catch (IOException ioe1) {
-                               logger.log(Level.INFO, "Could not load sone from posted XML file.", ioe1);
-                       } finally {
-                               Closer.close(soneInputStream);
-                               soneBucket.free();
-                       }
-                       template.set("errorParsingSone", true);
-               }
-       }
-
-}
index f4cc5b7..ff434b3 100644 (file)
@@ -55,7 +55,7 @@ public class KnownSonesPage extends SoneTemplatePage {
        @Override
        protected void processTemplate(Request request, Template template) throws RedirectException {
                super.processTemplate(request, template);
-               List<Sone> knownSones = new ArrayList<Sone>(webInterface.core().getKnownSones());
+               List<Sone> knownSones = new ArrayList<Sone>(webInterface.core().getSones());
                Collections.sort(knownSones, new Comparator<Sone>() {
 
                        @Override
diff --git a/src/main/java/net/pterodactylus/sone/web/LoadSonePage.java b/src/main/java/net/pterodactylus/sone/web/LoadSonePage.java
deleted file mode 100644 (file)
index 6802bb4..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Sone - LoadSonePage.java - Copyright © 2010 David Roden
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.web;
-
-import net.pterodactylus.sone.web.page.Page.Request.Method;
-import net.pterodactylus.util.template.Template;
-
-/**
- * This page lets the user a new Sone that has been previously managed on a
- * different node or installation. The data of the Sone is loaded from Freenet.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class LoadSonePage extends SoneTemplatePage {
-
-       /**
-        * Creates a new “load Sone” page.
-        *
-        * @param template
-        *            The template to render
-        * @param webInterface
-        *            The Sone web interface
-        */
-       public LoadSonePage(Template template, WebInterface webInterface) {
-               super("loadSone.html", template, "Page.LoadSone.Title", webInterface, false);
-       }
-
-       //
-       // TEMPLATEPAGE METHODS
-       //
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       protected void processTemplate(Request request, Template template) throws RedirectException {
-               super.processTemplate(request, template);
-               if (request.getMethod() == Method.POST) {
-                       String requestUri = request.getHttpRequest().getPartAsStringFailsafe("request-uri", 256);
-                       String insertUri = request.getHttpRequest().getPartAsStringFailsafe("insert-uri", 256);
-                       String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 64);
-                       webInterface.core().loadSone(requestUri, insertUri);
-                       throw new RedirectException(returnPage);
-               }
-       }
-
-}
index 486d8fb..4a3e2b1 100644 (file)
@@ -19,12 +19,13 @@ package net.pterodactylus.sone.web;
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.List;
+import java.util.logging.Logger;
 
 import net.pterodactylus.sone.data.Sone;
-import net.pterodactylus.sone.template.SoneAccessor;
+import net.pterodactylus.sone.freenet.wot.OwnIdentity;
 import net.pterodactylus.sone.web.page.Page.Request.Method;
+import net.pterodactylus.util.logging.Logging;
 import net.pterodactylus.util.template.Template;
 import freenet.clients.http.ToadletContext;
 
@@ -35,6 +36,10 @@ import freenet.clients.http.ToadletContext;
  */
 public class LoginPage extends SoneTemplatePage {
 
+       /** The logger. */
+       @SuppressWarnings("unused")
+       private static final Logger logger = Logging.getLogger(LoginPage.class);
+
        /**
         * Creates a new login page.
         *
@@ -57,19 +62,9 @@ public class LoginPage extends SoneTemplatePage {
        @Override
        protected void processTemplate(Request request, Template template) throws RedirectException {
                super.processTemplate(request, template);
-               List<Sone> localSones = new ArrayList<Sone>(webInterface.core().getSones());
-               Collections.sort(localSones, new Comparator<Sone>() {
-
-                       @Override
-                       public int compare(Sone leftSone, Sone rightSone) {
-                               int diff = SoneAccessor.getNiceName(leftSone).compareToIgnoreCase(SoneAccessor.getNiceName(rightSone));
-                               if (diff != 0) {
-                                       return diff;
-                               }
-                               return (int) Math.max(Integer.MIN_VALUE, Math.min(Integer.MAX_VALUE, rightSone.getTime() - leftSone.getTime()));
-                       }
-
-               });
+               /* get all own identities. */
+               List<Sone> localSones = new ArrayList<Sone>(webInterface.core().getLocalSones());
+               Collections.sort(localSones, Sone.NICE_NAME_COMPARATOR);
                template.set("sones", localSones);
                if (request.getMethod() == Method.POST) {
                        String soneId = request.getHttpRequest().getPartAsStringFailsafe("sone-id", 100);
@@ -85,6 +80,8 @@ public class LoginPage extends SoneTemplatePage {
                                throw new RedirectException("index.html");
                        }
                }
+               List<OwnIdentity> ownIdentitiesWithoutSone = CreateSonePage.getOwnIdentitiesWithoutSone(webInterface.core());
+               template.set("identitiesWithoutSone", ownIdentitiesWithoutSone);
        }
 
        //
index 0d3c9b7..c518a87 100644 (file)
@@ -33,8 +33,10 @@ import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.Reply;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.freenet.L10nFilter;
+import net.pterodactylus.sone.freenet.wot.Identity;
 import net.pterodactylus.sone.main.SonePlugin;
 import net.pterodactylus.sone.template.GetPagePlugin;
+import net.pterodactylus.sone.template.IdentityAccessor;
 import net.pterodactylus.sone.template.PostAccessor;
 import net.pterodactylus.sone.template.ReplyAccessor;
 import net.pterodactylus.sone.template.RequestChangeFilter;
@@ -178,6 +180,7 @@ public class WebInterface extends AbstractService {
                templateFactory.addAccessor(Sone.class, new SoneAccessor(core()));
                templateFactory.addAccessor(Post.class, new PostAccessor(core()));
                templateFactory.addAccessor(Reply.class, new ReplyAccessor(core()));
+               templateFactory.addAccessor(Identity.class, new IdentityAccessor(core()));
                templateFactory.addFilter("date", new DateFilter());
                templateFactory.addFilter("l10n", new L10nFilter(l10n()));
                templateFactory.addFilter("substring", new SubstringFilter());
@@ -191,11 +194,8 @@ public class WebInterface extends AbstractService {
 
                Template loginTemplate = templateFactory.createTemplate(createReader("/templates/login.html"));
                Template indexTemplate = templateFactory.createTemplate(createReader("/templates/index.html"));
-               Template addSoneTemplate = templateFactory.createTemplate(createReader("/templates/addSone.html"));
-               Template loadSoneTemplate = templateFactory.createTemplate(createReader("/templates/loadSone.html"));
                Template knownSonesTemplate = templateFactory.createTemplate(createReader("/templates/knownSones.html"));
                Template createSoneTemplate = templateFactory.createTemplate(createReader("/templates/createSone.html"));
-               Template importSoneTemplate = templateFactory.createTemplate(createReader("/templates/importSone.html"));
                Template createPostTemplate = templateFactory.createTemplate(createReader("/templates/createPost.html"));
                Template createReplyTemplate = templateFactory.createTemplate(createReader("/templates/createReply.html"));
                Template editProfileTemplate = templateFactory.createTemplate(createReader("/templates/editProfile.html"));
@@ -210,6 +210,7 @@ public class WebInterface extends AbstractService {
                Template unfollowSoneTemplate = templateFactory.createTemplate(createReader("/templates/unfollowSone.html"));
                Template deleteSoneTemplate = templateFactory.createTemplate(createReader("/templates/deleteSone.html"));
                Template noPermissionTemplate = templateFactory.createTemplate(createReader("/templates/noPermission.html"));
+               Template wotPluginMissingTemplate = templateFactory.createTemplate(createReader("/templates/wotPluginMissing.html"));
                Template logoutTemplate = templateFactory.createTemplate(createReader("/templates/logout.html"));
                Template optionsTemplate = templateFactory.createTemplate(createReader("/templates/options.html"));
                Template aboutTemplate = templateFactory.createTemplate(createReader("/templates/about.html"));
@@ -218,9 +219,6 @@ public class WebInterface extends AbstractService {
                PageToadletFactory pageToadletFactory = new PageToadletFactory(sonePlugin.pluginRespirator().getHLSimpleClient(), "/Sone/");
                pageToadlets.add(pageToadletFactory.createPageToadlet(new IndexPage(indexTemplate, this), "Index"));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new CreateSonePage(createSoneTemplate, this), "CreateSone"));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new ImportSonePage(importSoneTemplate, this), "ImportSone"));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new LoadSonePage(loadSoneTemplate, this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new AddSonePage(addSoneTemplate, this)));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new KnownSonesPage(knownSonesTemplate, this), "KnownSones"));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new EditProfilePage(editProfileTemplate, this), "EditProfile"));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new BackupProfilePage(backupProfileTemplate, this)));
@@ -241,6 +239,7 @@ public class WebInterface extends AbstractService {
                pageToadlets.add(pageToadletFactory.createPageToadlet(new AboutPage(aboutTemplate, this, SonePlugin.VERSION), "About"));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new SoneTemplatePage("help.html", helpTemplate, "Page.Help.Title", this), "Help"));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new SoneTemplatePage("noPermission.html", noPermissionTemplate, "Page.NoPermission.Title", this)));
+               pageToadlets.add(pageToadletFactory.createPageToadlet(new SoneTemplatePage("wotPluginMissing.html", wotPluginMissingTemplate, "Page.WotPluginMissing.Title", this)));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new StaticPage("css/", "/static/css/", "text/css")));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new StaticPage("javascript/", "/static/javascript/", "text/javascript")));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new StaticPage("images/", "/static/images/", "image/png")));
index 53e69e2..6bfe3e1 100644 (file)
@@ -6,8 +6,6 @@ Navigation.Menu.Item.Index.Name=Your Sone
 Navigation.Menu.Item.Index.Tooltip=Show your Sone
 Navigation.Menu.Item.CreateSone.Name=Create Sone
 Navigation.Menu.Item.CreateSone.Tooltip=Create a new Sone
-Navigation.Menu.Item.ImportSone.Name=Import Sone
-Navigation.Menu.Item.ImportSone.Tooltip=Import a Sone from a backup
 Navigation.Menu.Item.KnownSones.Name=Known Sones
 Navigation.Menu.Item.KnownSones.Tooltip=Shows all known Sones
 Navigation.Menu.Item.EditProfile.Name=Edit Profile
@@ -47,14 +45,7 @@ Page.Login.Page.Title=Login
 Page.Login.Label.SelectSone=Select Sone:
 Page.Login.Option.NoSone=Select Sone…
 
-Page.Login.LoadSone.Title=Load Sone
-Page.Login.LoadSone.Description=This function can be used to load an existing Sone. You need to specify both keys of the Sone to load it!
-Page.Login.LoadSone.Label.RequestURI=Sone Request URI:
-Page.Login.LoadSone.Label.InsertURI=Sone Insert URI:
-Page.Login.LoadSone.Button.LoadSone=Load Sone
-
 Page.Login.CreateSone.Title=Create Sone
-Page.Login.CreateSone.Description=Use this function if you want to create a completely new Sone, either at a location of your choosing or a new, random location. The name you specify can later be overridden in your profile, this name is just used when your Sone has not yet been completely downloaded by other people or when your profile is empty.
 Page.Login.CreateSone.Label.RequestURI=Sone Request URI:
 Page.Login.CreateSone.Label.InsertURI=Sone Insert URI:
 Page.Login.CreateSone.Label.Name=Sone name:
@@ -62,17 +53,11 @@ Page.Login.CreateSone.Label.DocumentName=Name in key:
 Page.Login.CreateSone.Button.CreateFromURI=Create at given URI
 Page.Login.CreateSone.Button.CreateRandom=Create new Sone
 
-Page.ImportSone.Title=Import Sone - Sone
-Page.ImportSone.Page.Title=Import Sone
-Page.ImportSone.Button.Import=Import
-
 Page.CreateSone.Title=Create Sone - Sone
 
 Page.DeleteSone.Title=Delete Sone - Sone
 Page.DeleteSone.Page.Title=Delete Sone “{zone}”?
-Page.DeleteSone.Page.Description=This will not delete the Sone from Freenet (because that is impossible), it will merely remove all references to this Sone from this plugin instance. You can write down the insert and request keys and reload your Sone from a different plugin instance or computer.
-Page.DeleteSone.Label.RequestURI=Request URI:
-Page.DeleteSone.Label.InsertURI=Insert URI:
+Page.DeleteSone.Page.Description=This will not delete the Sone from Freenet (because that is impossible), it will merely disconnect your web of trust identity from Sone.
 Page.DeleteSone.Button.Yes=Yes, delete.
 Page.DeleteSone.Button.No=No, do not delete.
 
@@ -86,13 +71,6 @@ Page.Index.AddSone.Title=Add Sone by Key
 Page.Index.AddSone.Description=Here you can enter the Freenet URI of another Sone that should be loaded. Once the Sone has been loaded, it will show up here.
 Page.Index.AddSone.Button.Add=Add Sone
 
-Page.AddSone.Title=Add Sone - Sone
-Page.AddSone.Page.Title=Add Sone
-Page.AddSone.Text.SoneLoading=The Sone at the given key is now being loaded. Please wait until it appears in your {link}Known Sones.{/link}
-Page.AddSone.Text.ReturnToIndex=Or {link}return to the index{/link}.
-Page.AddSone.Text.AddAnotherSone=Or you can add another Sone by entering it’s Freenet URI below:
-Page.AddSone.Button.AddSone=Add Sone
-
 Page.KnownSones.Title=Known Sones - Sone
 Page.KnownSones.Page.Title=Known Sones
 Page.KnownSones.Text.NoKnownSones=There are currently no known Sones.
@@ -169,12 +147,23 @@ Page.NoPermission.Title=Unauthorized Access - Sone
 Page.NoPermission.Page.Title=Unauthorized Access
 Page.NoPermission.Text.NoPermission=You tried to do something that you do not have sufficient authorization for. Please refrain from such actions in the future or we will be forced to take counter-measures!
 
+Page.WotPluginMissing.Title=Web of Trust Plugin Missing - Sone
+Page.WotPluginMissing.Page.Title=Web of Trust Plugin Missing
+Page.WotPluginMissing.Text.WotRequired=Because the Web of Trust is an integral part of Sone, the Web of Trust plugin has to be loaded in order to run Sone.
+Page.WotPluginMissing.Text.LoadPlugin=Please load the Web of Trust plugin in the {link}plugin manager{/link}.
+
 Page.Logout.Title=Logout - Sone
 
 View.Head.ProfileLink.Text=Your Profile
 
 Page.Tail.Text.KeyOfSone=Key of this Sone (give this to other people):
 
+View.CreateSone.Text.WotIdentityRequired=To create a Sone you need an identity from the {link}Web of Trust plugin{/link}.
+View.CreateSone.Select.Default=Select an identity
+View.CreateSone.Text.NoIdentities=You do not have any Web of Trust identities. Please head over to the {link}Web of Trust plugin{/link} and create an identity.
+View.CreateSone.Button.Create=Create Sone
+View.CreateSone.Text.Error.NoIdentity=You have not selected an identity.
+
 View.Sone.Label.LastUpdate=Last update:
 View.Sone.Button.UnfollowSone=unfollow
 View.Sone.Button.FollowSone=follow
@@ -211,3 +200,5 @@ WebInterface.Confirmation.DeleteReplyButton=Yes, delete!
 WebInterface.SelectBox.Choose=Choose…
 WebInterface.SelectBox.Yes=Yes
 WebInterface.SelectBox.No=No
+
+Warning.PluginNotConnected.Text=The Web of Trust plugin could not be found! This can happen if the Web of Trust plugin is not loaded or if your node is currently still starting up. In the first case, load the Web of Trust plugin using the {link}plugin manager{/link}, in the second case wait until the Web of Trust plugin has been loaded, because in either case Sone will not be working unless the Web of Trust plugin is loaded!
index 0feb601..afb97c0 100644 (file)
@@ -49,6 +49,12 @@ textarea {
        color: rgb(255, 172, 0);
 }
 
+#sone #plugin-warning {
+       border: solid 0.5em red;
+       padding: 0.5em;
+       margin-bottom: 1em;
+}
+
 #sone #profile {
        height: 80px;
        margin-bottom: 1ex;
diff --git a/src/main/resources/templates/addSone.html b/src/main/resources/templates/addSone.html
deleted file mode 100644 (file)
index a9766a8..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-<%include include/head.html>
-
-       <h1><%= Page.AddSone.Page.Title|l10n|html></h1>
-
-       <p><%= Page.AddSone.Text.SoneLoading|l10n|html|replace needle="{link}" replacement='<a href="knownSones.html">'|replace needle="{/link}" replacement='</a>'></p>
-
-       <p><%= Page.AddSone.Text.ReturnToIndex|l10n|html|replace needle="{link}" replacement='<a href="index.html">'|replace needle="{/link}" replacement='</a>'></p>
-
-       <p><%= Page.AddSone.Text.AddAnotherSone|l10n|html></p>
-
-       <form method="post">
-               <input type="hidden" name="formPassword" value="<% formPassword|html>" />
-               <input type="text" name="request-uri" />
-               <button type="submit"><%= Page.AddSone.Button.AddSone|l10n|html></button>
-       </form>
-
-<%include include/tail.html>
index 5f3340d..5591e7c 100644 (file)
@@ -2,7 +2,6 @@
 <sone>
 
        <id><% currentSone.id|xml></id>
-       <name><% currentSone.name|xml></name>
        <time><% currentSone.time|xml></time>
        <request-uri><% currentSone.requestUri|xml></request-uri>
        <insert-uri><% currentSone.insertUri|xml></insert-uri>
index ae1acaa..bd00cbf 100644 (file)
@@ -1,8 +1,5 @@
 <%include include/head.html>
 
-       <h1><%= Page.Login.CreateSone.Title|l10n|html></h1>
-       <p><%= Page.Login.CreateSone.Description|l10n|html></p>
-
        <%include include/createSone.html>
 
 <%include include/tail.html>
index ccb14f0..847e61d 100644 (file)
@@ -7,14 +7,6 @@
        <form method="post">
                <input type="hidden" name="formPassword" value="<% formPassword|html>" />
                <div>
-                       <label><%= Page.DeleteSone.Label.RequestURI|l10n|html></label>
-                       <input type="text" readonly="readonly" value="<% currentSone.requestUri|html>" />
-               </div>
-               <div>
-                       <label><%= Page.DeleteSone.Label.InsertURI|l10n|html></label>
-                       <input type="text" readonly="readonly" value="<% currentSone.insertUri|html>" />
-               </div>
-               <div>
                        <button type="submit" name="deleteSone" value="1"><%= Page.DeleteSone.Button.Yes|l10n|html></button>
                </div>
                <div>
diff --git a/src/main/resources/templates/importSone.html b/src/main/resources/templates/importSone.html
deleted file mode 100644 (file)
index 78ca8e1..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-<%include include/head.html>
-
-       <%include include/importSone.html>
-
-<%include include/tail.html>
index cb7e8b5..ebd204e 100644 (file)
@@ -1,52 +1,26 @@
-<script language="javascript">
-       $(document).ready(function() {
-               registerInputTextareaSwap("#sone #create-sone input[name=name]", "WebInterface.DefaultText.CreateSoneName", "name", false, true);
-               registerInputTextareaSwap("#sone #create-sone input[name=insert-uri]", "WebInterface.DefaultText.CreateSoneInsertURI", "insert-uri", true, true);
-               registerInputTextareaSwap("#sone #create-sone input[name=request-uri]", "WebInterface.DefaultText.CreateSoneRequestURI", "request-uri", true, true);
+<%if !identitiesWithoutSone.empty>
+       <h1><%= Page.Login.CreateSone.Title|l10n|html></h1>
 
-               /* hide all the labels. */
-               $("#sone #create-sone label").hide();
+       <p><%= View.CreateSone.Text.WotIdentityRequired|l10n|html|replace needle="{link}" replacement='<a href="/WoT/">'|replace needle="{/link}" replacement='</a>'></p>
 
-               /* now hide the “advanced” section. */
-               advancedSection = $("#sone #create-sone .advanced");
-               advancedSection.hide();
+       <form id="create-sone" action="createSone.html" method="post">
+               <input type="hidden" name="formPassword" value="<% formPassword|html>" />
 
-               /* show a checkbox instead. */
-               checkboxSection = $("<div></div>");
-               checkbox = $("<input type=\"checkbox\" />").click(function() {
-                       if (this.checked) {
-                               advancedSection.show();
-                               $("#sone #create-sone button[name=create-random]").hide();
-                       } else {
-                               advancedSection.hide();
-                               $("#sone #create-sone button[name=create-random]").show();
-                       }
-               })
-               checkboxSection.append(checkbox).append("Show advanced settings");
-               advancedSection.before(checkboxSection);
-       });
-</script>
+               <%if errorNoIdentity><p><%= View.CreateSone.Text.Error.NoIdentity|l10n|html></p><%/if>
 
-<form id="create-sone" action="createSone.html" method="post">
-       <input type="hidden" name="formPassword" value="<% formPassword|html>" />
-       <div<%if errorName> class="error"<%/if>>
-               <label for="name"><%= Page.Login.CreateSone.Label.Name|l10n|html></label>
-               <input type="text" name="name" value="<% name|html>"/>
-       </div>
-       <div>
-               <button type="submit" name="create-random" value="1"><%= Page.Login.CreateSone.Button.CreateRandom|l10n|html></button>
-       </div>
-       <div class="advanced">
-               <div<%if errorUri> class="error"<%/if>>
-                       <label for="insert-uri"><%= Page.Login.CreateSone.Label.InsertURI|l10n|html></label>
-                       <input type="text" name="insert-uri" value="<% insertUri|html>" />
-               </div>
-               <div<%if errorUri> class="error"<%/if>>
-                       <label for="request-uri"><%= Page.Login.CreateSone.Label.RequestURI|l10n|html></label>
-                       <input type="text" name="request-uri" value="<% requestUri|html>" />
-               </div>
-               <div>
-                       <button type="submit" name="create-from-uri" value="1"><%= Page.Login.CreateSone.Button.CreateFromURI|l10n|html></button>
-               </div>
-       </div>
-</form>
+               <%foreach identitiesWithoutSone identity>
+                       <%first>
+                               <select name="identity">
+                                       <option disabled="disabled"><%= View.CreateSone.Select.Default|l10n|html></option>
+                       <%/first>
+                               <option value="<% identity.id|html>"><% identity.uniqueNickname|html></option>
+                       <%last>
+                               </select>
+                               <button type="submit"><%= View.CreateSone.Button.Create|l10n|html></button>
+                       <%/last>
+               <%/foreach>
+
+       </form>
+<%else>
+       <p><%= View.CreateSone.Text.NoIdentities|l10n|html|replace needle="{link}" replacement='<a href="/WoT/OwnIdentities">'|replace needle="{/link}" replacement="</a>"></p>
+<%/if>
index 3a2ab57..5f0d567 100644 (file)
 
        <div id="main">
 
+               <%if !webInterface.core.identityManager.connected>
+                       <div id="plugin-warning">
+                               <%= Warning.PluginNotConnected.Text|l10n|html|replace needle="{link}" replacement="<a href=\"/plugins/\">"|replace needle="{/link}" replacement="</a>">
+                       </div>
+               <%/if>
+
                <div id="profile" class="<%ifnull currentSone>offline<%else>online<%/if>">
                        <a class="picture" href="index.html">&nbsp;</a>
                        <%ifnull ! currentSone>
diff --git a/src/main/resources/templates/include/importSone.html b/src/main/resources/templates/include/importSone.html
deleted file mode 100644 (file)
index 910afc3..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<h1><%= Page.ImportSone.Page.Title|l10n|html></h1>
-
-<form id="import-sone" action="importSone.html" method="post" enctype="mulitpart/form-data">
-       <input type="hidden" name="formPassword" value="<% formPassword|html>" />
-
-       <input type="file" name="sone-file" />
-
-       <button type="submit"><%= Page.ImportSone.Button.Import|l10n|html></button>
-
-</form>
diff --git a/src/main/resources/templates/include/loadSone.html b/src/main/resources/templates/include/loadSone.html
deleted file mode 100644 (file)
index 3288c63..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-<script language="javascript">
-       $(document).ready(function() {
-               registerInputTextareaSwap("#sone #load-sone input[name=request-uri]", "WebInterface.DefaultText.LoadSoneRequestURI", "request-uri", false, true);
-               registerInputTextareaSwap("#sone #load-sone input[name=insert-uri]", "WebInterface.DefaultText.LoadSoneInsertURI", "insert-uri", false, true);
-
-               /* hide all the labels. */
-               $("#sone #load-sone label").hide();
-       });
-</script>
-
-<form id="load-sone" action="loadSone.html" method="post">
-       <input type="hidden" name="formPassword" value="<% formPassword|html>" />
-       <input type="hidden" name="returnPage" value="<% request.uri|html>" />
-       <div>
-               <label for="request-uri"><%= Page.Login.LoadSone.Label.RequestURI|l10n|html></label>
-               <input type="text" name="request-uri" />
-       </div>
-       <div>
-               <label for="insert-uri"><%= Page.Login.LoadSone.Label.InsertURI|l10n|html></label>
-               <input type="text" name="insert-uri" />
-       </div>
-       <div>
-               <button type="submit"><%= Page.Login.LoadSone.Button.LoadSone|l10n|html></button>
-       </div>
-</form>
index c80dbe7..b947dc4 100644 (file)
@@ -1,8 +1,6 @@
 <?xml version="1.0" encoding="utf-8" ?>
 <sone>
 
-       <id><% currentSone.id></id>
-       <name><% currentSone.name|xml></name>
        <time><% currentSone.time></time>
 
        <profile>
index d7be4df..456152a 100644 (file)
@@ -6,17 +6,6 @@
                })
        </script>
 
-       <h1><%= Page.Index.AddSone.Title|l10n|html></h1>
-
-       <p><%= Page.Index.AddSone.Description|l10n|html></p>
-
-       <form id="add-sone" action="addSone.html" method="post">
-               <input type="hidden" name="formPassword" value="<% formPassword|html>" />
-               <input type="hidden" name="returnPage" value="<% request.uri|html>" />
-               <input type="text" name="request-uri" />
-               <button type="submit"><%= Page.Index.AddSone.Button.Add|l10n|html></button>
-       </form>
-
        <h1><%= Page.KnownSones.Page.Title|l10n|html></h1>
 
        <div id="known-sones">
diff --git a/src/main/resources/templates/loadSone.html b/src/main/resources/templates/loadSone.html
deleted file mode 100644 (file)
index 196af72..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-<%include include/head.html>
-
-<%include include/tail.html>
index ebe8ad1..9e41a8d 100644 (file)
                </form>
        <%/if>
 
-       <h1><%= Page.Login.LoadSone.Title|l10n|html></h1>
-       <p><%= Page.Login.LoadSone.Description|l10n|html></p>
-       <%include include/loadSone.html>
-
-       <%include include/importSone.html>
-
-       <h1><%= Page.Login.CreateSone.Title|l10n|html></h1>
-       <p><%= Page.Login.CreateSone.Description|l10n|html></p>
        <%include include/createSone.html>
 
 <%include include/tail.html>
diff --git a/src/main/resources/templates/wotPluginMissing.html b/src/main/resources/templates/wotPluginMissing.html
new file mode 100644 (file)
index 0000000..4db624e
--- /dev/null
@@ -0,0 +1,9 @@
+<%include include/head.html>
+
+       <h1><%= Page.WotPluginMissing.Page.Title|l10n|html></h1>
+
+       <p><%= Page.WotPluginMissing.Text.WotRequired|l10n|html></p>
+
+       <p><%= Page.WotPluginMissing.Text.LoadPlugin|l10n|html|replace needle="{link}" replacement='<a href="/plugins/">'|replace needle="{/link}" replacement='</a>'></p>
+
+<%include include/tail.html>