Move album parsing to new configuration parser.
[Sone.git] / src / main / java / net / pterodactylus / sone / core / Core.java
index 4ee0258..e08acd1 100644 (file)
@@ -20,6 +20,7 @@ package net.pterodactylus.sone.core;
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Predicates.not;
+import static java.lang.String.format;
 import static net.pterodactylus.sone.data.Sone.LOCAL_SONE_FILTER;
 
 import java.net.MalformedURLException;
@@ -38,9 +39,11 @@ import java.util.concurrent.TimeUnit;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidAlbumFound;
+import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidParentAlbumFound;
+import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidPostFound;
+import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidPostReplyFound;
 import net.pterodactylus.sone.core.Options.DefaultOption;
-import net.pterodactylus.sone.core.Options.Option;
-import net.pterodactylus.sone.core.Options.OptionWatcher;
 import net.pterodactylus.sone.core.SoneInserter.SetInsertionDelay;
 import net.pterodactylus.sone.core.event.ImageInsertFinishedEvent;
 import net.pterodactylus.sone.core.event.MarkPostKnownEvent;
@@ -67,6 +70,7 @@ 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.AlbumBuilder;
 import net.pterodactylus.sone.database.Database;
 import net.pterodactylus.sone.database.DatabaseException;
 import net.pterodactylus.sone.database.PostBuilder;
@@ -75,8 +79,6 @@ 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.fcp.FcpInterface.SetActive;
 import net.pterodactylus.sone.freenet.wot.Identity;
 import net.pterodactylus.sone.freenet.wot.IdentityManager;
 import net.pterodactylus.sone.freenet.wot.OwnIdentity;
@@ -94,6 +96,7 @@ import net.pterodactylus.util.number.Numbers;
 import net.pterodactylus.util.service.AbstractService;
 import net.pterodactylus.util.thread.NamedThreadFactory;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Optional;
 import com.google.common.base.Predicates;
 import com.google.common.collect.FluentIterable;
@@ -104,6 +107,7 @@ import com.google.common.collect.Multimaps;
 import com.google.common.eventbus.EventBus;
 import com.google.common.eventbus.Subscribe;
 import com.google.inject.Inject;
+import com.google.inject.Singleton;
 
 import freenet.keys.FreenetURI;
 
@@ -112,6 +116,7 @@ import freenet.keys.FreenetURI;
  *
  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
+@Singleton
 public class Core extends AbstractService implements SoneProvider, PostProvider, PostReplyProvider {
 
        /** The logger. */
@@ -222,14 +227,28 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                this.configuration = configuration;
                this.freenetInterface = freenetInterface;
                this.identityManager = identityManager;
-               this.soneDownloader = new SoneDownloader(this, freenetInterface);
-               this.imageInserter = new ImageInserter(freenetInterface);
+               this.soneDownloader = new SoneDownloaderImpl(this, freenetInterface);
+               this.imageInserter = new ImageInserter(freenetInterface, freenetInterface.new InsertTokenSupplier());
                this.updateChecker = new UpdateChecker(eventBus, freenetInterface);
                this.webOfTrustUpdater = webOfTrustUpdater;
                this.eventBus = eventBus;
                this.database = database;
        }
 
+       @VisibleForTesting
+       protected Core(Configuration configuration, FreenetInterface freenetInterface, IdentityManager identityManager, SoneDownloader soneDownloader, ImageInserter imageInserter, UpdateChecker updateChecker, WebOfTrustUpdater webOfTrustUpdater, EventBus eventBus, Database database) {
+               super("Sone Core");
+               this.configuration = configuration;
+               this.freenetInterface = freenetInterface;
+               this.identityManager = identityManager;
+               this.soneDownloader = soneDownloader;
+               this.imageInserter = imageInserter;
+               this.updateChecker = updateChecker;
+               this.webOfTrustUpdater = webOfTrustUpdater;
+               this.eventBus = eventBus;
+               this.database = database;
+       }
+
        //
        // ACCESSORS
        //
