Add method to get all posts of a Sone to post provider interface.
[Sone.git] / src / main / java / net / pterodactylus / sone / core / Core.java
index 4356120..9ccaa79 100644 (file)
@@ -56,8 +56,6 @@ 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.PostBuilder;
-import net.pterodactylus.sone.data.PostBuilderFactory;
 import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.data.Profile;
 import net.pterodactylus.sone.data.Profile.Field;
@@ -65,8 +63,14 @@ import net.pterodactylus.sone.data.Reply;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.data.Sone.ShowCustomAvatars;
 import net.pterodactylus.sone.data.Sone.SoneStatus;
+import net.pterodactylus.sone.database.PostBuilder;
+import net.pterodactylus.sone.database.PostBuilderFactory;
+import net.pterodactylus.sone.database.PostProvider;
+import net.pterodactylus.sone.database.PostReplyBuilder;
+import net.pterodactylus.sone.database.PostReplyBuilderFactory;
+import net.pterodactylus.sone.database.PostReplyProvider;
+import net.pterodactylus.sone.database.SoneProvider;
 import net.pterodactylus.sone.data.TemporaryImage;
-import net.pterodactylus.sone.data.impl.PostReplyImpl;
 import net.pterodactylus.sone.fcp.FcpInterface;
 import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired;
 import net.pterodactylus.sone.freenet.wot.Identity;
@@ -87,6 +91,7 @@ import net.pterodactylus.util.service.AbstractService;
 import net.pterodactylus.util.thread.NamedThreadFactory;
 
 import com.google.common.base.Function;
+import com.google.common.base.Optional;
 import com.google.common.base.Predicate;
 import com.google.common.base.Predicates;
 import com.google.common.collect.Collections2;
@@ -103,7 +108,7 @@ import freenet.keys.FreenetURI;
  *
  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
