Extract an interface out of Sone.
[Sone.git] / src / main / java / net / pterodactylus / sone / core / Core.java
index 901614e..7e93410 100644 (file)
@@ -23,7 +23,6 @@ import static com.google.common.base.Preconditions.checkNotNull;
 import java.net.MalformedURLException;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -56,18 +55,22 @@ 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.PostReplyBuilder;
-import net.pterodactylus.sone.data.PostReplyBuilderFactory;
 import net.pterodactylus.sone.data.Profile;
 import net.pterodactylus.sone.data.Profile.Field;
 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.data.SoneImpl;
 import net.pterodactylus.sone.data.TemporaryImage;
+import net.pterodactylus.sone.database.Database;
+import net.pterodactylus.sone.database.DatabaseException;
+import net.pterodactylus.sone.database.PostBuilder;
+import net.pterodactylus.sone.database.PostProvider;
+import net.pterodactylus.sone.database.PostReplyBuilder;
+import net.pterodactylus.sone.database.PostReplyProvider;
+import net.pterodactylus.sone.database.SoneProvider;
 import net.pterodactylus.sone.fcp.FcpInterface;
 import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired;
 import net.pterodactylus.sone.freenet.wot.Identity;
@@ -87,13 +90,14 @@ import net.pterodactylus.util.number.Numbers;
 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;
 import com.google.common.collect.FluentIterable;
-import com.google.common.collect.Ordering;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Multimaps;
 import com.google.common.eventbus.EventBus;
 import com.google.common.eventbus.Subscribe;
 import com.google.inject.Inject;
@@ -153,7 +157,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. */
@@ -174,36 +178,15 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
        /** All known Sones. */
        private final Set<String> knownSones = new HashSet<String>();
 
-       /** The post builder. */
-       private final PostBuilderFactory postBuilderFactory;
-
-       /** All posts. */
-       private final Map<String, Post> posts = new HashMap<String, Post>();
-
-       /** 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>();
-
-       /** All known replies. */
-       private final Set<String> knownReplies = new HashSet<String>();
+       /** The post database. */
+       private final Database database;
 
        /** All bookmarked posts. */
        /* synchronize access on itself. */
        private final Set<String> bookmarkedPosts = new HashSet<String>();
 
        /** Trusted identities, sorted by own identities. */
-       private final Map<OwnIdentity, Set<Identity>> trustedIdentities = Collections.synchronizedMap(new HashMap<OwnIdentity, Set<Identity>>());
-
-       /** All known albums. */
-       private final Map<String, Album> albums = new HashMap<String, Album>();
-
-       /** All known images. */
-       private final Map<String, Image> images = new HashMap<String, Image>();
+       private final Multimap<OwnIdentity, Identity> trustedIdentities = Multimaps.synchronizedSetMultimap(HashMultimap.<OwnIdentity, Identity>create());
 
        /** All temporary images. */
        private final Map<String, TemporaryImage> temporaryImages = new HashMap<String, TemporaryImage>();
@@ -227,13 +210,11 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         *            The WebOfTrust updater
         * @param eventBus
         *            The event bus
-        * @param postBuilderFactory
-        *            The post builder
-        * @param postReplyBuilderFactory
-        *            The post reply builder factory
+        * @param database
+        *            The database
         */
        @Inject