@@ -433,22 +452,6 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
        }
 
        /**
-        * Returns whether the target Sone is trusted by the origin Sone.
-        *
-        * @param origin
-        *            The origin Sone
-        * @param target
-        *            The target Sone
-        * @return {@code true} if the target Sone is trusted by the origin Sone
-        */
-       public boolean isSoneTrusted(Sone origin, Sone target) {
-               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.containsEntry(origin.getIdentity(), target.getIdentity());
-       }
-
-       /**
         * Returns a post builder.
         *
         * @return A new post builder
@@ -585,16 +588,8 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                return posts;
        }
 
-       /**
-        * Returns the album with the given ID, creating a new album if no album
-        * with the given ID can be found.
-        *
-        * @param albumId
-        *            The ID of the album
-        * @return The album with the given ID
-        */
-       public Album getAlbum(String albumId) {
-               return getAlbum(albumId, true);
+       public AlbumBuilder albumBuilder() {
+               return database.newAlbumBuilder();
        }
 
        /**
@@ -603,23 +598,11 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         *
         * @param albumId
         *            The ID of the album
-        * @param create
-        *            {@code true} to create a new album if none exists for the
-        *            given ID
         * @return The album with the given ID, or {@code null} if no album with the
-        *         given ID exists and {@code create} is {@code false}
+        *         given ID exists
         */
-       public Album getAlbum(String albumId, boolean create) {
-               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;
+       public Album getAlbum(String albumId) {
+               return database.getAlbum(albumId).orNull();
        }
 
        /**
@@ -755,12 +738,6 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        return null;
                }
                Sone sone = addLocalSone(ownIdentity);
-               sone.getOptions().addBooleanOption("AutoFollow", new DefaultOption<Boolean>(false));
-               sone.getOptions().addBooleanOption("EnableSoneInsertNotifications", new DefaultOption<Boolean>(false));
-               sone.getOptions().addBooleanOption("ShowNotification/NewSones", new DefaultOption<Boolean>(true));
-               sone.getOptions().addBooleanOption("ShowNotification/NewPosts", new DefaultOption<Boolean>(true));
-               sone.getOptions().addBooleanOption("ShowNotification/NewReplies", new DefaultOption<Boolean>(true));
-               sone.getOptions().addEnumOption("ShowCustomAvatars", new DefaultOption<ShowCustomAvatars>(ShowCustomAvatars.NEVER));
 
                followSone(sone, "nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI");
                touchConfiguration();
@@ -796,22 +773,14 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                                if (newSone) {
                                        eventBus.post(new NewSoneFoundEvent(sone));
                                        for (Sone localSone : getLocalSones()) {
-                                               if (localSone.getOptions().getBooleanOption("AutoFollow").get()) {
+                                               if (localSone.getOptions().isAutoFollow()) {
                                                        followSone(localSone, sone.getId());
                                                }
                                        }
                                }
                        }
                        soneDownloader.addSone(sone);
-                       soneDownloaders.execute(new Runnable() {
-
-                               @Override
-                               @SuppressWarnings("synthetic-access")
-                               public void run() {
-                                       soneDownloader.fetchSone(sone, sone.getRequestUri());
-                               }
-
-                       });
+                       soneDownloaders.execute(soneDownloader.fetchSoneWithUriAction(sone));
                        return sone;
                }
        }
@@ -1115,14 +1084,6 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                }
                logger.info(String.format("Loading local Sone: %s", sone));
 
-               /* initialize options. */
-               sone.getOptions().addBooleanOption("AutoFollow", new DefaultOption<Boolean>(false));
-               sone.getOptions().addBooleanOption("EnableSoneInsertNotifications", new DefaultOption<Boolean>(false));
-               sone.getOptions().addBooleanOption("ShowNotification/NewSones", new DefaultOption<Boolean>(true));
-               sone.getOptions().addBooleanOption("ShowNotification/NewPosts", new DefaultOption<Boolean>(true));
-               sone.getOptions().addBooleanOption("ShowNotification/NewReplies", new DefaultOption<Boolean>(true));
-               sone.getOptions().addEnumOption("ShowCustomAvatars", new DefaultOption<ShowCustomAvatars>(ShowCustomAvatars.NEVER));
-
                /* load Sone. */
                String sonePrefix = "Sone/" + sone.getId();
                Long soneTime = configuration.getLongValue(sonePrefix + "/Time").getValue(null);
@@ -1133,126 +1094,50 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                String lastInsertFingerprint = configuration.getStringValue(sonePrefix + "/LastInsertFingerprint").getValue("");
 
                /* load profile. */
-               Profile profile = new Profile(sone);
-               profile.setFirstName(configuration.getStringValue(sonePrefix + "/Profile/FirstName").getValue(null));
-               profile.setMiddleName(configuration.getStringValue(sonePrefix + "/Profile/MiddleName").getValue(null));
-               profile.setLastName(configuration.getStringValue(sonePrefix + "/Profile/LastName").getValue(null));
-               profile.setBirthDay(configuration.getIntValue(sonePrefix + "/Profile/BirthDay").getValue(null));
-               profile.setBirthMonth(configuration.getIntValue(sonePrefix + "/Profile/BirthMonth").getValue(null));
-               profile.setBirthYear(configuration.getIntValue(sonePrefix + "/Profile/BirthYear").getValue(null));
-
-               /* load profile fields. */
-               while (true) {
-                       String fieldPrefix = sonePrefix + "/Profile/Fields/" + profile.getFields().size();
-                       String fieldName = configuration.getStringValue(fieldPrefix + "/Name").getValue(null);
-                       if (fieldName == null) {
-                               break;
-                       }
-                       String fieldValue = configuration.getStringValue(fieldPrefix + "/Value").getValue("");
-                       profile.addField(fieldName).setValue(fieldValue);
-               }
+               ConfigurationSoneParser configurationSoneParser = new ConfigurationSoneParser(configuration, sone);
+               Profile profile = configurationSoneParser.parseProfile();
 
                /* load posts. */
-               Set<Post> posts = new HashSet<Post>();
-               while (true) {
-                       String postPrefix = sonePrefix + "/Posts/" + posts.size();
-                       String postId = configuration.getStringValue(postPrefix + "/ID").getValue(null);
-                       if (postId == null) {
-                               break;
-                       }
-                       String postRecipientId = configuration.getStringValue(postPrefix + "/Recipient").getValue(null);
-                       long postTime = configuration.getLongValue(postPrefix + "/Time").getValue((long) 0);
-                       String postText = configuration.getStringValue(postPrefix + "/Text").getValue(null);
-                       if ((postTime == 0) || (postText == null)) {
-                               logger.log(Level.WARNING, "Invalid post found, aborting load!");
-                               return;
-                       }
-                       PostBuilder postBuilder = postBuilder().withId(postId).from(sone.getId()).withTime(postTime).withText(postText);
-                       if ((postRecipientId != null) && (postRecipientId.length() == 43)) {
-                               postBuilder.to(postRecipientId);
-                       }
-                       posts.add(postBuilder.build());
+               Collection<Post> posts;
+               try {
+                       posts = configurationSoneParser.parsePosts(database);
+               } catch (InvalidPostFound ipf) {
+                       logger.log(Level.WARNING, "Invalid post found, aborting load!");
+                       return;
                }
 
                /* load replies. */
-               Set<PostReply> replies = new HashSet<PostReply>();
-               while (true) {
-                       String replyPrefix = sonePrefix + "/Replies/" + replies.size();
-                       String replyId = configuration.getStringValue(replyPrefix + "/ID").getValue(null);
-                       if (replyId == null) {
-                               break;
-                       }
-                       String postId = configuration.getStringValue(replyPrefix + "/Post/ID").getValue(null);
-                       long replyTime = configuration.getLongValue(replyPrefix + "/Time").getValue((long) 0);
-                       String replyText = configuration.getStringValue(replyPrefix + "/Text").getValue(null);
-                       if ((postId == null) || (replyTime == 0) || (replyText == null)) {
-                               logger.log(Level.WARNING, "Invalid reply found, aborting load!");
-                               return;
-                       }
-                       PostReplyBuilder postReplyBuilder = postReplyBuilder().withId(replyId).from(sone.getId()).to(postId).withTime(replyTime).withText(replyText);
-                       replies.add(postReplyBuilder.build());
+               Collection<PostReply> replies;
+               try {
+                       replies = configurationSoneParser.parsePostReplies(database);
+               } catch (InvalidPostReplyFound iprf) {
+                       logger.log(Level.WARNING, "Invalid reply found, aborting load!");
+                       return;
                }
 
                /* load post likes. */
-               Set<String> likedPostIds = new HashSet<String>();
-               while (true) {
-                       String likedPostId = configuration.getStringValue(sonePrefix + "/Likes/Post/" + likedPostIds.size() + "/ID").getValue(null);
-                       if (likedPostId == null) {
-                               break;
-                       }
-                       likedPostIds.add(likedPostId);
-               }
+               Set<String> likedPostIds =
+                               configurationSoneParser.parseLikedPostIds();
 
                /* load reply likes. */
-               Set<String> likedReplyIds = new HashSet<String>();
-               while (true) {
-                       String likedReplyId = configuration.getStringValue(sonePrefix + "/Likes/Reply/" + likedReplyIds.size() + "/ID").getValue(null);
-                       if (likedReplyId == null) {
-                               break;
-                       }
-                       likedReplyIds.add(likedReplyId);
-               }
+               Set<String> likedReplyIds =
+                               configurationSoneParser.parseLikedPostReplyIds();
 
                /* load friends. */
-               Set<String> friends = new HashSet<String>();
-               while (true) {
-                       String friendId = configuration.getStringValue(sonePrefix + "/Friends/" + friends.size() + "/ID").getValue(null);
-                       if (friendId == null) {
-                               break;
-                       }
-                       friends.add(friendId);
-               }
+               Set<String> friends = configurationSoneParser.parseFriends();
 
                /* load albums. */
-               List<Album> topLevelAlbums = new ArrayList<Album>();
-               int albumCounter = 0;
-               while (true) {
-                       String albumPrefix = sonePrefix + "/Albums/" + albumCounter++;
-                       String albumId = configuration.getStringValue(albumPrefix + "/ID").getValue(null);
-                       if (albumId == null) {
-                               break;
-                       }
-                       String albumTitle = configuration.getStringValue(albumPrefix + "/Title").getValue(null);
-                       String albumDescription = configuration.getStringValue(albumPrefix + "/Description").getValue(null);
-                       String albumParentId = configuration.getStringValue(albumPrefix + "/Parent").getValue(null);
-                       String albumImageId = configuration.getStringValue(albumPrefix + "/AlbumImage").getValue(null);
-                       if ((albumTitle == null) || (albumDescription == null)) {
-                               logger.log(Level.WARNING, "Invalid album found, aborting load!");
-                               return;
-                       }
-                       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) {
-                                       logger.log(Level.WARNING, String.format("Invalid parent album ID: %s", albumParentId));
-                                       return;
-                               }
-                               parentAlbum.addAlbum(album);
-                       } else {
-                               if (!topLevelAlbums.contains(album)) {
-                                       topLevelAlbums.add(album);
-                               }
-                       }
+               List<Album> topLevelAlbums;
+               try {
+                       topLevelAlbums =
+                                       configurationSoneParser.parseTopLevelAlbums(database);
+               } catch (InvalidAlbumFound iaf) {
+                       logger.log(Level.WARNING, "Invalid album found, aborting load!");
+                       return;
+               } catch (InvalidParentAlbumFound ipaf) {
+                       logger.log(Level.WARNING, format("Invalid parent album ID: %s",
+                                       ipaf.getAlbumParentId()));
+                       return;
                }
 
                /* load images. */
@@ -1274,7 +1159,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                                logger.log(Level.WARNING, "Invalid image found, aborting load!");
                                return;
                        }
-                       Album album = getAlbum(albumId, false);
+                       Album album = getAlbum(albumId);
                        if (album == null) {
                                logger.log(Level.WARNING, "Invalid album image encountered, aborting load!");
                                return;
@@ -1290,12 +1175,12 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                }
 
                /* load options. */
-               sone.getOptions().getBooleanOption("AutoFollow").set(configuration.getBooleanValue(sonePrefix + "/Options/AutoFollow").getValue(null));
-               sone.getOptions().getBooleanOption("EnableSoneInsertNotifications").set(configuration.getBooleanValue(sonePrefix + "/Options/EnableSoneInsertNotifications").getValue(null));
-               sone.getOptions().getBooleanOption("ShowNotification/NewSones").set(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewSones").getValue(null));
-               sone.getOptions().getBooleanOption("ShowNotification/NewPosts").set(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewPosts").getValue(null));
-               sone.getOptions().getBooleanOption("ShowNotification/NewReplies").set(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewReplies").getValue(null));
-               sone.getOptions().<ShowCustomAvatars> getEnumOption("ShowCustomAvatars").set(ShowCustomAvatars.valueOf(configuration.getStringValue(sonePrefix + "/Options/ShowCustomAvatars").getValue(ShowCustomAvatars.NEVER.name())));
+               sone.getOptions().setAutoFollow(configuration.getBooleanValue(sonePrefix + "/Options/AutoFollow").getValue(null));
+               sone.getOptions().setSoneInsertNotificationEnabled(configuration.getBooleanValue(sonePrefix + "/Options/EnableSoneInsertNotifications").getValue(null));
+               sone.getOptions().setShowNewSoneNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewSones").getValue(null));
+               sone.getOptions().setShowNewPostNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewPosts").getValue(null));
+               sone.getOptions().setShowNewReplyNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewReplies").getValue(null));
+               sone.getOptions().setShowCustomAvatars(ShowCustomAvatars.valueOf(configuration.getStringValue(sonePrefix + "/Options/ShowCustomAvatars").getValue(ShowCustomAvatars.NEVER.name())));
 
                /* if we’re still here, Sone was loaded successfully. */
                synchronized (sone) {
@@ -1338,34 +1223,6 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         *
         * @param sone
         *            The Sone that creates the post
-        * @param text
-        *            The text of the post
-        * @return The created post
-        */
-       public Post createPost(Sone sone, String text) {
-               return createPost(sone, System.currentTimeMillis(), text);
-       }
-
-       /**
-        * Creates a new post.
-        *
-        * @param sone
-        *            The Sone that creates the post
-        * @param time
-        *            The time of the post
-        * @param text
-        *            The text of the post
-        * @return The created post
-        */
-       public Post createPost(Sone sone, long time, String text) {
-               return createPost(sone, null, time, text);
-       }
-
-       /**
-        * Creates a new post.
-        *
-        * @param sone
-        *            The Sone that creates the post
         * @param recipient
         *            The recipient Sone, or {@code null} if this post does not have
         *            a recipient
@@ -1408,16 +1265,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                eventBus.post(new NewPostFoundEvent(post));
                sone.addPost(post);
                touchConfiguration();
-               localElementTicker.schedule(new Runnable() {
-
-                       /**
-                        * {@inheritDoc}
-                        */
-                       @Override
-                       public void run() {
-                               markPostKnown(post);
-                       }
-               }, 10, TimeUnit.SECONDS);
+               localElementTicker.schedule(new MarkPostKnown(post), 10, TimeUnit.SECONDS);
                return post;
        }
 
@@ -1455,16 +1303,6 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
        }
 
        /**
-        * Bookmarks the given post.
-        *
-        * @param post
-        *            The post to bookmark
-        */
-       public void bookmark(Post post) {
-               bookmarkPost(post.getId());
-       }
-
-       /**
         * Bookmarks the post with the given ID.
         *
         * @param id
@@ -1523,16 +1361,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                eventBus.post(new NewPostReplyFoundEvent(reply));
                sone.addReply(reply);
                touchConfiguration();
-               localElementTicker.schedule(new Runnable() {
-
-                       /**
-                        * {@inheritDoc}
-                        */
-                       @Override
-                       public void run() {
-                               markReplyKnown(reply);
-                       }
-               }, 10, TimeUnit.SECONDS);
+               localElementTicker.schedule(new MarkReplyKnown(reply), 10, TimeUnit.SECONDS);
                return reply;
        }
 