-public class Core extends AbstractService implements SoneProvider, PostProvider {
+public class Core extends AbstractService implements SoneProvider, PostProvider, PostReplyProvider {
 
        /** The logger. */
        private static final Logger logger = Logging.getLogger(Core.class);
@@ -151,7 +156,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
        private volatile FcpInterface fcpInterface;
 
        /** The times Sones were followed. */
-       private final Map<Sone, Long> soneFollowingTimes = new HashMap<Sone, Long>();
+       private final Map<String, Long> soneFollowingTimes = new HashMap<String, Long>();
 
        /** Locked local Sones. */
        /* synchronize on itself. */
@@ -181,6 +186,9 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
        /** All known posts. */
        private final Set<String> knownPosts = new HashSet<String>();
 
+       /** The post reply builder factory. */
+       private final PostReplyBuilderFactory postReplyBuilderFactory;
+
        /** All replies. */
        private final Map<String, PostReply> replies = new HashMap<String, PostReply>();
 
@@ -224,9 +232,11 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
         *            The event bus
         * @param postBuilderFactory
         *            The post builder
+        * @param postReplyBuilderFactory
+        *            The post reply builder factory
         */
        @Inject
-       public Core(Configuration configuration, FreenetInterface freenetInterface, IdentityManager identityManager, WebOfTrustUpdater webOfTrustUpdater, EventBus eventBus, PostBuilderFactory postBuilderFactory) {
+       public Core(Configuration configuration, FreenetInterface freenetInterface, IdentityManager identityManager, WebOfTrustUpdater webOfTrustUpdater, EventBus eventBus, PostBuilderFactory postBuilderFactory, PostReplyBuilderFactory postReplyBuilderFactory) {
                super("Sone Core");
                this.configuration = configuration;
                this.freenetInterface = freenetInterface;
@@ -237,6 +247,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
                this.webOfTrustUpdater = webOfTrustUpdater;
                this.eventBus = eventBus;
                this.postBuilderFactory = postBuilderFactory;
+               this.postReplyBuilderFactory = postReplyBuilderFactory;
        }
 
        //
@@ -336,12 +347,13 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
        }
 
        /**
-        * Returns all Sones, remote and local.
-        *
-        * @return All Sones
+        * {@inheritDocs}
         */
-       public Set<Sone> getSones() {
-               return new HashSet<Sone>(sones.values());
+       @Override
+       public Collection<Sone> getSones() {
+               synchronized (sones) {
+                       return Collections.unmodifiableCollection(sones.values());
+               }
        }
 
        /**
@@ -353,53 +365,17 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
         * @return The Sone with the given ID, or {@code null} if there is no such
         *         Sone
         */
-       public Sone getSone(String id) {
-               return getSone(id, true);
-       }
-
-       /**
-        * Returns the Sone with the given ID, regardless whether it’s local or
-        * remote.
-        *
-        * @param id
-        *            The ID of the Sone to get
-        * @param create
-        *            {@code true} to create a new Sone if none exists,
-        *            {@code false} to return {@code null} if a Sone with the given
-        *            ID does not exist
-        * @return The Sone with the given ID, or {@code null} if there is no such
-        *         Sone
-        */
        @Override
-       public Sone getSone(String id, boolean create) {
+       public Optional<Sone> getSone(String id) {
                synchronized (sones) {
-                       if (!sones.containsKey(id) && create) {
-                               Sone sone = new Sone(id, false);
-                               sones.put(id, sone);
-                       }
-                       return sones.get(id);
+                       return Optional.fromNullable(sones.get(id));
                }
        }
 
        /**
-        * Checks whether the core knows a Sone with the given ID.
-        *
-        * @param id
-        *            The ID of the Sone
-        * @return {@code true} if there is a Sone with the given ID, {@code false}
-        *         otherwise
-        */
-       public boolean hasSone(String id) {
-               synchronized (sones) {
-                       return sones.containsKey(id);
-               }
-       }
-
-       /**
-        * Returns all local Sones.
-        *
-        * @return All local Sones
+        * {@inheritDocs}
         */
+       @Override
        public Collection<Sone> getLocalSones() {
                synchronized (sones) {
                        return Collections2.filter(sones.values(), new Predicate<Sone>() {
@@ -438,10 +414,9 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
        }
 
        /**
-        * Returns all remote Sones.
-        *
-        * @return All remote Sones
+        * {@inheritDocs}
         */
+       @Override
        public Collection<Sone> getRemoteSones() {
                synchronized (sones) {
                        return Collections2.filter(sones.values(), new Predicate<Sone>() {
@@ -497,10 +472,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
         */
        public long getSoneFollowingTime(Sone sone) {
                synchronized (soneFollowingTimes) {
-                       if (soneFollowingTimes.containsKey(sone)) {
-                               return soneFollowingTimes.get(sone);
-                       }
-                       return Long.MAX_VALUE;
+                       return Optional.fromNullable(soneFollowingTimes.get(sone.getId())).or(Long.MAX_VALUE);
                }
        }
 
@@ -533,63 +505,60 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
         * {@inheritDoc}
         */
        @Override
-       public Post getPost(String postId) {
+       public Optional<Post> getPost(String postId) {
                synchronized (posts) {
-                       return posts.get(postId);
+                       return Optional.fromNullable(posts.get(postId));
                }
        }
 
        /**
-        * Returns all posts that have the given Sone as recipient.
-        *
-        * @see Post#getRecipient()
-        * @param recipient
-        *            The recipient of the posts
-        * @return All posts that have the given Sone as recipient
+        * {@inheritDocs}
         */
-       public Set<Post> getDirectedPosts(Sone recipient) {
-               checkNotNull(recipient, "recipient must not be null");
-               Set<Post> directedPosts = new HashSet<Post>();
+       @Override
+       public Collection<Post> getPosts(String soneId) {
+               return postDatabase.getPosts(soneId);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Collection<Post> getDirectedPosts(final String recipientId) {
+               checkNotNull(recipientId, "recipient must not be null");
                synchronized (posts) {
-                       for (Post post : posts.values()) {
-                               if (recipient.equals(post.getRecipient())) {
-                                       directedPosts.add(post);
+                       return Collections2.filter(posts.values(), new Predicate<Post>() {
+
+                               @Override
+                               public boolean apply(Post post) {
+                                       return recipientId.equals(post.getRecipientId().orNull());
                                }
-                       }
+                       });
                }
-               return directedPosts;
        }
 
        /**
-        * Returns the reply with the given ID. If there is no reply with the given
-        * ID yet, a new one is created, unless {@code create} is false in which
-        * case {@code null} is returned.
+        * Returns a post reply builder.
         *
-        * @param replyId
-        *            The ID of the reply to get
-        * @param create
-        *            {@code true} to always return a {@link Reply}, {@code false}
-        *            to return {@code null} if no reply can be found
-        * @return The reply, or {@code null} if there is no such reply
+        * @return A new post reply builder
+        */
+       public PostReplyBuilder postReplyBuilder() {
+               return postReplyBuilderFactory.newPostReplyBuilder();
+       }
+
+       /**
+        * {@inheritDoc}
         */
-       public PostReply getPostReply(String replyId, boolean create) {
+       @Override
+       public Optional<PostReply> getPostReply(String replyId) {
                synchronized (replies) {
-                       PostReply reply = replies.get(replyId);
-                       if (create && (reply == null)) {
-                               reply = new PostReplyImpl(replyId);
-                               replies.put(replyId, reply);
-                       }
-                       return reply;
+                       return Optional.fromNullable(replies.get(replyId));
                }
        }
 
        /**
-        * Returns all replies for the given post, order ascending by time.
-        *
-        * @param post
-        *            The post to get all replies for
-        * @return All replies for the given post
+        * {@inheritDoc}
         */
+       @Override
        public List<PostReply> getReplies(final Post post) {
                return Ordering.from(Reply.TIME_COMPARATOR).sortedCopy(FluentIterable.from(getSones()).transformAndConcat(new Function<Sone, Iterable<PostReply>>() {
 
@@ -601,7 +570,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
 
                        @Override
                        public boolean apply(PostReply reply) {
-                               return post.equals(reply.getPost());
+                               return post.getId().equals(reply.getPostId());
                        }
                }));
        }
@@ -675,9 +644,9 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
                Set<Post> posts = new HashSet<Post>();
                synchronized (bookmarkedPosts) {
                        for (String bookmarkedPostId : bookmarkedPosts) {
-                               Post post = getPost(bookmarkedPostId);
-                               if (post != null) {
-                                       posts.add(post);
+                               Optional<Post> post = getPost(bookmarkedPostId);
+                               if (!post.isPresent()) {
+                                       posts.add(post.get());
                                }
                        }
                }
@@ -826,6 +795,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
                        sone.setClient(new Client("Sone", SonePlugin.VERSION.toString()));
                        sone.setKnown(true);
                        /* TODO - load posts ’n stuff */
+                       trustedIdentities.put(ownIdentity, Collections.synchronizedSet(new HashSet<Identity>()));
                        sones.put(ownIdentity.getId(), sone);
                        final SoneInserter soneInserter = new SoneInserter(this, eventBus, freenetInterface, sone);
                        soneInserters.put(sone, soneInserter);
@@ -856,7 +826,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
                sone.getOptions().addBooleanOption("ShowNotification/NewReplies", new DefaultOption<Boolean>(true));
                sone.getOptions().addEnumOption("ShowCustomAvatars", new DefaultOption<ShowCustomAvatars>(ShowCustomAvatars.NEVER));
 
-               followSone(sone, getSone("nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI"));
+               followSone(sone, "nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI");
                touchConfiguration();
                return sone;
        }
@@ -876,7 +846,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
                synchronized (sones) {
                        final Sone sone = getRemoteSone(identity.getId(), true).setIdentity(identity);
                        boolean newSone = sone.getRequestUri() == null;
-                       sone.setRequestUri(getSoneUri(identity.getRequestUri()));
+                       sone.setRequestUri(SoneUri.create(identity.getRequestUri()));
                        sone.setLatestEdition(Numbers.safeParseLong(identity.getProperty("Sone.LatestEdition"), (long) 0));
                        if (newSone) {
                                synchronized (knownSones) {
@@ -887,7 +857,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
                                        eventBus.post(new NewSoneFoundEvent(sone));
                                        for (Sone localSone : getLocalSones()) {
                                                if (localSone.getOptions().getBooleanOption("AutoFollow").get()) {
-                                                       followSone(localSone, sone);
+                                                       followSone(localSone, sone.getId());
                                                }
                                        }
                                }
@@ -917,39 +887,21 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
        public void followSone(Sone sone, String soneId) {
                checkNotNull(sone, "sone must not be null");
                checkNotNull(soneId, "soneId must not be null");
-               Sone followedSone = getSone(soneId, true);
-               if (followedSone == null) {
-                       logger.log(Level.INFO, String.format("Ignored Sone with invalid ID: %s", soneId));
-                       return;
-               }
-               followSone(sone, getSone(soneId));
-       }
-
-       /**
-        * Lets the given local Sone follow the other given Sone. If the given Sone
-        * was not followed by any local Sone before, this will mark all elements of
-        * the followed Sone as read that have been created before the current
-        * moment.
-        *
-        * @param sone
-        *            The local Sone that should follow the other Sone
-        * @param followedSone
-        *            The Sone that should be followed
-        */
-       public void followSone(Sone sone, Sone followedSone) {
-               checkNotNull(sone, "sone must not be null");
-               checkNotNull(followedSone, "followedSone must not be null");
-               sone.addFriend(followedSone.getId());
+               sone.addFriend(soneId);
                synchronized (soneFollowingTimes) {
-                       if (!soneFollowingTimes.containsKey(followedSone)) {
+                       if (!soneFollowingTimes.containsKey(soneId)) {
                                long now = System.currentTimeMillis();
-                               soneFollowingTimes.put(followedSone, now);
-                               for (Post post : followedSone.getPosts()) {
+                               soneFollowingTimes.put(soneId, now);
+                               Optional<Sone> followedSone = getSone(soneId);
+                               if (!followedSone.isPresent()) {
+                                       return;
+                               }
+                               for (Post post : followedSone.get().getPosts()) {
                                        if (post.getTime() < now) {
                                                markPostKnown(post);
                                        }
                                }
-                               for (PostReply reply : followedSone.getReplies()) {
+                               for (PostReply reply : followedSone.get().getReplies()) {
                                        if (reply.getTime() < now) {
                                                markReplyKnown(reply);
                                        }
@@ -970,30 +922,14 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
        public void unfollowSone(Sone sone, String soneId) {
                checkNotNull(sone, "sone must not be null");
                checkNotNull(soneId, "soneId must not be null");
-               unfollowSone(sone, getSone(soneId, false));
-       }
-
-       /**
-        * Lets the given local Sone unfollow the other given Sone. If the given
-        * local Sone is the last local Sone that followed the given Sone, its
-        * following time will be removed.
-        *
-        * @param sone
-        *            The local Sone that should unfollow another Sone
-        * @param unfollowedSone
-        *            The Sone being unfollowed
-        */
-       public void unfollowSone(Sone sone, Sone unfollowedSone) {
-               checkNotNull(sone, "sone must not be null");
-               checkNotNull(unfollowedSone, "unfollowedSone must not be null");
-               sone.removeFriend(unfollowedSone.getId());
+               sone.removeFriend(soneId);
                boolean unfollowedSoneStillFollowed = false;
                for (Sone localSone : getLocalSones()) {
-                       unfollowedSoneStillFollowed |= localSone.hasFriend(unfollowedSone.getId());
+                       unfollowedSoneStillFollowed |= localSone.hasFriend(soneId);
                }
                if (!unfollowedSoneStillFollowed) {
                        synchronized (soneFollowingTimes) {
-                               soneFollowingTimes.remove(unfollowedSone);
+                               soneFollowingTimes.remove(soneId);
                        }
                }
                touchConfiguration();
@@ -1090,52 +1026,50 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
         *            of the age of the given Sone
         */
        public void updateSone(Sone sone, boolean soneRescueMode) {
-               if (hasSone(sone.getId())) {
-                       Sone storedSone = getSone(sone.getId());
-                       if (!soneRescueMode && !(sone.getTime() > storedSone.getTime())) {
+               Optional<Sone> storedSone = getSone(sone.getId());
+               if (storedSone.isPresent()) {
+                       if (!soneRescueMode && !(sone.getTime() > storedSone.get().getTime())) {
                                logger.log(Level.FINE, String.format("Downloaded Sone %s is not newer than stored Sone %s.", sone, storedSone));
                                return;
                        }
                        synchronized (posts) {
                                if (!soneRescueMode) {
-                                       for (Post post : storedSone.getPosts()) {
+                                       for (Post post : storedSone.get().getPosts()) {
                                                posts.remove(post.getId());
                                                if (!sone.getPosts().contains(post)) {
                                                        eventBus.post(new PostRemovedEvent(post));
                                                }
                                        }
                                }
-                               List<Post> storedPosts = storedSone.getPosts();
+                               List<Post> storedPosts = storedSone.get().getPosts();
                                synchronized (knownPosts) {
                                        for (Post post : sone.getPosts()) {
-                                               PostBuilder postBuilder = postBuilderFactory.newPostBuilder();
-                                               postBuilder.copyPost(post).from(storedSone);
-                                               Post newPost = postBuilder.build().setKnown(knownPosts.contains(post.getId()));
-                                               if (!storedPosts.contains(newPost)) {
-                                                       if (newPost.getTime() < getSoneFollowingTime(sone)) {
-                                                               knownPosts.add(newPost.getId());
-                                                               newPost.setKnown(true);
-                                                       } else if (!knownPosts.contains(newPost.getId())) {
-                                                               eventBus.post(new NewPostFoundEvent(newPost));
+                                               post.setKnown(knownPosts.contains(post.getId()));
+                                               if (!storedPosts.contains(post)) {
+                                                       if (post.getTime() < getSoneFollowingTime(sone)) {
+                                                               knownPosts.add(post.getId());
+                                                               post.setKnown(true);
+                                                       } else if (!knownPosts.contains(post.getId())) {
+                                                               eventBus.post(new NewPostFoundEvent(post));
                                                        }
                                                }
-                                               posts.put(newPost.getId(), newPost);
+                                               posts.put(post.getId(), post);
                                        }
                                }
                        }
                        synchronized (replies) {
                                if (!soneRescueMode) {
-                                       for (PostReply reply : storedSone.getReplies()) {
+                                       for (PostReply reply : storedSone.get().getReplies()) {
                                                replies.remove(reply.getId());
                                                if (!sone.getReplies().contains(reply)) {
                                                        eventBus.post(new PostReplyRemovedEvent(reply));
                                                }
                                        }
                                }
-                               Set<PostReply> storedReplies = storedSone.getReplies();
+                               Set<PostReply> storedReplies = storedSone.get().getReplies();
                                synchronized (knownReplies) {
                                        for (PostReply reply : sone.getReplies()) {
-                                               reply.setSone(storedSone).setKnown(knownReplies.contains(reply.getId()));
+                                               reply.setKnown(knownReplies.contains(reply.getId()));
                                                if (!storedReplies.contains(reply)) {
                                                        if (reply.getTime() < getSoneFollowingTime(sone)) {
                                                                knownReplies.add(reply.getId());
@@ -1150,7 +1084,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
                        }
                        synchronized (albums) {
                                synchronized (images) {
-                                       for (Album album : storedSone.getAlbums()) {
+                                       for (Album album : storedSone.get().getAlbums()) {
                                                albums.remove(album.getId());
                                                for (Image image : album.getImages()) {
                                                        images.remove(image.getId());
@@ -1164,36 +1098,9 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
                                        }
                                }
                        }
-                       synchronized (storedSone) {
-                               if (!soneRescueMode || (sone.getTime() > storedSone.getTime())) {
-                                       storedSone.setTime(sone.getTime());
-                               }
-                               storedSone.setClient(sone.getClient());
-                               storedSone.setProfile(sone.getProfile());
-                               if (soneRescueMode) {
-                                       for (Post post : sone.getPosts()) {
-                                               storedSone.addPost(post);
-                                       }
-                                       for (PostReply reply : sone.getReplies()) {
-                                               storedSone.addReply(reply);
-                                       }
-                                       for (String likedPostId : sone.getLikedPostIds()) {
-                                               storedSone.addLikedPostId(likedPostId);
-                                       }
-                                       for (String likedReplyId : sone.getLikedReplyIds()) {
-                                               storedSone.addLikedReplyId(likedReplyId);
-                                       }
-                                       for (Album album : sone.getAlbums()) {
-                                               storedSone.addAlbum(album);
-                                       }
-                               } else {
-                                       storedSone.setPosts(sone.getPosts());
-                                       storedSone.setReplies(sone.getReplies());
-                                       storedSone.setLikePostIds(sone.getLikedPostIds());
-                                       storedSone.setLikeReplyIds(sone.getLikedReplyIds());
-                                       storedSone.setAlbums(sone.getAlbums());
-                               }
-                               storedSone.setLatestEdition(sone.getLatestEdition());
+                       synchronized (sones) {
+                               sone.setOptions(storedSone.get().getOptions());
+                               sones.put(sone.getId(), sone);
                        }
                }
        }
@@ -1312,9 +1219,9 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
                                logger.log(Level.WARNING, "Invalid post found, aborting load!");
                                return;
                        }
-                       PostBuilder postBuilder = postBuilder().withId(postId).from(sone).withTime(postTime).withText(postText);
+                       PostBuilder postBuilder = postBuilder().withId(postId).from(sone.getId()).withTime(postTime).withText(postText);
                        if ((postRecipientId != null) && (postRecipientId.length() == 43)) {
-                               postBuilder.to(getSone(postRecipientId));
+                               postBuilder.to(postRecipientId);
                        }
                        posts.add(postBuilder.build());
                }
@@ -1334,7 +1241,9 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
                                logger.log(Level.WARNING, "Invalid reply found, aborting load!");
                                return;
                        }
-                       replies.add(getPostReply(replyId, true).setSone(sone).setPost(getPost(postId)).setTime(replyTime).setText(replyText));
+                       PostReplyBuilder postReplyBuilder = postReplyBuilderFactory.newPostReplyBuilder();
+                       postReplyBuilder.withId(replyId).from(sone.getId()).to(postId).withTime(replyTime).withText(replyText);
+                       replies.add(postReplyBuilder.build());
                }
 
                /* load post likes. */
@@ -1461,11 +1370,21 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
                                knownSones.add(friend);
                        }
                }
+               synchronized (this.posts) {
+                       for (Post post : posts) {
+                               this.posts.put(post.getId(), post);
+                       }
+               }
                synchronized (knownPosts) {
                        for (Post post : posts) {
                                knownPosts.add(post.getId());
                        }
                }
+               synchronized (this.replies) {
+                       for (PostReply postReply : replies) {
+                               this.replies.put(postReply.getId(), postReply);
+                       }
+               }
                synchronized (knownReplies) {
                        for (PostReply reply : replies) {
                                knownReplies.add(reply.getId());
@@ -1513,7 +1432,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
         *            The text of the post
         * @return The created post
         */
-       public Post createPost(Sone sone, Sone recipient, String text) {
+       public Post createPost(Sone sone, Optional<Sone> recipient, String text) {
                return createPost(sone, recipient, System.currentTimeMillis(), text);
        }
 
@@ -1531,7 +1450,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
         *            The text of the post
         * @return The created post
         */
-       public Post createPost(Sone sone, Sone recipient, long time, String text) {
+       public Post createPost(Sone sone, Optional<Sone> recipient, long time, String text) {
                checkNotNull(text, "text must not be null");
                checkArgument(text.trim().length() > 0, "text must not be empty");
                if (!sone.isLocal()) {
@@ -1539,9 +1458,9 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
                        return null;
                }
                PostBuilder postBuilder = postBuilderFactory.newPostBuilder();
-               postBuilder.from(sone).randomId().withTime(time).withText(text.trim());
-               if (recipient != null) {
-                       postBuilder.to(recipient);
+               postBuilder.from(sone.getId()).randomId().withTime(time).withText(text.trim());
+               if (recipient.isPresent()) {
+                       postBuilder.to(recipient.get().getId());
                }
                final Post post = postBuilder.build();
                synchronized (posts) {
@@ -1659,30 +1578,15 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
         * @return The created reply
         */
        public PostReply createReply(Sone sone, Post post, String text) {
-               return createReply(sone, post, System.currentTimeMillis(), text);
-       }
-
-       /**
-        * Creates a new 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
-        * @return The created reply
-        */
-       public PostReply createReply(Sone sone, Post post, long time, String text) {
                checkNotNull(text, "text must not be null");
                checkArgument(text.trim().length() > 0, "text must not be empty");
                if (!sone.isLocal()) {
                        logger.log(Level.FINE, String.format("Tried to create reply for non-local Sone: %s", sone));
                        return null;
                }
-               final PostReply reply = new PostReplyImpl(sone, post, System.currentTimeMillis(), text.trim());
+               PostReplyBuilder postReplyBuilder = postReplyBuilderFactory.newPostReplyBuilder();
+               postReplyBuilder.randomId().from(sone.getId()).to(post.getId()).currentTime().withText(text.trim());
+               final PostReply reply = postReplyBuilder.build();
                synchronized (replies) {
                        replies.put(reply.getId(), reply);
                }
@@ -1954,6 +1858,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
                webOfTrustUpdater.stop();
                updateChecker.stop();
                soneDownloader.stop();
+               soneDownloaders.shutdown();
                identityManager.stop();
        }
 
@@ -2009,7 +1914,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
                        for (Post post : sone.getPosts()) {
                                String postPrefix = sonePrefix + "/Posts/" + postCounter++;
                                configuration.getStringValue(postPrefix + "/ID").setValue(post.getId());
-                               configuration.getStringValue(postPrefix + "/Recipient").setValue((post.getRecipient() != null) ? post.getRecipient().getId() : null);
+                               configuration.getStringValue(postPrefix + "/Recipient").setValue(post.getRecipientId().orNull());
                                configuration.getLongValue(postPrefix + "/Time").setValue(post.getTime());
                                configuration.getStringValue(postPrefix + "/Text").setValue(post.getText());
                        }
@@ -2020,7 +1925,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
                        for (PostReply reply : sone.getReplies()) {
                                String replyPrefix = sonePrefix + "/Replies/" + replyCounter++;
                                configuration.getStringValue(replyPrefix + "/ID").setValue(reply.getId());
-                               configuration.getStringValue(replyPrefix + "/Post/ID").setValue(reply.getPost().getId());
+                               configuration.getStringValue(replyPrefix + "/Post/ID").setValue(reply.getPostId());
                                configuration.getLongValue(replyPrefix + "/Time").setValue(reply.getTime());
                                configuration.getStringValue(replyPrefix + "/Text").setValue(reply.getText());
                        }
@@ -2138,8 +2043,8 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
                        /* save Sone following times. */
                        soneCounter = 0;
                        synchronized (soneFollowingTimes) {
-                               for (Entry<Sone, Long> soneFollowingTime : soneFollowingTimes.entrySet()) {
-                                       configuration.getStringValue("SoneFollowingTimes/" + soneCounter + "/Sone").setValue(soneFollowingTime.getKey().getId());
+                               for (Entry<String, Long> soneFollowingTime : soneFollowingTimes.entrySet()) {
+                                       configuration.getStringValue("SoneFollowingTimes/" + soneCounter + "/Sone").setValue(soneFollowingTime.getKey());
                                        configuration.getLongValue("SoneFollowingTimes/" + soneCounter + "/Time").setValue(soneFollowingTime.getValue());
                                        ++soneCounter;
                                }
@@ -2188,7 +2093,6 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
        /**
         * Loads the configuration.
         */
-       @SuppressWarnings("unchecked")
        private void loadConfiguration() {
                /* create options. */
                options.addIntegerOption("InsertionDelay", new DefaultOption<Integer>(60, new IntegerRangePredicate(0, Integer.MAX_VALUE), new OptionWatcher<Integer>() {
@@ -2257,13 +2161,8 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
                                break;
                        }
                        long time = configuration.getLongValue("SoneFollowingTimes/" + soneCounter + "/Time").getValue(Long.MAX_VALUE);
-                       Sone followedSone = getSone(soneId);
-                       if (followedSone == null) {
-                               logger.log(Level.WARNING, String.format("Ignoring Sone with invalid ID: %s", soneId));
-                       } else {
-                               synchronized (soneFollowingTimes) {
-                                       soneFollowingTimes.put(getSone(soneId), time);
-                               }
+                       synchronized (soneFollowingTimes) {
+                               soneFollowingTimes.put(soneId, time);
                        }
                        ++soneCounter;
                }
@@ -2322,23 +2221,6 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
        }
 
        /**
-        * Generate a Sone URI from the given URI and latest edition.
-        *
-        * @param uriString
-        *            The URI to derive the Sone URI from
-        * @return The derived URI
-        */
-       private static FreenetURI getSoneUri(String uriString) {
-               try {
-                       FreenetURI uri = new FreenetURI(uriString).setDocName("Sone").setMetaString(new String[0]);
-                       return uri;
-               } catch (MalformedURLException mue1) {
-                       logger.log(Level.WARNING, String.format("Could not create Sone URI from URI: %s", uriString), mue1);
-                       return null;
-               }
-       }
-
-       /**
         * Notifies the core that a new {@link OwnIdentity} was added.
         *
         * @param ownIdentityAddedEvent
@@ -2349,7 +2231,6 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
                OwnIdentity ownIdentity = ownIdentityAddedEvent.ownIdentity();
                logger.log(Level.FINEST, String.format("Adding OwnIdentity: %s", ownIdentity));
                if (ownIdentity.hasContext("Sone")) {
-                       trustedIdentities.put(ownIdentity, Collections.synchronizedSet(new HashSet<Identity>()));
                        addLocalSone(ownIdentity);
                }
        }
@@ -2428,14 +2309,14 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
                        /* some local identity still trusts this identity, don’t remove. */
                        return;
                }
-               Sone sone = getSone(identity.getId(), false);
-               if (sone == null) {
+               Optional<Sone> sone = getSone(identity.getId());
+               if (!sone.isPresent()) {
                        /* TODO - we don’t have the Sone anymore. should this happen? */
                        return;
                }
                synchronized (posts) {
                        synchronized (knownPosts) {
-                               for (Post post : sone.getPosts()) {
+                               for (Post post : sone.get().getPosts()) {
                                        posts.remove(post.getId());
                                        eventBus.post(new PostRemovedEvent(post));
                                }
@@ -2443,7 +2324,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
                }
                synchronized (replies) {
                        synchronized (knownReplies) {
-                               for (PostReply reply : sone.getReplies()) {
+                               for (PostReply reply : sone.get().getReplies()) {
                                        replies.remove(reply.getId());
                                        eventBus.post(new PostReplyRemovedEvent(reply));
                                }
@@ -2452,7 +2333,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
                synchronized (sones) {
                        sones.remove(identity.getId());
                }
-               eventBus.post(new SoneRemovedEvent(sone));
+               eventBus.post(new SoneRemovedEvent(sone.get()));
        }
 
        /**