-       public Core(Configuration configuration, FreenetInterface freenetInterface, IdentityManager identityManager, WebOfTrustUpdater webOfTrustUpdater, EventBus eventBus, PostBuilderFactory postBuilderFactory, PostReplyBuilderFactory postReplyBuilderFactory) {
+       public Core(Configuration configuration, FreenetInterface freenetInterface, IdentityManager identityManager, WebOfTrustUpdater webOfTrustUpdater, EventBus eventBus, Database database) {
                super("Sone Core");
                this.configuration = configuration;
                this.freenetInterface = freenetInterface;
@@ -243,8 +224,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                this.updateChecker = new UpdateChecker(eventBus, freenetInterface);
                this.webOfTrustUpdater = webOfTrustUpdater;
                this.eventBus = eventBus;
-               this.postBuilderFactory = postBuilderFactory;
-               this.postReplyBuilderFactory = postReplyBuilderFactory;
+               this.database = database;
        }
 
        //
@@ -344,25 +324,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());
-       }
-
-       /**
-        * Returns the Sone with the given ID, regardless whether it’s local or
-        * remote.
-        *
-        * @param id
-        *            The ID of the Sone to get
-        * @return The Sone with the given ID, or {@code null} if there is no such
-        *         Sone
-        */
-       public Sone getSone(String id) {
-               return getSone(id, true);
+       @Override
+       public Collection<Sone> getSones() {
+               synchronized (sones) {
+                       return ImmutableSet.copyOf(sones.values());
+               }
        }
 
        /**
@@ -371,52 +339,29 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         *
         * @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>() {
+                       return FluentIterable.from(sones.values()).filter(new Predicate<Sone>() {
 
                                @Override
                                public boolean apply(Sone sone) {
                                        return sone.isLocal();
                                }
-                       });
+                       }).toSet();
                }
        }
 
@@ -434,11 +379,11 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                synchronized (sones) {
                        Sone sone = sones.get(id);
                        if ((sone == null) && create) {
-                               sone = new Sone(id, true);
+                               sone = new SoneImpl(id, true);
                                sones.put(id, sone);
                        }
                        if ((sone != null) && !sone.isLocal()) {
-                               sone = new Sone(id, true);
+                               sone = new SoneImpl(id, true);
                                sones.put(id, sone);
                        }
                        return sone;
@@ -446,19 +391,18 @@ 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>() {
+                       return FluentIterable.from(sones.values()).filter(new Predicate<Sone>() {
 
                                @Override
                                public boolean apply(Sone sone) {
                                        return !sone.isLocal();
                                }
-                       });
+                       }).toSet();
                }
        }
 
@@ -476,7 +420,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                synchronized (sones) {
                        Sone sone = sones.get(id);
                        if ((sone == null) && create && (id != null) && (id.length() == 43)) {
-                               sone = new Sone(id, false);
+                               sone = new SoneImpl(id, false);
                                sones.put(id, sone);
                        }
                        return sone;
@@ -505,10 +449,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);
                }
        }
 
@@ -525,7 +466,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                checkNotNull(origin, "origin must not be null");
                checkNotNull(target, "target must not be null");
                checkArgument(origin.getIdentity() instanceof OwnIdentity, "origin’s identity must be an OwnIdentity");
-               return trustedIdentities.containsKey(origin.getIdentity()) && trustedIdentities.get(origin.getIdentity()).contains(target.getIdentity());
+               return trustedIdentities.containsEntry(origin.getIdentity(), target.getIdentity());
        }
 
        /**
@@ -534,7 +475,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         * @return A new post builder
         */
        public PostBuilder postBuilder() {
-               return postBuilderFactory.newPostBuilder();
+               return database.newPostBuilder();
        }
 
        /**
@@ -542,30 +483,24 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         */
        @Override
        public Optional<Post> getPost(String postId) {
-               synchronized (posts) {
-                       return Optional.fromNullable(posts.get(postId));
-               }
+               return database.getPost(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
-        */
-       public Set<Post> getDirectedPosts(Sone recipient) {
-               checkNotNull(recipient, "recipient must not be null");
-               Set<Post> directedPosts = new HashSet<Post>();
-               synchronized (posts) {
-                       for (Post post : posts.values()) {
-                               if (recipient.equals(post.getRecipient())) {
-                                       directedPosts.add(post);
-                               }
-                       }
-               }
-               return directedPosts;
+        * {@inheritDocs}
+        */
+       @Override
+       public Collection<Post> getPosts(String soneId) {
+               return database.getPosts(soneId);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Collection<Post> getDirectedPosts(final String recipientId) {
+               checkNotNull(recipientId, "recipient must not be null");
+               return database.getDirectedPosts(recipientId);
        }
 
        /**
@@ -574,7 +509,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         * @return A new post reply builder
         */
        public PostReplyBuilder postReplyBuilder() {
-               return postReplyBuilderFactory.newPostReplyBuilder();
+               return database.newPostReplyBuilder();
        }
 
        /**
@@ -582,29 +517,15 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         */
        @Override
        public Optional<PostReply> getPostReply(String replyId) {
-               synchronized (replies) {
-                       return Optional.fromNullable(replies.get(replyId));
-               }
+               return database.getPostReply(replyId);
        }
 
        /**
         * {@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>>() {
-
-                       @Override
-                       public Iterable<PostReply> apply(Sone sone) {
-                               return sone.getReplies();
-                       }
-               }).filter(new Predicate<PostReply>() {
-
-                       @Override
-                       public boolean apply(PostReply reply) {
-                               return post.getId().equals(reply.getPostId());
-                       }
-               }));
+       public List<PostReply> getReplies(final String postId) {
+               return database.getReplies(postId);
        }
 
        /**
@@ -677,7 +598,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                synchronized (bookmarkedPosts) {
                        for (String bookmarkedPostId : bookmarkedPosts) {
                                Optional<Post> post = getPost(bookmarkedPostId);
-                               if (!post.isPresent()) {
+                               if (post.isPresent()) {
                                        posts.add(post.get());
                                }
                        }
@@ -710,14 +631,16 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         *         given ID exists and {@code create} is {@code false}
         */
        public Album getAlbum(String albumId, boolean create) {
-               synchronized (albums) {
-                       Album album = albums.get(albumId);
-                       if (create && (album == null)) {
-                               album = new Album(albumId);
-                               albums.put(albumId, album);
-                       }
-                       return album;
+               Optional<Album> album = database.getAlbum(albumId);
+               if (album.isPresent()) {
+                       return album.get();
+               }
+               if (!create) {
+                       return null;
                }
+               Album newAlbum = database.newAlbumBuilder().withId(albumId).build();
+               database.storeAlbum(newAlbum);
+               return newAlbum;
        }
 
        /**
@@ -744,14 +667,16 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         *         none was created
         */
        public Image getImage(String imageId, boolean create) {
-               synchronized (images) {
-                       Image image = images.get(imageId);
-                       if (create && (image == null)) {
-                               image = new Image(imageId);
-                               images.put(imageId, image);
-                       }
-                       return image;
+               Optional<Image> image = database.getImage(imageId);
+               if (image.isPresent()) {
+                       return image.get();
+               }
+               if (!create) {
+                       return null;
                }
+               Image newImage = database.newImageBuilder().withId(imageId).build();
+               database.storeImage(newImage);
+               return newImage;
        }
 
        /**
@@ -815,6 +740,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        logger.log(Level.WARNING, "Given OwnIdentity is null!");
                        return null;
                }
+               logger.info(String.format("Adding Sone from OwnIdentity: %s", ownIdentity));
                synchronized (sones) {
                        final Sone sone;
                        try {
@@ -857,7 +783,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;
        }
@@ -875,7 +801,11 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        return null;
                }
                synchronized (sones) {
-                       final Sone sone = getRemoteSone(identity.getId(), true).setIdentity(identity);
+                       final Sone sone = getRemoteSone(identity.getId(), true);
+                       if (sone.isLocal()) {
+                               return sone;
+                       }
+                       sone.setIdentity(identity);
                        boolean newSone = sone.getRequestUri() == null;
                        sone.setRequestUri(SoneUri.create(identity.getRequestUri()));
                        sone.setLatestEdition(Numbers.safeParseLong(identity.getProperty("Sone.LatestEdition"), (long) 0));
@@ -888,7 +818,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());
                                                }
                                        }
                                }
@@ -918,39 +848,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);
                                        }
@@ -971,30 +883,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();
@@ -1091,110 +987,72 @@ 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()) {
-                                               posts.remove(post.getId());
-                                               if (!sone.getPosts().contains(post)) {
-                                                       eventBus.post(new PostRemovedEvent(post));
-                                               }
-                                       }
+                       /* find removed posts. */
+                       Collection<Post> existingPosts = database.getPosts(sone.getId());
+                       for (Post oldPost : existingPosts) {
+                               if (!sone.getPosts().contains(oldPost)) {
+                                       eventBus.post(new PostRemovedEvent(oldPost));
                                }
-                               List<Post> storedPosts = storedSone.getPosts();
-                               synchronized (knownPosts) {
-                                       for (Post post : sone.getPosts()) {
-                                               PostBuilder postBuilder = postBuilderFactory.newPostBuilder();
-                                               postBuilder.copyPost(post).from(storedSone.getId());
-                                               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));
-                                                       }
-                                               }
-                                               posts.put(newPost.getId(), newPost);
-                                       }
+                       }
+                       /* find new posts. */
+                       for (Post newPost : sone.getPosts()) {
+                               if (existingPosts.contains(newPost)) {
+                                       continue;
+                               }
+                               if (newPost.getTime() < getSoneFollowingTime(sone)) {
+                                       newPost.setKnown(true);
+                               } else if (!newPost.isKnown()) {
+                                       eventBus.post(new NewPostFoundEvent(newPost));
                                }
                        }
-                       synchronized (replies) {
-                               if (!soneRescueMode) {
-                                       for (PostReply reply : storedSone.getReplies()) {
-                                               replies.remove(reply.getId());
-                                               if (!sone.getReplies().contains(reply)) {
-                                                       eventBus.post(new PostReplyRemovedEvent(reply));
-                                               }
+                       /* store posts. */
+                       database.storePosts(sone, sone.getPosts());
+                       if (!soneRescueMode) {
+                               for (PostReply reply : storedSone.get().getReplies()) {
+                                       if (!sone.getReplies().contains(reply)) {
+                                               eventBus.post(new PostReplyRemovedEvent(reply));
                                        }
                                }
-                               Set<PostReply> storedReplies = storedSone.getReplies();
-                               synchronized (knownReplies) {
-                                       for (PostReply reply : sone.getReplies()) {
-                                               reply.setKnown(knownReplies.contains(reply.getId()));
-                                               if (!storedReplies.contains(reply)) {
-                                                       if (reply.getTime() < getSoneFollowingTime(sone)) {
-                                                               knownReplies.add(reply.getId());
-                                                               reply.setKnown(true);
-                                                       } else if (!knownReplies.contains(reply.getId())) {
-                                                               eventBus.post(new NewPostReplyFoundEvent(reply));
-                                                       }
-                                               }
-                                               replies.put(reply.getId(), reply);
-                                       }
+                       }
+                       Set<PostReply> storedReplies = storedSone.get().getReplies();
+                       for (PostReply reply : sone.getReplies()) {
+                               if (storedReplies.contains(reply)) {
+                                       continue;
+                               }
+                               if (reply.getTime() < getSoneFollowingTime(sone)) {
+                                       reply.setKnown(true);
+                               } else if (!reply.isKnown()) {
+                                       eventBus.post(new NewPostReplyFoundEvent(reply));
                                }
                        }
-                       synchronized (albums) {
-                               synchronized (images) {
-                                       for (Album album : storedSone.getAlbums()) {
-                                               albums.remove(album.getId());
-                                               for (Image image : album.getImages()) {
-                                                       images.remove(image.getId());
-                                               }
-                                       }
-                                       for (Album album : sone.getAlbums()) {
-                                               albums.put(album.getId(), album);
-                                               for (Image image : album.getImages()) {
-                                                       images.put(image.getId(), image);
-                                               }
-                                       }
+                       database.storePostReplies(sone, sone.getReplies());
+                       for (Album album : storedSone.get().getRootAlbum().getAlbums()) {
+                               database.removeAlbum(album);
+                               for (Image image : album.getImages()) {
+                                       database.removeImage(image);
                                }
                        }
-                       synchronized (storedSone) {
-                               if (!soneRescueMode || (sone.getTime() > storedSone.getTime())) {
-                                       storedSone.setTime(sone.getTime());
+                       for (Album album : sone.getRootAlbum().getAlbums()) {
+                               database.storeAlbum(album);
+                               for (Image image : album.getImages()) {
+                                       database.storeImage(image);
                                }
-                               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());
+                       }
+                       synchronized (sones) {
+                               sone.setOptions(storedSone.get().getOptions());
+                               sone.setKnown(storedSone.get().isKnown());
+                               sone.setStatus((sone.getTime() == 0) ? SoneStatus.unknown : SoneStatus.idle);
+                               if (sone.isLocal()) {
+                                       soneInserters.get(storedSone.get()).setSone(sone);
+                                       touchConfiguration();
                                }
-                               storedSone.setLatestEdition(sone.getLatestEdition());
+                               sones.put(sone.getId(), sone);
                        }
                }
        }
@@ -1260,6 +1118,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        logger.log(Level.FINE, String.format("Tried to load non-local Sone: %s", sone));
                        return;
                }
+               logger.info(String.format("Loading local Sone: %s", sone));
 
                /* initialize options. */
                sone.getOptions().addBooleanOption("AutoFollow", new DefaultOption<Boolean>(false));
@@ -1335,8 +1194,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                                logger.log(Level.WARNING, "Invalid reply found, aborting load!");
                                return;
                        }
-                       PostReplyBuilder postReplyBuilder = postReplyBuilderFactory.newPostReplyBuilder();
-                       postReplyBuilder.withId(replyId).from(sone.getId()).to(postId).withTime(replyTime).withText(replyText);
+                       PostReplyBuilder postReplyBuilder = postReplyBuilder().withId(replyId).from(sone.getId()).to(postId).withTime(replyTime).withText(replyText);
                        replies.add(postReplyBuilder.build());
                }
 
@@ -1387,7 +1245,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                                logger.log(Level.WARNING, "Invalid album found, aborting load!");
                                return;
                        }
-                       Album album = getAlbum(albumId).setSone(sone).setTitle(albumTitle).setDescription(albumDescription).setAlbumImage(albumImageId);
+                       Album album = getAlbum(albumId).setSone(sone).modify().setTitle(albumTitle).setDescription(albumDescription).setAlbumImage(albumImageId).update();
                        if (albumParentId != null) {
                                Album parentAlbum = getAlbum(albumParentId, false);
                                if (parentAlbum == null) {
@@ -1426,8 +1284,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                                logger.log(Level.WARNING, "Invalid album image encountered, aborting load!");
                                return;
                        }
-                       Image image = getImage(imageId).setSone(sone).setCreationTime(creationTime).setKey(key);
-                       image.setTitle(title).setDescription(description).setWidth(width).setHeight(height);
+                       Image image = getImage(imageId).modify().setSone(sone).setCreationTime(creationTime).setKey(key).setTitle(title).setDescription(description).setWidth(width).setHeight(height).update();
                        album.addImage(image);
                }
 
@@ -1456,7 +1313,12 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        for (String friendId : friends) {
                                followSone(sone, friendId);
                        }
-                       sone.setAlbums(topLevelAlbums);
+                       for (Album album : sone.getRootAlbum().getAlbums()) {
+                               sone.getRootAlbum().removeAlbum(album);
+                       }
+                       for (Album album : topLevelAlbums) {
+                               sone.getRootAlbum().addAlbum(album);
+                       }
                        soneInserters.get(sone).setLastInsertFingerprint(lastInsertFingerprint);
                }
                synchronized (knownSones) {
@@ -1464,16 +1326,16 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                                knownSones.add(friend);
                        }
                }
-               synchronized (knownPosts) {
-                       for (Post post : posts) {
-                               knownPosts.add(post.getId());
-                       }
+               database.storePosts(sone, posts);
+               for (Post post : posts) {
+                       post.setKnown(true);
                }
-               synchronized (knownReplies) {
-                       for (PostReply reply : replies) {
-                               knownReplies.add(reply.getId());
-                       }
+               database.storePostReplies(sone, replies);
+               for (PostReply reply : replies) {
+                       reply.setKnown(true);
                }
+
+               logger.info(String.format("Sone loaded successfully: %s", sone));
        }
 
        /**
@@ -1516,7 +1378,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);
        }
 
@@ -1534,22 +1396,20 @@ 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()) {
                        logger.log(Level.FINE, String.format("Tried to create post for non-local Sone: %s", sone));
                        return null;
                }
-               PostBuilder postBuilder = postBuilderFactory.newPostBuilder();
+               PostBuilder postBuilder = database.newPostBuilder();
                postBuilder.from(sone.getId()).randomId().withTime(time).withText(text.trim());
-               if (recipient != null) {
-                       postBuilder.to(recipient.getId());
+               if (recipient.isPresent()) {
+                       postBuilder.to(recipient.get().getId());
                }
                final Post post = postBuilder.build();
-               synchronized (posts) {
-                       posts.put(post.getId(), post);
-               }
+               database.storePost(post);
                eventBus.post(new NewPostFoundEvent(post));
                sone.addPost(post);
                touchConfiguration();
@@ -1577,10 +1437,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        logger.log(Level.WARNING, String.format("Tried to delete post of non-local Sone: %s", post.getSone()));
                        return;
                }
-               post.getSone().removePost(post);
-               synchronized (posts) {
-                       posts.remove(post.getId());
-               }
+               database.removePost(post);
                eventBus.post(new PostRemovedEvent(post));
                markPostKnown(post);
                touchConfiguration();
@@ -1595,13 +1452,9 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         */
        public void markPostKnown(Post post) {
                post.setKnown(true);
-               synchronized (knownPosts) {
-                       eventBus.post(new MarkPostKnownEvent(post));
-                       if (knownPosts.add(post.getId())) {
-                               touchConfiguration();
-                       }
-               }
-               for (PostReply reply : getReplies(post)) {
+               eventBus.post(new MarkPostKnownEvent(post));
+               touchConfiguration();
+               for (PostReply reply : getReplies(post.getId())) {
                        markReplyKnown(reply);
                }
        }
@@ -1668,15 +1521,11 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        logger.log(Level.FINE, String.format("Tried to create reply for non-local Sone: %s", sone));
                        return null;
                }
-               PostReplyBuilder postReplyBuilder = postReplyBuilderFactory.newPostReplyBuilder();
+               PostReplyBuilder postReplyBuilder = postReplyBuilder();
                postReplyBuilder.randomId().from(sone.getId()).to(post.getId()).currentTime().withText(text.trim());
                final PostReply reply = postReplyBuilder.build();
-               synchronized (replies) {
-                       replies.put(reply.getId(), reply);
-               }
-               synchronized (knownReplies) {
-                       eventBus.post(new NewPostReplyFoundEvent(reply));
-               }
+               database.storePostReply(reply);
+               eventBus.post(new NewPostReplyFoundEvent(reply));
                sone.addReply(reply);
                touchConfiguration();
                localElementTicker.schedule(new Runnable() {
@@ -1704,13 +1553,8 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        logger.log(Level.FINE, String.format("Tried to delete non-local reply: %s", reply));
                        return;
                }
-               synchronized (replies) {
-                       replies.remove(reply.getId());
-               }
-               synchronized (knownReplies) {
-                       markReplyKnown(reply);
-                       knownReplies.remove(reply.getId());
-               }
+               database.removePostReply(reply);
+               markReplyKnown(reply);
                sone.removeReply(reply);
                touchConfiguration();
        }
@@ -1723,12 +1567,11 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         *            The reply to mark as known
         */
        public void markReplyKnown(PostReply reply) {
+               boolean previouslyKnown = reply.isKnown();
                reply.setKnown(true);
-               synchronized (knownReplies) {
-                       eventBus.post(new MarkPostReplyKnownEvent(reply));
-                       if (knownReplies.add(reply.getId())) {
-                               touchConfiguration();
-                       }
+               eventBus.post(new MarkPostReplyKnownEvent(reply));
+               if (!previouslyKnown) {
+                       touchConfiguration();
                }
        }
 
@@ -1740,7 +1583,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         * @return The new album
         */
        public Album createAlbum(Sone sone) {
-               return createAlbum(sone, null);
+               return createAlbum(sone, sone.getRootAlbum());
        }
 
        /**
@@ -1754,16 +1597,10 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         * @return The new album
         */
        public Album createAlbum(Sone sone, Album parent) {
-               Album album = new Album();
-               synchronized (albums) {
-                       albums.put(album.getId(), album);
-               }
+               Album album = database.newAlbumBuilder().randomId().build();
+               database.storeAlbum(album);
                album.setSone(sone);
-               if (parent != null) {
-                       parent.addAlbum(album);
-               } else {
-                       sone.addAlbum(album);
-               }
+               parent.addAlbum(album);
                return album;
        }
 
@@ -1780,14 +1617,8 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                if (!album.isEmpty()) {
                        return;
                }
-               if (album.getParent() == null) {
-                       album.getSone().removeAlbum(album);
-               } else {
-                       album.getParent().removeAlbum(album);
-               }
-               synchronized (albums) {
-                       albums.remove(album.getId());
-               }
+               album.getParent().removeAlbum(album);
+               database.removeAlbum(album);
                touchConfiguration();
        }
 
@@ -1808,11 +1639,9 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                checkNotNull(temporaryImage, "temporaryImage must not be null");
                checkArgument(sone.isLocal(), "sone must be a local Sone");
                checkArgument(sone.equals(album.getSone()), "album must belong to the given Sone");
-               Image image = new Image(temporaryImage.getId()).setSone(sone).setCreationTime(System.currentTimeMillis());
+               Image image = database.newImageBuilder().withId(temporaryImage.getId()).build().modify().setSone(sone).setCreationTime(System.currentTimeMillis()).update();
                album.addImage(image);
-               synchronized (images) {
-                       images.put(image.getId(), image);
-               }
+               database.storeImage(image);
                imageInserter.insertImage(temporaryImage, image);
                return image;
        }
@@ -1830,9 +1659,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                checkArgument(image.getSone().isLocal(), "image must belong to a local Sone");
                deleteTemporaryImage(image.getId());
                image.getAlbum().removeImage(image);
-               synchronized (images) {
-                       images.remove(image.getId());
-               }
+               database.removeImage(image);
                touchConfiguration();
        }
 
@@ -1905,6 +1732,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                identityManager.start();
                webOfTrustUpdater.init();
                webOfTrustUpdater.start();
+               database.start();
        }
 
        /**
@@ -1939,6 +1767,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        }
                }
                saveConfiguration();
+               database.stop();
                webOfTrustUpdater.stop();
                updateChecker.stop();
                soneDownloader.stop();
@@ -1998,7 +1827,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());
                        }
@@ -2037,7 +1866,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        configuration.getStringValue(sonePrefix + "/Friends/" + friendCounter + "/ID").setValue(null);
 
                        /* save albums. first, collect in a flat structure, top-level first. */
-                       List<Album> albums = sone.getAllAlbums();
+                       List<Album> albums = FluentIterable.from(sone.getRootAlbum().getAlbums()).transformAndConcat(Album.FLATTENER).toList();
 
                        int albumCounter = 0;
                        for (Album album : albums) {
@@ -2045,7 +1874,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                                configuration.getStringValue(albumPrefix + "/ID").setValue(album.getId());
                                configuration.getStringValue(albumPrefix + "/Title").setValue(album.getTitle());
                                configuration.getStringValue(albumPrefix + "/Description").setValue(album.getDescription());
-                               configuration.getStringValue(albumPrefix + "/Parent").setValue(album.getParent() == null ? null : album.getParent().getId());
+                               configuration.getStringValue(albumPrefix + "/Parent").setValue(album.getParent().equals(sone.getRootAlbum()) ? null : album.getParent().getId());
                                configuration.getStringValue(albumPrefix + "/AlbumImage").setValue(album.getAlbumImage() == null ? null : album.getAlbumImage().getId());
                        }
                        configuration.getStringValue(sonePrefix + "/Albums/" + albumCounter + "/ID").setValue(null);
@@ -2127,8 +1956,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;
                                }