@@ -1571,17 +1400,6 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
        }
 
        /**
-        * Creates a new top-level album for the given Sone.
-        *
-        * @param sone
-        *            The Sone to create the album for
-        * @return The new album
-        */
-       public Album createAlbum(Sone sone) {
-               return createAlbum(sone, sone.getRootAlbum());
-       }
-
-       /**
         * Creates a new album for the given Sone.
         *
         * @param sone
@@ -1592,9 +1410,8 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         * @return The new album
         */
        public Album createAlbum(Sone sone, Album parent) {
-               Album album = database.newAlbumBuilder().randomId().build();
+               Album album = database.newAlbumBuilder().randomId().by(sone).build();
                database.storeAlbum(album);
-               album.setSone(sone);
                parent.addAlbum(album);
                return album;
        }
@@ -1645,7 +1462,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         * Deletes the given image. This method will also delete a matching
         * temporary image.
         *
-        * @see #deleteTemporaryImage(TemporaryImage)
+        * @see #deleteTemporaryImage(String)
         * @param image
         *            The image to delete
         */
@@ -1677,17 +1494,6 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
        }
 
        /**
-        * Deletes the given temporary image.
-        *
-        * @param temporaryImage
-        *            The temporary image to delete
-        */
-       public void deleteTemporaryImage(TemporaryImage temporaryImage) {
-               checkNotNull(temporaryImage, "temporaryImage must not be null");
-               deleteTemporaryImage(temporaryImage.getId());
-       }
-
-       /**
         * Deletes the temporary image with the given ID.
         *
         * @param imageId
@@ -1895,12 +1701,12 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        configuration.getStringValue(sonePrefix + "/Images/" + imageCounter + "/ID").setValue(null);
 
                        /* save options. */
