✨ Add metrics to SoneInserter
[Sone.git] / src / main / java / net / pterodactylus / sone / core / Core.java
index ccf633c..c3af217 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Core.java - Copyright © 2010–2016 David Roden
+ * Sone - Core.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -37,32 +37,20 @@ import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.*;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
+import com.codahale.metrics.*;
 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.SoneChangeDetector.PostProcessor;
-import net.pterodactylus.sone.core.SoneChangeDetector.PostReplyProcessor;
-import net.pterodactylus.sone.core.event.ImageInsertFinishedEvent;
-import net.pterodactylus.sone.core.event.InsertionDelayChangedEvent;
-import net.pterodactylus.sone.core.event.MarkPostKnownEvent;
-import net.pterodactylus.sone.core.event.MarkPostReplyKnownEvent;
-import net.pterodactylus.sone.core.event.MarkSoneKnownEvent;
-import net.pterodactylus.sone.core.event.NewPostFoundEvent;
-import net.pterodactylus.sone.core.event.NewPostReplyFoundEvent;
-import net.pterodactylus.sone.core.event.NewSoneFoundEvent;
-import net.pterodactylus.sone.core.event.PostRemovedEvent;
-import net.pterodactylus.sone.core.event.PostReplyRemovedEvent;
-import net.pterodactylus.sone.core.event.SoneLockedEvent;
-import net.pterodactylus.sone.core.event.SoneRemovedEvent;
-import net.pterodactylus.sone.core.event.SoneUnlockedEvent;
+import net.pterodactylus.sone.core.event.*;
 import net.pterodactylus.sone.data.Album;
 import net.pterodactylus.sone.data.Client;
 import net.pterodactylus.sone.data.Image;
@@ -100,7 +88,6 @@ 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.collect.FluentIterable;
 import com.google.common.collect.HashMultimap;
 import com.google.common.collect.Multimap;
@@ -113,8 +100,6 @@ import kotlin.jvm.functions.Function1;
 
 /**
  * The Sone core.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 @Singleton
 public class Core extends AbstractService implements SoneProvider, PostProvider, PostReplyProvider {
@@ -125,6 +110,8 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
        /** The start time. */
        private final long startupTime = System.currentTimeMillis();
 
+       private final AtomicBoolean debug = new AtomicBoolean(false);
+
        /** The preferences. */
        private final Preferences preferences;
 
@@ -160,18 +147,18 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
 
        /** Locked local Sones. */
        /* synchronize on itself. */
-       private final Set<Sone> lockedSones = new HashSet<Sone>();
+       private final Set<Sone> lockedSones = new HashSet<>();
 
        /** Sone inserters. */
        /* synchronize access on this on sones. */
-       private final Map<Sone, SoneInserter> soneInserters = new HashMap<Sone, SoneInserter>();
+       private final Map<Sone, SoneInserter> soneInserters = new HashMap<>();
 
        /** Sone rescuers. */
        /* synchronize access on this on sones. */
-       private final Map<Sone, SoneRescuer> soneRescuers = new HashMap<Sone, SoneRescuer>();
+       private final Map<Sone, SoneRescuer> soneRescuers = new HashMap<>();
 
        /** All known Sones. */
-       private final Set<String> knownSones = new HashSet<String>();
+       private final Set<String> knownSones = new HashSet<>();
 
        /** The post database. */
        private final Database database;
@@ -180,7 +167,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
        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>();
+       private final Map<String, TemporaryImage> temporaryImages = new HashMap<>();
 
        /** Ticker for threads that mark own elements as known. */
        private final ScheduledExecutorService localElementTicker = Executors.newScheduledThreadPool(1);
@@ -188,6 +175,8 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
        /** The time the configuration was last touched. */
        private volatile long lastConfigurationUpdate;
 
+       private final MetricRegistry metricRegistry;
+
        /**
         * Creates a new core.
         *
@@ -205,7 +194,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         *            The database
         */
        @Inject
-       public Core(Configuration configuration, FreenetInterface freenetInterface, IdentityManager identityManager, SoneDownloader soneDownloader, ImageInserter imageInserter, UpdateChecker updateChecker, WebOfTrustUpdater webOfTrustUpdater, EventBus eventBus, Database database) {
+       public Core(Configuration configuration, FreenetInterface freenetInterface, IdentityManager identityManager, SoneDownloader soneDownloader, ImageInserter imageInserter, UpdateChecker updateChecker, WebOfTrustUpdater webOfTrustUpdater, EventBus eventBus, Database database, MetricRegistry metricRegistry) {
                super("Sone Core");
                this.configuration = configuration;
                this.freenetInterface = freenetInterface;
@@ -216,6 +205,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                this.webOfTrustUpdater = webOfTrustUpdater;
                this.eventBus = eventBus;
                this.database = database;
+               this.metricRegistry = metricRegistry;
                preferences = new Preferences(eventBus);
        }
 
@@ -232,6 +222,16 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                return startupTime;
        }
 
