Move friend-related functionality into the database.
[Sone.git] / src / main / java / net / pterodactylus / sone / core / Core.java
index f3927d9..17905f3 100644 (file)
@@ -23,7 +23,9 @@ import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.primitives.Longs.tryParse;
 import static java.lang.String.format;
 import static java.util.logging.Level.WARNING;
+import static java.util.logging.Logger.getLogger;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -79,7 +81,6 @@ import net.pterodactylus.sone.database.PostReplyBuilder;
 import net.pterodactylus.sone.database.PostReplyProvider;
 import net.pterodactylus.sone.database.SoneBuilder;
 import net.pterodactylus.sone.database.SoneProvider;
-import net.pterodactylus.sone.fcp.FcpInterface;
 import net.pterodactylus.sone.freenet.wot.Identity;
 import net.pterodactylus.sone.freenet.wot.IdentityManager;
 import net.pterodactylus.sone.freenet.wot.OwnIdentity;
@@ -91,12 +92,11 @@ import net.pterodactylus.sone.freenet.wot.event.OwnIdentityRemovedEvent;
 import net.pterodactylus.sone.main.SonePlugin;
 import net.pterodactylus.util.config.Configuration;
 import net.pterodactylus.util.config.ConfigurationException;
-import net.pterodactylus.util.logging.Logging;
-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.Function;
 import com.google.common.base.Optional;
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.HashMultimap;
@@ -116,7 +116,7 @@ import com.google.inject.Singleton;
 public class Core extends AbstractService implements SoneProvider, PostProvider, PostReplyProvider {
 
        /** The logger. */
-       private static final Logger logger = Logging.getLogger(Core.class);
+       private static final Logger logger = getLogger("Sone.Core");
 
        /** The start time. */
        private final long startupTime = System.currentTimeMillis();
@@ -154,9 +154,6 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
        /** The trust updater. */
        private final WebOfTrustUpdater webOfTrustUpdater;
 
-       /** The FCP interface. */
-       private volatile FcpInterface fcpInterface;
-
        /** The times Sones were followed. */
        private final Map<String, Long> soneFollowingTimes = new HashMap<String, Long>();
 
@@ -178,10 +175,6 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
        /** 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 Multimap<OwnIdentity, Identity> trustedIdentities = Multimaps.synchronizedSetMultimap(HashMultimap.<OwnIdentity, Identity>create());
 
@@ -281,16 +274,6 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
        }
 
        /**
-        * Sets the FCP interface to use.
-        *
-        * @param fcpInterface
-        *            The FCP interface to use
-        */
-       public void setFcpInterface(FcpInterface fcpInterface) {
-               this.fcpInterface = fcpInterface;
-       }
-
-       /**
         * Returns the Sone rescuer for the given local Sone.
         *
         * @param sone
@@ -336,6 +319,11 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                return database.getSones();
        }
 
+       @Override
+       public Function<String, Optional<Sone>> soneLoader() {
+               return database.soneLoader();
+       }
+
        /**
         * Returns the Sone with the given ID, regardless whether it’s local or
         * remote.
@@ -521,21 +509,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         *         otherwise
         */
        public boolean isBookmarked(Post post) {
-               return isPostBookmarked(post.getId());
-       }
-
-       /**
-        * Returns whether the post with the given ID is bookmarked.
-        *
-        * @param id
-        *            The ID of the post to check
-        * @return {@code true} if the post with the given ID is bookmarked,
-        *         {@code false} otherwise
-        */
-       public boolean isPostBookmarked(String id) {
-               synchronized (bookmarkedPosts) {
-                       return bookmarkedPosts.contains(id);
-               }
+               return database.isPostBookmarked(post);
        }
 
        /**
@@ -544,16 +518,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         * @return All bookmarked posts
         */
        public Set<Post> getBookmarkedPosts() {
-               Set<Post> posts = new HashSet<Post>();
-               synchronized (bookmarkedPosts) {
-                       for (String bookmarkedPostId : bookmarkedPosts) {
-                               Optional<Post> post = getPost(bookmarkedPostId);
-                               if (post.isPresent()) {
-                                       posts.add(post.get());
-                               }
-                       }
-               }
-               return posts;
+               return database.getBookmarkedPosts();
        }
 
        public AlbumBuilder albumBuilder() {
@@ -676,10 +641,9 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                }
                logger.info(String.format("Adding Sone from OwnIdentity: %s", ownIdentity));
                Sone sone = database.newSoneBuilder().local().from(ownIdentity).build();
-               sone.setLatestEdition(Numbers.safeParseLong(ownIdentity.getProperty("Sone.LatestEdition"), 0L));
+               sone.setLatestEdition(fromNullable(tryParse(ownIdentity.getProperty("Sone.LatestEdition"))).or(0L));
                sone.setClient(new Client("Sone", SonePlugin.VERSION.toString()));
                sone.setKnown(true);
-               /* TODO - load posts ’n stuff */
                SoneInserter soneInserter = new SoneInserter(this, eventBus, freenetInterface, ownIdentity.getId());
                eventBus.register(soneInserter);
                synchronized (soneInserters) {
@@ -762,7 +726,7 @@ 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.addFriend(soneId);
+               database.addFriend(sone, soneId);
                synchronized (soneFollowingTimes) {
                        if (!soneFollowingTimes.containsKey(soneId)) {
                                long now = System.currentTimeMillis();
@@ -797,7 +761,7 @@ 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");
-               sone.removeFriend(soneId);
+               database.removeFriend(sone, soneId);
                boolean unfollowedSoneStillFollowed = false;
                for (Sone localSone : getLocalSones()) {
                        unfollowedSoneStillFollowed |= localSone.hasFriend(soneId);
@@ -907,42 +871,12 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                                logger.log(Level.FINE, String.format("Downloaded Sone %s is not newer than stored Sone %s.", sone, storedSone));
                                return;
                        }
-                       /* find removed posts. */
-                       SoneChangeDetector soneChangeDetector = new SoneChangeDetector(storedSone.get());
-                       soneChangeDetector.onNewPosts(new PostProcessor() {
-                               @Override
-                               public void processPost(Post post) {
-                                       if (post.getTime() < getSoneFollowingTime(sone)) {
-                                               post.setKnown(true);
-                                       } else if (!post.isKnown()) {
-                                               eventBus.post(new NewPostFoundEvent(post));
-                                       }
-                               }
-                       });
-                       soneChangeDetector.onRemovedPosts(new PostProcessor() {
-                               @Override
-                               public void processPost(Post post) {
-                                       eventBus.post(new PostRemovedEvent(post));
-                               }
-                       });
-                       soneChangeDetector.onNewPostReplies(new PostReplyProcessor() {
-                               @Override
-                               public void processPostReply(PostReply postReply) {
-                                       if (postReply.getTime() < getSoneFollowingTime(sone)) {
-                                               postReply.setKnown(true);
-                                       } else if (!postReply.isKnown()) {
-                                               eventBus.post(new NewPostReplyFoundEvent(postReply));
-                                       }
-                               }
-                       });
-                       soneChangeDetector.onRemovedPostReplies(new PostReplyProcessor() {
-                               @Override
-                               public void processPostReply(PostReply postReply) {
-                                       eventBus.post(new PostReplyRemovedEvent(postReply));
-                               }
-                       });
-                       soneChangeDetector.detectChanges(sone);
+                       List<Object> events =
+                                       collectEventsForChangesInSone(storedSone.get(), sone);
                        database.storeSone(sone);
+                       for (Object event : events) {
+                               eventBus.post(event);
+                       }
                        sone.setOptions(storedSone.get().getOptions());
                        sone.setKnown(storedSone.get().isKnown());
                        sone.setStatus((sone.getTime() == 0) ? SoneStatus.unknown : SoneStatus.idle);
@@ -952,6 +886,47 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                }
        }
 
+       private List<Object> collectEventsForChangesInSone(Sone oldSone,
+                       final Sone newSone) {
+               final List<Object> events = new ArrayList<Object>();
+               SoneChangeDetector soneChangeDetector = new SoneChangeDetector(
+                               oldSone);
+               soneChangeDetector.onNewPosts(new PostProcessor() {
+                       @Override
+                       public void processPost(Post post) {
+                               if (post.getTime() < getSoneFollowingTime(newSone)) {
+                                       post.setKnown(true);
+                               } else if (!post.isKnown()) {
+                                       events.add(new NewPostFoundEvent(post));
+                               }
+                       }
+               });
+               soneChangeDetector.onRemovedPosts(new PostProcessor() {
+                       @Override
+                       public void processPost(Post post) {
+                               events.add(new PostRemovedEvent(post));
+                       }
+               });
+               soneChangeDetector.onNewPostReplies(new PostReplyProcessor() {
+                       @Override
+                       public void processPostReply(PostReply postReply) {
+                               if (postReply.getTime() < getSoneFollowingTime(newSone)) {
+                                       postReply.setKnown(true);
+                               } else if (!postReply.isKnown()) {
+                                       events.add(new NewPostReplyFoundEvent(postReply));
+                               }
+                       }
+               });
+               soneChangeDetector.onRemovedPostReplies(new PostReplyProcessor() {
+                       @Override
+                       public void processPostReply(PostReply postReply) {
+                               events.add(new PostReplyRemovedEvent(postReply));
+                       }
+               });
+               soneChangeDetector.detectChanges(newSone);
+               return events;
+       }
+
        /**
         * Deletes the given Sone. This will remove the Sone from the
         * {@link #getLocalSones() local Sones}, stop its {@link SoneInserter} and
@@ -969,10 +944,9 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        logger.log(Level.WARNING, String.format("Tried to delete non-local Sone: %s", sone));
                        return;
                }
-               // FIXME – implement in database
-//             sones.remove(sone.getId());
                SoneInserter soneInserter = soneInserters.remove(sone);
                soneInserter.stop();
+               database.removeSone(sone);
                webOfTrustUpdater.removeContext((OwnIdentity) sone.getIdentity(), "Sone");
                webOfTrustUpdater.removeProperty((OwnIdentity) sone.getIdentity(), "Sone.LatestEdition");
                try {
@@ -1053,9 +1027,6 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                Set<String> likedReplyIds =
                                configurationSoneParser.parseLikedPostReplyIds();
 
-               /* load friends. */
-               Set<String> friends = configurationSoneParser.parseFriends();
-
                /* load albums. */
                List<Album> topLevelAlbums;
                try {
@@ -1107,9 +1078,6 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        sone.setReplies(replies);
                        sone.setLikePostIds(likedPostIds);
                        sone.setLikeReplyIds(likedReplyIds);
-                       for (String friendId : friends) {
-                               followSone(sone, friendId);
-                       }
                        for (Album album : sone.getRootAlbum().getAlbums()) {
                                sone.getRootAlbum().removeAlbum(album);
                        }
@@ -1121,11 +1089,6 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                                soneInserters.get(sone).setLastInsertFingerprint(lastInsertFingerprint);
                        }
                }
-               synchronized (knownSones) {
-                       for (String friend : friends) {
-                               knownSones.add(friend);
-                       }
-               }
                for (Post post : posts) {
                        post.setKnown(true);
                }
@@ -1149,24 +1112,6 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         * @return The created post
         */
        public Post createPost(Sone sone, Optional<Sone> recipient, String text) {
-               return createPost(sone, recipient, System.currentTimeMillis(), 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
-        * @param time
-        *            The time of the post
-        * @param text
-        *            The text of the post
-        * @return The created post
-        */
-       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()) {
@@ -1174,7 +1119,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        return null;
                }
                PostBuilder postBuilder = database.newPostBuilder();
-               postBuilder.from(sone.getId()).randomId().withTime(time).withText(text.trim());
+               postBuilder.from(sone.getId()).randomId().currentTime().withText(text.trim());
                if (recipient.isPresent()) {
                        postBuilder.to(recipient.get().getId());
                }
@@ -1220,16 +1165,8 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                }
        }
 