-                       configuration.getBooleanValue(sonePrefix + "/Options/AutoFollow").setValue(sone.getOptions().getBooleanOption("AutoFollow").getReal());
-                       configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewSones").setValue(sone.getOptions().getBooleanOption("ShowNotification/NewSones").getReal());
-                       configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewPosts").setValue(sone.getOptions().getBooleanOption("ShowNotification/NewPosts").getReal());
-                       configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewReplies").setValue(sone.getOptions().getBooleanOption("ShowNotification/NewReplies").getReal());
-                       configuration.getBooleanValue(sonePrefix + "/Options/EnableSoneInsertNotifications").setValue(sone.getOptions().getBooleanOption("EnableSoneInsertNotifications").getReal());
-                       configuration.getStringValue(sonePrefix + "/Options/ShowCustomAvatars").setValue(sone.getOptions().<ShowCustomAvatars> getEnumOption("ShowCustomAvatars").get().name());
+                       configuration.getBooleanValue(sonePrefix + "/Options/AutoFollow").setValue(sone.getOptions().isAutoFollow());
+                       configuration.getBooleanValue(sonePrefix + "/Options/EnableSoneInsertNotifications").setValue(sone.getOptions().isSoneInsertNotificationEnabled());
+                       configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewSones").setValue(sone.getOptions().isShowNewSoneNotifications());
+                       configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewPosts").setValue(sone.getOptions().isShowNewPostNotifications());
+                       configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewReplies").setValue(sone.getOptions().isShowNewReplyNotifications());
+                       configuration.getStringValue(sonePrefix + "/Options/ShowCustomAvatars").setValue(sone.getOptions().getShowCustomAvatars().name());
 
                        configuration.save();
 