+       @Nonnull
+       public boolean getDebug() {
+               return debug.get();
+       }
+
+       public void setDebug() {
+               debug.set(true);
+               eventBus.post(new DebugActivatedEvent());
+       }
+
        /**
         * Returns the options used by the core.
         *
@@ -448,7 +448,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         * @return The Sones that like the given post
         */
        public Set<Sone> getLikes(Post post) {
-               Set<Sone> sones = new HashSet<Sone>();
+               Set<Sone> sones = new HashSet<>();
                for (Sone sone : getSones()) {
                        if (sone.getLikedPostIds().contains(post.getId())) {
                                sones.add(sone);
@@ -465,7 +465,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         * @return The Sones that like the given reply
         */
        public Set<Sone> getLikes(PostReply reply) {
-               Set<Sone> sones = new HashSet<Sone>();
+               Set<Sone> sones = new HashSet<>();
                for (Sone sone : getSones()) {
                        if (sone.getLikedReplyIds().contains(reply.getId())) {
                                sones.add(sone);
@@ -622,7 +622,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                sone.setLatestEdition(fromNullable(tryParse(property)).or(0L));
                sone.setClient(new Client("Sone", SonePlugin.getPluginVersion()));
                sone.setKnown(true);
-               SoneInserter soneInserter = new SoneInserter(this, eventBus, freenetInterface, ownIdentity.getId());
+               SoneInserter soneInserter = new SoneInserter(this, eventBus, freenetInterface, metricRegistry, ownIdentity.getId());
                soneInserter.insertionDelayChanged(new InsertionDelayChangedEvent(preferences.getInsertionDelay()));
                eventBus.register(soneInserter);
                synchronized (soneInserters) {
@@ -853,44 +853,33 @@ 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() < database.getFollowingTime(newSone.getId())) {
-                                       post.setKnown(true);
-                               } else if (!post.isKnown()) {
-                                       events.add(new NewPostFoundEvent(post));
-                               }
+       private List<Object> collectEventsForChangesInSone(Sone oldSone, Sone newSone) {
+               List<Object> events = new ArrayList<>();
+               SoneComparison soneComparison = new SoneComparison(oldSone, newSone);
+               for (Post newPost : soneComparison.getNewPosts()) {
+                       if (newPost.getSone().equals(newSone)) {
+                               newPost.setKnown(true);
+                       } else if (newPost.getTime() < database.getFollowingTime(newSone.getId())) {
+                               newPost.setKnown(true);
+                       } else if (!newPost.isKnown()) {
+                               events.add(new NewPostFoundEvent(newPost));
                        }
-               });
-               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() < database.getFollowingTime(newSone.getId())) {
-                                       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));
+               }
+               for (Post post : soneComparison.getRemovedPosts()) {
+                       events.add(new PostRemovedEvent(post));
+               }
+               for (PostReply postReply : soneComparison.getNewPostReplies()) {
+                       if (postReply.getSone().equals(newSone)) {
+                               postReply.setKnown(true);
+                       } else if (postReply.getTime() < database.getFollowingTime(newSone.getId())) {
+                               postReply.setKnown(true);
+                       } else if (!postReply.isKnown()) {
+                               events.add(new NewPostReplyFoundEvent(postReply));
                        }
-               });
-               soneChangeDetector.detectChanges(newSone);
+               }
+               for (PostReply postReply : soneComparison.getRemovedPostReplies()) {
+                       events.add(new PostReplyRemovedEvent(postReply));
+               }
                return events;
        }
 
@@ -1078,7 +1067,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         *            The text of the post
         * @return The created post
         */
-       public Post createPost(Sone sone, Optional<Sone> recipient, String text) {
+       public Post createPost(Sone sone, @Nullable Sone recipient, String text) {
                checkNotNull(text, "text must not be null");
                checkArgument(text.trim().length() > 0, "text must not be empty");
                if (!sone.isLocal()) {
@@ -1087,8 +1076,8 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                }
                PostBuilder postBuilder = database.newPostBuilder();
                postBuilder.from(sone.getId()).randomId().currentTime().withText(text.trim());
-               if (recipient.isPresent()) {
-                       postBuilder.to(recipient.get().getId());
+               if (recipient != null) {
+                       postBuilder.to(recipient.getId());
                }
                final Post post = postBuilder.build();
                database.storePost(post);
@@ -1343,7 +1332,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                identityManager.start();
                webOfTrustUpdater.init();
                webOfTrustUpdater.start();
-               database.start();
+               database.startAsync();
        }
 
        /**
@@ -1383,7 +1372,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        }
                }
                saveConfiguration();
-               database.stop();
+               database.stopAsync();
                webOfTrustUpdater.stop();
                updateChecker.stop();
                soneDownloader.stop();
@@ -1516,8 +1505,6 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        configuration.getStringValue(sonePrefix + "/Options/ShowCustomAvatars").setValue(sone.getOptions().getShowCustomAvatars().name());
                        configuration.getStringValue(sonePrefix + "/Options/LoadLinkedImages").setValue(sone.getOptions().getLoadLinkedImages().name());
 
-                       configuration.save();
-
                        webOfTrustUpdater.setProperty((OwnIdentity) sone.getIdentity(), "Sone.LatestEdition", String.valueOf(sone.getLatestEdition()));
 
                        logger.log(Level.INFO, String.format("Sone %s saved.", sone));
@@ -1695,9 +1682,9 @@ 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().modify().setKey(imageInsertFinishedEvent.resultingUri().toString()).update();
-               deleteTemporaryImage(imageInsertFinishedEvent.image().getId());
+               logger.log(Level.WARNING, String.format("Image insert finished for %s: %s", imageInsertFinishedEvent.getImage(), imageInsertFinishedEvent.getResultingUri()));
+               imageInsertFinishedEvent.getImage().modify().setKey(imageInsertFinishedEvent.getResultingUri().toString()).update();
+               deleteTemporaryImage(imageInsertFinishedEvent.getImage().getId());
                touchConfiguration();
        }