From: David ‘Bombe’ Roden Date: Sun, 9 Feb 2020 10:42:34 +0000 (+0100) Subject: 🔀 Merge “feature/notification-handler” into “next” X-Git-Tag: v81^2~5 X-Git-Url: https://git.pterodactylus.net/?a=commitdiff_plain;h=f98ec5222b0611a7e473c01cb6ef3a4ef73bc294;hp=de6be950b15db8012395f43217af6565fadecbf4;p=Sone.git 🔀 Merge “feature/notification-handler” into “next” --- diff --git a/build.gradle b/build.gradle index 78c3110..e5eaff7 100644 --- a/build.gradle +++ b/build.gradle @@ -57,8 +57,23 @@ dependencies { apply from: 'version.gradle' -test { +task parallelTest(type: Test) { maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1 + useJUnit { + excludeCategories 'net.pterodactylus.sone.test.NotParallel' + } +} + +task notParallelTest(type: Test) { + maxParallelForks = 1 + useJUnit { + includeCategories 'net.pterodactylus.sone.test.NotParallel' + } +} + +test { + exclude '**' + dependsOn parallelTest, notParallelTest } task fatJar(type: Jar) { diff --git a/src/main/java/net/pterodactylus/sone/main/SonePlugin.java b/src/main/java/net/pterodactylus/sone/main/SonePlugin.java index a3cb385..d3dd3bd 100644 --- a/src/main/java/net/pterodactylus/sone/main/SonePlugin.java +++ b/src/main/java/net/pterodactylus/sone/main/SonePlugin.java @@ -22,11 +22,15 @@ import static java.util.logging.Logger.*; import java.util.logging.Logger; import java.util.logging.*; +import javax.annotation.Nonnull; + import net.pterodactylus.sone.core.*; +import net.pterodactylus.sone.core.event.*; import net.pterodactylus.sone.fcp.*; import net.pterodactylus.sone.freenet.wot.*; import net.pterodactylus.sone.web.*; import net.pterodactylus.sone.web.notification.NotificationHandler; +import net.pterodactylus.sone.web.notification.NotificationHandlerModule; import freenet.l10n.BaseL10n.*; import freenet.l10n.*; @@ -58,7 +62,7 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr private final LoadingCache> classCache = CacheBuilder.newBuilder() .build(new CacheLoader>() { @Override - public Class load(String key) throws Exception { + public Class load(@Nonnull String key) throws Exception { return SonePlugin.class.getClassLoader().loadClass(key); } }); @@ -106,6 +110,9 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr /** The core. */ private Core core; + /** The event bus. */ + private EventBus eventBus; + /** The web interface. */ private WebInterface webInterface; @@ -197,16 +204,33 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr /* create the web interface. */ webInterface = injector.getInstance(WebInterface.class); - NotificationHandler notificationHandler = injector.getInstance(NotificationHandler.class); + + /* we need to request this to install all notification handlers. */ + injector.getInstance(NotificationHandler.class); + + /* and this is required to shutdown all tickers. */ + injector.getInstance(TickerShutdown.class); /* start core! */ core.start(); /* start the web interface! */ webInterface.start(); - webInterface.setFirstStart(injector.getInstance(Key.get(Boolean.class, Names.named("FirstStart")))); - webInterface.setNewConfig(injector.getInstance(Key.get(Boolean.class, Names.named("NewConfig")))); - notificationHandler.start(); + + /* send some events on startup */ + eventBus = injector.getInstance(EventBus.class); + + /* first start? */ + if (injector.getInstance(Key.get(Boolean.class, Names.named("FirstStart")))) { + eventBus.post(new FirstStart()); + } else { + /* new config? */ + if (injector.getInstance(Key.get(Boolean.class, Names.named("NewConfig")))) { + eventBus.post(new ConfigNotRead()); + } + } + + eventBus.post(new Startup()); } @VisibleForTesting @@ -214,8 +238,9 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr FreenetModule freenetModule = new FreenetModule(pluginRespirator); AbstractModule soneModule = new SoneModule(this, new EventBus()); Module webInterfaceModule = new WebInterfaceModule(); + Module notificationHandlerModule = new NotificationHandlerModule(); - return createInjector(freenetModule, soneModule, webInterfaceModule); + return createInjector(freenetModule, soneModule, webInterfaceModule, notificationHandlerModule); } @VisibleForTesting @@ -228,6 +253,9 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr */ @Override public void terminate() { + /* send shutdown event. */ + eventBus.post(new Shutdown()); + try { /* stop the web interface. */ webInterface.stop(); diff --git a/src/main/java/net/pterodactylus/sone/web/WebInterface.java b/src/main/java/net/pterodactylus/sone/web/WebInterface.java index bcc7a11..edc3bab 100644 --- a/src/main/java/net/pterodactylus/sone/web/WebInterface.java +++ b/src/main/java/net/pterodactylus/sone/web/WebInterface.java @@ -21,25 +21,17 @@ import static com.google.common.collect.FluentIterable.from; import static java.util.logging.Logger.getLogger; import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; import java.util.Set; import java.util.TimeZone; import java.util.UUID; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; import java.util.logging.Logger; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import javax.inject.Named; import net.pterodactylus.sone.core.Core; import net.pterodactylus.sone.core.ElementLoader; import net.pterodactylus.sone.core.event.*; -import net.pterodactylus.sone.data.Image; import net.pterodactylus.sone.data.Post; import net.pterodactylus.sone.data.PostReply; import net.pterodactylus.sone.data.Sone; @@ -58,9 +50,6 @@ import net.pterodactylus.sone.template.LinkedElementRenderFilter; import net.pterodactylus.sone.template.ParserFilter; import net.pterodactylus.sone.template.RenderFilter; import net.pterodactylus.sone.template.ShortenFilter; -import net.pterodactylus.sone.text.Part; -import net.pterodactylus.sone.text.SonePart; -import net.pterodactylus.sone.text.SoneTextParser; import net.pterodactylus.sone.text.TimeTextConverter; import net.pterodactylus.sone.web.ajax.BookmarkAjaxPage; import net.pterodactylus.sone.web.ajax.CreatePostAjaxPage; @@ -94,7 +83,6 @@ import net.pterodactylus.sone.web.page.TemplateRenderer; import net.pterodactylus.sone.web.pages.*; import net.pterodactylus.util.notify.Notification; import net.pterodactylus.util.notify.NotificationManager; -import net.pterodactylus.util.notify.TemplateNotification; import net.pterodactylus.util.template.Template; import net.pterodactylus.util.template.TemplateContextFactory; import net.pterodactylus.util.web.RedirectPage; @@ -106,7 +94,6 @@ import freenet.clients.http.ToadletContext; import com.codahale.metrics.*; import com.google.common.base.Optional; -import com.google.common.collect.Collections2; import com.google.common.collect.ImmutableSet; import com.google.common.eventbus.Subscribe; import com.google.inject.Inject; @@ -136,9 +123,6 @@ public class WebInterface implements SessionProvider { private final TemplateContextFactory templateContextFactory; private final TemplateRenderer templateRenderer; - /** The Sone text parser. */ - private final SoneTextParser soneTextParser; - /** The parser filter. */ private final ParserFilter parserFilter; private final ShortenFilter shortenFilter; @@ -157,9 +141,6 @@ public class WebInterface implements SessionProvider { private final MetricRegistry metricRegistry; private final Translation translation; - /** The “new Sone” notification. */ - private final ListNotification newSoneNotification; - /** The “new post” notification. */ private final ListNotification newPostNotification; @@ -172,33 +153,6 @@ public class WebInterface implements SessionProvider { /** The invisible “local reply” notification. */ private final ListNotification localReplyNotification; - /** The “you have been mentioned” notification. */ - private final ListNotification mentionNotification; - - /** Notifications for sone inserts. */ - private final Map soneInsertNotifications = new HashMap<>(); - - /** Sone locked notification ticker objects. */ - private final Map> lockedSonesTickerObjects = Collections.synchronizedMap(new HashMap>()); - - /** The “Sone locked” notification. */ - private final ListNotification lockedSonesNotification; - - /** The “new version” notification. */ - private final TemplateNotification newVersionNotification; - - /** The “inserting images” notification. */ - private final ListNotification insertingImagesNotification; - - /** The “inserted images” notification. */ - private final ListNotification insertedImagesNotification; - - /** The “image insert failed” notification. */ - private final ListNotification imageInsertFailedNotification; - - /** Scheduled executor for time-based notifications. */ - private final ScheduledExecutorService ticker = Executors.newScheduledThreadPool(1); - @Inject public WebInterface(SonePlugin sonePlugin, Loaders loaders, ListNotificationFilter listNotificationFilter, PostVisibilityFilter postVisibilityFilter, ReplyVisibilityFilter replyVisibilityFilter, @@ -208,7 +162,10 @@ public class WebInterface implements SessionProvider { RenderFilter renderFilter, LinkedElementRenderFilter linkedElementRenderFilter, PageToadletRegistry pageToadletRegistry, MetricRegistry metricRegistry, Translation translation, L10nFilter l10nFilter, - NotificationManager notificationManager) { + NotificationManager notificationManager, @Named("newRemotePost") ListNotification newPostNotification, + @Named("newRemotePostReply") ListNotification newReplyNotification, + @Named("localPost") ListNotification localPostNotification, + @Named("localReply") ListNotification localReplyNotification) { this.sonePlugin = sonePlugin; this.loaders = loaders; this.listNotificationFilter = listNotificationFilter; @@ -225,46 +182,15 @@ public class WebInterface implements SessionProvider { this.l10nFilter = l10nFilter; this.translation = translation; this.notificationManager = notificationManager; + this.newPostNotification = newPostNotification; + this.newReplyNotification = newReplyNotification; + this.localPostNotification = localPostNotification; + this.localReplyNotification = localReplyNotification; formPassword = sonePlugin.pluginRespirator().getToadletContainer().getFormPassword(); - soneTextParser = new SoneTextParser(getCore(), getCore()); this.templateContextFactory = templateContextFactory; templateContextFactory.addTemplateObject("webInterface", this); templateContextFactory.addTemplateObject("formPassword", formPassword); - - /* create notifications. */ - Template newSoneNotificationTemplate = loaders.loadTemplate("/templates/notify/newSoneNotification.html"); - newSoneNotification = new ListNotification<>("new-sone-notification", "sones", newSoneNotificationTemplate, false); - - Template newPostNotificationTemplate = loaders.loadTemplate("/templates/notify/newPostNotification.html"); - newPostNotification = new ListNotification<>("new-post-notification", "posts", newPostNotificationTemplate, false); - - Template localPostNotificationTemplate = loaders.loadTemplate("/templates/notify/newPostNotification.html"); - localPostNotification = new ListNotification<>("local-post-notification", "posts", localPostNotificationTemplate, false); - - Template newReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html"); - newReplyNotification = new ListNotification<>("new-reply-notification", "replies", newReplyNotificationTemplate, false); - - Template localReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html"); - localReplyNotification = new ListNotification<>("local-reply-notification", "replies", localReplyNotificationTemplate, false); - - Template mentionNotificationTemplate = loaders.loadTemplate("/templates/notify/mentionNotification.html"); - mentionNotification = new ListNotification<>("mention-notification", "posts", mentionNotificationTemplate, false); - - Template lockedSonesTemplate = loaders.loadTemplate("/templates/notify/lockedSonesNotification.html"); - lockedSonesNotification = new ListNotification<>("sones-locked-notification", "sones", lockedSonesTemplate); - - Template newVersionTemplate = loaders.loadTemplate("/templates/notify/newVersionNotification.html"); - newVersionNotification = new TemplateNotification("new-version-notification", newVersionTemplate); - - Template insertingImagesTemplate = loaders.loadTemplate("/templates/notify/inserting-images-notification.html"); - insertingImagesNotification = new ListNotification<>("inserting-images-notification", "images", insertingImagesTemplate); - - Template insertedImagesTemplate = loaders.loadTemplate("/templates/notify/inserted-images-notification.html"); - insertedImagesNotification = new ListNotification<>("inserted-images-notification", "images", insertedImagesTemplate); - - Template imageInsertFailedTemplate = loaders.loadTemplate("/templates/notify/image-insert-failed-notification.html"); - imageInsertFailedNotification = new ListNotification<>("image-insert-failed-notification", "images", imageInsertFailedTemplate); } // @@ -402,16 +328,6 @@ public class WebInterface implements SessionProvider { return formPassword; } - /** - * Returns the posts that have been announced as new in the - * {@link #newPostNotification}. - * - * @return The new posts - */ - public Set getNewPosts() { - return ImmutableSet. builder().addAll(newPostNotification.getElements()).addAll(localPostNotification.getElements()).build(); - } - @Nonnull public Collection getNewPosts(@Nullable Sone currentSone) { Set allNewPosts = ImmutableSet. builder() @@ -421,16 +337,6 @@ public class WebInterface implements SessionProvider { return from(allNewPosts).filter(postVisibilityFilter.isVisible(currentSone)).toSet(); } - /** - * Returns the replies that have been announced as new in the - * {@link #newReplyNotification}. - * - * @return The new replies - */ - public Set getNewReplies() { - return ImmutableSet. builder().addAll(newReplyNotification.getElements()).addAll(localReplyNotification.getElements()).build(); - } - @Nonnull public Collection getNewReplies(@Nullable Sone currentSone) { Set allNewReplies = ImmutableSet.builder() @@ -440,51 +346,6 @@ public class WebInterface implements SessionProvider { return from(allNewReplies).filter(replyVisibilityFilter.isVisible(currentSone)).toSet(); } - /** - * Sets whether the current start of the plugin is the first start. It is - * considered a first start if the configuration file does not exist. - * - * @param firstStart - * {@code true} if no configuration file existed when Sone was - * loaded, {@code false} otherwise - */ - public void setFirstStart(boolean firstStart) { - if (firstStart) { - Template firstStartNotificationTemplate = loaders.loadTemplate("/templates/notify/firstStartNotification.html"); - Notification firstStartNotification = new TemplateNotification("first-start-notification", firstStartNotificationTemplate); - notificationManager.addNotification(firstStartNotification); - } - } - - /** - * Sets whether Sone was started with a fresh configuration file. - * - * @param newConfig - * {@code true} if Sone was started with a fresh configuration, - * {@code false} if the existing configuration could be read - */ - public void setNewConfig(boolean newConfig) { - if (newConfig && !hasFirstStartNotification()) { - Template configNotReadNotificationTemplate = loaders.loadTemplate("/templates/notify/configNotReadNotification.html"); - Notification configNotReadNotification = new TemplateNotification("config-not-read-notification", configNotReadNotificationTemplate); - notificationManager.addNotification(configNotReadNotification); - } - } - - // - // PRIVATE ACCESSORS - // - - /** - * Returns whether the first start notification is currently displayed. - * - * @return {@code true} if the first-start notification is currently - * displayed, {@code false} otherwise - */ - private boolean hasFirstStartNotification() { - return notificationManager.getNotification("first-start-notification") != null; - } - // // ACTIONS // @@ -494,36 +355,6 @@ public class WebInterface implements SessionProvider { */ public void start() { registerToadlets(); - - /* notification templates. */ - Template startupNotificationTemplate = loaders.loadTemplate("/templates/notify/startupNotification.html"); - - final TemplateNotification startupNotification = new TemplateNotification("startup-notification", startupNotificationTemplate); - notificationManager.addNotification(startupNotification); - - ticker.schedule(new Runnable() { - - @Override - public void run() { - startupNotification.dismiss(); - } - }, 2, TimeUnit.MINUTES); - - Template wotMissingNotificationTemplate = loaders.loadTemplate("/templates/notify/wotMissingNotification.html"); - final TemplateNotification wotMissingNotification = new TemplateNotification("wot-missing-notification", wotMissingNotificationTemplate); - ticker.scheduleAtFixedRate(new Runnable() { - - @Override - @SuppressWarnings("synthetic-access") - public void run() { - if (getCore().getIdentityManager().isConnected()) { - wotMissingNotification.dismiss(); - } else { - notificationManager.addNotification(wotMissingNotification); - } - } - - }, 15, 15, TimeUnit.SECONDS); } /** @@ -531,7 +362,6 @@ public class WebInterface implements SessionProvider { */ public void stop() { pageToadletRegistry.unregisterToadlets(); - ticker.shutdownNow(); } // @@ -627,330 +457,6 @@ public class WebInterface implements SessionProvider { pageToadletRegistry.registerToadlets(); } - /** - * Returns all {@link Sone#isLocal() local Sone}s that are referenced by - * {@link SonePart}s in the given text (after parsing it using - * {@link SoneTextParser}). - * - * @param text - * The text to parse - * @return All mentioned local Sones - */ - private Collection getMentionedSones(String text) { - /* we need no context to find mentioned Sones. */ - Set mentionedSones = new HashSet<>(); - for (Part part : soneTextParser.parse(text, null)) { - if (part instanceof SonePart) { - mentionedSones.add(((SonePart) part).getSone()); - } - } - return Collections2.filter(mentionedSones, Sone.LOCAL_SONE_FILTER); - } - - /** - * Returns the Sone insert notification for the given Sone. If no - * notification for the given Sone exists, a new notification is created and - * cached. - * - * @param sone - * The Sone to get the insert notification for - * @return The Sone insert notification - */ - private TemplateNotification getSoneInsertNotification(Sone sone) { - synchronized (soneInsertNotifications) { - TemplateNotification templateNotification = soneInsertNotifications.get(sone); - if (templateNotification == null) { - templateNotification = new TemplateNotification(loaders.loadTemplate("/templates/notify/soneInsertNotification.html")); - templateNotification.set("insertSone", sone); - soneInsertNotifications.put(sone, templateNotification); - } - return templateNotification; - } - } - - private boolean localSoneMentionedInNewPostOrReply(Post post) { - if (!post.getSone().isLocal()) { - if (!getMentionedSones(post.getText()).isEmpty() && !post.isKnown()) { - return true; - } - } - for (PostReply postReply : getCore().getReplies(post.getId())) { - if (postReply.getSone().isLocal()) { - continue; - } - if (!getMentionedSones(postReply.getText()).isEmpty() && !postReply.isKnown()) { - return true; - } - } - return false; - } - - // - // EVENT HANDLERS - // - - /** - * Notifies the web interface that a new {@link Sone} was found. - * - * @param newSoneFoundEvent - * The event - */ - @Subscribe - public void newSoneFound(NewSoneFoundEvent newSoneFoundEvent) { - newSoneNotification.add(newSoneFoundEvent.getSone()); - if (!hasFirstStartNotification()) { - notificationManager.addNotification(newSoneNotification); - } - } - - /** - * Notifies the web interface that a new {@link Post} was found. - * - * @param newPostFoundEvent - * The event - */ - @Subscribe - public void newPostFound(NewPostFoundEvent newPostFoundEvent) { - Post post = newPostFoundEvent.getPost(); - boolean isLocal = post.getSone().isLocal(); - if (isLocal) { - localPostNotification.add(post); - } else { - newPostNotification.add(post); - } - if (!hasFirstStartNotification()) { - notificationManager.addNotification(isLocal ? localPostNotification : newPostNotification); - if (!getMentionedSones(post.getText()).isEmpty() && !isLocal) { - mentionNotification.add(post); - notificationManager.addNotification(mentionNotification); - } - } else { - getCore().markPostKnown(post); - } - } - - /** - * Notifies the web interface that a new {@link PostReply} was found. - * - * @param newPostReplyFoundEvent - * The event - */ - @Subscribe - public void newReplyFound(NewPostReplyFoundEvent newPostReplyFoundEvent) { - PostReply reply = newPostReplyFoundEvent.getPostReply(); - boolean isLocal = reply.getSone().isLocal(); - if (isLocal) { - localReplyNotification.add(reply); - } else { - newReplyNotification.add(reply); - } - if (!hasFirstStartNotification()) { - notificationManager.addNotification(isLocal ? localReplyNotification : newReplyNotification); - if (reply.getPost().isPresent() && localSoneMentionedInNewPostOrReply(reply.getPost().get())) { - mentionNotification.add(reply.getPost().get()); - notificationManager.addNotification(mentionNotification); - } - } else { - getCore().markReplyKnown(reply); - } - } - - /** - * Notifies the web interface that a {@link Sone} was marked as known. - * - * @param markSoneKnownEvent - * The event - */ - @Subscribe - public void markSoneKnown(MarkSoneKnownEvent markSoneKnownEvent) { - newSoneNotification.remove(markSoneKnownEvent.getSone()); - } - - @Subscribe - public void markPostKnown(MarkPostKnownEvent markPostKnownEvent) { - removePost(markPostKnownEvent.getPost()); - } - - @Subscribe - public void markReplyKnown(MarkPostReplyKnownEvent markPostReplyKnownEvent) { - removeReply(markPostReplyKnownEvent.getPostReply()); - } - - @Subscribe - public void soneRemoved(SoneRemovedEvent soneRemovedEvent) { - newSoneNotification.remove(soneRemovedEvent.getSone()); - } - - @Subscribe - public void postRemoved(PostRemovedEvent postRemovedEvent) { - removePost(postRemovedEvent.getPost()); - } - - private void removePost(Post post) { - newPostNotification.remove(post); - localPostNotification.remove(post); - if (!localSoneMentionedInNewPostOrReply(post)) { - mentionNotification.remove(post); - } - } - - @Subscribe - public void replyRemoved(PostReplyRemovedEvent postReplyRemovedEvent) { - removeReply(postReplyRemovedEvent.getPostReply()); - } - - private void removeReply(PostReply reply) { - newReplyNotification.remove(reply); - localReplyNotification.remove(reply); - if (reply.getPost().isPresent() && !localSoneMentionedInNewPostOrReply(reply.getPost().get())) { - mentionNotification.remove(reply.getPost().get()); - } - } - - /** - * Notifies the web interface that a Sone was locked. - * - * @param soneLockedEvent - * The event - */ - @Subscribe - public void soneLocked(SoneLockedEvent soneLockedEvent) { - final Sone sone = soneLockedEvent.getSone(); - ScheduledFuture tickerObject = ticker.schedule(new Runnable() { - - @Override - @SuppressWarnings("synthetic-access") - public void run() { - lockedSonesNotification.add(sone); - notificationManager.addNotification(lockedSonesNotification); - } - }, 5, TimeUnit.MINUTES); - lockedSonesTickerObjects.put(sone, tickerObject); - } - - /** - * Notifies the web interface that a Sone was unlocked. - * - * @param soneUnlockedEvent - * The event - */ - @Subscribe - public void soneUnlocked(SoneUnlockedEvent soneUnlockedEvent) { - lockedSonesNotification.remove(soneUnlockedEvent.getSone()); - lockedSonesTickerObjects.remove(soneUnlockedEvent.getSone()).cancel(false); - } - - /** - * Notifies the web interface that a {@link Sone} is being inserted. - * - * @param soneInsertingEvent - * The event - */ - @Subscribe - public void soneInserting(SoneInsertingEvent soneInsertingEvent) { - TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertingEvent.getSone()); - soneInsertNotification.set("soneStatus", "inserting"); - if (soneInsertingEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) { - notificationManager.addNotification(soneInsertNotification); - } - } - - /** - * Notifies the web interface that a {@link Sone} was inserted. - * - * @param soneInsertedEvent - * The event - */ - @Subscribe - public void soneInserted(SoneInsertedEvent soneInsertedEvent) { - TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertedEvent.getSone()); - soneInsertNotification.set("soneStatus", "inserted"); - soneInsertNotification.set("insertDuration", soneInsertedEvent.getInsertDuration() / 1000); - if (soneInsertedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) { - notificationManager.addNotification(soneInsertNotification); - } - } - - /** - * Notifies the web interface that a {@link Sone} insert was aborted. - * - * @param soneInsertAbortedEvent - * The event - */ - @Subscribe - public void soneInsertAborted(SoneInsertAbortedEvent soneInsertAbortedEvent) { - TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertAbortedEvent.getSone()); - soneInsertNotification.set("soneStatus", "insert-aborted"); - soneInsertNotification.set("insert-error", soneInsertAbortedEvent.getCause()); - if (soneInsertAbortedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) { - notificationManager.addNotification(soneInsertNotification); - } - } - - /** - * Notifies the web interface that a new Sone version was found. - * - * @param updateFoundEvent - * The event - */ - @Subscribe - public void updateFound(UpdateFoundEvent updateFoundEvent) { - newVersionNotification.set("latestVersion", updateFoundEvent.getVersion()); - newVersionNotification.set("latestEdition", updateFoundEvent.getLatestEdition()); - newVersionNotification.set("releaseTime", updateFoundEvent.getReleaseTime()); - newVersionNotification.set("disruptive", updateFoundEvent.isDisruptive()); - notificationManager.addNotification(newVersionNotification); - } - - /** - * Notifies the web interface that an image insert was started - * - * @param imageInsertStartedEvent - * The event - */ - @Subscribe - public void imageInsertStarted(ImageInsertStartedEvent imageInsertStartedEvent) { - insertingImagesNotification.add(imageInsertStartedEvent.getImage()); - notificationManager.addNotification(insertingImagesNotification); - } - - /** - * Notifies the web interface that an {@link Image} insert was aborted. - * - * @param imageInsertAbortedEvent - * The event - */ - @Subscribe - public void imageInsertAborted(ImageInsertAbortedEvent imageInsertAbortedEvent) { - insertingImagesNotification.remove(imageInsertAbortedEvent.getImage()); - } - - /** - * Notifies the web interface that an {@link Image} insert is finished. - * - * @param imageInsertFinishedEvent - * The event - */ - @Subscribe - public void imageInsertFinished(ImageInsertFinishedEvent imageInsertFinishedEvent) { - insertingImagesNotification.remove(imageInsertFinishedEvent.getImage()); - insertedImagesNotification.add(imageInsertFinishedEvent.getImage()); - notificationManager.addNotification(insertedImagesNotification); - } - - /** - * Notifies the web interface that an {@link Image} insert has failed. - * - * @param imageInsertFailedEvent - * The event - */ - @Subscribe - public void imageInsertFailed(ImageInsertFailedEvent imageInsertFailedEvent) { - insertingImagesNotification.remove(imageInsertFailedEvent.getImage()); - imageInsertFailedNotification.add(imageInsertFailedEvent.getImage()); - notificationManager.addNotification(imageInsertFailedNotification); - } - @Subscribe public void debugActivated(@Nonnull DebugActivatedEvent debugActivatedEvent) { pageToadletRegistry.activateDebugMode(); diff --git a/src/main/kotlin/net/pterodactylus/sone/core/event/ConfigNotRead.kt b/src/main/kotlin/net/pterodactylus/sone/core/event/ConfigNotRead.kt new file mode 100644 index 0000000..c8f1573 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/core/event/ConfigNotRead.kt @@ -0,0 +1,26 @@ +/** + * Sone - ConfigNotRead.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.core.event + +/** + * Event that signals that Sone could not read an existing configuration + * successfully, and a new configuration was created. This is different from + * [FirstStart] in that `FirstStart` signals that there *was* no existing + * configuration to be read. + */ +class ConfigNotRead diff --git a/src/main/kotlin/net/pterodactylus/sone/core/event/FirstStart.kt b/src/main/kotlin/net/pterodactylus/sone/core/event/FirstStart.kt new file mode 100644 index 0000000..4ff81f3 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/core/event/FirstStart.kt @@ -0,0 +1,24 @@ +/** + * Sone - FirstStart.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.core.event + +/** + * Event that signals that Sone was started for the first time. This event + * will only be triggered once, on startup. + */ +class FirstStart diff --git a/src/main/kotlin/net/pterodactylus/sone/core/event/MentionOfLocalSoneFoundEvent.kt b/src/main/kotlin/net/pterodactylus/sone/core/event/MentionOfLocalSoneFoundEvent.kt new file mode 100644 index 0000000..b63203b --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/core/event/MentionOfLocalSoneFoundEvent.kt @@ -0,0 +1,27 @@ +/** + * Sone - MentionOfLocalSoneFoundEvent.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.core.event + +import net.pterodactylus.sone.data.* + +/** + * Event that signals that a new post or reply was found that mentioned a local + * Sone, which happens if the [SoneTextParser] locates a [SonePart] in a post + * or reply. + */ +data class MentionOfLocalSoneFoundEvent(val post: Post) diff --git a/src/main/kotlin/net/pterodactylus/sone/core/event/MentionOfLocalSoneRemovedEvent.kt b/src/main/kotlin/net/pterodactylus/sone/core/event/MentionOfLocalSoneRemovedEvent.kt new file mode 100644 index 0000000..4898de9 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/core/event/MentionOfLocalSoneRemovedEvent.kt @@ -0,0 +1,27 @@ +/** + * Sone - MentionOfLocalSoneRemovedEvent.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.core.event + +import net.pterodactylus.sone.data.* + +/** + * Event that signals that a post or reply that mentioned a local Sone was + * removed so that the given post and its replies do not contain a mention of + * a local Sone anymore. + */ +data class MentionOfLocalSoneRemovedEvent(val post: Post) diff --git a/src/main/kotlin/net/pterodactylus/sone/core/event/Shutdown.kt b/src/main/kotlin/net/pterodactylus/sone/core/event/Shutdown.kt new file mode 100644 index 0000000..4bc2f61 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/core/event/Shutdown.kt @@ -0,0 +1,23 @@ +/** + * Sone - Shutdown.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.core.event + +/** + * Event that signals the shutdown of Sone. + */ +class Shutdown diff --git a/src/main/kotlin/net/pterodactylus/sone/core/event/Startup.kt b/src/main/kotlin/net/pterodactylus/sone/core/event/Startup.kt new file mode 100644 index 0000000..9a9d273 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/core/event/Startup.kt @@ -0,0 +1,23 @@ +/** + * Sone - Startup.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.core.event + +/** + * Event that signals the startup of Sone. + */ +class Startup diff --git a/src/main/kotlin/net/pterodactylus/sone/core/event/WebOfTrustAppeared.kt b/src/main/kotlin/net/pterodactylus/sone/core/event/WebOfTrustAppeared.kt new file mode 100644 index 0000000..97daff1 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/core/event/WebOfTrustAppeared.kt @@ -0,0 +1,23 @@ +/** + * Sone - WebOfTrustAppeared.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.core.event + +/** + * Event that signals that the web of trust is reachable. + */ +class WebOfTrustAppeared diff --git a/src/main/kotlin/net/pterodactylus/sone/core/event/WebOfTrustDisappeared.kt b/src/main/kotlin/net/pterodactylus/sone/core/event/WebOfTrustDisappeared.kt new file mode 100644 index 0000000..63b4a1f --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/core/event/WebOfTrustDisappeared.kt @@ -0,0 +1,23 @@ +/** + * Sone - WebOfTrustDisappeared.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.core.event + +/** + * Event that signals that the web of trust is not reachable. + */ +class WebOfTrustDisappeared diff --git a/src/main/kotlin/net/pterodactylus/sone/database/PostReplyProvider.kt b/src/main/kotlin/net/pterodactylus/sone/database/PostReplyProvider.kt index cc797d7..4cd0d82 100644 --- a/src/main/kotlin/net/pterodactylus/sone/database/PostReplyProvider.kt +++ b/src/main/kotlin/net/pterodactylus/sone/database/PostReplyProvider.kt @@ -17,11 +17,14 @@ package net.pterodactylus.sone.database +import com.google.inject.* import net.pterodactylus.sone.data.* +import net.pterodactylus.sone.database.memory.* /** * Interface for objects that can provide [PostReply]s. */ +@ImplementedBy(MemoryDatabase::class) interface PostReplyProvider { fun getPostReply(id: String): PostReply? diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/FreenetURIs.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/FreenetURIs.kt index 3a6d43c..6d40b17 100644 --- a/src/main/kotlin/net/pterodactylus/sone/freenet/FreenetURIs.kt +++ b/src/main/kotlin/net/pterodactylus/sone/freenet/FreenetURIs.kt @@ -1,6 +1,6 @@ package net.pterodactylus.sone.freenet import freenet.keys.* -import freenet.support.Base64.* +import net.pterodactylus.sone.utils.* -val FreenetURI.routingKeyString: String get() = encode(routingKey) +val FreenetURI.routingKeyString: String get() = routingKey.asFreenetBase64 diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/wot/WebOfTrustPinger.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/WebOfTrustPinger.kt new file mode 100644 index 0000000..026389f --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/WebOfTrustPinger.kt @@ -0,0 +1,56 @@ +/** + * Sone - WebOfTrustPinger.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.freenet.wot + +import com.google.common.eventbus.* +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.sone.freenet.plugin.* +import net.pterodactylus.sone.utils.* +import java.util.concurrent.atomic.* +import java.util.function.* +import javax.inject.* + +/** + * [Runnable] that is scheduled via an [Executor][java.util.concurrent.Executor], + * checks whether the web of trust plugin can be communicated with, sends + * events if its status changes and reschedules itself. + */ +class WebOfTrustPinger @Inject constructor( + private val eventBus: EventBus, + @Named("webOfTrustReacher") private val webOfTrustReacher: Runnable, + @Named("webOfTrustReschedule") private val reschedule: Consumer) : Runnable { + + private val lastState = AtomicBoolean(false) + + override fun run() { + try { + webOfTrustReacher() + if (!lastState.get()) { + eventBus.post(WebOfTrustAppeared()) + lastState.set(true) + } + } catch (e: PluginException) { + if (lastState.get()) { + eventBus.post(WebOfTrustDisappeared()) + lastState.set(false) + } + } + reschedule(this) + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/main/SoneModule.kt b/src/main/kotlin/net/pterodactylus/sone/main/SoneModule.kt index 97b342a..749de0d 100644 --- a/src/main/kotlin/net/pterodactylus/sone/main/SoneModule.kt +++ b/src/main/kotlin/net/pterodactylus/sone/main/SoneModule.kt @@ -7,15 +7,20 @@ import com.google.inject.* import com.google.inject.matcher.* import com.google.inject.name.Names.* import com.google.inject.spi.* -import freenet.l10n.* import net.pterodactylus.sone.database.* import net.pterodactylus.sone.database.memory.* import net.pterodactylus.sone.freenet.* import net.pterodactylus.sone.freenet.wot.* import net.pterodactylus.util.config.* import net.pterodactylus.util.config.ConfigurationException +import net.pterodactylus.util.logging.* import net.pterodactylus.util.version.Version import java.io.* +import java.util.concurrent.* +import java.util.concurrent.Executors.* +import java.util.logging.* +import javax.inject.* +import javax.inject.Singleton open class SoneModule(private val sonePlugin: SonePlugin, private val eventBus: EventBus) : AbstractModule() { @@ -55,14 +60,26 @@ open class SoneModule(private val sonePlugin: SonePlugin, private val eventBus: loaders?.let { bind(Loaders::class.java).toInstance(it) } bind(MetricRegistry::class.java).`in`(Singleton::class.java) bind(WebOfTrustConnector::class.java).to(PluginWebOfTrustConnector::class.java).`in`(Singleton::class.java) + bind(TickerShutdown::class.java).`in`(Singleton::class.java) bindListener(Matchers.any(), object : TypeListener { override fun hear(typeLiteral: TypeLiteral, typeEncounter: TypeEncounter) { - typeEncounter.register(InjectionListener { injectee -> eventBus.register(injectee) }) + typeEncounter.register(InjectionListener { injectee -> + logger.fine { "Injecting $injectee..." } + eventBus.register(injectee) + }) } }) } + @Provides + @Singleton + @Named("notification") + fun getNotificationTicker(): ScheduledExecutorService = + newSingleThreadScheduledExecutor() + + private val logger: Logger = Logging.getLogger(javaClass) + } private fun String.parseVersion(): Version = Version.parse(this) diff --git a/src/main/kotlin/net/pterodactylus/sone/main/TickerShutdown.kt b/src/main/kotlin/net/pterodactylus/sone/main/TickerShutdown.kt new file mode 100644 index 0000000..c95da09 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/main/TickerShutdown.kt @@ -0,0 +1,36 @@ +/** + * Sone - TickerShutdown.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.main + +import com.google.common.eventbus.* +import net.pterodactylus.sone.core.event.* +import java.util.concurrent.* +import javax.inject.* + +/** + * Wrapper around all [tickers][ScheduledExecutorService] used in Sone, + * ensuring proper shutdown. + */ +class TickerShutdown @Inject constructor(@Named("notification") private val notificationTicker: ScheduledExecutorService) { + + @Subscribe + fun shutdown(@Suppress("UNUSED_PARAMETER") shutdown: Shutdown) { + notificationTicker.shutdown() + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/notify/Notifications.kt b/src/main/kotlin/net/pterodactylus/sone/notify/Notifications.kt new file mode 100644 index 0000000..329eedd --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/notify/Notifications.kt @@ -0,0 +1,32 @@ +/** + * Sone - Notifications.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.notify + +import net.pterodactylus.util.notify.* + +/** + * Returns whether the notification manager contains a notification with the given ID. + */ +operator fun NotificationManager.contains(id: String) = + getNotification(id) != null + +/** + * Returns whether the notification manager currently has a “first start” notification. + */ +fun NotificationManager.hasFirstStartNotification() = + "first-start-notification" in this diff --git a/src/main/kotlin/net/pterodactylus/sone/text/SoneMentionDetector.kt b/src/main/kotlin/net/pterodactylus/sone/text/SoneMentionDetector.kt new file mode 100644 index 0000000..b688e86 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/text/SoneMentionDetector.kt @@ -0,0 +1,94 @@ +/** + * Sone - SoneMentionDetector.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.text + +import com.google.common.eventbus.* +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.sone.data.* +import net.pterodactylus.sone.database.* +import net.pterodactylus.sone.utils.* +import javax.inject.* + +/** + * Listens to [NewPostFoundEvent]s and [NewPostReplyFoundEvent], parses the + * texts and emits a [MentionOfLocalSoneFoundEvent] if a [SoneTextParser] + * finds a [SonePart] that points to a local [Sone]. + */ +class SoneMentionDetector @Inject constructor(private val eventBus: EventBus, private val soneTextParser: SoneTextParser, private val postReplyProvider: PostReplyProvider) { + + @Subscribe + fun onNewPost(newPostFoundEvent: NewPostFoundEvent) { + newPostFoundEvent.post.let { post -> + post.sone.isLocal.onFalse { + if (post.text.hasLinksToLocalSones()) { + mentionedPosts += post + eventBus.post(MentionOfLocalSoneFoundEvent(post)) + } + } + } + } + + @Subscribe + fun onNewPostReply(event: NewPostReplyFoundEvent) { + event.postReply.let { postReply -> + postReply.sone.isLocal.onFalse { + if (postReply.text.hasLinksToLocalSones()) { + postReply.post + .also { mentionedPosts += it } + .let(::MentionOfLocalSoneFoundEvent) + ?.also(eventBus::post) + } + } + } + } + + @Subscribe + fun onPostRemoved(event: PostRemovedEvent) { + unmentionPost(event.post) + } + + @Subscribe + fun onPostMarkedKnown(event: MarkPostKnownEvent) { + unmentionPost(event.post) + } + + @Subscribe + fun onReplyRemoved(event: PostReplyRemovedEvent) { + event.postReply.post.let { + if ((!it.text.hasLinksToLocalSones() || it.isKnown) && (it.replies.filterNot { it == event.postReply }.none { it.text.hasLinksToLocalSones() && !it.isKnown })) { + unmentionPost(it) + } + } + } + + private fun unmentionPost(post: Post) { + if (post in mentionedPosts) { + eventBus.post(MentionOfLocalSoneRemovedEvent(post)) + mentionedPosts -= post + } + } + + private val mentionedPosts = mutableSetOf() + + private fun String.hasLinksToLocalSones() = soneTextParser.parse(this, null) + .filterIsInstance() + .any { it.sone.isLocal } + + private val Post.replies get() = postReplyProvider.getReplies(id) + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/text/SoneTextParser.kt b/src/main/kotlin/net/pterodactylus/sone/text/SoneTextParser.kt index 554c19b..b711ca8 100644 --- a/src/main/kotlin/net/pterodactylus/sone/text/SoneTextParser.kt +++ b/src/main/kotlin/net/pterodactylus/sone/text/SoneTextParser.kt @@ -7,6 +7,7 @@ import net.pterodactylus.sone.data.impl.* import net.pterodactylus.sone.database.* import net.pterodactylus.sone.text.LinkType.* import net.pterodactylus.sone.text.LinkType.USK +import net.pterodactylus.sone.utils.* import org.bitpedia.util.* import java.net.* import javax.inject.* @@ -71,7 +72,7 @@ class SoneTextParser @Inject constructor(private val soneProvider: SoneProvider? ?.takeIf { (it.size > 1) || ((it.size == 1) && (it.single() != "")) } ?.lastOrNull() ?: uri.docName - ?: "${uri.keyType}@${uri.routingKey.freenetBase64}" + ?: "${uri.keyType}@${uri.routingKey.asFreenetBase64}" }.let { FreenetLinkPart(linkWithoutBacklink.removeSuffix("/"), it, trusted = context?.routingKey?.contentEquals(FreenetURI(linkWithoutBacklink).routingKey) == true) } } catch (e: MalformedURLException) { PlainTextPart(linkWithoutBacklink) @@ -115,7 +116,7 @@ private fun List.mergeAdjacentPlainTextParts() = fold(emptyList()) { private fun List.removeEmptyPlainTextParts() = filterNot { it == PlainTextPart("") } -private val String.decodedId: String get() = Base64.encode(Base32.decode(this)) +private val String.decodedId: String get() = Base32.decode(this).asFreenetBase64 private val String.withoutProtocol get() = substring(indexOf("//") + 2) private val String.withoutUrlParameters get() = split('?').first() @@ -138,7 +139,7 @@ private val String.withoutMiddlePathComponents } private val String.withoutTrailingSlash get() = if (endsWith("/")) substring(0, length - 1) else this private val SoneTextParserContext.routingKey: ByteArray? get() = postingSone?.routingKey -private val Sone.routingKey: ByteArray get() = Base64.decode(id) +private val Sone.routingKey: ByteArray get() = id.fromFreenetBase64 private enum class LinkType(private val scheme: String, private val freenetLink: Boolean) { @@ -199,5 +200,3 @@ private fun isPunctuation(char: Char) = char in punctuationChars private val whitespace = Regex("[\\u000a\u0020\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u200c\u200d\u202f\u205f\u2060\u2800\u3000]") private data class NextLink(val position: Int, val linkType: LinkType, val link: String, val remainder: String) - -private val ByteArray.freenetBase64 get() = Base64.encode(this)!! diff --git a/src/main/kotlin/net/pterodactylus/sone/utils/Booleans.kt b/src/main/kotlin/net/pterodactylus/sone/utils/Booleans.kt index 49deb87..1d3e097 100644 --- a/src/main/kotlin/net/pterodactylus/sone/utils/Booleans.kt +++ b/src/main/kotlin/net/pterodactylus/sone/utils/Booleans.kt @@ -11,6 +11,14 @@ fun Boolean.ifTrue(block: () -> R): R? = if (this) block() else null fun Boolean.ifFalse(block: () -> R): R? = if (!this) block() else null /** + * Returns `this` but runs the given block if `this` is `true`. + * + * @param block The block to run if `this` is `true` + * @return `this` + */ +fun Boolean.onTrue(block: () -> Unit): Boolean = also { if (this) block() } + +/** * Returns `this` but runs the given block if `this` is `false`. * * @param block The block to run if `this` is `false` diff --git a/src/main/kotlin/net/pterodactylus/sone/utils/Freenet.kt b/src/main/kotlin/net/pterodactylus/sone/utils/Freenet.kt new file mode 100644 index 0000000..a98117f --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/utils/Freenet.kt @@ -0,0 +1,23 @@ +/** + * Sone - Freenet.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.utils + +import freenet.support.* + +val ByteArray.asFreenetBase64: String get() = Base64.encode(this) +val String.fromFreenetBase64: ByteArray get() = Base64.decode(this) diff --git a/src/main/kotlin/net/pterodactylus/sone/utils/Functions.kt b/src/main/kotlin/net/pterodactylus/sone/utils/Functions.kt new file mode 100644 index 0000000..99f43b6 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/utils/Functions.kt @@ -0,0 +1,9 @@ +package net.pterodactylus.sone.utils + +import java.util.function.* + +/** Allows easy invocation of Java Consumers. */ +operator fun Consumer.invoke(t: T) = accept(t) + +/** Allows easy invocation of Java Runnables. */ +operator fun Runnable.invoke() = run() diff --git a/src/main/kotlin/net/pterodactylus/sone/web/WebInterfaceModule.kt b/src/main/kotlin/net/pterodactylus/sone/web/WebInterfaceModule.kt index 63f3fc6..3d87aa7 100644 --- a/src/main/kotlin/net/pterodactylus/sone/web/WebInterfaceModule.kt +++ b/src/main/kotlin/net/pterodactylus/sone/web/WebInterfaceModule.kt @@ -1,6 +1,5 @@ package net.pterodactylus.sone.web -import com.google.common.eventbus.* import com.google.inject.* import freenet.support.api.* import net.pterodactylus.sone.core.* @@ -11,7 +10,6 @@ import net.pterodactylus.sone.freenet.wot.* import net.pterodactylus.sone.main.* import net.pterodactylus.sone.template.* import net.pterodactylus.sone.text.* -import net.pterodactylus.sone.web.notification.* import net.pterodactylus.util.notify.* import net.pterodactylus.util.template.* import javax.inject.* @@ -134,9 +132,4 @@ class WebInterfaceModule : AbstractModule() { fun getNotificationManager() = NotificationManager() - @Provides - @Singleton - fun getNotificationHandler(eventBus: EventBus, loaders: Loaders, notificationManager: NotificationManager) = - NotificationHandler(eventBus, loaders, notificationManager) - } diff --git a/src/main/kotlin/net/pterodactylus/sone/web/notification/ConfigNotReadHandler.kt b/src/main/kotlin/net/pterodactylus/sone/web/notification/ConfigNotReadHandler.kt new file mode 100644 index 0000000..4ad525b --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/notification/ConfigNotReadHandler.kt @@ -0,0 +1,35 @@ +/** + * Sone - ConfigNotReadHandler.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.notification + +import com.google.common.eventbus.* +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.util.notify.* +import javax.inject.* + +/** + * Handler for [ConfigNotRead] events. + */ +class ConfigNotReadHandler @Inject constructor(private val notificationManager: NotificationManager, @Named("configNotRead") private val notification: TemplateNotification) { + + @Subscribe + fun configNotRead(@Suppress("UNUSED_PARAMETER") configNotRead: ConfigNotRead) { + notificationManager.addNotification(notification) + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/notification/FirstStartHandler.kt b/src/main/kotlin/net/pterodactylus/sone/web/notification/FirstStartHandler.kt new file mode 100644 index 0000000..dc9c507 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/notification/FirstStartHandler.kt @@ -0,0 +1,35 @@ +/** + * Sone - FirstStartHandler.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.notification + +import com.google.common.eventbus.* +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.util.notify.* +import javax.inject.* + +/** + * Handles the notification shown on first start of Sone. + */ +class FirstStartHandler @Inject constructor(private val notificationManager: NotificationManager, @Named("firstStart") private val notification: TemplateNotification) { + + @Subscribe + fun firstStart(@Suppress("UNUSED_PARAMETER") firstStart: FirstStart) { + notificationManager.addNotification(notification) + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/notification/ImageInsertHandler.kt b/src/main/kotlin/net/pterodactylus/sone/web/notification/ImageInsertHandler.kt new file mode 100644 index 0000000..0d36de6 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/notification/ImageInsertHandler.kt @@ -0,0 +1,66 @@ +/** + * Sone - ImageInsertHandler.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.notification + +import com.google.common.eventbus.* +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.sone.data.* +import net.pterodactylus.sone.notify.* +import net.pterodactylus.util.notify.* +import javax.inject.* + +/** + * Notification handler for the various image-insert-related events. + * + * @see ImageInsertStartedEvent + * @see ImageInsertAbortedEvent + * @see ImageInsertFailedEvent + * @see ImageInsertFinishedEvent + */ +class ImageInsertHandler @Inject constructor( + private val notificationManager: NotificationManager, + @Named("imageInserting") private val imageInsertingNotification: ListNotification, + @Named("imageFailed") private val imageFailedNotification: ListNotification, + @Named("imageInserted") private val imageInsertedNotification: ListNotification) { + + @Subscribe + fun imageInsertStarted(imageInsertStartedEvent: ImageInsertStartedEvent) { + imageInsertingNotification.add(imageInsertStartedEvent.image) + notificationManager.addNotification(imageInsertingNotification) + } + + @Subscribe + fun imageInsertAborted(imageInsertAbortedEvent: ImageInsertAbortedEvent) { + imageInsertingNotification.remove(imageInsertAbortedEvent.image) + } + + @Subscribe + fun imageInsertFailed(imageInsertFailedEvent: ImageInsertFailedEvent) { + imageInsertingNotification.remove(imageInsertFailedEvent.image) + imageFailedNotification.add(imageInsertFailedEvent.image) + notificationManager.addNotification(imageFailedNotification) + } + + @Subscribe + fun imageInsertFinished(imageInsertFinishedEvent: ImageInsertFinishedEvent) { + imageInsertingNotification.remove(imageInsertFinishedEvent.image) + imageInsertedNotification.add(imageInsertFinishedEvent.image) + notificationManager.addNotification(imageInsertedNotification) + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/notification/LocalPostHandler.kt b/src/main/kotlin/net/pterodactylus/sone/web/notification/LocalPostHandler.kt new file mode 100644 index 0000000..cd78dd0 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/notification/LocalPostHandler.kt @@ -0,0 +1,59 @@ +/** + * Sone - NewLocalPostHandler.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.notification + +import com.google.common.eventbus.* +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.sone.data.* +import net.pterodactylus.sone.notify.* +import net.pterodactylus.util.notify.* +import javax.inject.* + +/** + * Handler for local posts. + */ +class LocalPostHandler @Inject constructor(private val notificationManager: NotificationManager, @Named("localPost") private val notification: ListNotification) { + + @Subscribe + fun newPostFound(newPostFoundEvent: NewPostFoundEvent) { + newPostFoundEvent.post.onLocal { post -> + notification.add(post) + if (!notificationManager.hasFirstStartNotification()) { + notificationManager.addNotification(notification) + } + } + } + + @Subscribe + fun postRemoved(postRemovedEvent: PostRemovedEvent) { + postRemovedEvent.post.onLocal { post -> + notification.remove(post) + } + } + + @Subscribe + fun postMarkedAsKnown(markPostKnownEvent: MarkPostKnownEvent) { + markPostKnownEvent.post.onLocal { post -> + notification.remove(post) + } + } + +} + +private fun Post.onLocal(action: (Post) -> Unit) = + if (sone.isLocal) action(this) else Unit diff --git a/src/main/kotlin/net/pterodactylus/sone/web/notification/LocalReplyHandler.kt b/src/main/kotlin/net/pterodactylus/sone/web/notification/LocalReplyHandler.kt new file mode 100644 index 0000000..424295e --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/notification/LocalReplyHandler.kt @@ -0,0 +1,54 @@ +/** + * Sone - LocalReplyHandler.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.notification + +import com.google.common.eventbus.* +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.sone.data.* +import net.pterodactylus.sone.notify.* +import net.pterodactylus.util.notify.* +import javax.inject.* + +/** + * Handler for local replies. + */ +class LocalReplyHandler @Inject constructor(private val notificationManager: NotificationManager, @Named("localReply") private val notification: ListNotification) { + + @Subscribe + fun newReplyFound(event: NewPostReplyFoundEvent) = + event.postReply.onLocal { + notification.add(it) + if (!notificationManager.hasFirstStartNotification()) { + notificationManager.addNotification(notification) + } + } + + @Subscribe + fun replyRemoved(event: PostReplyRemovedEvent) { + notification.remove(event.postReply) + } + + @Subscribe + fun replyMarkedAsKnown(event: MarkPostReplyKnownEvent) { + notification.remove(event.postReply) + } + +} + +private fun PostReply.onLocal(action: (PostReply) -> Unit) = + if (sone.isLocal) action(this) else Unit diff --git a/src/main/kotlin/net/pterodactylus/sone/web/notification/MarkPostKnownDuringFirstStartHandler.kt b/src/main/kotlin/net/pterodactylus/sone/web/notification/MarkPostKnownDuringFirstStartHandler.kt new file mode 100644 index 0000000..caca76e --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/notification/MarkPostKnownDuringFirstStartHandler.kt @@ -0,0 +1,43 @@ +/** + * Sone - MarkPostKnownDuringFirstStartHandler.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.notification + +import com.google.common.eventbus.* +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.sone.data.* +import net.pterodactylus.sone.notify.* +import net.pterodactylus.sone.utils.* +import net.pterodactylus.util.notify.* +import java.util.function.* +import javax.inject.* + +/** + * Handler that marks a [new][NewPostFoundEvent] [post][Post] as known while + * the [notification manager][NotificationManager] shows a [first start notification] + * [NotificationManager.hasFirstStartNotification]. + */ +class MarkPostKnownDuringFirstStartHandler @Inject constructor(private val notificationManager: NotificationManager, private val markPostAsKnown: Consumer) { + + @Subscribe + fun newPostFound(newPostFoundEvent: NewPostFoundEvent) { + if (notificationManager.hasFirstStartNotification()) { + markPostAsKnown(newPostFoundEvent.post) + } + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/notification/MarkPostReplyKnownDuringFirstStartHandler.kt b/src/main/kotlin/net/pterodactylus/sone/web/notification/MarkPostReplyKnownDuringFirstStartHandler.kt new file mode 100644 index 0000000..6a7f083 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/notification/MarkPostReplyKnownDuringFirstStartHandler.kt @@ -0,0 +1,43 @@ +/** + * Sone - MarkPostReplyKnownDuringFirstStartHandlerTest.kt - Copyright © 2020 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.notification + +import com.google.common.eventbus.* +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.sone.data.* +import net.pterodactylus.sone.notify.* +import net.pterodactylus.sone.utils.* +import net.pterodactylus.util.notify.* +import java.util.function.* +import javax.inject.* + +/** + * Handler that marks post replies [as known][net.pterodactylus.sone.core.Core.markReplyKnown] + * while the [first start notification][net.pterodactylus.util.notify.NotificationManager.hasFirstStartNotification] + * is shown. + */ +class MarkPostReplyKnownDuringFirstStartHandler @Inject constructor(private val notificationManager: NotificationManager, private val markAsKnown: Consumer) { + + @Subscribe + fun newPostReply(event: NewPostReplyFoundEvent) { + if (notificationManager.hasFirstStartNotification()) { + markAsKnown(event.postReply) + } + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/notification/NewRemotePostHandler.kt b/src/main/kotlin/net/pterodactylus/sone/web/notification/NewRemotePostHandler.kt new file mode 100644 index 0000000..6efff85 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/notification/NewRemotePostHandler.kt @@ -0,0 +1,53 @@ +/** + * Sone - NewRemotePostHandler.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.notification + +import com.google.common.eventbus.* +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.sone.data.* +import net.pterodactylus.sone.notify.* +import net.pterodactylus.util.notify.* +import javax.inject.* + +/** + * Handler for [NewPostFoundEvent]s that adds the new post to the “new posts” notification and + * displays the notification if the “first start” notification is not being shown. + */ +class NewRemotePostHandler @Inject constructor(private val notificationManager: NotificationManager, @Named("newRemotePost") private val notification: ListNotification) { + + @Subscribe + fun newPostFound(newPostFoundEvent: NewPostFoundEvent) { + if (!newPostFoundEvent.post.sone.isLocal) { + notification.add(newPostFoundEvent.post) + if (!notificationManager.hasFirstStartNotification()) { + notificationManager.addNotification(notification) + } + } + } + + @Subscribe + fun postRemoved(event: PostRemovedEvent) { + notification.remove(event.post) + } + + @Subscribe + fun postMarkedKnown(event: MarkPostKnownEvent) { + notification.remove(event.post) + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/notification/NewSoneHandler.kt b/src/main/kotlin/net/pterodactylus/sone/web/notification/NewSoneHandler.kt new file mode 100644 index 0000000..f47d456 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/notification/NewSoneHandler.kt @@ -0,0 +1,50 @@ +/** + * Sone - NewSoneHandler.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.notification + +import com.google.common.eventbus.* +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.sone.data.* +import net.pterodactylus.sone.notify.* +import net.pterodactylus.util.notify.* +import javax.inject.* + +/** + * Notification handler for “new Sone discovered” events. + */ +class NewSoneHandler @Inject constructor(private val notificationManager: NotificationManager, @Named("newSone") private val notification: ListNotification) { + + @Subscribe + fun newSoneFound(newSoneFoundEvent: NewSoneFoundEvent) { + if (!notificationManager.hasFirstStartNotification()) { + notification.add(newSoneFoundEvent.sone) + notificationManager.addNotification(notification) + } + } + + @Subscribe + fun markedSoneKnown(markSoneKnownEvent: MarkSoneKnownEvent) { + notification.remove(markSoneKnownEvent.sone) + } + + @Subscribe + fun soneRemoved(soneRemovedEvent: SoneRemovedEvent) { + notification.remove(soneRemovedEvent.sone) + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/notification/NewVersionHandler.kt b/src/main/kotlin/net/pterodactylus/sone/web/notification/NewVersionHandler.kt new file mode 100644 index 0000000..95ef0ef --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/notification/NewVersionHandler.kt @@ -0,0 +1,39 @@ +/** + * Sone - NewVersionHandler.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.notification + +import com.google.common.eventbus.* +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.util.notify.* +import javax.inject.* + +/** + * Handler for the “new version” notification. + */ +class NewVersionHandler @Inject constructor(private val notificationManager: NotificationManager, @Named("newVersion") private val notification: TemplateNotification) { + + @Subscribe + fun newVersionFound(updateFoundEvent: UpdateFoundEvent) { + notification.set("latestVersion", updateFoundEvent.version) + notification.set("releaseTime", updateFoundEvent.releaseTime) + notification.set("latestEdition", updateFoundEvent.latestEdition) + notification.set("disruptive", updateFoundEvent.isDisruptive) + notificationManager.addNotification(notification) + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/notification/NotificationHandler.kt b/src/main/kotlin/net/pterodactylus/sone/web/notification/NotificationHandler.kt index 7f81f9a..28b82c0 100644 --- a/src/main/kotlin/net/pterodactylus/sone/web/notification/NotificationHandler.kt +++ b/src/main/kotlin/net/pterodactylus/sone/web/notification/NotificationHandler.kt @@ -17,20 +17,33 @@ package net.pterodactylus.sone.web.notification -import com.google.common.eventbus.* -import net.pterodactylus.sone.main.* -import net.pterodactylus.util.notify.* +import net.pterodactylus.sone.freenet.wot.* +import net.pterodactylus.sone.text.* import javax.inject.* /** - * Handler for notifications that can create notifications and register them with an event bus. + * Container that causes notification handlers to be created and (more importantly) registered + * on creation with the event bus. */ -@Suppress("UnstableApiUsage") -class NotificationHandler @Inject constructor(private val eventBus: EventBus, private val loaders: Loaders, private val notificationManager: NotificationManager) { - - fun start() { - SoneLockedOnStartupHandler(notificationManager, loaders.loadTemplate("/templates/notify/soneLockedOnStartupNotification.html")) - .also(eventBus::register) - } - -} +@Suppress("UNUSED_PARAMETER") +class NotificationHandler @Inject constructor( + markPostKnownDuringFirstStartHandler: MarkPostKnownDuringFirstStartHandler, + markPostReplyKnownDuringFirstStartHandler: MarkPostReplyKnownDuringFirstStartHandler, + newSoneHandler: NewSoneHandler, + newRemotePostHandler: NewRemotePostHandler, + remotePostReplyHandler: RemotePostReplyHandler, + soneLockedOnStartupHandler: SoneLockedOnStartupHandler, + soneLockedHandler: SoneLockedHandler, + localPostHandler: LocalPostHandler, + localReplyHandler: LocalReplyHandler, + newVersionHandler: NewVersionHandler, + imageInsertHandler: ImageInsertHandler, + firstStartHandler: FirstStartHandler, + configNotReadHandler: ConfigNotReadHandler, + startupHandler: StartupHandler, + webOfTrustPinger: WebOfTrustPinger, + webOfTrustHandler: WebOfTrustHandler, + soneMentionDetector: SoneMentionDetector, + soneMentionedHandler: SoneMentionedHandler, + soneInsertHandler: SoneInsertHandler +) diff --git a/src/main/kotlin/net/pterodactylus/sone/web/notification/NotificationHandlerModule.kt b/src/main/kotlin/net/pterodactylus/sone/web/notification/NotificationHandlerModule.kt new file mode 100644 index 0000000..bdbbc32 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/notification/NotificationHandlerModule.kt @@ -0,0 +1,192 @@ +/** + * Sone - NotificationHandlerModuleTest.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.notification + +import com.google.inject.* +import com.google.inject.binder.* +import net.pterodactylus.sone.core.* +import net.pterodactylus.sone.data.* +import net.pterodactylus.sone.freenet.wot.* +import net.pterodactylus.sone.main.* +import net.pterodactylus.sone.notify.* +import net.pterodactylus.sone.text.* +import net.pterodactylus.util.notify.* +import java.util.concurrent.* +import java.util.concurrent.TimeUnit.* +import java.util.function.* +import javax.inject.* +import javax.inject.Singleton + +/** + * Guice module for creating all notification handlers. + */ +class NotificationHandlerModule : AbstractModule() { + + override fun configure() { + bind(NotificationHandler::class.java).`in`(Singleton::class.java) + bind().asSingleton() + bind().asSingleton() + bind().asSingleton() + bind().asSingleton() + bind().asSingleton() + bind().asSingleton() + bind().asSingleton() + bind().asSingleton() + bind().asSingleton() + bind().asSingleton() + bind().asSingleton() + bind().asSingleton() + bind().asSingleton() + bind().asSingleton() + bind().asSingleton() + bind().asSingleton() + bind().asSingleton() + bind().asSingleton() + } + + @Provides + fun getMarkPostKnownHandler(core: Core): Consumer = Consumer { core.markPostKnown(it) } + + @Provides + fun getMarkPostReplyKnownHandler(core: Core): Consumer = Consumer { core.markReplyKnown(it) } + + @Provides + @Singleton + @Named("soneLockedOnStartup") + fun getSoneLockedOnStartupNotification(loaders: Loaders) = + ListNotification("sone-locked-on-startup", "sones", loaders.loadTemplate("/templates/notify/soneLockedOnStartupNotification.html")) + + @Provides + @Named("newSone") + fun getNewSoneNotification(loaders: Loaders) = + ListNotification("new-sone-notification", "sones", loaders.loadTemplate("/templates/notify/newSoneNotification.html"), dismissable = false) + + @Provides + @Singleton + @Named("newRemotePost") + fun getNewPostNotification(loaders: Loaders) = + ListNotification("new-post-notification", "posts", loaders.loadTemplate("/templates/notify/newPostNotification.html"), dismissable = false) + + @Provides + @Singleton + @Named("newRemotePostReply") + fun getNewRemotePostReplyNotification(loaders: Loaders) = + ListNotification("new-reply-notification", "replies", loaders.loadTemplate("/templates/notify/newReplyNotification.html"), dismissable = false) + + @Provides + @Singleton + @Named("soneLocked") + fun getSoneLockedNotification(loaders: Loaders) = + ListNotification("sones-locked-notification", "sones", loaders.loadTemplate("/templates/notify/lockedSonesNotification.html"), dismissable = true) + + @Provides + @Singleton + @Named("localPost") + fun getLocalPostNotification(loaders: Loaders) = + ListNotification("local-post-notification", "posts", loaders.loadTemplate("/templates/notify/newPostNotification.html"), dismissable = false) + + @Provides + @Singleton + @Named("localReply") + fun getLocalReplyNotification(loaders: Loaders) = + ListNotification("local-reply-notification", "replies", loaders.loadTemplate("/templates/notify/newReplyNotification.html"), dismissable = false) + + @Provides + @Singleton + @Named("newVersion") + fun getNewVersionNotification(loaders: Loaders) = + TemplateNotification("new-version-notification", loaders.loadTemplate("/templates/notify/newVersionNotification.html")) + + @Provides + @Singleton + @Named("imageInserting") + fun getImageInsertingNotification(loaders: Loaders) = + ListNotification("inserting-images-notification", "images", loaders.loadTemplate("/templates/notify/inserting-images-notification.html"), dismissable = true) + + @Provides + @Singleton + @Named("imageFailed") + fun getImageInsertingFailedNotification(loaders: Loaders) = + ListNotification("image-insert-failed-notification", "images", loaders.loadTemplate("/templates/notify/image-insert-failed-notification.html"), dismissable = true) + + @Provides + @Singleton + @Named("imageInserted") + fun getImageInsertedNotification(loaders: Loaders) = + ListNotification("inserted-images-notification", "images", loaders.loadTemplate("/templates/notify/inserted-images-notification.html"), dismissable = true) + + @Provides + @Singleton + @Named("firstStart") + fun getFirstStartNotification(loaders: Loaders) = + TemplateNotification("first-start-notification", loaders.loadTemplate("/templates/notify/firstStartNotification.html")) + + @Provides + @Singleton + @Named("configNotRead") + fun getConfigNotReadNotification(loaders: Loaders) = + TemplateNotification("config-not-read-notification", loaders.loadTemplate("/templates/notify/configNotReadNotification.html")) + + @Provides + @Singleton + @Named("startup") + fun getStartupNotification(loaders: Loaders) = + TemplateNotification("startup-notification", loaders.loadTemplate("/templates/notify/startupNotification.html")) + + @Provides + @Singleton + @Named("webOfTrust") + fun getWebOfTrustNotification(loaders: Loaders) = + TemplateNotification("wot-missing-notification", loaders.loadTemplate("/templates/notify/wotMissingNotification.html")) + + @Provides + @Singleton + @Named("webOfTrustReacher") + fun getWebOfTrustReacher(webOfTrustConnector: WebOfTrustConnector): Runnable = + Runnable { webOfTrustConnector.ping() } + + @Provides + @Singleton + @Named("webOfTrustReschedule") + fun getWebOfTrustReschedule(@Named("notification") ticker: ScheduledExecutorService) = + Consumer { ticker.schedule(it, 15, SECONDS) } + + @Provides + @Singleton + @Named("soneMentioned") + fun getSoneMentionedNotification(loaders: Loaders) = + ListNotification("mention-notification", "posts", loaders.loadTemplate("/templates/notify/mentionNotification.html"), dismissable = false) + + @Provides + @Singleton + fun getSoneNotificationSupplier(loaders: Loaders): SoneInsertNotificationSupplier = + mutableMapOf() + .let { cache -> + { sone -> + cache.computeIfAbsent(sone) { + loaders.loadTemplate("/templates/notify/soneInsertNotification.html") + .let(::TemplateNotification) + .also { it["insertSone"] = sone } + } + } + } + + private inline fun bind(): AnnotatedBindingBuilder = bind(T::class.java) + private fun ScopedBindingBuilder.asSingleton() = `in`(Singleton::class.java) + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/notification/RemotePostReplyHandler.kt b/src/main/kotlin/net/pterodactylus/sone/web/notification/RemotePostReplyHandler.kt new file mode 100644 index 0000000..7dec7c9 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/notification/RemotePostReplyHandler.kt @@ -0,0 +1,55 @@ +/** + * Sone - RemotePostReplyHandler.kt - Copyright © 2020 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.notification + +import com.google.common.eventbus.* +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.sone.data.* +import net.pterodactylus.sone.notify.* +import net.pterodactylus.sone.utils.* +import net.pterodactylus.util.notify.* +import javax.inject.* + +/** + * Handler for remote replies. + */ +class RemotePostReplyHandler @Inject constructor(private val notificationManager: NotificationManager, @Named("newRemotePostReply") private val notification: ListNotification) { + + @Subscribe + fun newPostReplyFound(event: NewPostReplyFoundEvent) { + event.postReply.let { postReply -> + postReply.sone.isLocal.onFalse { + if (!notificationManager.hasFirstStartNotification()) { + notification.add(event.postReply) + notificationManager.addNotification(notification) + } + } + } + } + + @Subscribe + fun postReplyRemoved(event: PostReplyRemovedEvent) { + notification.remove(event.postReply) + } + + @Subscribe + fun postReplyMarkedAsKnown(event: MarkPostReplyKnownEvent) { + notification.remove(event.postReply) + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/notification/SoneInsertHandler.kt b/src/main/kotlin/net/pterodactylus/sone/web/notification/SoneInsertHandler.kt new file mode 100644 index 0000000..b12acb4 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/notification/SoneInsertHandler.kt @@ -0,0 +1,58 @@ +/** + * Sone - SoneInsertHandler.kt - Copyright © 2020 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.notification + +import com.google.common.eventbus.* +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.sone.data.* +import net.pterodactylus.util.notify.* +import javax.inject.* + +/** + * Handler for all notifications concerning Sone-insert events. + */ +class SoneInsertHandler @Inject constructor(private val notificationManager: NotificationManager, private val soneNotifications: SoneInsertNotificationSupplier) { + + @Subscribe + fun soneInserting(event: SoneInsertingEvent) { + showNotification(event.sone, "inserting") + } + + @Subscribe + fun soneInserted(event: SoneInsertedEvent) { + showNotification(event.sone, "inserted", "insertDuration" to event.insertDuration / 1000) + } + + @Subscribe + fun soneInsertAborted(event: SoneInsertAbortedEvent) { + showNotification(event.sone, "insert-aborted") + } + + private fun showNotification(sone: Sone, status: String, vararg templateVariables: Pair) { + if (sone.options.isSoneInsertNotificationEnabled) { + soneNotifications(sone).let { notification -> + notification["soneStatus"] = status + templateVariables.forEach { notification[it.first] = it.second } + notificationManager.addNotification(notification) + } + } + } + +} + +typealias SoneInsertNotificationSupplier = (@JvmSuppressWildcards Sone) -> @JvmSuppressWildcards TemplateNotification diff --git a/src/main/kotlin/net/pterodactylus/sone/web/notification/SoneLockedHandler.kt b/src/main/kotlin/net/pterodactylus/sone/web/notification/SoneLockedHandler.kt new file mode 100644 index 0000000..2217bad --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/notification/SoneLockedHandler.kt @@ -0,0 +1,65 @@ +/** + * Sone - SoneLockedHandler.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.notification + +import com.google.common.eventbus.* +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.sone.data.* +import net.pterodactylus.sone.notify.* +import net.pterodactylus.util.notify.* +import java.util.concurrent.* +import java.util.concurrent.atomic.* +import javax.inject.* + +/** + * Handler for [SoneLockedEvent]s and [SoneUnlockedEvent]s that can schedule notifications after + * a certain timeout. + */ +class SoneLockedHandler @Inject constructor( + private val notificationManager: NotificationManager, + @Named("soneLocked") private val notification: ListNotification, + @Named("notification") private val executor: ScheduledExecutorService) { + + private val future: AtomicReference> = AtomicReference() + + @Subscribe + fun soneLocked(soneLockedEvent: SoneLockedEvent) { + synchronized(future) { + notification.add(soneLockedEvent.sone) + future.get()?.also(this::cancelPreviousFuture) + future.set(executor.schedule(::showNotification, 5, TimeUnit.MINUTES)) + } + } + + @Subscribe + fun soneUnlocked(soneUnlockedEvent: SoneUnlockedEvent) { + synchronized(future) { + notification.remove(soneUnlockedEvent.sone) + future.get()?.also(::cancelPreviousFuture) + } + } + + private fun cancelPreviousFuture(future: ScheduledFuture<*>) { + future.cancel(true) + } + + private fun showNotification() { + notificationManager.addNotification(notification) + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/notification/SoneLockedOnStartupHandler.kt b/src/main/kotlin/net/pterodactylus/sone/web/notification/SoneLockedOnStartupHandler.kt index d6ec08f..c6de63f 100644 --- a/src/main/kotlin/net/pterodactylus/sone/web/notification/SoneLockedOnStartupHandler.kt +++ b/src/main/kotlin/net/pterodactylus/sone/web/notification/SoneLockedOnStartupHandler.kt @@ -22,15 +22,13 @@ import net.pterodactylus.sone.core.event.* import net.pterodactylus.sone.data.* import net.pterodactylus.sone.notify.* import net.pterodactylus.util.notify.* -import net.pterodactylus.util.template.* +import javax.inject.* /** * Handler for [SoneLockedOnStartup][net.pterodactylus.sone.core.event.SoneLockedOnStartup] events * that adds the appropriate notification to the [NotificationManager]. */ -class SoneLockedOnStartupHandler(private val notificationManager: NotificationManager, template: Template) { - - private val notification = ListNotification("sone-locked-on-startup", "sones", template) +class SoneLockedOnStartupHandler @Inject constructor(private val notificationManager: NotificationManager, @Named("soneLockedOnStartup") private val notification: ListNotification) { @Subscribe @Suppress("UnstableApiUsage") diff --git a/src/main/kotlin/net/pterodactylus/sone/web/notification/SoneMentionedHandler.kt b/src/main/kotlin/net/pterodactylus/sone/web/notification/SoneMentionedHandler.kt new file mode 100644 index 0000000..a03e490 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/notification/SoneMentionedHandler.kt @@ -0,0 +1,47 @@ +/** + * Sone - SoneMentionedHandler.kt - Copyright © 2020 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.notification + +import com.google.common.eventbus.* +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.sone.data.* +import net.pterodactylus.sone.notify.* +import net.pterodactylus.util.notify.* +import javax.inject.* + +/** + * Handler for the [MentionOfLocalSoneFoundEvent] and + * [MentionOfLocalSoneRemovedEvent] events that add the corresponding + * notification to the notification manager. + */ +class SoneMentionedHandler @Inject constructor(private val notificationManager: NotificationManager, @Named("soneMentioned") private val notification: ListNotification) { + + @Subscribe + fun mentionOfLocalSoneFound(event: MentionOfLocalSoneFoundEvent) { + if (!notificationManager.hasFirstStartNotification()) { + notification.add(event.post) + notificationManager.addNotification(notification) + } + } + + @Subscribe + fun mentionOfLocalSoneRemoved(event: MentionOfLocalSoneRemovedEvent) { + notification.remove(event.post) + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/notification/StartupHandler.kt b/src/main/kotlin/net/pterodactylus/sone/web/notification/StartupHandler.kt new file mode 100644 index 0000000..4d81576 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/notification/StartupHandler.kt @@ -0,0 +1,41 @@ +/** + * Sone - StartupHandler.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.notification + +import com.google.common.eventbus.* +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.util.notify.* +import java.util.concurrent.* +import java.util.concurrent.TimeUnit.* +import javax.inject.* + +/** + * Handler for the [Startup] event notification. + */ +class StartupHandler @Inject constructor( + private val notificationManager: NotificationManager, + @Named("startup") private val notification: TemplateNotification, + @Named("notification") private val ticker: ScheduledExecutorService) { + + @Subscribe + fun startup(@Suppress("UNUSED_PARAMETER") startup: Startup) { + notificationManager.addNotification(notification) + ticker.schedule({ notificationManager.removeNotification(notification) }, 2, MINUTES) + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/notification/WebOfTrustHandler.kt b/src/main/kotlin/net/pterodactylus/sone/web/notification/WebOfTrustHandler.kt new file mode 100644 index 0000000..f331643 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/notification/WebOfTrustHandler.kt @@ -0,0 +1,41 @@ +/** + * Sone - WebOfTrustHandler.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.notification + +import com.google.common.eventbus.* +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.util.notify.* +import javax.inject.* + +/** + * Handler for web of trust-related notifications and the [WebOfTrustAppeared] + * and [WebOfTrustDisappeared] events. + */ +class WebOfTrustHandler @Inject constructor(private val notificationManager: NotificationManager, @Named("webOfTrust") private val notification: TemplateNotification) { + + @Subscribe + fun webOfTrustAppeared(@Suppress("UNUSED_PARAMETER") webOfTrustAppeared: WebOfTrustAppeared) { + notificationManager.removeNotification(notification) + } + + @Subscribe + fun webOfTrustDisappeared(@Suppress("UNUSED_PARAMETER") webOfTrustDisappeared: WebOfTrustDisappeared) { + notificationManager.addNotification(notification) + } + +} diff --git a/src/main/resources/i18n/sone.en.properties b/src/main/resources/i18n/sone.en.properties index 1c0f2cb..4376025 100644 --- a/src/main/resources/i18n/sone.en.properties +++ b/src/main/resources/i18n/sone.en.properties @@ -463,4 +463,4 @@ Notification.Mention.Text=You have been mentioned in the following posts: Notification.SoneIsInserting.Text=Your Sone sone://{0} is now being inserted. Notification.SoneIsInserted.Text=Your Sone sone://{0} has been inserted in {1,number} {1,choice,0#seconds|1#second|1. + */ + +package net.pterodactylus.sone.freenet.wot + +import com.google.common.eventbus.* +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.sone.freenet.plugin.* +import net.pterodactylus.sone.utils.* +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.* +import java.util.concurrent.atomic.* +import java.util.function.* +import kotlin.test.* + +/** + * Unit test for [WebOfTrustPinger]. + */ +class WebOfTrustPingerTest { + + private val eventBus = EventBus() + private val webOfTrustReachable = AtomicBoolean() + private val webOfTrustReacher = Runnable { webOfTrustReachable.get().onFalse { throw PluginException() } } + private val rescheduled = AtomicBoolean() + private val reschedule: Consumer = Consumer { if (it == pinger) rescheduled.set(true) } + private val pinger = WebOfTrustPinger(eventBus, webOfTrustReacher, reschedule) + + @Test + fun `pinger sends wot appeared event when run first and wot is reachable`() { + webOfTrustReachable.set(true) + val appearedReceived = AtomicBoolean() + eventBus.register(WebOfTrustAppearedCatcher { appearedReceived.set(true) }) + pinger() + assertThat(appearedReceived.get(), equalTo(true)) + } + + @Test + fun `pinger reschedules when wot is reachable`() { + webOfTrustReachable.set(true) + pinger() + assertThat(rescheduled.get(), equalTo(true)) + } + + @Test + fun `pinger sends wot disappeared event when run first and wot is not reachable`() { + val appearedReceived = AtomicBoolean() + eventBus.register(WebOfTrustAppearedCatcher { appearedReceived.set(true) }) + pinger() + assertThat(appearedReceived.get(), equalTo(false)) + } + + @Test + fun `pinger reschedules when wot is not reachable`() { + pinger() + assertThat(rescheduled.get(), equalTo(true)) + } + + @Test + fun `pinger sends wot disappeared event when run twice and wot is not reachable on second call`() { + val disappearedReceived = AtomicBoolean() + eventBus.register(WebOfTrustDisappearedCatcher { disappearedReceived.set(true) }) + webOfTrustReachable.set(true) + pinger() + webOfTrustReachable.set(false) + pinger() + assertThat(disappearedReceived.get(), equalTo(true)) + } + + @Test + fun `pinger sends wot appeared event only once`() { + webOfTrustReachable.set(true) + val appearedReceived = AtomicBoolean() + eventBus.register(WebOfTrustAppearedCatcher { appearedReceived.set(true) }) + pinger() + appearedReceived.set(false) + pinger() + assertThat(appearedReceived.get(), equalTo(false)) + } + + @Test + fun `pinger sends wot disappeared event only once`() { + val disappearedReceived = AtomicBoolean() + eventBus.register(WebOfTrustDisappearedCatcher { disappearedReceived.set(true) }) + pinger() + disappearedReceived.set(false) + pinger() + assertThat(disappearedReceived.get(), equalTo(false)) + } + +} + +private class WebOfTrustAppearedCatcher(private val received: () -> Unit) { + @Subscribe + fun webOfTrustAppeared(@Suppress("UNUSED_PARAMETER") webOfTrustAppeared: WebOfTrustAppeared) { + received() + } +} + +private class WebOfTrustDisappearedCatcher(private val received: () -> Unit) { + @Subscribe + fun webOfTrustDisappeared(@Suppress("UNUSED_PARAMETER") webOfTrustDisappeared: WebOfTrustDisappeared) { + received() + } +} diff --git a/src/test/kotlin/net/pterodactylus/sone/main/FreenetModuleTest.kt b/src/test/kotlin/net/pterodactylus/sone/main/FreenetModuleTest.kt index e94e34e..c6aed88 100644 --- a/src/test/kotlin/net/pterodactylus/sone/main/FreenetModuleTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/main/FreenetModuleTest.kt @@ -34,12 +34,6 @@ class FreenetModuleTest { private val module = FreenetModule(pluginRespirator) private val injector = Guice.createInjector(module) - private inline fun verifySingletonInstance() { - val firstInstance = injector.getInstance() - val secondInstance = injector.getInstance() - assertThat(firstInstance, sameInstance(secondInstance)) - } - @Test fun `plugin respirator is not bound`() { expectedException.expect(Exception::class.java) @@ -53,7 +47,7 @@ class FreenetModuleTest { @Test fun `node is returned as singleton`() { - verifySingletonInstance() + injector.verifySingletonInstance() } @Test @@ -63,7 +57,7 @@ class FreenetModuleTest { @Test fun `high level simply client is returned as singleton`() { - verifySingletonInstance() + injector.verifySingletonInstance() } @Test @@ -73,7 +67,7 @@ class FreenetModuleTest { @Test fun `session manager is returned as singleton`() { - verifySingletonInstance() + injector.verifySingletonInstance() verify(pluginRespirator).getSessionManager("Sone") } @@ -84,7 +78,7 @@ class FreenetModuleTest { @Test fun `toadlet container is returned as singleten`() { - verifySingletonInstance() + injector.verifySingletonInstance() } @Test @@ -94,7 +88,7 @@ class FreenetModuleTest { @Test fun `page maker is returned as singleton`() { - verifySingletonInstance() + injector.verifySingletonInstance() } @Test @@ -106,7 +100,7 @@ class FreenetModuleTest { @Test fun `plugin respirator facade is returned as singleton`() { - verifySingletonInstance() + injector.verifySingletonInstance() } @Test @@ -116,7 +110,7 @@ class FreenetModuleTest { @Test fun `plugin connector facade is returned as singleton`() { - verifySingletonInstance() + injector.verifySingletonInstance() } } diff --git a/src/test/kotlin/net/pterodactylus/sone/main/SoneModuleTest.kt b/src/test/kotlin/net/pterodactylus/sone/main/SoneModuleTest.kt index e95955c..c628334 100644 --- a/src/test/kotlin/net/pterodactylus/sone/main/SoneModuleTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/main/SoneModuleTest.kt @@ -6,7 +6,6 @@ import com.google.common.eventbus.* import com.google.inject.Guice.* import com.google.inject.name.Names.* import freenet.l10n.* -import freenet.pluginmanager.* import net.pterodactylus.sone.core.* import net.pterodactylus.sone.database.* import net.pterodactylus.sone.database.memory.* @@ -18,13 +17,16 @@ import net.pterodactylus.util.config.* import net.pterodactylus.util.version.Version import org.hamcrest.MatcherAssert.* import org.hamcrest.Matchers.* +import org.junit.experimental.categories.* import org.mockito.Mockito.* import java.io.* +import java.util.concurrent.* import java.util.concurrent.atomic.* import kotlin.test.* const val versionString = "v80" +@Category(NotParallel::class) class SoneModuleTest { private val currentDir: File = File(".") @@ -190,9 +192,7 @@ class SoneModuleTest { @Test fun `core is created as singleton`() { - val firstCore = injector.getInstance() - val secondCore = injector.getInstance() - assertThat(secondCore, sameInstance(firstCore)) + injector.verifySingletonInstance() } @Test @@ -209,27 +209,23 @@ class SoneModuleTest { } @Test - fun `metrics registry can be created`() { - assertThat(injector.getInstance(), notNullValue()) + fun `metrics registry is created as singleton`() { + injector.verifySingletonInstance() } @Test - fun `metrics registry is created as singleton`() { - val firstMetricRegistry = injector.getInstance() - val secondMetricRegistry = injector.getInstance() - assertThat(firstMetricRegistry, sameInstance(secondMetricRegistry)) + fun `wot connector is created as singleton`() { + injector.verifySingletonInstance() } @Test - fun `wot connector can be created`() { - assertThat(injector.getInstance(), notNullValue()) + fun `notification ticker is created as singleton`() { + injector.verifySingletonInstance(named("notification")) } @Test - fun `wot connector is created as singleton`() { - val firstWebOfTrustConnector = injector.getInstance() - val secondWebOfTrustConnector = injector.getInstance() - assertThat(firstWebOfTrustConnector, sameInstance(secondWebOfTrustConnector)) + fun `ticker shutdown is created as singleton`() { + injector.verifySingletonInstance() } } diff --git a/src/test/kotlin/net/pterodactylus/sone/main/SonePluginTest.kt b/src/test/kotlin/net/pterodactylus/sone/main/SonePluginTest.kt index eef312f..d1e2cec 100644 --- a/src/test/kotlin/net/pterodactylus/sone/main/SonePluginTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/main/SonePluginTest.kt @@ -1,11 +1,13 @@ package net.pterodactylus.sone.main +import com.google.common.eventbus.* import com.google.inject.* import freenet.client.async.* import freenet.l10n.BaseL10n.LANGUAGE.* import freenet.node.* import freenet.pluginmanager.* import net.pterodactylus.sone.core.* +import net.pterodactylus.sone.core.event.* import net.pterodactylus.sone.fcp.* import net.pterodactylus.sone.freenet.wot.* import net.pterodactylus.sone.test.* @@ -13,17 +15,20 @@ import net.pterodactylus.sone.web.* import net.pterodactylus.sone.web.notification.* import org.hamcrest.MatcherAssert.* import org.hamcrest.Matchers.* +import org.junit.experimental.categories.* import org.mockito.Mockito.* +import java.io.* +import java.util.concurrent.atomic.* import kotlin.test.* /** * Unit test for [SonePlugin]. */ @Dirty +@Category(NotParallel::class) class SonePluginTest { - private var injector = mockInjector() - private val sonePlugin by lazy { SonePlugin { injector } } + private var sonePlugin = SonePlugin { injector } private val pluginRespirator = deepMock() private val node = deepMock() private val clientCore = deepMock() @@ -71,11 +76,12 @@ class SonePluginTest { assertThat(injector.getInstance(), notNullValue()) } - private fun runSonePluginWithRealInjector(): Injector { + private fun runSonePluginWithRealInjector(injectorConsumer: (Injector) -> Unit = {}): Injector { lateinit var injector: Injector - val sonePlugin = SonePlugin { + sonePlugin = SonePlugin { Guice.createInjector(*it).also { injector = it + injectorConsumer(it) } } sonePlugin.setLanguage(ENGLISH) @@ -91,27 +97,153 @@ class SonePluginTest { } @Test - fun `notification handler is being started`() { + fun `notification handler is being requested`() { sonePlugin.runPlugin(pluginRespirator) - val notificationHandler = injector.getInstance() - verify(notificationHandler).start() + assertThat(getInjected(NotificationHandler::class.java), notNullValue()) } -} + @Test + fun `ticker shutdown is being requested`() { + sonePlugin.runPlugin(pluginRespirator) + assertThat(getInjected(TickerShutdown::class.java), notNullValue()) + } + + private class FirstStartListener(private val firstStartReceived: AtomicBoolean) { + @Subscribe + fun firstStart(@Suppress("UNUSED_PARAMETER") firstStart: FirstStart) { + firstStartReceived.set(true) + } + } -private fun mockInjector() = mock().apply { - val injected = mutableMapOf, Annotation?>, Any>() - fun mockValue(clazz: Class<*>) = false.takeIf { clazz.name == java.lang.Boolean::class.java.name } ?: mock(clazz) - whenever(getInstance(any>())).then { - injected.getOrPut((it.getArgument(0) as Key<*>).let { it.typeLiteral to it.annotation }) { - it.getArgument>(0).typeLiteral.type.typeName.toClass().let(::mockValue) + @Test + fun `first-start event is sent to event bus when first start is true`() { + File("sone.properties").delete() + val firstStartReceived = AtomicBoolean() + runSonePluginWithRealInjector { + val eventBus = it.getInstance(EventBus::class.java) + eventBus.register(FirstStartListener(firstStartReceived)) } + assertThat(firstStartReceived.get(), equalTo(true)) } - whenever(getInstance(any>())).then { - injected.getOrPut(TypeLiteral.get(it.getArgument(0) as Class<*>) to null) { - it.getArgument>(0).let(::mockValue) + + @Test + fun `first-start event is not sent to event bus when first start is false`() { + File("sone.properties").deleteAfter { + writeText("# empty") + val firstStartReceived = AtomicBoolean() + runSonePluginWithRealInjector { + val eventBus = it.getInstance(EventBus::class.java) + eventBus.register(FirstStartListener(firstStartReceived)) + } + assertThat(firstStartReceived.get(), equalTo(false)) + } + } + + private class ConfigNotReadListener(private val configNotReadReceiver: AtomicBoolean) { + @Subscribe + fun configNotRead(@Suppress("UNUSED_PARAMETER") configNotRead: ConfigNotRead) { + configNotReadReceiver.set(true) } } + + @Test + fun `config-not-read event is sent to event bus when new config is true`() { + File("sone.properties").deleteAfter { + writeText("Invalid") + val configNotReadReceived = AtomicBoolean() + runSonePluginWithRealInjector { + val eventBus = it.getInstance(EventBus::class.java) + eventBus.register(ConfigNotReadListener(configNotReadReceived)) + } + assertThat(configNotReadReceived.get(), equalTo(true)) + } + } + + @Test + fun `config-not-read event is not sent to event bus when first start is true`() { + File("sone.properties").delete() + val configNotReadReceived = AtomicBoolean() + runSonePluginWithRealInjector { + val eventBus = it.getInstance(EventBus::class.java) + eventBus.register(ConfigNotReadListener(configNotReadReceived)) + } + assertThat(configNotReadReceived.get(), equalTo(false)) + } + + @Test + fun `config-not-read event is not sent to event bus when new config is false`() { + File("sone.properties").deleteAfter { + writeText("# comment") + val configNotReadReceived = AtomicBoolean() + runSonePluginWithRealInjector { + val eventBus = it.getInstance(EventBus::class.java) + eventBus.register(ConfigNotReadListener(configNotReadReceived)) + } + assertThat(configNotReadReceived.get(), equalTo(false)) + } + } + + private class StartupListener(private val startupReceived: () -> Unit) { + @Subscribe + fun startup(@Suppress("UNUSED_PARAMETER") startup: Startup) { + startupReceived() + } + } + + @Test + fun `startup event is sent to event bus`() { + val startupReceived = AtomicBoolean() + runSonePluginWithRealInjector { + val eventBus = it.getInstance(EventBus::class.java) + eventBus.register(StartupListener { startupReceived.set(true) }) + } + assertThat(startupReceived.get(), equalTo(true)) + } + + private class ShutdownListener(private val shutdownReceived: () -> Unit) { + @Subscribe + fun shutdown(@Suppress("UNUSED_PARAMETER") shutdown: Shutdown) { + shutdownReceived() + } + } + + @Test + fun `shutdown event is sent to event bus on terminate`() { + val shutdownReceived = AtomicBoolean() + runSonePluginWithRealInjector { + val eventBus = it.getInstance(EventBus::class.java) + eventBus.register(ShutdownListener { shutdownReceived.set(true) }) + } + sonePlugin.terminate() + assertThat(shutdownReceived.get(), equalTo(true)) + } + + private fun getInjected(clazz: Class, annotation: Annotation? = null): T? = + injected[TypeLiteral.get(clazz) to annotation] as? T + + private val injected = + mutableMapOf, Annotation?>, Any>() + + private val injector = mock().apply { + fun mockValue(clazz: Class<*>) = false.takeIf { clazz.name == java.lang.Boolean::class.java.name } ?: mock(clazz) + whenever(getInstance(any>())).then { + injected.getOrPut((it.getArgument(0) as Key<*>).let { it.typeLiteral to it.annotation }) { + it.getArgument>(0).typeLiteral.type.typeName.toClass().let(::mockValue) + } + } + whenever(getInstance(any>())).then { + injected.getOrPut(TypeLiteral.get(it.getArgument(0) as Class<*>) to null) { + it.getArgument>(0).let(::mockValue) + } + } + } + } private fun String.toClass(): Class<*> = SonePlugin::class.java.classLoader.loadClass(this) + +private fun File.deleteAfter(action: File.() -> Unit) = try { + action(this) +} finally { + this.delete() +} diff --git a/src/test/kotlin/net/pterodactylus/sone/main/TickerShutdownTest.kt b/src/test/kotlin/net/pterodactylus/sone/main/TickerShutdownTest.kt new file mode 100644 index 0000000..c02b962 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/main/TickerShutdownTest.kt @@ -0,0 +1,46 @@ +/** + * Sone - TickerShutdownTest.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.main + +import com.google.common.eventbus.* +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.sone.test.* +import org.mockito.Mockito.* +import java.util.concurrent.* +import kotlin.test.* + +/** + * Unit test for [TickerShutdown]. + */ +@Suppress("UnstableApiUsage") +class TickerShutdownTest { + + private val eventBus = EventBus() + private val notificationTicker = mock() + + init { + eventBus.register(TickerShutdown(notificationTicker)) + } + + @Test + fun `ticker is shutdown on shutdown`() { + eventBus.post(Shutdown()) + verify(notificationTicker).shutdown() + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/test/Guice.kt b/src/test/kotlin/net/pterodactylus/sone/test/Guice.kt index c8f2417..b0bed5c 100644 --- a/src/test/kotlin/net/pterodactylus/sone/test/Guice.kt +++ b/src/test/kotlin/net/pterodactylus/sone/test/Guice.kt @@ -2,6 +2,8 @@ package net.pterodactylus.sone.test import com.google.inject.* import com.google.inject.name.* +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.* import org.mockito.* import javax.inject.Provider import kotlin.reflect.* @@ -17,6 +19,13 @@ inline fun Injector.getInstance(annotation: Annotation? = null ?.let { getInstance(Key.get(object : TypeLiteral() {}, it)) } ?: getInstance(Key.get(object : TypeLiteral() {})) + +inline fun Injector.verifySingletonInstance(annotation: Annotation? = null) { + val firstInstance = getInstance(annotation) + val secondInstance = getInstance(annotation) + assertThat(firstInstance, sameInstance(secondInstance)) +} + fun supply(javaClass: Class): Source = object : Source { override fun fromInstance(instance: T) = Module { it.bind(javaClass).toInstance(instance) } override fun byInstance(instance: T) = Module { it.bind(javaClass).toProvider(Provider { instance }) } diff --git a/src/test/kotlin/net/pterodactylus/sone/test/Matchers.kt b/src/test/kotlin/net/pterodactylus/sone/test/Matchers.kt index 7fc9428..85ed9e7 100644 --- a/src/test/kotlin/net/pterodactylus/sone/test/Matchers.kt +++ b/src/test/kotlin/net/pterodactylus/sone/test/Matchers.kt @@ -5,8 +5,27 @@ import net.pterodactylus.sone.freenet.wot.* import net.pterodactylus.sone.utils.* import net.pterodactylus.util.web.* import org.hamcrest.* +import org.hamcrest.Matchers import org.hamcrest.Matchers.* +/** + * Returns a [hamcrest matcher][Matcher] constructed from the given predicate. + */ +fun matches(description: String? = null, predicate: (T) -> Boolean) = object : TypeSafeDiagnosingMatcher() { + + override fun matchesSafely(item: T, mismatchDescription: Description) = + predicate(item).onFalse { + mismatchDescription.appendValue(item).appendText(" did not match predicate") + } + + override fun describeTo(description: Description) { + description.appendText("matches predicate ").appendValue(predicate) + } + +}.let { matcher -> + description?.let { describedAs(description, matcher) } ?: matcher +} + fun hasHeader(name: String, value: String) = object : TypeSafeDiagnosingMatcher
() { override fun matchesSafely(item: Header, mismatchDescription: Description) = compare(item.name, { it.equals(name, ignoreCase = true) }) { mismatchDescription.appendText("name is ").appendValue(it) } diff --git a/src/test/kotlin/net/pterodactylus/sone/test/Mocks.kt b/src/test/kotlin/net/pterodactylus/sone/test/Mocks.kt new file mode 100644 index 0000000..3b2c716 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/test/Mocks.kt @@ -0,0 +1,61 @@ +/** + * Sone - Mocks.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.test + +import com.google.common.base.* +import freenet.crypt.* +import freenet.keys.* +import net.pterodactylus.sone.data.* +import net.pterodactylus.sone.data.SoneOptions.* +import net.pterodactylus.sone.data.impl.* +import net.pterodactylus.sone.text.* +import net.pterodactylus.sone.utils.* + +val remoteSone1 = createRemoteSone() +val remoteSone2 = createRemoteSone() + +val localSone1 = createLocalSone() +val localSone2 = createLocalSone() + +fun createId() = InsertableClientSSK.createRandom(DummyRandomSource(), "").uri.routingKey.asFreenetBase64 + +fun createLocalSone(id: String? = createId()) = object : IdOnlySone(id) { + private val options = DefaultSoneOptions() + override fun getOptions() = options + override fun isLocal() = true +} +fun createRemoteSone(id: String? = createId()) = IdOnlySone(id) + +fun createPost(text: String = "", sone: Sone = remoteSone1, known: Boolean = false): Post.EmptyPost { + return object : Post.EmptyPost("post-id") { + override fun getSone() = sone + override fun getText() = text + override fun isKnown() = known + } +} + +fun emptyPostReply(text: String = "", post: Post? = createPost(), sone: Sone = remoteSone1, known: Boolean = false) = object : PostReply { + override val id = "reply-id" + override fun getSone() = sone + override fun getPostId() = post!!.id + override fun getPost(): Optional = Optional.fromNullable(post) + override fun getTime() = 1L + override fun getText() = text + override fun isKnown() = known + override fun setKnown(known: Boolean): PostReply = this +} diff --git a/src/test/kotlin/net/pterodactylus/sone/test/NotParallel.kt b/src/test/kotlin/net/pterodactylus/sone/test/NotParallel.kt new file mode 100644 index 0000000..6e00fbf --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/test/NotParallel.kt @@ -0,0 +1,8 @@ +package net.pterodactylus.sone.test + +/** + * Marker class for a JUnit [org.junit.experimental.categories.Category], to + * mark tests that should not be run parallel to other tests. + */ +class NotParallel + diff --git a/src/test/kotlin/net/pterodactylus/sone/test/TestLoaders.kt b/src/test/kotlin/net/pterodactylus/sone/test/TestLoaders.kt new file mode 100644 index 0000000..f71e9f5 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/test/TestLoaders.kt @@ -0,0 +1,21 @@ +package net.pterodactylus.sone.test + +import net.pterodactylus.sone.main.* +import net.pterodactylus.util.template.* +import net.pterodactylus.util.web.* + +/** + * [Loaders] implementation for use in tests. Use [templates] to control what templates are + * returned by the [loadTemplate] method. + */ +class TestLoaders : Loaders { + + val templates = mutableMapOf() + + override fun loadTemplate(path: String) = templates[path] ?: Template() + + override fun loadStaticPage(basePath: String, prefix: String, mimeType: String) = TestPage() + + override fun getTemplateProvider() = TemplateProvider { _, _ -> Template() } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/test/TestPage.kt b/src/test/kotlin/net/pterodactylus/sone/test/TestPage.kt new file mode 100644 index 0000000..762f789 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/test/TestPage.kt @@ -0,0 +1,14 @@ +package net.pterodactylus.sone.test + +import net.pterodactylus.util.web.* + +/** + * Dummy implementation of a [Page]. + */ +class TestPage : Page { + + override fun getPath() = "" + override fun isPrefixPage() = false + override fun handleRequest(freenetRequest: REQ, response: Response) = response + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/text/SoneMentionDetectorTest.kt b/src/test/kotlin/net/pterodactylus/sone/text/SoneMentionDetectorTest.kt new file mode 100644 index 0000000..7208230 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/text/SoneMentionDetectorTest.kt @@ -0,0 +1,263 @@ +/** + * Sone - SoneMentionDetectorTest.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.text + +import com.google.common.eventbus.* +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.sone.data.* +import net.pterodactylus.sone.database.* +import net.pterodactylus.sone.test.* +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.* +import kotlin.test.* + +/** + * Unit test for [SoneMentionDetector]. + */ +@Suppress("UnstableApiUsage") +class SoneMentionDetectorTest { + + private val caughtExceptions = mutableListOf() + private val eventBus = EventBus { exception, _ -> caughtExceptions += exception } + private val soneProvider = TestSoneProvider() + private val postProvider = TestPostProvider() + private val soneTextParser = SoneTextParser(soneProvider, postProvider) + private val capturedFoundEvents = mutableListOf() + private val capturedRemovedEvents = mutableListOf() + private val postReplyProvider = TestPostReplyProvider() + + init { + eventBus.register(SoneMentionDetector(eventBus, soneTextParser, postReplyProvider)) + eventBus.register(object : Any() { + @Subscribe + fun captureFoundEvent(mentionOfLocalSoneFoundEvent: MentionOfLocalSoneFoundEvent) { + capturedFoundEvents += mentionOfLocalSoneFoundEvent + } + + @Subscribe + fun captureRemovedEvent(event: MentionOfLocalSoneRemovedEvent) { + capturedRemovedEvents += event + } + }) + } + + @Test + fun `detector does not emit event on post that does not contain any sones`() { + val post = createPost() + eventBus.post(NewPostFoundEvent(post)) + assertThat(capturedFoundEvents, emptyIterable()) + } + + @Test + fun `detector does not emit event on post that does contain two remote sones`() { + val post = createPost("text mentions sone://${remoteSone1.id} and sone://${remoteSone2.id}.") + eventBus.post(NewPostFoundEvent(post)) + assertThat(capturedFoundEvents, emptyIterable()) + } + + @Test + fun `detector emits event on post that contains links to a remote and a local sone`() { + val post = createPost("text mentions sone://${localSone1.id} and sone://${remoteSone2.id}.") + eventBus.post(NewPostFoundEvent(post)) + assertThat(capturedFoundEvents, contains(MentionOfLocalSoneFoundEvent(post))) + } + + @Test + fun `detector emits one event on post that contains two links to the same local sone`() { + val post = createPost("text mentions sone://${localSone1.id} and sone://${localSone1.id}.") + eventBus.post(NewPostFoundEvent(post)) + assertThat(capturedFoundEvents, contains(MentionOfLocalSoneFoundEvent(post))) + } + + @Test + fun `detector emits one event on post that contains links to two local sones`() { + val post = createPost("text mentions sone://${localSone1.id} and sone://${localSone2.id}.") + eventBus.post(NewPostFoundEvent(post)) + assertThat(capturedFoundEvents, contains(MentionOfLocalSoneFoundEvent(post))) + } + + @Test + fun `detector does not emit event for post by local sone`() { + val post = createPost("text mentions sone://${localSone1.id} and sone://${localSone2.id}.", localSone1) + eventBus.post(NewPostFoundEvent(post)) + assertThat(capturedFoundEvents, emptyIterable()) + } + + @Test + fun `detector does not emit event for reply that contains no sones`() { + val reply = emptyPostReply() + eventBus.post(NewPostReplyFoundEvent(reply)) + assertThat(capturedFoundEvents, emptyIterable()) + } + + @Test + fun `detector does not emit event for reply that contains two links to remote sones`() { + val reply = emptyPostReply("text mentions sone://${remoteSone1.id} and sone://${remoteSone2.id}.") + eventBus.post(NewPostReplyFoundEvent(reply)) + assertThat(capturedFoundEvents, emptyIterable()) + } + + @Test + fun `detector emits event on reply that contains links to a remote and a local sone`() { + val post = createPost() + val reply = emptyPostReply("text mentions sone://${remoteSone1.id} and sone://${localSone1.id}.", post) + eventBus.post(NewPostReplyFoundEvent(reply)) + assertThat(capturedFoundEvents, contains(MentionOfLocalSoneFoundEvent(post))) + } + + @Test + fun `detector emits one event on reply that contains two links to the same local sone`() { + val post = createPost() + val reply = emptyPostReply("text mentions sone://${localSone1.id} and sone://${localSone1.id}.", post) + eventBus.post(NewPostReplyFoundEvent(reply)) + assertThat(capturedFoundEvents, contains(MentionOfLocalSoneFoundEvent(post))) + } + + @Test + fun `detector emits one event on reply that contains two links to local sones`() { + val post = createPost() + val reply = emptyPostReply("text mentions sone://${localSone1.id} and sone://${localSone2.id}.", post) + eventBus.post(NewPostReplyFoundEvent(reply)) + assertThat(capturedFoundEvents, contains(MentionOfLocalSoneFoundEvent(post))) + } + + @Test + fun `detector does not emit event for reply by local sone`() { + val reply = emptyPostReply("text mentions sone://${localSone1.id} and sone://${localSone2.id}.", sone = localSone1) + eventBus.post(NewPostReplyFoundEvent(reply)) + assertThat(capturedFoundEvents, emptyIterable()) + } + + @Test + fun `detector does not emit event for reply without post`() { + val reply = emptyPostReply("text mentions sone://${localSone1.id} and sone://${localSone2.id}.", post = null) + eventBus.post(NewPostReplyFoundEvent(reply)) + assertThat(caughtExceptions, emptyIterable()) + assertThat(capturedFoundEvents, emptyIterable()) + } + + @Test + fun `detector does not emit removed event when a post without mention is removed`() { + val post = createPost() + eventBus.post(PostRemovedEvent(post)) + assertThat(capturedRemovedEvents, emptyIterable()) + } + + @Test + fun `detector does emit removed event when post with mention is removed`() { + val post = createPost("sone://${localSone1.id}") + eventBus.post(NewPostFoundEvent(post)) + eventBus.post(PostRemovedEvent(post)) + assertThat(capturedRemovedEvents, contains(MentionOfLocalSoneRemovedEvent(post))) + } + + @Test + fun `detector does not emit removed event when a post without mention is marked as known`() { + val post = createPost() + eventBus.post(MarkPostKnownEvent(post)) + assertThat(capturedRemovedEvents, emptyIterable()) + } + + @Test + fun `detector does emit removed event when post with mention is marked as known`() { + val post = createPost("sone://${localSone1.id}") + eventBus.post(NewPostFoundEvent(post)) + eventBus.post(MarkPostKnownEvent(post)) + assertThat(capturedRemovedEvents, contains(MentionOfLocalSoneRemovedEvent(post))) + } + + @Test + fun `detector does emit removed event when reply with mention is removed and no more mentions in that post exist`() { + val post = createPost() + val reply = emptyPostReply("sone://${localSone1.id}", post) + postReplyProvider.postReplies[post.id] = listOf(reply) + eventBus.post(NewPostReplyFoundEvent(reply)) + eventBus.post(PostReplyRemovedEvent(reply)) + assertThat(capturedRemovedEvents, contains(MentionOfLocalSoneRemovedEvent(post))) + } + + @Test + fun `detector does not emit removed event when reply with mention is removed and post mentions local sone`() { + val post = createPost("sone://${localSone1.id}") + val reply = emptyPostReply("sone://${localSone1.id}", post) + eventBus.post(NewPostReplyFoundEvent(reply)) + eventBus.post(PostReplyRemovedEvent(reply)) + assertThat(capturedRemovedEvents, emptyIterable()) + } + + @Test + fun `detector does emit removed event when reply with mention is removed and post mentions local sone but is known`() { + val post = createPost("sone://${localSone1.id}", known = true) + val reply = emptyPostReply("sone://${localSone1.id}", post) + eventBus.post(NewPostReplyFoundEvent(reply)) + eventBus.post(PostReplyRemovedEvent(reply)) + assertThat(capturedRemovedEvents, contains(MentionOfLocalSoneRemovedEvent(post))) + } + + @Test + fun `detector does not emit removed event when reply with mention is removed and post has other replies with mentions`() { + val post = createPost() + val reply1 = emptyPostReply("sone://${localSone1.id}", post) + val reply2 = emptyPostReply("sone://${localSone1.id}", post) + postReplyProvider.postReplies[post.id] = listOf(reply1, reply2) + eventBus.post(NewPostReplyFoundEvent(reply1)) + eventBus.post(PostReplyRemovedEvent(reply1)) + assertThat(capturedRemovedEvents, emptyIterable()) + } + + @Test + fun `detector does emit removed event when reply with mention is removed and post has other replies with mentions which are known`() { + val post = createPost() + val reply1 = emptyPostReply("sone://${localSone1.id}", post) + val reply2 = emptyPostReply("sone://${localSone1.id}", post, known = true) + postReplyProvider.postReplies[post.id] = listOf(reply1, reply2) + eventBus.post(NewPostReplyFoundEvent(reply1)) + eventBus.post(PostReplyRemovedEvent(reply1)) + assertThat(capturedRemovedEvents, contains(MentionOfLocalSoneRemovedEvent(post))) + } + +} + +private class TestSoneProvider : SoneProvider { + + override val sones: Collection get() = remoteSones + localSones + override val localSones: Collection get() = setOf(localSone1, localSone2) + override val remoteSones: Collection get() = setOf(remoteSone1, remoteSone2) + override val soneLoader: (String) -> Sone? get() = this::getSone + override fun getSone(soneId: String): Sone? = + localSones.firstOrNull { it.id == soneId } ?: remoteSones.firstOrNull { it.id == soneId } + +} + +private class TestPostProvider : PostProvider { + + override fun getPost(postId: String): Post? = null + override fun getPosts(soneId: String): Collection = emptyList() + override fun getDirectedPosts(recipientId: String): Collection = emptyList() + +} + +private class TestPostReplyProvider : PostReplyProvider { + + val replies = mutableMapOf() + val postReplies = mutableMapOf>() + + override fun getPostReply(id: String) = replies[id] + override fun getReplies(postId: String) = postReplies[postId] ?: emptyList() + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/utils/BooleansTest.kt b/src/test/kotlin/net/pterodactylus/sone/utils/BooleansTest.kt index a1b6da7..bd81f08 100644 --- a/src/test/kotlin/net/pterodactylus/sone/utils/BooleansTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/utils/BooleansTest.kt @@ -30,6 +30,26 @@ class BooleansTest { } @Test + fun `onTrue returns true on true`() { + assertThat(true.onTrue {}, equalTo(true)) + } + + @Test + fun `onTrue returns false on false`() { + assertThat(false.onTrue {}, equalTo(false)) + } + + @Test + fun `onTrue is not executed on false`() { + assertThat(false.onTrue { throw RuntimeException() }, equalTo(false)) + } + + @Test(expected = RuntimeException::class) + fun `onTrue is executed on true`() { + true.onTrue { throw RuntimeException() } + } + + @Test fun `onFalse returns true on true`() { assertThat(true.onFalse {}, equalTo(true)) } diff --git a/src/test/kotlin/net/pterodactylus/sone/web/PageToadletRegistryTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/PageToadletRegistryTest.kt index 739a0dd..2134661 100644 --- a/src/test/kotlin/net/pterodactylus/sone/web/PageToadletRegistryTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/web/PageToadletRegistryTest.kt @@ -5,7 +5,6 @@ import freenet.clients.http.* import net.pterodactylus.sone.main.* import net.pterodactylus.sone.test.* import net.pterodactylus.sone.web.page.* -import net.pterodactylus.util.web.* import org.junit.* import org.junit.rules.* import org.mockito.Mockito.* @@ -36,7 +35,7 @@ class PageToadletRegistryTest { verify(pageMaker).addNavigationCategory("/Sone/index.html", "Navigation.Menu.Sone.Name", "Navigation.Menu.Sone.Tooltip", sonePlugin) } - private val page = TestPage() + private val page = TestPage() @Test fun `adding a page without menuname will add it correctly`() { @@ -147,10 +146,4 @@ class PageToadletRegistryTest { whenever(this.menuName).thenReturn(menuName) } - private class TestPage : Page { - override fun getPath() = "" - override fun isPrefixPage() = false - override fun handleRequest(freenetRequest: FreenetRequest, response: Response) = response - } - } diff --git a/src/test/kotlin/net/pterodactylus/sone/web/WebInterfaceModuleTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/WebInterfaceModuleTest.kt index c03d9f8..8840caf 100644 --- a/src/test/kotlin/net/pterodactylus/sone/web/WebInterfaceModuleTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/web/WebInterfaceModuleTest.kt @@ -256,9 +256,7 @@ class WebInterfaceModuleTest { @Test fun `template context factory is created as singleton`() { - val factory1 = injector.getInstance() - val factory2 = injector.getInstance() - assertThat(factory1, sameInstance(factory2)) + injector.verifySingletonInstance() } @Test @@ -280,26 +278,12 @@ class WebInterfaceModuleTest { @Test fun `page toadlet factory is created with correct prefix`() { val page = mock>() - assertThat(injector.getInstance().createPageToadlet(page).path(), startsWith("/Sone/")) + assertThat(injector.getInstance().createPageToadlet(page).path(), startsWith("/Sone/")) } @Test fun `notification manager is created as singleton`() { - val firstNotificationManager = injector.getInstance() - val secondNotificationManager = injector.getInstance() - assertThat(firstNotificationManager, sameInstance(secondNotificationManager)) - } - - @Test - fun `notification handler can be created`() { - assertThat(injector.getInstance(), notNullValue()) - } - - @Test - fun `notification handler is created as singleton`() { - val firstNotificationHandler = injector.getInstance() - val secondNotificationHandler = injector.getInstance() - assertThat(firstNotificationHandler, sameInstance(secondNotificationHandler)) + injector.verifySingletonInstance() } } diff --git a/src/test/kotlin/net/pterodactylus/sone/web/notification/ConfigNotReadHandlerTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/notification/ConfigNotReadHandlerTest.kt new file mode 100644 index 0000000..cc02606 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/notification/ConfigNotReadHandlerTest.kt @@ -0,0 +1,48 @@ +/** + * Sone - ConfigNotReadHandler.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.notification + +import com.google.common.eventbus.* +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.util.notify.* +import net.pterodactylus.util.template.* +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.* +import org.junit.* + +/** + * Unit test for [ConfigNotReadHandler]. + */ +@Suppress("UnstableApiUsage") +class ConfigNotReadHandlerTest { + + private val eventBus = EventBus() + private val notificationManager = NotificationManager() + private val notification = TemplateNotification("", Template()) + + init { + eventBus.register(ConfigNotReadHandler(notificationManager, notification)) + } + + @Test + fun `handler adds notification to manager when config was not read`() { + eventBus.post(ConfigNotRead()) + assertThat(notificationManager.notifications, contains(notification)) + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/notification/FirstStartHandlerTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/notification/FirstStartHandlerTest.kt new file mode 100644 index 0000000..2bda6d1 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/notification/FirstStartHandlerTest.kt @@ -0,0 +1,53 @@ +/** + * Sone - FirstStartHandlerTest.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.notification + +import com.google.common.eventbus.* +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.util.notify.* +import net.pterodactylus.util.template.* +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.* +import kotlin.test.* + +/** + * Unit test for [FirstStartHandler]. + */ +@Suppress("UnstableApiUsage") +class FirstStartHandlerTest { + + private val eventBus = EventBus() + private val notificationManager = NotificationManager() + private val notification = TemplateNotification(Template()) + + init { + eventBus.register(FirstStartHandler(notificationManager, notification)) + } + + @Test + fun `handler can be created`() { + FirstStartHandler(notificationManager, notification) + } + + @Test + fun `handler adds notification to manager on first start event`() { + eventBus.post(FirstStart()) + assertThat(notificationManager.notifications, contains(notification)) + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/notification/ImageInsertHandlerTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/notification/ImageInsertHandlerTest.kt new file mode 100644 index 0000000..3f570a9 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/notification/ImageInsertHandlerTest.kt @@ -0,0 +1,107 @@ +/** + * Sone - ImageInsertHandler.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.notification + +import com.google.common.eventbus.* +import freenet.keys.FreenetURI.* +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.sone.data.* +import net.pterodactylus.sone.data.impl.* +import net.pterodactylus.sone.notify.* +import net.pterodactylus.util.notify.* +import net.pterodactylus.util.template.* +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.* +import kotlin.test.* + +/** + * Unit test for [ImageInsertHandler]. + */ +@Suppress("UnstableApiUsage") +class ImageInsertHandlerTest { + + private val eventBus = EventBus() + private val notificationManager = NotificationManager() + private val imageInsertingNotification = ListNotification("", "", Template()) + private val imageFailedNotification = ListNotification("", "", Template()) + private val imageInsertedNotification = ListNotification("", "", Template()) + + init { + eventBus.register(ImageInsertHandler(notificationManager, imageInsertingNotification, imageFailedNotification, imageInsertedNotification)) + } + + @Test + fun `handler adds notification when image insert starts`() { + eventBus.post(ImageInsertStartedEvent(image)) + assertThat(notificationManager.notifications, contains(imageInsertingNotification)) + } + + @Test + fun `handler adds image to notification when image insert starts`() { + eventBus.post(ImageInsertStartedEvent(image)) + assertThat(imageInsertingNotification.elements, contains(image)) + } + + @Test + fun `handler removes image from inserting notification when insert is aborted`() { + eventBus.post(ImageInsertStartedEvent(image)) + eventBus.post(ImageInsertAbortedEvent(image)) + assertThat(imageInsertingNotification.elements, emptyIterable()) + } + + @Test + fun `handler removes image from inserting notification when insert fails`() { + eventBus.post(ImageInsertStartedEvent(image)) + eventBus.post(ImageInsertFailedEvent(image, Throwable())) + assertThat(imageInsertingNotification.elements, emptyIterable()) + } + + @Test + fun `handler adds image to insert-failed notification when insert fails`() { + eventBus.post(ImageInsertFailedEvent(image, Throwable())) + assertThat(imageFailedNotification.elements, contains(image)) + } + + @Test + fun `handler adds insert-failed notification to manager when insert fails`() { + eventBus.post(ImageInsertFailedEvent(image, Throwable())) + assertThat(notificationManager.notifications, contains(imageFailedNotification)) + } + + @Test + fun `handler removes image from inserting notification when insert succeeds`() { + eventBus.post(ImageInsertStartedEvent(image)) + eventBus.post(ImageInsertFinishedEvent(image, EMPTY_CHK_URI)) + assertThat(imageInsertingNotification.elements, emptyIterable()) + } + + @Test + fun `handler adds image to inserted notification when insert succeeds`() { + eventBus.post(ImageInsertFinishedEvent(image, EMPTY_CHK_URI)) + assertThat(imageInsertedNotification.elements, contains(image)) + } + + @Test + fun `handler adds inserted notification to manager when insert succeeds`() { + eventBus.post(ImageInsertFinishedEvent(image, EMPTY_CHK_URI)) + assertThat(notificationManager.notifications, contains(imageInsertedNotification)) + } + +} + +private val image: Image = ImageImpl() diff --git a/src/test/kotlin/net/pterodactylus/sone/web/notification/LocalPostHandlerTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/notification/LocalPostHandlerTest.kt new file mode 100644 index 0000000..1d97ae8 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/notification/LocalPostHandlerTest.kt @@ -0,0 +1,118 @@ +/** + * Sone - NewLocalPostHandlerTest.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.notification + +import com.google.common.eventbus.* +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.sone.data.* +import net.pterodactylus.sone.data.impl.* +import net.pterodactylus.sone.notify.* +import net.pterodactylus.util.notify.* +import net.pterodactylus.util.template.* +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.* +import kotlin.test.* + +/** + * Unit test for [LocalPostHandler]. + */ +class LocalPostHandlerTest { + + private val eventBus = EventBus() + private val notificationManager = NotificationManager() + private val notification = ListNotification("", "", Template()) + + init { + eventBus.register(LocalPostHandler(notificationManager, notification)) + } + + @Test + fun `handler adds post by local sone to notification`() { + eventBus.post(NewPostFoundEvent(localPost)) + assertThat(notification.elements, contains(localPost)) + } + + @Test + fun `handler does not add post by remote sone to notification`() { + eventBus.post(NewPostFoundEvent(remotePost)) + assertThat(notification.elements, emptyIterable()) + } + + @Test + fun `handler does not add notification to manager for post by remote sone`() { + eventBus.post(NewPostFoundEvent(remotePost)) + assertThat(notificationManager.notifications, not(hasItem(notification))) + } + + @Test + fun `handler adds notification to manager`() { + eventBus.post(NewPostFoundEvent(localPost)) + assertThat(notificationManager.notifications, contains(notification)) + } + + @Test + fun `handler does not add notification during first start`() { + notificationManager.firstStart() + eventBus.post(NewPostFoundEvent(localPost)) + assertThat(notificationManager.notifications, not(hasItem(notification))) + } + + @Test + fun `handler removes post from notification when post is removed`() { + notification.add(localPost) + notificationManager.addNotification(notification) + eventBus.post(PostRemovedEvent(localPost)) + assertThat(notification.elements, emptyIterable()) + } + + @Test + fun `handler does not remove remote post from notification when post is removed`() { + notification.add(remotePost) + notificationManager.addNotification(notification) + eventBus.post(PostRemovedEvent(remotePost)) + assertThat(notification.elements, contains(remotePost)) + } + + @Test + fun `handler removes post from notification when post is marked as known`() { + notification.add(localPost) + notificationManager.addNotification(notification) + eventBus.post(MarkPostKnownEvent(localPost)) + assertThat(notification.elements, emptyIterable()) + } + + @Test + fun `handler does not remove remote post from notification when post is marked as known`() { + notification.add(remotePost) + notificationManager.addNotification(notification) + eventBus.post(MarkPostKnownEvent(remotePost)) + assertThat(notification.elements, contains(remotePost)) + } + +} + +private val localSone: Sone = object : IdOnlySone("local-sone") { + override fun isLocal() = true +} +private val localPost: Post = object : Post.EmptyPost("local-post") { + override fun getSone() = localSone +} +private val remoteSone: Sone = IdOnlySone("remote-sone") +private val remotePost: Post = object : Post.EmptyPost("remote-post") { + override fun getSone() = remoteSone +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/notification/LocalReplyHandlerTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/notification/LocalReplyHandlerTest.kt new file mode 100644 index 0000000..750d083 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/notification/LocalReplyHandlerTest.kt @@ -0,0 +1,85 @@ +/** + * Sone - LocalReplyHandlerTest.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.notification + +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.sone.data.* +import net.pterodactylus.sone.notify.* +import net.pterodactylus.sone.test.* +import net.pterodactylus.util.template.* +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.* +import kotlin.test.* + +/** + * Unit test for [LocalReplyHandler]. + */ +class LocalReplyHandlerTest { + + private val notification = ListNotification("", "", Template()) + private val localReplyHandlerTester = NotificationHandlerTester { LocalReplyHandler(it, notification) } + + @Test + fun `handler does not add reply to notification`() { + localReplyHandlerTester.sendEvent(NewPostReplyFoundEvent(remoteReply)) + assertThat(notification.elements, emptyIterable()) + } + + @Test + fun `handler does add local reply to notification`() { + localReplyHandlerTester.sendEvent(NewPostReplyFoundEvent(localReply)) + assertThat(notification.elements, contains(localReply)) + } + + @Test + fun `handler adds notification to manager`() { + localReplyHandlerTester.sendEvent(NewPostReplyFoundEvent(localReply)) + assertThat(localReplyHandlerTester.notifications, hasItem(notification)) + } + + @Test + fun `handler does not add notification to manager for remote reply`() { + localReplyHandlerTester.sendEvent(NewPostReplyFoundEvent(remoteReply)) + assertThat(localReplyHandlerTester.notifications, not(hasItem(notification))) + } + + @Test + fun `handler does not add notification to manager during first start`() { + localReplyHandlerTester.firstStart() + localReplyHandlerTester.sendEvent(NewPostReplyFoundEvent(localReply)) + assertThat(localReplyHandlerTester.notifications, not(hasItem(notification))) + } + + @Test + fun `handler removes reply from notification if reply is removed`() { + notification.add(localReply) + localReplyHandlerTester.sendEvent(PostReplyRemovedEvent(localReply)) + assertThat(notification.elements, not(hasItem(localReply))) + } + + @Test + fun `handler removes reply from notification if reply is marked as known`() { + notification.add(localReply) + localReplyHandlerTester.sendEvent(MarkPostReplyKnownEvent(localReply)) + assertThat(notification.elements, not(hasItem(localReply))) + } + +} + +private val localReply = emptyPostReply(sone = localSone1) +private val remoteReply = emptyPostReply() diff --git a/src/test/kotlin/net/pterodactylus/sone/web/notification/MarkPostKnownDuringFirstStartHandlerTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/notification/MarkPostKnownDuringFirstStartHandlerTest.kt new file mode 100644 index 0000000..c2f8e41 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/notification/MarkPostKnownDuringFirstStartHandlerTest.kt @@ -0,0 +1,59 @@ +/** + * Sone - MarkPostKnownDuringFirstStartHandlerTest.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.notification + +import com.google.common.eventbus.* +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.sone.data.* +import net.pterodactylus.util.notify.* +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.* +import java.util.function.* +import kotlin.test.* + +/** + * Unit test for [MarkPostKnownDuringFirstStartHandler]. + */ +@Suppress("UnstableApiUsage") +class MarkPostKnownDuringFirstStartHandlerTest { + + private val eventBus = EventBus() + private val notificationManager = NotificationManager() + private val markedPosts = mutableListOf() + private val handler = MarkPostKnownDuringFirstStartHandler(notificationManager, Consumer { markedPosts += it }) + + init { + eventBus.register(handler) + } + + @Test + fun `post is not marked as known if not during first start`() { + eventBus.post(NewPostFoundEvent(post)) + assertThat(markedPosts, emptyIterable()) + } + + @Test + fun `new post is marked as known during first start`() { + notificationManager.firstStart() + eventBus.post(NewPostFoundEvent(post)) + assertThat(markedPosts, contains(post)) + } + +} + +private val post: Post = Post.EmptyPost("post") diff --git a/src/test/kotlin/net/pterodactylus/sone/web/notification/MarkPostReplyKnownDuringFirstStartHandlerTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/notification/MarkPostReplyKnownDuringFirstStartHandlerTest.kt new file mode 100644 index 0000000..3cb463f --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/notification/MarkPostReplyKnownDuringFirstStartHandlerTest.kt @@ -0,0 +1,50 @@ +/** + * Sone - MarkPostReplyKnownDuringFirstStartHandlerTest.kt - Copyright © 2020 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.notification + +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.sone.data.* +import net.pterodactylus.sone.test.* +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.* +import java.util.function.* +import kotlin.test.* + +/** + * Unit test for [MarkPostReplyKnownDuringFirstStartHandler]. + */ +class MarkPostReplyKnownDuringFirstStartHandlerTest { + + private val markedAsKnown = mutableListOf() + private val notificationTester = NotificationHandlerTester { MarkPostReplyKnownDuringFirstStartHandler(it, Consumer { markedAsKnown += it }) } + private val postReply = emptyPostReply() + + @Test + fun `post reply is marked as known on new reply during first start`() { + notificationTester.firstStart() + notificationTester.sendEvent(NewPostReplyFoundEvent(postReply)) + assertThat(markedAsKnown, contains(postReply)) + } + + @Test + fun `post reply is not marked as known on new reply if not during first start`() { + notificationTester.sendEvent(NewPostReplyFoundEvent(postReply)) + assertThat(markedAsKnown, not(hasItem(postReply))) + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/notification/NewRemotePostHandlerTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/notification/NewRemotePostHandlerTest.kt new file mode 100644 index 0000000..805731d --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/notification/NewRemotePostHandlerTest.kt @@ -0,0 +1,97 @@ +/** + * Sone - NewRemotePostHandlerTest.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.notification + +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.sone.data.* +import net.pterodactylus.sone.data.Post.* +import net.pterodactylus.sone.data.impl.* +import net.pterodactylus.sone.notify.* +import net.pterodactylus.util.notify.* +import net.pterodactylus.util.template.* +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.* +import kotlin.test.* + +/** + * Unit test for [NewRemotePostHandler]. + */ +@Suppress("UnstableApiUsage") +class NewRemotePostHandlerTest { + + private val notification = ListNotification("", "", Template()) + private val remotePostHandlerTest = NotificationHandlerTester { NewRemotePostHandler(it, notification) } + + @Test + fun `handler adds remote post to new-post notification`() { + remotePostHandlerTest.sendEvent(NewPostFoundEvent(remotePost)) + assertThat(notification.elements, contains(remotePost)) + } + + @Test + fun `handler does not add local post to new-post notification`() { + remotePostHandlerTest.sendEvent(NewPostFoundEvent(localPost)) + assertThat(notification.elements, emptyIterable()) + } + + @Test + fun `handler adds notification for remote post to notification manager`() { + remotePostHandlerTest.sendEvent(NewPostFoundEvent(remotePost)) + assertThat(remotePostHandlerTest.notifications, contains(notification)) + } + + @Test + fun `handler does not add notification for local post to notification manager`() { + remotePostHandlerTest.sendEvent(NewPostFoundEvent(localPost)) + assertThat(remotePostHandlerTest.notifications, emptyIterable()) + } + + @Test + fun `handler does not add notification to notification manager during first start`() { + remotePostHandlerTest.firstStart() + remotePostHandlerTest.sendEvent(NewPostFoundEvent(remotePost)) + assertThat(remotePostHandlerTest.notifications, not(hasItem(notification))) + } + + @Test + fun `handler removes post from notification if post is removed`() { + notification.add(remotePost) + remotePostHandlerTest.sendEvent(PostRemovedEvent(remotePost)) + assertThat(notification.elements, not(hasItem(remotePost))) + } + + @Test + fun `handler removes post from notification if post is marked as known`() { + notification.add(remotePost) + remotePostHandlerTest.sendEvent(MarkPostKnownEvent(remotePost)) + assertThat(notification.elements, not(hasItem(remotePost))) + } + +} + +private val remoteSone: Sone = IdOnlySone("remote-sone") +private val remotePost: Post = object : EmptyPost("remote-post") { + override fun getSone() = remoteSone +} + +private val localSone: Sone = object : IdOnlySone("local-sone") { + override fun isLocal() = true +} +private val localPost: Post = object : EmptyPost("local-post") { + override fun getSone() = localSone +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/notification/NewSoneHandlerTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/notification/NewSoneHandlerTest.kt new file mode 100644 index 0000000..acdcf2f --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/notification/NewSoneHandlerTest.kt @@ -0,0 +1,78 @@ +/** + * Sone - NewSoneHandlerTest.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.notification + +import com.google.common.eventbus.* +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.sone.data.* +import net.pterodactylus.sone.data.impl.* +import net.pterodactylus.sone.notify.* +import net.pterodactylus.util.notify.* +import net.pterodactylus.util.template.* +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.* +import kotlin.test.* + +class NewSoneHandlerTest { + + @Suppress("UnstableApiUsage") + private val eventBus = EventBus() + private val notificationManager = NotificationManager() + private val notification = ListNotification("", "", Template()) + private val handler = NewSoneHandler(notificationManager, notification) + + init { + eventBus.register(handler) + } + + @Test + fun `handler adds notification if new sone event is fired`() { + eventBus.post(NewSoneFoundEvent(sone)) + assertThat(notificationManager.notifications, contains(notification)) + } + + @Test + fun `handler adds sone to notification`() { + eventBus.post(NewSoneFoundEvent(sone)) + assertThat(notification.elements, contains(sone)) + } + + @Test + fun `handler does not add notification on new sone event if first-start notification is present`() { + notificationManager.firstStart() + eventBus.post(NewSoneFoundEvent(sone)) + assertThat(notificationManager.notifications, not(contains(notification))) + } + + @Test + fun `handler removes sone from notification if sone is marked as known`() { + notification.add(sone) + eventBus.post(MarkSoneKnownEvent(sone)) + assertThat(notification.elements, emptyIterable()) + } + + @Test + fun `handler removes sone from notification if sone is removed`() { + notification.add(sone) + eventBus.post(SoneRemovedEvent(sone)) + assertThat(notification.elements, emptyIterable()) + } + +} + +private val sone: Sone = IdOnlySone("sone-id") diff --git a/src/test/kotlin/net/pterodactylus/sone/web/notification/NewVersionHandlerTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/notification/NewVersionHandlerTest.kt new file mode 100644 index 0000000..d37f60b --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/notification/NewVersionHandlerTest.kt @@ -0,0 +1,69 @@ +/** + * Sone - NewVersionHandlerTest.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.notification + +import com.google.common.eventbus.* +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.util.notify.* +import net.pterodactylus.util.template.* +import net.pterodactylus.util.version.* +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.* +import kotlin.test.* + +/** + * Unit test for [NewVersionHandler]. + */ +@Suppress("UnstableApiUsage") +class NewVersionHandlerTest { + + private val eventBus = EventBus() + private val notificationManager = NotificationManager() + private val notification = TemplateNotification(Template()) + + init { + eventBus.register(NewVersionHandler(notificationManager, notification)) + eventBus.post(UpdateFoundEvent(Version(1, 2, 3), 1000L, 2000L, true)) + } + + @Test + fun `new-version handler adds notification to manager on new version`() { + assertThat(notificationManager.notifications, contains(notification)) + } + + @Test + fun `handler sets version in notification`() { + assertThat(notification.get("latestVersion"), equalTo(Version(1, 2, 3))) + } + + @Test + fun `handler sets release time in notification`() { + assertThat(notification.get("releaseTime"), equalTo(1000L)) + } + + @Test + fun `handler sets edition in notification`() { + assertThat(notification.get("latestEdition"), equalTo(2000L)) + } + + @Test + fun `handler sets disruptive flag in notification`() { + assertThat(notification.get("disruptive"), equalTo(true)) + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/notification/NotificationHandlerModuleTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/notification/NotificationHandlerModuleTest.kt new file mode 100644 index 0000000..40e8f4d --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/notification/NotificationHandlerModuleTest.kt @@ -0,0 +1,611 @@ +/** + * Sone - NotificationHandlerModuleTest.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.notification + +import com.google.inject.* +import com.google.inject.Guice.* +import com.google.inject.name.Names.* +import net.pterodactylus.sone.core.* +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.sone.data.* +import net.pterodactylus.sone.data.Post.* +import net.pterodactylus.sone.data.impl.* +import net.pterodactylus.sone.database.* +import net.pterodactylus.sone.freenet.wot.* +import net.pterodactylus.sone.main.* +import net.pterodactylus.sone.notify.* +import net.pterodactylus.sone.test.* +import net.pterodactylus.sone.text.* +import net.pterodactylus.sone.utils.* +import net.pterodactylus.util.notify.* +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.* +import org.mockito.* +import org.mockito.Mockito.* +import java.util.concurrent.* +import java.util.concurrent.TimeUnit.* +import java.util.function.* +import kotlin.test.* + +/** + * Unit test for [NotificationHandlerModule]. + */ +class NotificationHandlerModuleTest { + + private val core = mock() + private val webOfTrustConnector = mock() + private val ticker = mock() + private val notificationManager = NotificationManager() + private val loaders = TestLoaders() + private val injector: Injector = createInjector( + Core::class.isProvidedBy(core), + NotificationManager::class.isProvidedBy(notificationManager), + Loaders::class.isProvidedBy(loaders), + WebOfTrustConnector::class.isProvidedBy(webOfTrustConnector), + ScheduledExecutorService::class.withNameIsProvidedBy(ticker, "notification"), + SoneTextParser::class.isProvidedByMock(), + PostReplyProvider::class.isProvidedByMock(), + NotificationHandlerModule() + ) + + @Test + fun `notification handler is created as singleton`() { + injector.verifySingletonInstance() + } + + @Test + fun `mark-post-known-during-first-start handler is created as singleton`() { + injector.verifySingletonInstance() + } + + @Test + fun `mark-post-known-during-first-start handler is created with correct action`() { + notificationManager.firstStart() + val handler = injector.getInstance() + val post = mock() + handler.newPostFound(NewPostFoundEvent(post)) + verify(core).markPostKnown(post) + } + + @Test + fun `mark-post-reply-known-during-first-start handler is created as singleton`() { + injector.verifySingletonInstance() + } + + @Test + fun `mark-post-reply-known-during-first-start handler is created with correct action`() { + notificationManager.firstStart() + val handler = injector.getInstance() + val postReply = mock() + handler.newPostReply(NewPostReplyFoundEvent(postReply)) + verify(core).markReplyKnown(postReply) + } + + @Test + fun `sone-locked-on-startup handler is created as singleton`() { + injector.verifySingletonInstance() + } + + @Test + fun `module can create sone-locked-on-startup notification with correct id`() { + val notification = injector.getInstance>(named("soneLockedOnStartup")) + assertThat(notification.id, equalTo("sone-locked-on-startup")) + } + + @Test + fun `sone-locked-on-startup notification is created as singleton`() { + injector.verifySingletonInstance>(named("soneLockedOnStartup")) + } + + @Test + fun `module can create sone-locked-on-startup notification with correct template and key`() { + loaders.templates += "/templates/notify/soneLockedOnStartupNotification.html" to "<% sones>".asTemplate() + val notification = injector.getInstance>(named("soneLockedOnStartup")) + val sone1 = IdOnlySone("sone1") + val sone2 = IdOnlySone("sone2") + notification.add(sone1) + notification.add(sone2) + assertThat(notification.render(), equalTo(listOf(sone1, sone2).toString())) + } + + @Test + fun `sone-locked-on-startup notification is dismissable`() { + assertThat(injector.getInstance>(named("soneLockedOnStartup")).isDismissable, equalTo(true)) + } + + @Test + fun `new-sone handler is created as singleton`() { + injector.verifySingletonInstance() + } + + @Test + fun `new-sone notification has correct ID`() { + assertThat(injector.getInstance>(named("newSone")).id, equalTo("new-sone-notification")) + } + + @Test + fun `new-sone notification has correct key and template`() { + loaders.templates += "/templates/notify/newSoneNotification.html" to "<% sones>".asTemplate() + val notification = injector.getInstance>(named("newSone")) + val sones = listOf(IdOnlySone("sone1"), IdOnlySone("sone2")) + sones.forEach(notification::add) + assertThat(notification.render(), equalTo(sones.toString())) + } + + @Test + fun `new-sone notification is not dismissable`() { + assertThat(injector.getInstance>(named("newSone")).isDismissable, equalTo(false)) + } + + @Test + fun `new-remote-post handler is created as singleton`() { + injector.verifySingletonInstance() + } + + @Test + fun `new-remote-post notification is created as singleton`() { + injector.verifySingletonInstance>(named("newRemotePost")) + } + + @Test + fun `new-remote-post notification has correct ID`() { + assertThat(injector.getInstance>(named("newRemotePost")).id, equalTo("new-post-notification")) + } + + @Test + fun `new-remote-post notification is not dismissable`() { + assertThat(injector.getInstance>(named("newRemotePost")).isDismissable, equalTo(false)) + } + + @Test + fun `new-remote-post notification has correct key and template`() { + loaders.templates += "/templates/notify/newPostNotification.html" to "<% posts>".asTemplate() + val notification = injector.getInstance>(named("newRemotePost")) + val posts = listOf(EmptyPost("post1"), EmptyPost("post2")) + posts.forEach(notification::add) + assertThat(notification.render(), equalTo(posts.toString())) + } + + @Test + fun `remote-post handler is created as singleton`() { + injector.verifySingletonInstance() + } + + @Test + fun `new-remote-post-reply notification is created as singleton`() { + injector.verifySingletonInstance>(named("newRemotePostReply")) + } + + @Test + fun `new-remote-post-reply notification has correct ID`() { + assertThat(injector.getInstance>(named("newRemotePostReply")).id, equalTo("new-reply-notification")) + } + + @Test + fun `new-remote-post-reply notification is not dismissable`() { + assertThat(injector.getInstance>(named("newRemotePostReply")).isDismissable, equalTo(false)) + } + + @Test + fun `new-remote-post-reply notification has correct key and template`() { + loaders.templates += "/templates/notify/newReplyNotification.html" to "<% replies>".asTemplate() + val notification = injector.getInstance>(named("newRemotePostReply")) + val postReplies = listOf(emptyPostReply(), emptyPostReply()) + postReplies.forEach(notification::add) + assertThat(notification.render(), equalTo(postReplies.toString())) + } + + @Test + fun `sone-locked notification is created as singleton`() { + injector.verifySingletonInstance>(named("soneLocked")) + } + + @Test + fun `sone-locked notification is dismissable`() { + assertThat(injector.getInstance>(named("soneLocked")).isDismissable, equalTo(true)) + } + + @Test + fun `sone-locked notification has correct ID`() { + assertThat(injector.getInstance>(named("soneLocked")).id, equalTo("sones-locked-notification")) + } + + @Test + fun `sone-locked notification has correct key and template`() { + loaders.templates += "/templates/notify/lockedSonesNotification.html" to "<% sones>".asTemplate() + val notification = injector.getInstance>(named("soneLocked")) + val sones = listOf(IdOnlySone("sone1"), IdOnlySone("sone2")) + sones.forEach(notification::add) + assertThat(notification.render(), equalTo(sones.toString())) + } + + @Test + fun `sone-locked handler is created as singleton`() { + injector.verifySingletonInstance() + } + + @Test + fun `local-post notification is not dismissable`() { + assertThat(injector.getInstance>(named("localPost")).isDismissable, equalTo(false)) + } + + @Test + fun `local-post notification has correct ID`() { + assertThat(injector.getInstance>(named("localPost")).id, equalTo("local-post-notification")) + } + + @Test + fun `local-post notification has correct key and template`() { + loaders.templates += "/templates/notify/newPostNotification.html" to "<% posts>".asTemplate() + val notification = injector.getInstance>(named("localPost")) + val posts = listOf(EmptyPost("post1"), EmptyPost("post2")) + posts.forEach(notification::add) + assertThat(notification.render(), equalTo(posts.toString())) + } + + @Test + fun `local-post notification is created as singleton`() { + injector.verifySingletonInstance>(named("localPost")) + } + + @Test + fun `local-post handler is created as singleton`() { + injector.verifySingletonInstance() + } + + @Test + fun `local-reply notification is not dismissable`() { + assertThat(injector.getInstance>(named("localReply")).isDismissable, equalTo(false)) + } + + @Test + fun `local-reply notification has correct ID`() { + assertThat(injector.getInstance>(named("localReply")).id, equalTo("local-reply-notification")) + } + + @Test + fun `local-reply notification has correct key and template`() { + loaders.templates += "/templates/notify/newReplyNotification.html" to "<% replies>".asTemplate() + val notification = injector.getInstance>(named("localReply")) + val replies = listOf(emptyPostReply("reply1"), emptyPostReply("reply2")) + replies.forEach(notification::add) + assertThat(notification.render(), equalTo(replies.toString())) + } + + @Test + fun `local-reply notification is created as singleton`() { + injector.verifySingletonInstance>(named("localReply")) + } + + @Test + fun `local-reply handler is created as singleton`() { + injector.verifySingletonInstance() + } + + @Test + fun `new-version notification is created as singleton`() { + injector.verifySingletonInstance(named("newVersion")) + } + + @Test + fun `new-version notification has correct ID`() { + assertThat(injector.getInstance(named("newVersion")).id, equalTo("new-version-notification")) + } + + @Test + fun `new-version notification is dismissable`() { + assertThat(injector.getInstance(named("newVersion")).isDismissable, equalTo(true)) + } + + @Test + fun `new-version notification loads correct template`() { + loaders.templates += "/templates/notify/newVersionNotification.html" to "1".asTemplate() + val notification = injector.getInstance(named("newVersion")) + assertThat(notification.render(), equalTo("1")) + } + + @Test + fun `new-version handler is created as singleton`() { + injector.verifySingletonInstance() + } + + @Test + fun `inserting-image notification is created as singleton`() { + injector.verifySingletonInstance>(named("imageInserting")) + } + + @Test + fun `inserting-image notification has correct ID`() { + assertThat(injector.getInstance>(named("imageInserting")).id, equalTo("inserting-images-notification")) + } + + @Test + fun `inserting-image notification is dismissable`() { + assertThat(injector.getInstance>(named("imageInserting")).isDismissable, equalTo(true)) + } + + @Test + fun `inserting-image notification loads correct template`() { + loaders.templates += "/templates/notify/inserting-images-notification.html" to "<% images>".asTemplate() + val notification = injector.getInstance>(named("imageInserting")) + val images = listOf(ImageImpl(), ImageImpl()).onEach(notification::add) + assertThat(notification.render(), equalTo(images.toString())) + } + + @Test + fun `inserting-image-failed notification is created as singleton`() { + injector.verifySingletonInstance>(named("imageFailed")) + } + + @Test + fun `inserting-image-failed notification has correct ID`() { + assertThat(injector.getInstance>(named("imageFailed")).id, equalTo("image-insert-failed-notification")) + } + + @Test + fun `inserting-image-failed notification is dismissable`() { + assertThat(injector.getInstance>(named("imageFailed")).isDismissable, equalTo(true)) + } + + @Test + fun `inserting-image-failed notification loads correct template`() { + loaders.templates += "/templates/notify/image-insert-failed-notification.html" to "<% images>".asTemplate() + val notification = injector.getInstance>(named("imageFailed")) + val images = listOf(ImageImpl(), ImageImpl()).onEach(notification::add) + assertThat(notification.render(), equalTo(images.toString())) + } + + @Test + fun `inserted-image notification is created as singleton`() { + injector.verifySingletonInstance>(named("imageInserted")) + } + + @Test + fun `inserted-image notification has correct ID`() { + assertThat(injector.getInstance>(named("imageInserted")).id, equalTo("inserted-images-notification")) + } + + @Test + fun `inserted-image notification is dismissable`() { + assertThat(injector.getInstance>(named("imageInserted")).isDismissable, equalTo(true)) + } + + @Test + fun `inserted-image notification loads correct template`() { + loaders.templates += "/templates/notify/inserted-images-notification.html" to "<% images>".asTemplate() + val notification = injector.getInstance>(named("imageInserted")) + val images = listOf(ImageImpl(), ImageImpl()).onEach(notification::add) + assertThat(notification.render(), equalTo(images.toString())) + } + + @Test + fun `image insert handler is created as singleton`() { + injector.verifySingletonInstance() + } + + @Test + fun `first-start notification is created as singleton`() { + injector.verifySingletonInstance(named("firstStart")) + } + + @Test + fun `first-start notification has correct ID`() { + assertThat(injector.getInstance(named("firstStart")).id, equalTo("first-start-notification")) + } + + @Test + fun `first-start notification is dismissable`() { + assertThat(injector.getInstance(named("firstStart")).isDismissable, equalTo(true)) + } + + @Test + fun `first-start notification loads correct template`() { + loaders.templates += "/templates/notify/firstStartNotification.html" to "1".asTemplate() + val notification = injector.getInstance(named("firstStart")) + assertThat(notification.render(), equalTo("1")) + } + + @Test + fun `first-start handler is created as singleton`() { + injector.verifySingletonInstance() + } + + @Test + fun `config-not-read notification is created as singleton`() { + injector.verifySingletonInstance(named("configNotRead")) + } + + @Test + fun `config-not-read notification has correct ID `() { + assertThat(injector.getInstance(named("configNotRead")).id, equalTo("config-not-read-notification")) + } + + @Test + fun `config-not-read notification is dismissable`() { + assertThat(injector.getInstance(named("configNotRead")).isDismissable, equalTo(true)) + } + + @Test + fun `config-not-read notification loads correct template`() { + loaders.templates += "/templates/notify/configNotReadNotification.html" to "1".asTemplate() + val notification = injector.getInstance(named("configNotRead")) + assertThat(notification.render(), equalTo("1")) + } + + @Test + fun `config-not-read handler is created as singleton`() { + injector.verifySingletonInstance() + } + + @Test + fun `startup notification can be created`() { + injector.verifySingletonInstance(named("startup")) + } + + @Test + fun `startup notification has correct ID`() { + assertThat(injector.getInstance(named("startup")).id, equalTo("startup-notification")) + } + + @Test + fun `startup notification is dismissable`() { + assertThat(injector.getInstance(named("startup")).isDismissable, equalTo(true)) + } + + @Test + fun `startup notification loads correct template`() { + loaders.templates += "/templates/notify/startupNotification.html" to "1".asTemplate() + val notification = injector.getInstance(named("startup")) + assertThat(notification.render(), equalTo("1")) + } + + @Test + fun `startup handler is created as singleton`() { + injector.verifySingletonInstance() + } + + @Test + fun `web-of-trust notification is created as singleton`() { + injector.verifySingletonInstance(named("webOfTrust")) + } + + @Test + fun `web-of-trust notification has correct ID`() { + assertThat(injector.getInstance(named("webOfTrust")).id, equalTo("wot-missing-notification")) + } + + @Test + fun `web-of-trust notification is dismissable`() { + assertThat(injector.getInstance(named("webOfTrust")).isDismissable, equalTo(true)) + } + + @Test + fun `web-of-trust notification loads correct template`() { + loaders.templates += "/templates/notify/wotMissingNotification.html" to "1".asTemplate() + val notification = injector.getInstance(named("webOfTrust")) + assertThat(notification.render(), equalTo("1")) + } + + @Test + fun `web-of-trust handler is created as singleton`() { + injector.verifySingletonInstance(named("webOfTrust")) + } + + @Test + fun `web-of-trust reacher is created as singleton`() { + injector.verifySingletonInstance(named("webOfTrustReacher")) + } + + @Test + fun `web-of-trust reacher access the wot connector`() { + injector.getInstance(named("webOfTrustReacher")).run() + verify(webOfTrustConnector).ping() + } + + @Test + fun `web-of-trust reschedule is created as singleton`() { + injector.verifySingletonInstance>(named("webOfTrustReschedule")) + } + + @Test + fun `web-of-trust reschedule schedules at the correct delay`() { + val webOfTrustPinger = injector.getInstance() + injector.getInstance>(named("webOfTrustReschedule"))(webOfTrustPinger) + verify(ticker).schedule(ArgumentMatchers.eq(webOfTrustPinger), ArgumentMatchers.eq(15L), ArgumentMatchers.eq(SECONDS)) + } + + @Test + fun `sone mention detector is created as singleton`() { + assertThat(injector.getInstance(), notNullValue()) + } + + @Test + fun `sone-mentioned notification is created as singleton`() { + injector.verifySingletonInstance>(named("soneMentioned")) + } + + @Test + fun `sone-mentioned notification has correct ID`() { + assertThat(injector.getInstance>(named("soneMentioned")).id, equalTo("mention-notification")) + } + + @Test + fun `sone-mentioned notification is not dismissable`() { + assertThat(injector.getInstance>(named("soneMentioned")).isDismissable, equalTo(false)) + } + + @Test + fun `sone-mentioned notification loads correct template`() { + loaders.templates += "/templates/notify/mentionNotification.html" to "<% posts>".asTemplate() + val notification = injector.getInstance>(named("soneMentioned")) + val posts = listOf(EmptyPost("1"), EmptyPost("2")).onEach(notification::add) + assertThat(notification.render(), equalTo(posts.toString())) + } + + @Test + fun `sone-mentioned handler is created as singleton`() { + injector.verifySingletonInstance() + } + + @Test + fun `sone insert notification supplier is created as singleton`() { + injector.verifySingletonInstance() + } + + @Test + fun `sone insert notification template is loaded correctly`() { + loaders.templates += "/templates/notify/soneInsertNotification.html" to "foo".asTemplate() + injector.getInstance() + .invoke(createRemoteSone()) + .render() + .let { assertThat(it, equalTo("foo")) } + } + + @Test + fun `sone notification supplier returns different notifications for different sones`() { + val supplier = injector.getInstance() + listOf(createRemoteSone(), createRemoteSone(), createRemoteSone()) + .map(supplier) + .distinct() + .let { assertThat(it, hasSize(3)) } + } + + @Test + fun `sone notification supplier caches notifications for a sone`() { + val supplier = injector.getInstance() + val sone = createRemoteSone() + listOf(sone, sone, sone) + .map(supplier) + .distinct() + .let { assertThat(it, hasSize(1)) } + } + + @Test + fun `sone notification supplier sets sone in notification template`() { + val supplier = injector.getInstance() + val sone = createRemoteSone() + val templateNotification = supplier(sone) + assertThat(templateNotification["insertSone"], sameInstance(sone)) + } + + @Test + fun `sone insert handler is created as singleton`() { + injector.verifySingletonInstance() + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/notification/NotificationHandlerTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/notification/NotificationHandlerTest.kt deleted file mode 100644 index d145038..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/notification/NotificationHandlerTest.kt +++ /dev/null @@ -1,95 +0,0 @@ -/** - * Sone - NotificationHandlerTest.kt - Copyright © 2019 David ‘Bombe’ 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 - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.web.notification - -import com.google.common.eventbus.* -import com.google.inject.* -import com.google.inject.Guice.* -import net.pterodactylus.sone.main.* -import net.pterodactylus.sone.test.* -import net.pterodactylus.util.notify.* -import net.pterodactylus.util.template.* -import net.pterodactylus.util.web.* -import org.hamcrest.MatcherAssert.* -import org.hamcrest.Matchers.* -import kotlin.test.* - -/** - * Unit test for [NotificationHandler]. - */ -class NotificationHandlerTest { - - private val eventBus = TestEventBus() - private val loaders = TestLoaders() - private val notificationManager = NotificationManager() - private val handler = NotificationHandler(eventBus, loaders, notificationManager) - - @Test - fun `notification handler can be created by guice`() { - val injector = createInjector( - EventBus::class.isProvidedBy(eventBus), - NotificationManager::class.isProvidedBy(notificationManager), - Loaders::class.isProvidedBy(loaders) - ) - assertThat(injector.getInstance(), notNullValue()) - } - - @Test - fun `notification handler registers handler for sone-locked event`() { - handler.start() - assertThat(eventBus.registeredObjects.any { it.javaClass == SoneLockedOnStartupHandler::class.java }, equalTo(true)) - } - - @Test - fun `notification handler loads sone-locked notification template`() { - handler.start() - assertThat(loaders.requestedTemplatePaths.any { it == "/templates/notify/soneLockedOnStartupNotification.html" }, equalTo(true)) - } - -} - -@Suppress("UnstableApiUsage") -private class TestEventBus : EventBus() { - private val _registeredObjects = mutableListOf() - val registeredObjects: List - get() = _registeredObjects - - override fun register(`object`: Any) { - super.register(`object`) - _registeredObjects += `object` - } - -} - -private class TestLoaders : Loaders { - val requestedTemplatePaths = mutableListOf() - - override fun loadTemplate(path: String) = - Template().also { requestedTemplatePaths += path } - - override fun loadStaticPage(basePath: String, prefix: String, mimeType: String) = object : Page { - - override fun getPath() = "" - override fun isPrefixPage() = false - override fun handleRequest(request: REQ, response: Response) = response - - } - - override fun getTemplateProvider() = TemplateProvider { _, _ -> Template() } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/notification/NotificationHandlerTester.kt b/src/test/kotlin/net/pterodactylus/sone/web/notification/NotificationHandlerTester.kt new file mode 100644 index 0000000..4d5627f --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/notification/NotificationHandlerTester.kt @@ -0,0 +1,58 @@ +/** + * Sone - NotificationHandlerTester.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.notification + +import com.google.common.eventbus.* +import net.pterodactylus.util.notify.* + +/** + * Helper for testing event handlers that deal with notifications. It contains + * a notification manager and an [event bus][EventBus] and automatically + * registers the created handler on the event bus. + * + * ``` + * val notification = SomeNotification() + * val notificationTester = NotificationTester { SomeHandler(it, notification) } + * + * fun test() { + * notificationTester.sendEvent(SomeEvent()) + * assertThat(notificationTester.elements, hasItem(notification)) + * } + * ``` + */ +@Suppress("UnstableApiUsage") +class NotificationHandlerTester(createHandler: (NotificationManager) -> Any) { + + private val eventBus = EventBus() + private val notificationManager = NotificationManager() + + /** Returns all notifications of the notification manager. */ + val notifications: Set + get() = notificationManager.notifications + + init { + eventBus.register(createHandler(notificationManager)) + } + + /** Sends an event to the event bus. */ + fun sendEvent(event: Any) = eventBus.post(event) + + /** Sets the first-start notification on the notification manager. */ + fun firstStart() = notificationManager.firstStart() + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/notification/RemotePostReplyHandlerTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/notification/RemotePostReplyHandlerTest.kt new file mode 100644 index 0000000..9c5361c --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/notification/RemotePostReplyHandlerTest.kt @@ -0,0 +1,93 @@ +/** + * Sone - RemotePostReplyHandler.kt - Copyright © 2020 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.notification + +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.sone.data.* +import net.pterodactylus.sone.notify.* +import net.pterodactylus.sone.test.* +import net.pterodactylus.util.notify.* +import net.pterodactylus.util.template.* +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.* +import kotlin.test.* + +/** + * Unit test for [RemotePostReplyHandler]. + */ +class RemotePostReplyHandlerTest { + + private val notification = ListNotification("", "", Template()) + private val notificationHandlerTester = NotificationHandlerTester { RemotePostReplyHandler(it, notification) } + private val postReply = emptyPostReply() + + @Test + fun `reply is added to notification on new reply`() { + notificationHandlerTester.sendEvent(NewPostReplyFoundEvent(postReply)) + assertThat(notification.elements, hasItem(postReply)) + } + + @Test + fun `notification is added to manager on new reply`() { + notificationHandlerTester.sendEvent(NewPostReplyFoundEvent(postReply)) + assertThat(notificationHandlerTester.notifications, hasItem(notification)) + } + + @Test + fun `reply is not added to notification on new reply during first start`() { + notificationHandlerTester.firstStart() + notificationHandlerTester.sendEvent(NewPostReplyFoundEvent(postReply)) + assertThat(notification.elements, not(hasItem(postReply))) + } + + @Test + fun `notification is not added to manager on new reply during first start`() { + notificationHandlerTester.firstStart() + notificationHandlerTester.sendEvent(NewPostReplyFoundEvent(postReply)) + assertThat(notificationHandlerTester.notifications, not(hasItem(notification))) + } + + @Test + fun `reply is not added to notification on new local reply`() { + val postReply = emptyPostReply(sone = localSone1) + notificationHandlerTester.sendEvent(NewPostReplyFoundEvent(postReply)) + assertThat(notification.elements, not(hasItem(postReply))) + } + + @Test + fun `notification is not added to manager on new local reply`() { + val postReply = emptyPostReply(sone = localSone1) + notificationHandlerTester.sendEvent(NewPostReplyFoundEvent(postReply)) + assertThat(notificationHandlerTester.notifications, not(hasItem(notification))) + } + + @Test + fun `reply is removed from notification when removed`() { + notification.add(postReply) + notificationHandlerTester.sendEvent(PostReplyRemovedEvent(postReply)) + assertThat(notification.elements, not(hasItem(postReply))) + } + + @Test + fun `reply is removed from notification when marked as known`() { + notification.add(postReply) + notificationHandlerTester.sendEvent(MarkPostReplyKnownEvent(postReply)) + assertThat(notification.elements, not(hasItem(postReply))) + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/notification/SoneInsertHandlerTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/notification/SoneInsertHandlerTest.kt new file mode 100644 index 0000000..6a11a90 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/notification/SoneInsertHandlerTest.kt @@ -0,0 +1,112 @@ +/** + * Sone - SoneInsertHandlerTest.kt - Copyright © 2020 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.notification + +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.sone.test.* +import net.pterodactylus.util.notify.* +import net.pterodactylus.util.template.* +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.* +import kotlin.test.* + +/** + * Unit test for [SoneInsertHandler]. + */ +class SoneInsertHandlerTest { + + private val localSone = createLocalSone() + private val notification1 = TemplateNotification(Template()) + private val notification2 = TemplateNotification(Template()) + private val soneInsertHandlerTester = NotificationHandlerTester { + SoneInsertHandler(it) { sone -> + if (sone == localSone) notification1 else notification2 + } + } + + @Test + fun `handler adds notification to manager when sone insert starts`() { + localSone.options.isSoneInsertNotificationEnabled = true + soneInsertHandlerTester.sendEvent(SoneInsertingEvent(localSone)) + assertThat(soneInsertHandlerTester.notifications, hasItem(notification1)) + } + + @Test + fun `handler sets sone status in notification when sone insert starts`() { + localSone.options.isSoneInsertNotificationEnabled = true + soneInsertHandlerTester.sendEvent(SoneInsertingEvent(localSone)) + assertThat(notification1.get("soneStatus"), equalTo("inserting")) + } + + @Test + fun `handler does not add notification to manager if option is disabled`() { + localSone.options.isSoneInsertNotificationEnabled = false + soneInsertHandlerTester.sendEvent(SoneInsertingEvent(localSone)) + assertThat(soneInsertHandlerTester.notifications, not(hasItem(notification1))) + } + + @Test + fun `handler adds notification to manager when sone insert finishes`() { + localSone.options.isSoneInsertNotificationEnabled = true + soneInsertHandlerTester.sendEvent(SoneInsertedEvent(localSone, 123456, "")) + assertThat(soneInsertHandlerTester.notifications, hasItem(notification1)) + } + + @Test + fun `handler sets sone status in notification when sone insert finishes`() { + localSone.options.isSoneInsertNotificationEnabled = true + soneInsertHandlerTester.sendEvent(SoneInsertedEvent(localSone, 123456, "")) + assertThat(notification1.get("soneStatus"), equalTo("inserted")) + } + + @Test + fun `handler sets insert duration in notification when sone insert finishes`() { + localSone.options.isSoneInsertNotificationEnabled = true + soneInsertHandlerTester.sendEvent(SoneInsertedEvent(localSone, 123456, "")) + assertThat(notification1.get("insertDuration"), equalTo(123L)) + } + + @Test + fun `handler does not add notification for finished insert to manager if option is disabled`() { + localSone.options.isSoneInsertNotificationEnabled = false + soneInsertHandlerTester.sendEvent(SoneInsertedEvent(localSone, 123456, "")) + assertThat(soneInsertHandlerTester.notifications, not(hasItem(notification1))) + } + + @Test + fun `handler adds notification to manager when sone insert aborts`() { + localSone.options.isSoneInsertNotificationEnabled = true + soneInsertHandlerTester.sendEvent(SoneInsertAbortedEvent(localSone, Exception())) + assertThat(soneInsertHandlerTester.notifications, hasItem(notification1)) + } + + @Test + fun `handler sets sone status in notification when sone insert aborts`() { + localSone.options.isSoneInsertNotificationEnabled = true + soneInsertHandlerTester.sendEvent(SoneInsertAbortedEvent(localSone, Exception())) + assertThat(notification1.get("soneStatus"), equalTo("insert-aborted")) + } + + @Test + fun `handler does not add notification for aborted insert to manager if option is disabled`() { + localSone.options.isSoneInsertNotificationEnabled = false + soneInsertHandlerTester.sendEvent(SoneInsertAbortedEvent(localSone, Exception())) + assertThat(soneInsertHandlerTester.notifications, not(hasItem(notification1))) + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/notification/SoneLockedHandlerTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/notification/SoneLockedHandlerTest.kt new file mode 100644 index 0000000..f7c185a --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/notification/SoneLockedHandlerTest.kt @@ -0,0 +1,116 @@ +/** + * Sone - SoneLockedHandlerTest.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.notification + +import com.google.common.eventbus.* +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.sone.data.* +import net.pterodactylus.sone.data.impl.* +import net.pterodactylus.sone.notify.* +import net.pterodactylus.sone.utils.* +import net.pterodactylus.util.notify.* +import net.pterodactylus.util.template.* +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.* +import java.util.concurrent.* +import kotlin.test.* + +/** + * Unit test for [SoneLockedHandler]. + */ +@Suppress("UnstableApiUsage") +class SoneLockedHandlerTest { + + private val eventBus = EventBus() + private val notificationManager = NotificationManager() + private val notification = ListNotification("", "", Template()) + private val executor = TestScheduledThreadPoolExecutor() + + init { + SoneLockedHandler(notificationManager, notification, executor).also(eventBus::register) + } + + @AfterTest + fun shutdownExecutor() = executor.shutdown() + + @Test + fun `notification is not added before the command is run`() { + eventBus.post(SoneLockedEvent(sone)) + assertThat(notificationManager.notifications, emptyIterable()) + } + + @Test + fun `sone is added to notification immediately`() { + eventBus.post(SoneLockedEvent(sone)) + assertThat(notification.elements, contains(sone)) + } + + @Test + fun `notification is added to notification manager from command`() { + eventBus.post(SoneLockedEvent(sone)) + executor.scheduleds.single().command() + assertThat(notificationManager.notifications, contains(notification)) + } + + @Test + fun `command is registered with a delay of five minutes`() { + eventBus.post(SoneLockedEvent(sone)) + with(executor.scheduleds.single()) { + assertThat(timeUnit.toNanos(delay), equalTo(TimeUnit.MINUTES.toNanos(5))) + } + } + + @Test + fun `unlocking sone after locking will cancel the future`() { + eventBus.post(SoneLockedEvent(sone)) + eventBus.post(SoneUnlockedEvent(sone)) + assertThat(executor.scheduleds.first().future.isCancelled, equalTo(true)) + } + + @Test + fun `unlocking sone after locking will remove the sone from the notification`() { + eventBus.post(SoneLockedEvent(sone)) + eventBus.post(SoneUnlockedEvent(sone)) + assertThat(notification.elements, emptyIterable()) + } + + @Test + fun `unlocking sone after showing the notification will remove the sone from the notification`() { + eventBus.post(SoneLockedEvent(sone)) + executor.scheduleds.single().command() + eventBus.post(SoneUnlockedEvent(sone)) + assertThat(notification.elements, emptyIterable()) + } + + @Test + fun `locking two sones will cancel the first command`() { + eventBus.post(SoneLockedEvent(sone)) + eventBus.post(SoneLockedEvent(sone)) + assertThat(executor.scheduleds.first().future.isCancelled, equalTo(true)) + } + + @Test + fun `locking two sones will schedule a second command`() { + eventBus.post(SoneLockedEvent(sone)) + eventBus.post(SoneLockedEvent(sone)) + assertThat(executor.scheduleds[1], notNullValue()) + } + +} + +private val sone: Sone = IdOnlySone("sone") diff --git a/src/test/kotlin/net/pterodactylus/sone/web/notification/SoneLockedOnStartupHandlerTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/notification/SoneLockedOnStartupHandlerTest.kt index 0b9d1e1..36a8836 100644 --- a/src/test/kotlin/net/pterodactylus/sone/web/notification/SoneLockedOnStartupHandlerTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/web/notification/SoneLockedOnStartupHandlerTest.kt @@ -19,10 +19,11 @@ package net.pterodactylus.sone.web.notification import com.google.common.eventbus.* import net.pterodactylus.sone.core.event.* +import net.pterodactylus.sone.data.* import net.pterodactylus.sone.data.impl.* import net.pterodactylus.sone.notify.* -import net.pterodactylus.sone.utils.* import net.pterodactylus.util.notify.* +import net.pterodactylus.util.template.* import org.hamcrest.MatcherAssert.* import org.hamcrest.Matchers.* import kotlin.test.* @@ -35,29 +36,24 @@ class SoneLockedOnStartupHandlerTest { @Suppress("UnstableApiUsage") private val eventBus = EventBus() private val manager = NotificationManager() - private val notification by lazy { manager.notifications.single() as ListNotification<*> } + private val notification = ListNotification("", "", Template()) init { - SoneLockedOnStartupHandler(manager, template).also(eventBus::register) - eventBus.post(SoneLockedOnStartup(sone)) - } - - @Test - fun `notification has correct id`() { - assertThat(notification.id, equalTo("sone-locked-on-startup")) + SoneLockedOnStartupHandler(manager, notification).also(eventBus::register) } @Test fun `handler adds sone to notification when event is posted`() { + eventBus.post(SoneLockedOnStartup(sone)) assertThat(notification.elements, contains(sone)) } @Test - fun `handler creates notification with correct key`() { - assertThat(notification.render(), equalTo(listOf(sone).toString())) + fun `handler adds notification to manager`() { + eventBus.post(SoneLockedOnStartup(sone)) + assertThat(manager.notifications, contains(notification)) } } private val sone = IdOnlySone("sone-id") -private val template = "<% sones>".asTemplate() diff --git a/src/test/kotlin/net/pterodactylus/sone/web/notification/SoneMentionedHandlerTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/notification/SoneMentionedHandlerTest.kt new file mode 100644 index 0000000..4e91aa5 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/notification/SoneMentionedHandlerTest.kt @@ -0,0 +1,87 @@ +/** + * Sone - SoneMentionedHandlerTest.kt - Copyright © 2020 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.notification + +import com.google.common.eventbus.* +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.sone.data.* +import net.pterodactylus.sone.data.Post.* +import net.pterodactylus.sone.notify.* +import net.pterodactylus.util.notify.* +import net.pterodactylus.util.template.* +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.* +import kotlin.test.* + +/** + * Unit test for [SoneMentionedHandler]. + */ +@Suppress("UnstableApiUsage") +class SoneMentionedHandlerTest { + + private val notificationManager = NotificationManager() + private val notification = ListNotification("", "", Template()) + private val eventBus = EventBus() + + init { + eventBus.register(SoneMentionedHandler(notificationManager, notification)) + } + + @Test + fun `handler adds notification to manager on event`() { + eventBus.post(MentionOfLocalSoneFoundEvent(post)) + assertThat(notificationManager.notifications, contains(notification)) + } + + @Test + fun `handler adds post to notification on event`() { + eventBus.post(MentionOfLocalSoneFoundEvent(post)) + assertThat(notification.elements, contains(post)) + } + + @Test + fun `handler does not add notification during first start`() { + notificationManager.firstStart() + eventBus.post(MentionOfLocalSoneFoundEvent(post)) + assertThat(notificationManager.notifications, not(hasItem(notification))) + } + + @Test + fun `handler does not add post to notification during first start`() { + notificationManager.firstStart() + eventBus.post(MentionOfLocalSoneFoundEvent(post)) + assertThat(notification.elements, not(hasItem(post))) + } + + @Test + fun `handler removes post from notification`() { + notification.add(post) + eventBus.post(MentionOfLocalSoneRemovedEvent(post)) + assertThat(notification.elements, not(hasItem(post))) + } + + @Test + fun `handler removes notification from manager`() { + notificationManager.addNotification(notification) + eventBus.post(MentionOfLocalSoneRemovedEvent(post)) + assertThat(notificationManager.notifications, not(hasItem(notification))) + } + +} + +private val post = EmptyPost("") diff --git a/src/test/kotlin/net/pterodactylus/sone/web/notification/StartupHandlerTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/notification/StartupHandlerTest.kt new file mode 100644 index 0000000..bc2c8e6 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/notification/StartupHandlerTest.kt @@ -0,0 +1,66 @@ +/** + * Sone - StartupHandlerTest.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.notification + +import com.google.common.eventbus.* +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.sone.utils.* +import net.pterodactylus.util.notify.* +import net.pterodactylus.util.template.* +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.* +import java.util.concurrent.TimeUnit.* +import kotlin.test.* + +/** + * Unit test for [StartupHandler]. + */ +class StartupHandlerTest { + + private val eventBus = EventBus() + private val notificationManager = NotificationManager() + private val notification = TemplateNotification("", Template()) + private val executor = TestScheduledThreadPoolExecutor() + + init { + eventBus.register(StartupHandler(notificationManager, notification, executor)) + } + + @AfterTest + fun shutdownExecutor() = executor.shutdown() + + @Test + fun `handler adds notification to manager on startup`() { + eventBus.post(Startup()) + assertThat(notificationManager.notifications, contains(notification)) + } + + @Test + fun `handler registers command on with 2-minute delay`() { + eventBus.post(Startup()) + assertThat(with(executor.scheduleds.single()) { timeUnit.toNanos(delay) }, equalTo(MINUTES.toNanos(2))) + } + + @Test + fun `registered command removes notification from manager`() { + eventBus.post(Startup()) + executor.scheduleds.single().command() + assertThat(notificationManager.notifications, emptyIterable()) + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/notification/Testing.kt b/src/test/kotlin/net/pterodactylus/sone/web/notification/Testing.kt new file mode 100644 index 0000000..4009ea0 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/notification/Testing.kt @@ -0,0 +1,45 @@ +/** + * Sone - Testing.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.notification + +import net.pterodactylus.util.notify.* +import java.io.* +import java.util.concurrent.* + +/** Information about a scheduled runnable. */ +data class Scheduled(val command: Runnable, val delay: Long, val timeUnit: TimeUnit, val future: ScheduledFuture<*>) + +/** + * [ScheduledThreadPoolExecutor] extension that stores parameters and return + * values for the [ScheduledThreadPoolExecutor.schedule] method. + */ +class TestScheduledThreadPoolExecutor : ScheduledThreadPoolExecutor(1) { + + val scheduleds = mutableListOf() + + override fun schedule(command: Runnable, delay: Long, unit: TimeUnit): ScheduledFuture<*> = + super.schedule(command, delay, unit) + .also { scheduleds += Scheduled(command, delay, unit, it) } + +} + +fun NotificationManager.firstStart() { + addNotification(object : AbstractNotification("first-start-notification") { + override fun render(writer: Writer?) = Unit + }) +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/notification/WebOfTrustHandlerTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/notification/WebOfTrustHandlerTest.kt new file mode 100644 index 0000000..ffd2536 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/notification/WebOfTrustHandlerTest.kt @@ -0,0 +1,54 @@ +/** + * Sone - WebOfTrustHandlerTest.kt - Copyright © 2019 David ‘Bombe’ 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.notification + +import com.google.common.eventbus.* +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.util.notify.* +import net.pterodactylus.util.template.* +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.* +import kotlin.test.* + +/** + * Unit test for [WebOfTrustHandler]. + */ +class WebOfTrustHandlerTest { + + private val eventBus = EventBus() + private val notificationManager = NotificationManager() + private val notification = TemplateNotification("", Template()) + + init { + eventBus.register(WebOfTrustHandler(notificationManager, notification)) + } + + @Test + fun `handler adds notification if wot goes down`() { + eventBus.post(WebOfTrustDisappeared()) + assertThat(notificationManager.notifications, contains(notification)) + } + + @Test + fun `handler removes notification if wot appears`() { + notificationManager.addNotification(notification) + eventBus.post(WebOfTrustAppeared()) + assertThat(notificationManager.notifications, emptyIterable()) + } + +}