@@ -2000,15 +1806,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                options.addIntegerOption("NegativeTrust", new DefaultOption<Integer>(-25, new IntegerRangePredicate(-100, 100)));
                options.addStringOption("TrustComment", new DefaultOption<String>("Set from Sone Web Interface"));
                options.addBooleanOption("ActivateFcpInterface", new DefaultOption<Boolean>(false, fcpInterface.new SetActive()));
-               options.addIntegerOption("FcpFullAccessRequired", new DefaultOption<Integer>(2, new OptionWatcher<Integer>() {
-
-                       @Override
-                       @SuppressWarnings("synthetic-access")
-                       public void optionChanged(Option<Integer> option, Integer oldValue, Integer newValue) {
-                               fcpInterface.setFullAccessRequired(FullAccessRequired.values()[newValue]);
-                       }
-
-               }));
+               options.addIntegerOption("FcpFullAccessRequired", new DefaultOption<Integer>(2, fcpInterface.new SetFullAccessRequired()));
 
                loadConfigurationValue("InsertionDelay");
                loadConfigurationValue("PostsPerPage");
@@ -2127,22 +1925,15 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         */
        @Subscribe
        public void identityUpdated(IdentityUpdatedEvent identityUpdatedEvent) {
-               final Identity identity = identityUpdatedEvent.identity();
-               soneDownloaders.execute(new Runnable() {
-
-                       @Override
-                       @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);
-                               soneDownloader.fetchSone(sone);
-                       }
-               });
+               Identity identity = identityUpdatedEvent.identity();
+               final 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);
+               soneDownloaders.execute(soneDownloader.fetchSoneAction(sone));
        }
 
        /**
@@ -2202,4 +1993,36 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                touchConfiguration();
        }
 
+       @VisibleForTesting
+       class MarkPostKnown implements Runnable {
+
+               private final Post post;
+
+               public MarkPostKnown(Post post) {
+                       this.post = post;
+               }
+
+               @Override
+               public void run() {
+                       markPostKnown(post);
+               }
+
+       }
+
+       @VisibleForTesting
+       class MarkReplyKnown implements Runnable {
+
+               private final PostReply postReply;
+
+               public MarkReplyKnown(PostReply postReply) {
+                       this.postReply = postReply;
+               }
+
+               @Override
+               public void run() {
+                       markReplyKnown(postReply);
+               }
+
+       }
+
 }