-       /**
-        * Bookmarks the post with the given ID.
-        *
-        * @param id
-        *            The ID of the post to bookmark
-        */
-       public void bookmarkPost(String id) {
-               synchronized (bookmarkedPosts) {
-                       bookmarkedPosts.add(id);
-               }
+       public void bookmarkPost(Post post) {
+               database.bookmarkPost(post);
        }
 
        /**
@@ -1238,20 +1175,8 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         * @param post
         *            The post to unbookmark
         */
-       public void unbookmark(Post post) {
-               unbookmarkPost(post.getId());
-       }
-
-       /**
-        * Removes the post with the given ID from the bookmarks.
-        *
-        * @param id
-        *            The ID of the post to unbookmark
-        */
-       public void unbookmarkPost(String id) {
-               synchronized (bookmarkedPosts) {
-                       bookmarkedPosts.remove(id);
-               }
+       public void unbookmarkPost(Post post) {
+               database.unbookmarkPost(post);
        }
 
        /**
@@ -1577,13 +1502,6 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        }
                        configuration.getStringValue(sonePrefix + "/Likes/Reply/" + replyLikeCounter + "/ID").setValue(null);
 
-                       /* save friends. */
-                       int friendCounter = 0;
-                       for (String friendId : sone.getFriends()) {
-                               configuration.getStringValue(sonePrefix + "/Friends/" + friendCounter++ + "/ID").setValue(friendId);
-                       }
-                       configuration.getStringValue(sonePrefix + "/Friends/" + friendCounter + "/ID").setValue(null);
-
                        /* save albums. first, collect in a flat structure, top-level first. */
                        List<Album> albums = FluentIterable.from(sone.getRootAlbum().getAlbums()).transformAndConcat(Album.FLATTENER).toList();
 
@@ -1675,15 +1593,6 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        /* save known posts. */
                        database.save();
 
-                       /* save bookmarked posts. */
-                       int bookmarkedPostCounter = 0;
-                       synchronized (bookmarkedPosts) {
-                               for (String bookmarkedPostId : bookmarkedPosts) {
-                                       configuration.getStringValue("Bookmarks/Post/" + bookmarkedPostCounter++ + "/ID").setValue(bookmarkedPostId);
-                               }
-                       }
-                       configuration.getStringValue("Bookmarks/Post/" + bookmarkedPostCounter++ + "/ID").setValue(null);
-
                        /* now save it. */
                        configuration.save();
 
@@ -1729,19 +1638,6 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        }
                        ++soneCounter;
                }