@@ -2136,22 +1965,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        }
 
                        /* save known posts. */
-                       int postCounter = 0;
-                       synchronized (knownPosts) {
-                               for (String knownPostId : knownPosts) {
-                                       configuration.getStringValue("KnownPosts/" + postCounter++ + "/ID").setValue(knownPostId);
-                               }
-                               configuration.getStringValue("KnownPosts/" + postCounter + "/ID").setValue(null);
-                       }
-
-                       /* save known replies. */
-                       int replyCounter = 0;
-                       synchronized (knownReplies) {
-                               for (String knownReplyId : knownReplies) {
-                                       configuration.getStringValue("KnownReplies/" + replyCounter++ + "/ID").setValue(knownReplyId);
-                               }
-                               configuration.getStringValue("KnownReplies/" + replyCounter + "/ID").setValue(null);
-                       }
+                       database.save();
 
                        /* save bookmarked posts. */
                        int bookmarkedPostCounter = 0;
@@ -2167,6 +1981,8 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
 
                } catch (ConfigurationException ce1) {
                        logger.log(Level.SEVERE, "Could not store configuration!", ce1);
+               } catch (DatabaseException de1) {
+                       logger.log(Level.SEVERE, "Could not save database!", de1);
                } finally {
                        synchronized (configuration) {
                                storingConfiguration = false;
@@ -2177,7 +1993,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>() {
@@ -2246,41 +2061,12 @@ 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;
                }
 
-               /* load known posts. */
-               int postCounter = 0;
-               while (true) {
-                       String knownPostId = configuration.getStringValue("KnownPosts/" + postCounter++ + "/ID").getValue(null);
-                       if (knownPostId == null) {
-                               break;
-                       }
-                       synchronized (knownPosts) {
-                               knownPosts.add(knownPostId);
-                       }
-               }
-
-               /* load known replies. */
-               int replyCounter = 0;
-               while (true) {
-                       String knownReplyId = configuration.getStringValue("KnownReplies/" + replyCounter++ + "/ID").getValue(null);
-                       if (knownReplyId == null) {
-                               break;
-                       }
-                       synchronized (knownReplies) {
-                               knownReplies.add(knownReplyId);
-                       }
-               }
-
                /* load bookmarked posts. */
                int bookmarkedPostCounter = 0;
                while (true) {
@@ -2321,7 +2107,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);
                }
        }
