Merge branch 'next' into new-database-38
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Thu, 13 Sep 2012 12:34:58 +0000 (14:34 +0200)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Thu, 13 Sep 2012 14:19:41 +0000 (16:19 +0200)
Conflicts:
src/main/java/net/pterodactylus/sone/core/Core.java
src/main/java/net/pterodactylus/sone/data/Sone.java
src/main/java/net/pterodactylus/sone/main/SonePlugin.java
src/main/java/net/pterodactylus/sone/text/SoneTextParser.java

1  2 
src/main/java/net/pterodactylus/sone/core/Core.java
src/main/java/net/pterodactylus/sone/database/memory/MemorySone.java
src/main/java/net/pterodactylus/sone/main/SonePlugin.java
src/main/java/net/pterodactylus/sone/template/ProfileAccessor.java
src/main/java/net/pterodactylus/sone/template/SoneAccessor.java
src/main/java/net/pterodactylus/sone/text/SoneTextParser.java
src/main/java/net/pterodactylus/sone/web/SearchPage.java
src/main/java/net/pterodactylus/sone/web/WebInterface.java
src/main/java/net/pterodactylus/sone/web/ajax/EditImageAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/GetLikesAjaxPage.java

@@@ -47,7 -46,7 +47,8 @@@ import net.pterodactylus.sone.data.Sone
  import net.pterodactylus.sone.data.Sone.ShowCustomAvatars;
  import net.pterodactylus.sone.data.Sone.SoneStatus;
  import net.pterodactylus.sone.data.TemporaryImage;
+ import net.pterodactylus.sone.data.impl.PostImpl;
 +import net.pterodactylus.sone.database.Database;
  import net.pterodactylus.sone.fcp.FcpInterface;
  import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired;
  import net.pterodactylus.sone.freenet.wot.Identity;
@@@ -182,11 -185,12 +184,13 @@@ public class Core extends AbstractServi
         *            The freenet interface
         * @param identityManager
         *            The identity manager
+        * @param webOfTrustUpdater
+        *            The WebOfTrust updater
         */