-
-               /* load bookmarked posts. */
-               int bookmarkedPostCounter = 0;
-               while (true) {
-                       String bookmarkedPostId = configuration.getStringValue("Bookmarks/Post/" + bookmarkedPostCounter++ + "/ID").getValue(null);
-                       if (bookmarkedPostId == null) {
-                               break;
-                       }
-                       synchronized (bookmarkedPosts) {
-                               bookmarkedPosts.add(bookmarkedPostId);
-                       }
-               }
-
        }
 
        /**
@@ -1799,7 +1695,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                if (sone.isLocal()) {
                        return;
                }
-               sone.setLatestEdition(Numbers.safeParseLong(identity.getProperty("Sone.LatestEdition"), sone.getLatestEdition()));
+               sone.setLatestEdition(fromNullable(tryParse(identity.getProperty("Sone.LatestEdition"))).or(sone.getLatestEdition()));
                soneDownloader.addSone(sone);
                soneDownloaders.execute(soneDownloader.fetchSoneAction(sone));
        }
@@ -1828,16 +1724,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        /* TODO - we don’t have the Sone anymore. should this happen? */
                        return;
                }
-               database.removePosts(sone.get());
-               for (Post post : sone.get().getPosts()) {
-                       eventBus.post(new PostRemovedEvent(post));
-               }
-               database.removePostReplies(sone.get());
-               for (PostReply reply : sone.get().getReplies()) {
-                       eventBus.post(new PostReplyRemovedEvent(reply));
-               }
-//             TODO – implement in database
-//             sones.remove(identity.getId());
+               database.removeSone(sone.get());
                eventBus.post(new SoneRemovedEvent(sone.get()));
        }