Use database methods where possible.
[Sone.git] / src / main / java / net / pterodactylus / sone / core / Core.java
index 1541066..d60bbda 100644 (file)
 
 package net.pterodactylus.sone.core;
 
+import static com.google.common.base.Optional.fromNullable;
 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 com.google.common.primitives.Longs.tryParse;
+import static java.lang.String.format;
+import static java.util.logging.Level.WARNING;
 import static net.pterodactylus.sone.data.Sone.LOCAL_SONE_FILTER;
+import static net.pterodactylus.sone.data.Sone.toAllAlbums;
 
 import java.net.MalformedURLException;
 import java.util.ArrayList;
@@ -38,9 +43,14 @@ 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.InvalidImageFound;
+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.SoneChangeDetector.PostProcessor;
+import net.pterodactylus.sone.core.SoneChangeDetector.PostReplyProcessor;
 import net.pterodactylus.sone.core.SoneInserter.SetInsertionDelay;
 import net.pterodactylus.sone.core.event.ImageInsertFinishedEvent;
 import net.pterodactylus.sone.core.event.MarkPostKnownEvent;
@@ -67,8 +77,10 @@ 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.ImageBuilder;
 import net.pterodactylus.sone.database.PostBuilder;
 import net.pterodactylus.sone.database.PostProvider;
 import net.pterodactylus.sone.database.PostReplyBuilder;
@@ -334,9 +346,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         */
        @Override
        public Collection<Sone> getSones() {
-               synchronized (sones) {
-                       return ImmutableSet.copyOf(sones.values());
-               }
+               return database.getSones();
        }
 
        /**
@@ -350,9 +360,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         */
        @Override
        public Optional<Sone> getSone(String id) {
-               synchronized (sones) {
-                       return Optional.fromNullable(sones.get(id));
-               }
+               return database.getSone(id);
        }
 
        /**
@@ -360,9 +368,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         */
        @Override
        public Collection<Sone> getLocalSones() {
-               synchronized (sones) {
-                       return FluentIterable.from(sones.values()).filter(LOCAL_SONE_FILTER).toSet();
-               }
+               return database.getLocalSones();
        }
 
        /**
@@ -395,9 +401,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         */
        @Override
        public Collection<Sone> getRemoteSones() {
-               synchronized (sones) {
-                       return FluentIterable.from(sones.values()).filter(not(LOCAL_SONE_FILTER)).toSet();
-               }
+               return database.getRemoteSones();
        }
 
        /**
@@ -584,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 getOrCreateAlbum(String albumId) {
-               return getAlbum(albumId, true);
+       public AlbumBuilder albumBuilder() {
+               return database.newAlbumBuilder();
        }
 
        /**
@@ -602,23 +598,15 @@ 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();
+       }
+
+       public ImageBuilder imageBuilder() {
+               return database.newImageBuilder();
        }
 
        /**
@@ -772,6 +760,8 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        logger.log(Level.WARNING, "Given Identity is null!");
                        return null;
                }
+               final Long latestEdition = tryParse(fromNullable(
+                               identity.getProperty("Sone.LatestEdition")).or("0"));
                synchronized (sones) {
                        final Sone sone = getRemoteSone(identity.getId(), true);
                        if (sone.isLocal()) {
@@ -780,7 +770,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        sone.setIdentity(identity);
                        boolean newSone = sone.getRequestUri() == null;
                        sone.setRequestUri(SoneUri.create(identity.getRequestUri()));
-                       sone.setLatestEdition(Numbers.safeParseLong(identity.getProperty("Sone.LatestEdition"), (long) 0));
+                       sone.setLatestEdition(latestEdition);
                        if (newSone) {
                                synchronized (knownSones) {
                                        newSone = !knownSones.contains(sone.getId());
@@ -950,7 +940,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         *            {@code true} if the stored Sone should be updated regardless
         *            of the age of the given Sone
         */
-       public void updateSone(Sone sone, boolean soneRescueMode) {
+       public void updateSone(final Sone sone, boolean soneRescueMode) {
                Optional<Sone> storedSone = getSone(sone.getId());
                if (storedSone.isPresent()) {
                        if (!soneRescueMode && !(sone.getTime() > storedSone.get().getTime())) {
@@ -958,72 +948,41 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                                return;
                        }
                        /* find removed posts. */
-                       Collection<Post> removedPosts = new ArrayList<Post>();
-                       Collection<Post> newPosts = new ArrayList<Post>();
-                       Collection<Post> existingPosts = database.getPosts(sone.getId());
-                       for (Post oldPost : existingPosts) {
-                               if (!sone.getPosts().contains(oldPost)) {
-                                       removedPosts.add(oldPost);
-                               }
-                       }
-                       /* 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()) {
-                                       newPosts.add(newPost);
-                               }
-                       }
-                       /* store posts. */
-                       database.storePosts(sone, sone.getPosts());
-                       Collection<PostReply> newPostReplies = new ArrayList<PostReply>();
-                       Collection<PostReply> removedPostReplies = new ArrayList<PostReply>();
-                       if (!soneRescueMode) {
-                               for (PostReply reply : storedSone.get().getReplies()) {
-                                       if (!sone.getReplies().contains(reply)) {
-                                               removedPostReplies.add(reply);
+                       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));
                                        }
                                }
-                       }
-                       Set<PostReply> storedReplies = storedSone.get().getReplies();
-                       for (PostReply reply : sone.getReplies()) {
-                               if (storedReplies.contains(reply)) {
-                                       continue;
+                       });
+                       soneChangeDetector.onRemovedPosts(new PostProcessor() {
+                               @Override
+                               public void processPost(Post post) {
+                                       eventBus.post(new PostRemovedEvent(post));
                                }
-                               if (reply.getTime() < getSoneFollowingTime(sone)) {
-                                       reply.setKnown(true);
-                               } else if (!reply.isKnown()) {
-                                       newPostReplies.add(reply);
+                       });
+                       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));
+                                       }
                                }