-       public Core(Configuration configuration, Database database, FreenetInterface freenetInterface, IdentityManager identityManager) {
 -      public Core(Configuration configuration, FreenetInterface freenetInterface, IdentityManager identityManager, WebOfTrustUpdater webOfTrustUpdater) {
++      public Core(Configuration configuration, Database database, FreenetInterface freenetInterface, IdentityManager identityManager, WebOfTrustUpdater webOfTrustUpdater) {
                super("Sone Core");
                this.configuration = configuration;
 +              this.database = database;
                this.freenetInterface = freenetInterface;
                this.identityManager = identityManager;
                this.soneDownloader = new SoneDownloader(this, freenetInterface);
        }
  
        /**
-        * Retrieves the trust relationship from the origin to the target. If the
-        * trust relationship can not be retrieved, {@code null} is returned.
-        *
-        * @see Identity#getTrust(OwnIdentity)
-        * @param origin
-        *            The origin of the trust tree
-        * @param target
-        *            The target of the trust
-        * @return The trust relationship
-        */
-       public Trust getTrust(Sone origin, Sone target) {
-               if (!origin.isLocal()) {
-                       logger.log(Level.WARNING, String.format("Tried to get trust from remote Sone: %s", origin));
-                       return null;
-               }
-               return target.getIdentity().getTrust((OwnIdentity) origin.getIdentity());
-       }
-       /**
--       * Sets the trust value of the given origin Sone for the target Sone.
++       * Sets the trust value of the given origin Sone for
++       * the target Sone.
         *
         * @param origin
         *            The origin Sone
                        logger.log(Level.WARNING, String.format("Tried to delete Sone of non-own identity: %s", sone));
                        return;
                }
 -              synchronized (localSones) {
 -                      if (!localSones.containsKey(sone.getId())) {
 -                              logger.log(Level.WARNING, String.format("Tried to delete non-local Sone: %s", sone));
 -                              return;
 -                      }
 -                      localSones.remove(sone.getId());
 -                      SoneInserter soneInserter = soneInserters.remove(sone);
 -                      soneInserter.removeSoneInsertListener(this);
 -                      soneInserter.stop();
 +              if (!sone.isLocal()) {
 +                      logger.log(Level.WARNING, String.format("Tried to delete non-local Sone: %s", sone));
 +                      return;
                }
-               try {
-                       ((OwnIdentity) sone.getIdentity()).removeContext("Sone");
-                       ((OwnIdentity) sone.getIdentity()).removeProperty("Sone.LatestEdition");
-               } catch (WebOfTrustException wote1) {
-                       logger.log(Level.WARNING, String.format("Could not remove context and properties from Sone: %s", sone), wote1);
-               }
 +              database.removeSone(sone.getId());
 +              SoneInserter soneInserter = soneInserters.remove(sone);
 +              soneInserter.removeSoneInsertListener(this);
 +              soneInserter.stop();
+               webOfTrustUpdater.removeContext((OwnIdentity) sone.getIdentity(), "Sone");
+               webOfTrustUpdater.removeProperty((OwnIdentity) sone.getIdentity(), "Sone.LatestEdition");
                try {
                        configuration.getLongValue("Sone/" + sone.getId() + "/Time").setValue(null);
                } catch (ConfigurationException ce1) {
         */
        @Override
        public void serviceStop() {
-               for (SoneInserter soneInserter : soneInserters.values()) {
-                       soneInserter.removeSoneInsertListener(this);
-                       soneInserter.stop();
 -              synchronized (localSones) {
 -                      for (Entry<Sone, SoneInserter> soneInserter : soneInserters.entrySet()) {
 -                              soneInserter.getValue().removeSoneInsertListener(this);
 -                              soneInserter.getValue().stop();
 -                              saveSone(soneInserter.getKey());
 -                      }
++              for (Entry<Sone, SoneInserter> soneInserter : soneInserters.entrySet()) {
++                      soneInserter.getValue().removeSoneInsertListener(this);
++                      soneInserter.getValue().stop();
++                      saveSone(soneInserter.getKey());
                }
+               saveConfiguration();
+               webOfTrustUpdater.stop();
                updateChecker.stop();
                updateChecker.removeUpdateListener(this);
                soneDownloader.stop();
index 1c5912f,0000000..da487b3
mode 100644,000000..100644
--- /dev/null
@@@ -1,735 -1,0 +1,737 @@@
-               albums.add(album);
 +/*
 + * Sone - MemorySone.java - Copyright © 2010–2012 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.database.memory;
 +
 +import java.util.ArrayList;
 +import java.util.Collection;
 +import java.util.Collections;
 +import java.util.List;
 +import java.util.Set;
 +import java.util.concurrent.CopyOnWriteArrayList;
 +import java.util.concurrent.CopyOnWriteArraySet;
 +import java.util.logging.Level;
 +import java.util.logging.Logger;
 +
 +import net.pterodactylus.sone.core.Options;
 +import net.pterodactylus.sone.data.Album;
 +import net.pterodactylus.sone.data.Client;
 +import net.pterodactylus.sone.data.Image;
 +import net.pterodactylus.sone.data.Post;
 +import net.pterodactylus.sone.data.PostReply;
 +import net.pterodactylus.sone.data.Profile;
 +import net.pterodactylus.sone.data.Reply;
 +import net.pterodactylus.sone.data.Sone;
 +import net.pterodactylus.sone.database.Database;
 +import net.pterodactylus.sone.freenet.wot.Identity;
 +import net.pterodactylus.util.logging.Logging;
 +import net.pterodactylus.util.validation.Validation;
 +import freenet.keys.FreenetURI;
 +
 +/**
 + * Implementation of a {@link Sone} that keeps all added data in memory. A
 + * self-created instance of this class should be converted to a {@link Database}
 + * -based instance of {@link Sone} as soon as possible (unless it was returned
 + * by a {@link MemoryDatabase}).
 + *
 + * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
 + */
 +public class MemorySone implements Sone {
 +
 +      /** The logger. */
 +      private static final Logger logger = Logging.getLogger(Sone.class);
 +
 +      /** The ID of this Sone. */
 +      private final String id;
 +
 +      /** Whether this is a local Sone. */
 +      private final boolean local;
 +
 +      /** The identity of this Sone. */
 +      private Identity identity;
 +
 +      /** The URI under which the Sone is stored in Freenet. */
 +      private volatile FreenetURI requestUri;
 +
 +      /** The URI used to insert a new version of this Sone. */
 +      /* This will be null for remote Sones! */
 +      private volatile FreenetURI insertUri;
 +
 +      /** The latest edition of the Sone. */
 +      private volatile long latestEdition;
 +
 +      /** The time of the last inserted update. */
 +      private volatile long time;
 +
 +      /** The status of this Sone. */
 +      private volatile SoneStatus status = SoneStatus.unknown;
 +
 +      /** The profile of this Sone. */
 +      private volatile Profile profile = new Profile(this);
 +
 +      /** The client used by the Sone. */
 +      private volatile Client client;
 +
 +      /** Whether this Sone is known. */
 +      private volatile boolean known;
 +
 +      /** All friend Sones. */
 +      private final Set<String> friendSones = new CopyOnWriteArraySet<String>();
 +
 +      /** All posts. */
 +      private final Set<Post> posts = new CopyOnWriteArraySet<Post>();
 +
 +      /** All replies. */
 +      private final Set<PostReply> replies = new CopyOnWriteArraySet<PostReply>();
 +
 +      /** The IDs of all liked posts. */
 +      private final Set<String> likedPostIds = new CopyOnWriteArraySet<String>();
 +
 +      /** The IDs of all liked replies. */
 +      private final Set<String> likedReplyIds = new CopyOnWriteArraySet<String>();
 +
 +      /** The albums of this Sone. */
 +      private final List<Album> albums = new CopyOnWriteArrayList<Album>();
 +
 +      /** Sone-specific options. */
 +      private final Options options = new Options();
 +
 +      /**
 +       * Creates a new Sone.
 +       *
 +       * @param id
 +       *            The ID of the Sone
 +       * @param local
 +       *            {@code true} if this Sone is local, {@code false} otherwise
 +       */
 +      public MemorySone(String id, boolean local) {
 +              this.id = id;
 +              this.local = local;
 +      }
 +
 +      //
 +      // ACCESSORS
 +      //
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public String getId() {
 +              return id;
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public Identity getIdentity() {
 +              return identity;
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public Sone setIdentity(Identity identity) throws IllegalArgumentException {
 +              if (!identity.getId().equals(id)) {
 +                      throw new IllegalArgumentException("Identity’s ID does not match Sone’s ID!");
 +              }
 +              this.identity = identity;
 +              return this;
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public boolean isLocal() {
 +              return local;
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public String getName() {
 +              return (identity != null) ? identity.getNickname() : null;
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public FreenetURI getRequestUri() {
 +              return (requestUri != null) ? requestUri.setSuggestedEdition(latestEdition) : null;
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public Sone setRequestUri(FreenetURI requestUri) {
 +              if (this.requestUri == null) {
 +                      this.requestUri = requestUri.setKeyType("USK").setDocName("Sone").setMetaString(new String[0]);
 +                      return this;
 +              }
 +              if (!this.requestUri.equalsKeypair(requestUri)) {
 +                      logger.log(Level.WARNING, String.format("Request URI %s tried to overwrite %s!", requestUri, this.requestUri));
 +                      return this;
 +              }
 +              return this;
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public FreenetURI getInsertUri() {
 +              return (insertUri != null) ? insertUri.setSuggestedEdition(latestEdition) : null;
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public Sone setInsertUri(FreenetURI insertUri) {
 +              if (this.insertUri == null) {
 +                      this.insertUri = insertUri.setKeyType("USK").setDocName("Sone").setMetaString(new String[0]);
 +                      return this;
 +              }
 +              if (!this.insertUri.equalsKeypair(insertUri)) {
 +                      logger.log(Level.WARNING, String.format("Request URI %s tried to overwrite %s!", insertUri, this.insertUri));
 +                      return this;
 +              }
 +              return this;
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public long getLatestEdition() {
 +              return latestEdition;
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public void setLatestEdition(long latestEdition) {
 +              if (!(latestEdition > this.latestEdition)) {
 +                      logger.log(Level.FINE, String.format("New latest edition %d is not greater than current latest edition %d!", latestEdition, this.latestEdition));
 +                      return;
 +              }
 +              this.latestEdition = latestEdition;
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public long getTime() {
 +              return time;
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public Sone setTime(long time) {
 +              this.time = time;
 +              return this;
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public SoneStatus getStatus() {
 +              return status;
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public Sone setStatus(SoneStatus status) {
 +              Validation.begin().isNotNull("Sone Status", status).check();
 +              this.status = status;
 +              return this;
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public Profile getProfile() {
 +              return new Profile(profile);
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public void setProfile(Profile profile) {
 +              this.profile = new Profile(profile);
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public Client getClient() {
 +              return client;
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public Sone setClient(Client client) {
 +              this.client = client;
 +              return this;
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public boolean isKnown() {
 +              return known;
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public Sone setKnown(boolean known) {
 +              this.known = known;
 +              return this;
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public List<String> getFriends() {
 +              List<String> friends = new ArrayList<String>(friendSones);
 +              return friends;
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public boolean hasFriend(String friendSoneId) {
 +              return friendSones.contains(friendSoneId);
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public Sone addFriend(String friendSone) {
 +              if (!friendSone.equals(id)) {
 +                      friendSones.add(friendSone);
 +              }
 +              return this;
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public Sone removeFriend(String friendSoneId) {
 +              friendSones.remove(friendSoneId);
 +              return this;
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public List<Post> getPosts() {
 +              List<Post> sortedPosts;
 +              synchronized (this) {
 +                      sortedPosts = new ArrayList<Post>(posts);
 +              }
 +              Collections.sort(sortedPosts, Post.TIME_COMPARATOR);
 +              return sortedPosts;
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public Sone setPosts(Collection<Post> posts) {
 +              synchronized (this) {
 +                      this.posts.clear();
 +                      this.posts.addAll(posts);
 +              }
 +              return this;
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public void addPost(Post post) {
 +              if (post.getSone().equals(this) && posts.add(post)) {
 +                      logger.log(Level.FINEST, String.format("Adding %s to “%s”.", post, getName()));
 +              }
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public void removePost(Post post) {
 +              if (post.getSone().equals(this)) {
 +                      posts.remove(post);
 +              }
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public Set<PostReply> getReplies() {
 +              return Collections.unmodifiableSet(replies);
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public Sone setReplies(Collection<PostReply> replies) {
 +              this.replies.clear();
 +              this.replies.addAll(replies);
 +              return this;
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public void addReply(PostReply reply) {
 +              if (reply.getSone().equals(this)) {
 +                      replies.add(reply);
 +              }
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public void removeReply(PostReply reply) {
 +              if (reply.getSone().equals(this)) {
 +                      replies.remove(reply);
 +              }
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public Set<String> getLikedPostIds() {
 +              return Collections.unmodifiableSet(likedPostIds);
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public Sone setLikePostIds(Set<String> likedPostIds) {
 +              this.likedPostIds.clear();
 +              this.likedPostIds.addAll(likedPostIds);
 +              return this;
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public boolean isLikedPostId(String postId) {
 +              return likedPostIds.contains(postId);
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public Sone addLikedPostId(String postId) {
 +              likedPostIds.add(postId);
 +              return this;
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public Sone removeLikedPostId(String postId) {
 +              likedPostIds.remove(postId);
 +              return this;
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public Set<String> getLikedReplyIds() {
 +              return Collections.unmodifiableSet(likedReplyIds);
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public Sone setLikeReplyIds(Set<String> likedReplyIds) {
 +              this.likedReplyIds.clear();
 +              this.likedReplyIds.addAll(likedReplyIds);
 +              return this;
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public boolean isLikedReplyId(String replyId) {
 +              return likedReplyIds.contains(replyId);
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public Sone addLikedReplyId(String replyId) {
 +              likedReplyIds.add(replyId);
 +              return this;
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public Sone removeLikedReplyId(String replyId) {
 +              likedReplyIds.remove(replyId);
 +              return this;
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public List<Album> getAlbums() {
 +              return Collections.unmodifiableList(albums);
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public List<Album> getAllAlbums() {
 +              List<Album> flatAlbums = new ArrayList<Album>();
 +              flatAlbums.addAll(albums);
 +              int lastAlbumIndex = 0;
 +              while (lastAlbumIndex < flatAlbums.size()) {
 +                      int previousAlbumCount = flatAlbums.size();
 +                      for (Album album : new ArrayList<Album>(flatAlbums.subList(lastAlbumIndex, flatAlbums.size()))) {
 +                              flatAlbums.addAll(album.getAlbums());
 +                      }
 +                      lastAlbumIndex = previousAlbumCount;
 +              }
 +              return flatAlbums;
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public List<Image> getAllImages() {
 +              List<Image> allImages = new ArrayList<Image>();
 +              for (Album album : getAllAlbums()) {
 +                      allImages.addAll(album.getImages());
 +              }
 +              return allImages;
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public void addAlbum(Album album) {
 +              Validation.begin().isNotNull("Album", album).check().isEqual("Album Owner", album.getSone(), this).check();
++              if (!albums.contains(album)) {
++                      albums.add(album);
++              }
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public void setAlbums(Collection<? extends Album> albums) {
 +              Validation.begin().isNotNull("Albums", albums).check();
 +              this.albums.clear();
 +              for (Album album : albums) {
 +                      addAlbum(album);
 +              }
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public void removeAlbum(Album album) {
 +              Validation.begin().isNotNull("Album", album).check().isEqual("Album Owner", album.getSone(), this).check();
 +              albums.remove(album);
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public Album moveAlbumUp(Album album) {
 +              Validation.begin().isNotNull("Album", album).check().isEqual("Album Owner", album.getSone(), this).isNull("Album Parent", album.getParent()).check();
 +              int oldIndex = albums.indexOf(album);
 +              if (oldIndex <= 0) {
 +                      return null;
 +              }
 +              albums.remove(oldIndex);
 +              albums.add(oldIndex - 1, album);
 +              return albums.get(oldIndex);
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public Album moveAlbumDown(Album album) {
 +              Validation.begin().isNotNull("Album", album).check().isEqual("Album Owner", album.getSone(), this).isNull("Album Parent", album.getParent()).check();
 +              int oldIndex = albums.indexOf(album);
 +              if ((oldIndex < 0) || (oldIndex >= (albums.size() - 1))) {
 +                      return null;
 +              }
 +              albums.remove(oldIndex);
 +              albums.add(oldIndex + 1, album);
 +              return albums.get(oldIndex);
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public Options getOptions() {
 +              return options;
 +      }
 +
 +      //
 +      // FINGERPRINTABLE METHODS
 +      //
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public synchronized String getFingerprint() {
 +              StringBuilder fingerprint = new StringBuilder();
 +              fingerprint.append(profile.getFingerprint());
 +
 +              fingerprint.append("Posts(");
 +              for (Post post : getPosts()) {
 +                      fingerprint.append("Post(").append(post.getId()).append(')');
 +              }
 +              fingerprint.append(")");
 +
 +              List<PostReply> replies = new ArrayList<PostReply>(getReplies());
 +              Collections.sort(replies, Reply.TIME_COMPARATOR);
 +              fingerprint.append("Replies(");
 +              for (PostReply reply : replies) {
 +                      fingerprint.append("Reply(").append(reply.getId()).append(')');
 +              }
 +              fingerprint.append(')');
 +
 +              List<String> likedPostIds = new ArrayList<String>(getLikedPostIds());
 +              Collections.sort(likedPostIds);
 +              fingerprint.append("LikedPosts(");
 +              for (String likedPostId : likedPostIds) {
 +                      fingerprint.append("Post(").append(likedPostId).append(')');
 +              }
 +              fingerprint.append(')');
 +
 +              List<String> likedReplyIds = new ArrayList<String>(getLikedReplyIds());
 +              Collections.sort(likedReplyIds);
 +              fingerprint.append("LikedReplies(");
 +              for (String likedReplyId : likedReplyIds) {
 +                      fingerprint.append("Reply(").append(likedReplyId).append(')');
 +              }
 +              fingerprint.append(')');
 +
 +              fingerprint.append("Albums(");
 +              for (Album album : albums) {
 +                      fingerprint.append(album.getFingerprint());
 +              }
 +              fingerprint.append(')');
 +
 +              return fingerprint.toString();
 +      }
 +
 +      //
 +      // INTERFACE Comparable<Sone>
 +      //
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public int compareTo(Sone sone) {
 +              return NICE_NAME_COMPARATOR.compare(this, sone);
 +      }
 +
 +      //
 +      // OBJECT METHODS
 +      //
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public int hashCode() {
 +              return id.hashCode();
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public boolean equals(Object object) {
 +              if (!(object instanceof Sone)) {
 +                      return false;
 +              }
 +              return ((Sone) object).getId().equals(id);
 +      }
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
 +      public String toString() {
 +              return getClass().getName() + "[identity=" + identity + ",requestUri=" + requestUri + ",insertUri(" + String.valueOf(insertUri).length() + "),friends(" + friendSones.size() + "),posts(" + posts.size() + "),replies(" + replies.size() + ")]";
 +      }
 +
 +}
@@@ -24,8 -24,7 +24,9 @@@ import java.util.logging.Logger
  
  import net.pterodactylus.sone.core.Core;
  import net.pterodactylus.sone.core.FreenetInterface;
+ import net.pterodactylus.sone.core.WebOfTrustUpdater;
 +import net.pterodactylus.sone.database.Database;
 +import net.pterodactylus.sone.database.memory.MemoryDatabase;
  import net.pterodactylus.sone.fcp.FcpInterface;
  import net.pterodactylus.sone.freenet.PluginStoreConfigurationBackend;
  import net.pterodactylus.sone.freenet.plugin.PluginConnector;
@@@ -190,11 -189,12 +191,15 @@@ public class SonePlugin implements Fred
                        identityManager = new IdentityManager(webOfTrustConnector);
                        identityManager.setContext("Sone");
  
 +                      /* create Sone database. */
 +                      Database soneDatabase = new MemoryDatabase();
 +
+                       /* create trust updater. */
+                       WebOfTrustUpdater trustUpdater = new WebOfTrustUpdater(webOfTrustConnector);
+                       trustUpdater.init();
                        /* create core. */
-                       core = new Core(oldConfiguration, soneDatabase, freenetInterface, identityManager);
 -                      core = new Core(oldConfiguration, freenetInterface, identityManager, trustUpdater);
++                      core = new Core(oldConfiguration, soneDatabase, freenetInterface, identityManager, trustUpdater);
  
                        /* create the web interface. */
                        webInterface = new WebInterface(this);
@@@ -30,7 -30,7 +30,8 @@@ import net.pterodactylus.sone.core.Post
  import net.pterodactylus.sone.core.SoneProvider;
  import net.pterodactylus.sone.data.Post;
  import net.pterodactylus.sone.data.Sone;
 +import net.pterodactylus.sone.database.memory.MemorySone;
+ import net.pterodactylus.util.io.Closer;
  import net.pterodactylus.util.logging.Logging;
  import freenet.keys.FreenetURI;
  
@@@ -110,187 -110,193 +111,193 @@@ public class SoneTextParser implements 
        public Iterable<Part> parse(SoneTextParserContext context, Reader source) throws IOException {
                PartContainer parts = new PartContainer();
                BufferedReader bufferedReader = (source instanceof BufferedReader) ? (BufferedReader) source : new BufferedReader(source);
-               String line;
-               boolean lastLineEmpty = true;
-               int emptyLines = 0;
-               while ((line = bufferedReader.readLine()) != null) {
-                       if (line.trim().length() == 0) {
-                               if (lastLineEmpty) {
+               try {
+                       String line;
+                       boolean lastLineEmpty = true;
+                       int emptyLines = 0;
+                       while ((line = bufferedReader.readLine()) != null) {
+                               if (line.trim().length() == 0) {
+                                       if (lastLineEmpty) {
+                                               continue;
+                                       }
+                                       parts.add(new PlainTextPart("\n"));
+                                       ++emptyLines;
+                                       lastLineEmpty = emptyLines == 2;
                                        continue;
                                }
-                               parts.add(new PlainTextPart("\n"));
-                               ++emptyLines;
-                               lastLineEmpty = emptyLines == 2;
-                               continue;
-                       }
-                       emptyLines = 0;
-                       /*
-                        * lineComplete tracks whether the block you are parsing is the
-                        * first block of the line. this is important because sometimes you
-                        * have to add an additional line break.
-                        */
-                       boolean lineComplete = true;
-                       while (line.length() > 0) {
-                               int nextKsk = line.indexOf("KSK@");
-                               int nextChk = line.indexOf("CHK@");
-                               int nextSsk = line.indexOf("SSK@");
-                               int nextUsk = line.indexOf("USK@");
-                               int nextHttp = line.indexOf("http://");
-                               int nextHttps = line.indexOf("https://");
-                               int nextSone = line.indexOf("sone://");
-                               int nextPost = line.indexOf("post://");
-                               if ((nextKsk == -1) && (nextChk == -1) && (nextSsk == -1) && (nextUsk == -1) && (nextHttp == -1) && (nextHttps == -1) && (nextSone == -1) && (nextPost == -1)) {
-                                       if (lineComplete && !lastLineEmpty) {
-                                               parts.add(new PlainTextPart("\n" + line));
-                                       } else {
-                                               parts.add(new PlainTextPart(line));
+                               emptyLines = 0;
+                               /*
+                                * lineComplete tracks whether the block you are parsing is the
+                                * first block of the line. this is important because sometimes
+                                * you have to add an additional line break.
+                                */
+                               boolean lineComplete = true;
+                               while (line.length() > 0) {
+                                       int nextKsk = line.indexOf("KSK@");
+                                       int nextChk = line.indexOf("CHK@");
+                                       int nextSsk = line.indexOf("SSK@");
+                                       int nextUsk = line.indexOf("USK@");
+                                       int nextHttp = line.indexOf("http://");
+                                       int nextHttps = line.indexOf("https://");
+                                       int nextSone = line.indexOf("sone://");
+                                       int nextPost = line.indexOf("post://");
+                                       if ((nextKsk == -1) && (nextChk == -1) && (nextSsk == -1) && (nextUsk == -1) && (nextHttp == -1) && (nextHttps == -1) && (nextSone == -1) && (nextPost == -1)) {
+                                               if (lineComplete && !lastLineEmpty) {
+                                                       parts.add(new PlainTextPart("\n" + line));
+                                               } else {
+                                                       parts.add(new PlainTextPart(line));
+                                               }
+                                               break;
+                                       }
+                                       int next = Integer.MAX_VALUE;
+                                       LinkType linkType = null;
+                                       if ((nextKsk > -1) && (nextKsk < next)) {
+                                               next = nextKsk;
+                                               linkType = LinkType.KSK;
+                                       }
+                                       if ((nextChk > -1) && (nextChk < next)) {
+                                               next = nextChk;
+                                               linkType = LinkType.CHK;
+                                       }
+                                       if ((nextSsk > -1) && (nextSsk < next)) {
+                                               next = nextSsk;
+                                               linkType = LinkType.SSK;
+                                       }
+                                       if ((nextUsk > -1) && (nextUsk < next)) {
+                                               next = nextUsk;
+                                               linkType = LinkType.USK;
+                                       }
+                                       if ((nextHttp > -1) && (nextHttp < next)) {
+                                               next = nextHttp;
+                                               linkType = LinkType.HTTP;
+                                       }
+                                       if ((nextHttps > -1) && (nextHttps < next)) {
+                                               next = nextHttps;
+                                               linkType = LinkType.HTTPS;
+                                       }
+                                       if ((nextSone > -1) && (nextSone < next)) {
+                                               next = nextSone;
+                                               linkType = LinkType.SONE;
+                                       }
+                                       if ((nextPost > -1) && (nextPost < next)) {
+                                               next = nextPost;
+                                               linkType = LinkType.POST;
                                        }
-                                       break;
-                               }
-                               int next = Integer.MAX_VALUE;
-                               LinkType linkType = null;
-                               if ((nextKsk > -1) && (nextKsk < next)) {
-                                       next = nextKsk;
-                                       linkType = LinkType.KSK;
-                               }
-                               if ((nextChk > -1) && (nextChk < next)) {
-                                       next = nextChk;
-                                       linkType = LinkType.CHK;
-                               }
-                               if ((nextSsk > -1) && (nextSsk < next)) {
-                                       next = nextSsk;
-                                       linkType = LinkType.SSK;
-                               }
-                               if ((nextUsk > -1) && (nextUsk < next)) {
-                                       next = nextUsk;
-                                       linkType = LinkType.USK;
-                               }
-                               if ((nextHttp > -1) && (nextHttp < next)) {
-                                       next = nextHttp;
-                                       linkType = LinkType.HTTP;
-                               }
-                               if ((nextHttps > -1) && (nextHttps < next)) {
-                                       next = nextHttps;
-                                       linkType = LinkType.HTTPS;
-                               }
-                               if ((nextSone > -1) && (nextSone < next)) {
-                                       next = nextSone;
-                                       linkType = LinkType.SONE;
-                               }
-                               if ((nextPost > -1) && (nextPost < next)) {
-                                       next = nextPost;
-                                       linkType = LinkType.POST;
-                               }
  
-                               /* cut off “freenet:” from before keys. */
-                               if (((linkType == LinkType.KSK) || (linkType == LinkType.CHK) || (linkType == LinkType.SSK) || (linkType == LinkType.USK)) && (next >= 8) && (line.substring(next - 8, next).equals("freenet:"))) {
-                                       next -= 8;
-                                       line = line.substring(0, next) + line.substring(next + 8);
-                               }
+                                       /* cut off “freenet:” from before keys. */
+                                       if (((linkType == LinkType.KSK) || (linkType == LinkType.CHK) || (linkType == LinkType.SSK) || (linkType == LinkType.USK)) && (next >= 8) && (line.substring(next - 8, next).equals("freenet:"))) {
+                                               next -= 8;
+                                               line = line.substring(0, next) + line.substring(next + 8);
+                                       }
  
-                               /* if there is text before the next item, write it out. */
-                               if (lineComplete && !lastLineEmpty) {
-                                       parts.add(new PlainTextPart("\n"));
-                               }
-                               if (next > 0) {
-                                       parts.add(new PlainTextPart(line.substring(0, next)));
-                                       line = line.substring(next);
-                                       next = 0;
-                               }
-                               lineComplete = false;
+                                       /* if there is text before the next item, write it out. */
+                                       if (lineComplete && !lastLineEmpty) {
+                                               parts.add(new PlainTextPart("\n"));
+                                       }
+                                       if (next > 0) {
+                                               parts.add(new PlainTextPart(line.substring(0, next)));
+                                               line = line.substring(next);
+                                               next = 0;
+                                       }
+                                       lineComplete = false;
  
-                               if (linkType == LinkType.SONE) {
-                                       if (line.length() >= (7 + 43)) {
-                                               String soneId = line.substring(7, 50);
-                                               Sone sone = soneProvider.getSone(soneId, false);
-                                               if (sone == null) {
-                                                       /*
-                                                        * don’t use create=true above, we don’t want the
-                                                        * empty shell.
-                                                        */
-                                                       sone = new MemorySone(soneId, false);
+                                       if (linkType == LinkType.SONE) {
+                                               if (line.length() >= (7 + 43)) {
+                                                       String soneId = line.substring(7, 50);
+                                                       Sone sone = soneProvider.getSone(soneId, false);
+                                                       if (sone == null) {
+                                                               /*
+                                                                * don’t use create=true above, we don’t want
+                                                                * the empty shell.
+                                                                */
 -                                                              sone = new Sone(soneId);
++                                                              sone = new MemorySone(soneId, false);
+                                                       }
+                                                       parts.add(new SonePart(sone));
+                                                       line = line.substring(50);
+                                               } else {
+                                                       parts.add(new PlainTextPart(line));
+                                                       line = "";
                                                }
-                                               parts.add(new SonePart(sone));
-                                               line = line.substring(50);
-                                       } else {
-                                               parts.add(new PlainTextPart(line));
-                                               line = "";
+                                               continue;
                                        }
-                                       continue;
-                               }
-                               if (linkType == LinkType.POST) {
-                                       if (line.length() >= (7 + 36)) {
-                                               String postId = line.substring(7, 43);
-                                               Post post = postProvider.getPost(postId, false);
-                                               if ((post != null) && (post.getSone() != null)) {
-                                                       parts.add(new PostPart(post));
+                                       if (linkType == LinkType.POST) {
+                                               if (line.length() >= (7 + 36)) {
+                                                       String postId = line.substring(7, 43);
+                                                       Post post = postProvider.getPost(postId, false);
+                                                       if ((post != null) && (post.getSone() != null)) {
+                                                               parts.add(new PostPart(post));
+                                                       } else {
+                                                               parts.add(new PlainTextPart(line.substring(0, 43)));
+                                                       }
+                                                       line = line.substring(43);
                                                } else {
-                                                       parts.add(new PlainTextPart(line.substring(0, 43)));
+                                                       parts.add(new PlainTextPart(line));
+                                                       line = "";
                                                }
-                                               line = line.substring(43);
-                                       } else {
-                                               parts.add(new PlainTextPart(line));
-                                               line = "";
+                                               continue;
                                        }
-                                       continue;
-                               }
-                               Matcher matcher = whitespacePattern.matcher(line);
-                               int nextSpace = matcher.find(0) ? matcher.start() : line.length();
-                               String link = line.substring(0, nextSpace);
-                               String name = link;
-                               logger.log(Level.FINER, String.format("Found link: %s", link));
-                               logger.log(Level.FINEST, String.format("CHK: %d, SSK: %d, USK: %d", nextChk, nextSsk, nextUsk));
+                                       Matcher matcher = whitespacePattern.matcher(line);
+                                       int nextSpace = matcher.find(0) ? matcher.start() : line.length();
+                                       String link = line.substring(0, nextSpace);
+                                       String name = link;
+                                       logger.log(Level.FINER, String.format("Found link: %s", link));
+                                       logger.log(Level.FINEST, String.format("CHK: %d, SSK: %d, USK: %d", nextChk, nextSsk, nextUsk));
  
-                               if ((linkType == LinkType.KSK) || (linkType == LinkType.CHK) || (linkType == LinkType.SSK) || (linkType == LinkType.USK)) {
-                                       FreenetURI uri;
-                                       if (name.indexOf('?') > -1) {
-                                               name = name.substring(0, name.indexOf('?'));
-                                       }
-                                       if (name.endsWith("/")) {
-                                               name = name.substring(0, name.length() - 1);
-                                       }
-                                       try {
-                                               uri = new FreenetURI(name);
-                                               name = uri.lastMetaString();
-                                               if (name == null) {
-                                                       name = uri.getDocName();
+                                       if ((linkType == LinkType.KSK) || (linkType == LinkType.CHK) || (linkType == LinkType.SSK) || (linkType == LinkType.USK)) {
+                                               FreenetURI uri;
+                                               if (name.indexOf('?') > -1) {
+                                                       name = name.substring(0, name.indexOf('?'));
                                                }
-                                               if (name == null) {
-                                                       name = link.substring(0, Math.min(9, link.length()));
+                                               if (name.endsWith("/")) {
+                                                       name = name.substring(0, name.length() - 1);
                                                }
-                                               boolean fromPostingSone = ((linkType == LinkType.SSK) || (linkType == LinkType.USK)) && (context != null) && (context.getPostingSone() != null) && link.substring(4, Math.min(link.length(), 47)).equals(context.getPostingSone().getId());
-                                               parts.add(new FreenetLinkPart(link, name, fromPostingSone));
-                                       } catch (MalformedURLException mue1) {
-                                               /* not a valid link, insert as plain text. */
-                                               parts.add(new PlainTextPart(link));
-                                       } catch (NullPointerException npe1) {
-                                               /* FreenetURI sometimes throws these, too. */
-                                               parts.add(new PlainTextPart(link));
-                                       } catch (ArrayIndexOutOfBoundsException aioobe1) {
-                                               /* oh, and these, too. */
-                                               parts.add(new PlainTextPart(link));
-                                       }
-                               } else if ((linkType == LinkType.HTTP) || (linkType == LinkType.HTTPS)) {
-                                       name = link.substring(linkType == LinkType.HTTP ? 7 : 8);
-                                       int firstSlash = name.indexOf('/');
-                                       int lastSlash = name.lastIndexOf('/');
-                                       if ((lastSlash - firstSlash) > 3) {
-                                               name = name.substring(0, firstSlash + 1) + "…" + name.substring(lastSlash);
-                                       }
-                                       if (name.endsWith("/")) {
-                                               name = name.substring(0, name.length() - 1);
-                                       }
-                                       if (((name.indexOf('/') > -1) && (name.indexOf('.') < name.lastIndexOf('.', name.indexOf('/'))) || ((name.indexOf('/') == -1) && (name.indexOf('.') < name.lastIndexOf('.')))) && name.startsWith("www.")) {
-                                               name = name.substring(4);
-                                       }
-                                       if (name.indexOf('?') > -1) {
-                                               name = name.substring(0, name.indexOf('?'));
+                                               try {
+                                                       uri = new FreenetURI(name);
+                                                       name = uri.lastMetaString();
+                                                       if (name == null) {
+                                                               name = uri.getDocName();
+                                                       }
+                                                       if (name == null) {
+                                                               name = link.substring(0, Math.min(9, link.length()));
+                                                       }
+                                                       boolean fromPostingSone = ((linkType == LinkType.SSK) || (linkType == LinkType.USK)) && (context != null) && (context.getPostingSone() != null) && link.substring(4, Math.min(link.length(), 47)).equals(context.getPostingSone().getId());
+                                                       parts.add(new FreenetLinkPart(link, name, fromPostingSone));
+                                               } catch (MalformedURLException mue1) {
+                                                       /* not a valid link, insert as plain text. */
+                                                       parts.add(new PlainTextPart(link));
+                                               } catch (NullPointerException npe1) {
+                                                       /* FreenetURI sometimes throws these, too. */
+                                                       parts.add(new PlainTextPart(link));
+                                               } catch (ArrayIndexOutOfBoundsException aioobe1) {
+                                                       /* oh, and these, too. */
+                                                       parts.add(new PlainTextPart(link));
+                                               }
+                                       } else if ((linkType == LinkType.HTTP) || (linkType == LinkType.HTTPS)) {
+                                               name = link.substring(linkType == LinkType.HTTP ? 7 : 8);
+                                               int firstSlash = name.indexOf('/');
+                                               int lastSlash = name.lastIndexOf('/');
+                                               if ((lastSlash - firstSlash) > 3) {
+                                                       name = name.substring(0, firstSlash + 1) + "…" + name.substring(lastSlash);
+                                               }
+                                               if (name.endsWith("/")) {
+                                                       name = name.substring(0, name.length() - 1);
+                                               }
+                                               if (((name.indexOf('/') > -1) && (name.indexOf('.') < name.lastIndexOf('.', name.indexOf('/'))) || ((name.indexOf('/') == -1) && (name.indexOf('.') < name.lastIndexOf('.')))) && name.startsWith("www.")) {
+                                                       name = name.substring(4);
+                                               }
+                                               if (name.indexOf('?') > -1) {
+                                                       name = name.substring(0, name.indexOf('?'));
+                                               }
+                                               parts.add(new LinkPart(link, name));
                                        }
-                                       parts.add(new LinkPart(link, name));
+                                       line = line.substring(nextSpace);
                                }
-                               line = line.substring(nextSpace);
+                               lastLineEmpty = false;
+                       }
+               } finally {
+                       if (bufferedReader != source) {
+                               Closer.close(bufferedReader);
                        }
-                       lastLineEmpty = false;
                }
                for (int partIndex = parts.size() - 1; partIndex >= 0; --partIndex) {
                        Part part = parts.getPart(partIndex);