@@ -2336,7 +2121,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
        public void ownIdentityRemoved(OwnIdentityRemovedEvent ownIdentityRemovedEvent) {
                OwnIdentity ownIdentity = ownIdentityRemovedEvent.ownIdentity();
                logger.log(Level.FINEST, String.format("Removing OwnIdentity: %s", ownIdentity));
-               trustedIdentities.remove(ownIdentity);
+               trustedIdentities.removeAll(ownIdentity);
        }
 
        /**
@@ -2349,7 +2134,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
        public void identityAdded(IdentityAddedEvent identityAddedEvent) {
                Identity identity = identityAddedEvent.identity();
                logger.log(Level.FINEST, String.format("Adding Identity: %s", identity));
-               trustedIdentities.get(identityAddedEvent.ownIdentity()).add(identity);
+               trustedIdentities.put(identityAddedEvent.ownIdentity(), identity);
                addRemoteSone(identity);
        }
 
@@ -2368,6 +2153,9 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        @SuppressWarnings("synthetic-access")
                        public void run() {
                                Sone sone = getRemoteSone(identity.getId(), false);
+                               if (sone.isLocal()) {
+                                       return;
+                               }
                                sone.setIdentity(identity);
                                sone.setLatestEdition(Numbers.safeParseLong(identity.getProperty("Sone.LatestEdition"), sone.getLatestEdition()));
                                soneDownloader.addSone(sone);
@@ -2386,9 +2174,9 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
        public void identityRemoved(IdentityRemovedEvent identityRemovedEvent) {
                OwnIdentity ownIdentity = identityRemovedEvent.ownIdentity();
                Identity identity = identityRemovedEvent.identity();
-               trustedIdentities.get(ownIdentity).remove(identity);
+               trustedIdentities.remove(ownIdentity, identity);
                boolean foundIdentity = false;
-               for (Entry<OwnIdentity, Set<Identity>> trustedIdentity : trustedIdentities.entrySet()) {
+               for (Entry<OwnIdentity, Collection<Identity>> trustedIdentity : trustedIdentities.asMap().entrySet()) {
                        if (trustedIdentity.getKey().equals(ownIdentity)) {
                                continue;
                        }
@@ -2400,31 +2188,23 @@ 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()) {
-                                       posts.remove(post.getId());
-                                       eventBus.post(new PostRemovedEvent(post));
-                               }
-                       }
+               database.removePosts(sone.get());
+               for (Post post : sone.get().getPosts()) {
+                       eventBus.post(new PostRemovedEvent(post));
                }
-               synchronized (replies) {
-                       synchronized (knownReplies) {
-                               for (PostReply reply : sone.getReplies()) {
-                                       replies.remove(reply.getId());
-                                       eventBus.post(new PostReplyRemovedEvent(reply));
-                               }
-                       }
+               database.removePostReplies(sone.get());
+               for (PostReply reply : sone.get().getReplies()) {
+                       eventBus.post(new PostReplyRemovedEvent(reply));
                }
                synchronized (sones) {
                        sones.remove(identity.getId());
                }
-               eventBus.post(new SoneRemovedEvent(sone));
+               eventBus.post(new SoneRemovedEvent(sone.get()));
        }
 
        /**
@@ -2436,7 +2216,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
        @Subscribe
        public void imageInsertFinished(ImageInsertFinishedEvent imageInsertFinishedEvent) {
                logger.log(Level.WARNING, String.format("Image insert finished for %s: %s", imageInsertFinishedEvent.image(), imageInsertFinishedEvent.resultingUri()));
-               imageInsertFinishedEvent.image().setKey(imageInsertFinishedEvent.resultingUri().toString());
+               imageInsertFinishedEvent.image().modify().setKey(imageInsertFinishedEvent.resultingUri().toString()).update();
                deleteTemporaryImage(imageInsertFinishedEvent.image().getId());
                touchConfiguration();
        }