-                       }
-                       database.storePostReplies(sone, sone.getReplies());
-                       for (Album album : storedSone.get().getRootAlbum().getAlbums()) {
-                               database.removeAlbum(album);
-                               for (Image image : album.getImages()) {
-                                       database.removeImage(image);
+                       });
+                       soneChangeDetector.onRemovedPostReplies(new PostReplyProcessor() {
+                               @Override
+                               public void processPostReply(PostReply postReply) {
+                                       eventBus.post(new PostReplyRemovedEvent(postReply));
                                }
-                       }
-                       for (Post removedPost : removedPosts) {
-                               eventBus.post(new PostRemovedEvent(removedPost));
-                       }
-                       for (Post newPost : newPosts) {
-                               eventBus.post(new NewPostFoundEvent(newPost));
-                       }
-                       for (PostReply removedPostReply : removedPostReplies) {
-                               eventBus.post(new PostReplyRemovedEvent(removedPostReply));
-                       }
-                       for (PostReply newPostReply : newPostReplies) {
-                               eventBus.post(new NewPostReplyFoundEvent(newPostReply));
-                       }
-                       for (Album album : sone.getRootAlbum().getAlbums()) {
-                               database.storeAlbum(album);
-                               for (Image image : album.getImages()) {
-                                       database.storeImage(image);
-                               }
-                       }
+                       });
+                       soneChangeDetector.detectChanges(sone);
+                       database.storeSone(sone);
                        synchronized (sones) {
                                sone.setOptions(storedSone.get().getOptions());
                                sone.setKnown(storedSone.get().isKnown());
@@ -1132,99 +1091,49 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                }
 
                /* 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 = getOrCreateAlbum(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. */
-               int imageCounter = 0;
-               while (true) {
-                       String imagePrefix = sonePrefix + "/Images/" + imageCounter++;
-                       String imageId = configuration.getStringValue(imagePrefix + "/ID").getValue(null);
-                       if (imageId == null) {
-                               break;
-                       }
-                       String albumId = configuration.getStringValue(imagePrefix + "/Album").getValue(null);
-                       String key = configuration.getStringValue(imagePrefix + "/Key").getValue(null);
-                       String title = configuration.getStringValue(imagePrefix + "/Title").getValue(null);
-                       String description = configuration.getStringValue(imagePrefix + "/Description").getValue(null);
-                       Long creationTime = configuration.getLongValue(imagePrefix + "/CreationTime").getValue(null);
-                       Integer width = configuration.getIntValue(imagePrefix + "/Width").getValue(null);
-                       Integer height = configuration.getIntValue(imagePrefix + "/Height").getValue(null);
-                       if ((albumId == null) || (key == null) || (title == null) || (description == null) || (creationTime == null) || (width == null) || (height == null)) {
-                               logger.log(Level.WARNING, "Invalid image found, aborting load!");
-                               return;
-                       }
-                       Album album = getAlbum(albumId, false);
-                       if (album == null) {
-                               logger.log(Level.WARNING, "Invalid album image encountered, aborting load!");
-                               return;
-                       }
-                       Image image = getImage(imageId).modify().setSone(sone).setCreationTime(creationTime).setKey(key).setTitle(title).setDescription(description).setWidth(width).setHeight(height).update();
-                       album.addImage(image);
+               try {
+                       configurationSoneParser.parseImages(database);
+               } catch (InvalidImageFound iif) {
+                       logger.log(WARNING, "Invalid image found, aborting load!");
+                       return;
+               } catch (InvalidParentAlbumFound ipaf) {
+                       logger.log(Level.WARNING,
+                                       format("Invalid album image (%s) encountered, aborting load!",
+                                                       ipaf.getAlbumParentId()));
+                       return;
                }
 
                /* load avatar. */
                String avatarId = configuration.getStringValue(sonePrefix + "/Profile/Avatar").getValue(null);
                if (avatarId != null) {
-                       profile.setAvatar(getImage(avatarId, false));
+                       final Map<String, Image> images =
+                                       configurationSoneParser.getImages();
+                       profile.setAvatar(images.get(avatarId));
                }
 
                /* load options. */
@@ -1253,6 +1162,12 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                                sone.getRootAlbum().addAlbum(album);
                        }
                        soneInserters.get(sone).setLastInsertFingerprint(lastInsertFingerprint);
+                       for (Album album : toAllAlbums.apply(sone)) {
+                               database.storeAlbum(album);
+                               for (Image image : album.getImages()) {
+                                       database.storeImage(image);
+                               }
+                       }
                }
                synchronized (knownSones) {
                        for (String friend : friends) {
@@ -1463,9 +1378,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;
        }
@@ -2001,19 +1915,14 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                OwnIdentity ownIdentity = identityRemovedEvent.ownIdentity();
                Identity identity = identityRemovedEvent.identity();
                trustedIdentities.remove(ownIdentity, identity);
-               boolean foundIdentity = false;
                for (Entry<OwnIdentity, Collection<Identity>> trustedIdentity : trustedIdentities.asMap().entrySet()) {
                        if (trustedIdentity.getKey().equals(ownIdentity)) {
                                continue;
                        }
                        if (trustedIdentity.getValue().contains(identity)) {
-                               foundIdentity = true;
+                               return;
                        }
                }
-               if (foundIdentity) {
-                       /* some local identity still trusts this identity, don’t remove. */
-                       return;
-               }
                Optional<Sone> sone = getSone(identity.getId());
                if (!sone.isPresent()) {
                        /* TODO - we don’t have the Sone anymore. should this happen? */