From: David ‘Bombe’ Roden Date: Thu, 2 Jan 2020 21:00:17 +0000 (+0100) Subject: 🔀 Merge “next” into “feature/notification-handlers” X-Git-Tag: v81^2~5^2~24 X-Git-Url: https://git.pterodactylus.net/?p=Sone.git;a=commitdiff_plain;h=146335994dc415527bc4c8ec9a0981d739444ea8;hp=42a5ebcf3058a72c7181552c125a9d6614919a85 🔀 Merge “next” into “feature/notification-handlers” --- 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..50c63ee 100644 --- a/src/main/java/net/pterodactylus/sone/web/WebInterface.java +++ b/src/main/java/net/pterodactylus/sone/web/WebInterface.java @@ -21,25 +21,20 @@ 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; @@ -157,9 +152,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; @@ -178,27 +170,6 @@ public class WebInterface implements SessionProvider { /** 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 +179,8 @@ 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("localPost") ListNotification localPostNotification) { this.sonePlugin = sonePlugin; this.loaders = loaders; this.listNotificationFilter = listNotificationFilter; @@ -225,6 +197,8 @@ public class WebInterface implements SessionProvider { this.l10nFilter = l10nFilter; this.translation = translation; this.notificationManager = notificationManager; + this.newPostNotification = newPostNotification; + this.localPostNotification = localPostNotification; formPassword = sonePlugin.pluginRespirator().getToadletContainer().getFormPassword(); soneTextParser = new SoneTextParser(getCore(), getCore()); @@ -233,15 +207,6 @@ public class WebInterface implements SessionProvider { 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); @@ -250,21 +215,6 @@ public class WebInterface implements SessionProvider { 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 +352,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() @@ -440,37 +380,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 // @@ -494,36 +403,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 +410,6 @@ public class WebInterface implements SessionProvider { */ public void stop() { pageToadletRegistry.unregisterToadlets(); - ticker.shutdownNow(); } // @@ -690,20 +568,6 @@ public class WebInterface implements SessionProvider { // /** - * 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 @@ -713,19 +577,11 @@ public class WebInterface implements SessionProvider { 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); } } @@ -755,17 +611,6 @@ public class WebInterface implements SessionProvider { } } - /** - * 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()); @@ -777,18 +622,12 @@ public class WebInterface implements SessionProvider { } @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); } @@ -808,39 +647,6 @@ public class WebInterface implements SessionProvider { } /** - * 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 @@ -887,70 +693,6 @@ public class WebInterface implements SessionProvider { } } - /** - * 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/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/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..5f96050 --- /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(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/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..46961c0 --- /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(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..aceda7e --- /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(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..a5153ce --- /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/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/NewRemotePostHandler.kt b/src/main/kotlin/net/pterodactylus/sone/web/notification/NewRemotePostHandler.kt new file mode 100644 index 0000000..0c2368a --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/notification/NewRemotePostHandler.kt @@ -0,0 +1,43 @@ +/** + * 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) + } + } + } + +} 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..21a5b37 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,25 @@ 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 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, + newSoneHandler: NewSoneHandler, + newRemotePostHandler: NewRemotePostHandler, + soneLockedOnStartupHandler: SoneLockedOnStartupHandler, + soneLockedHandler: SoneLockedHandler, + newVersionHandler: NewVersionHandler, + imageInsertHandler: ImageInsertHandler, + firstStartHandler: FirstStartHandler, + configNotReadHandler: ConfigNotReadHandler, + startupHandler: StartupHandler, + webOfTrustPinger: WebOfTrustPinger, + webOfTrustHandler: WebOfTrustHandler +) 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..74c9f43 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/notification/NotificationHandlerModule.kt @@ -0,0 +1,150 @@ +/** + * 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.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() + } + + @Provides + fun getMarkPostKnownHandler(core: Core): Consumer = Consumer { core.markPostKnown(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("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("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) } + + 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/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/StartupHandler.kt b/src/main/kotlin/net/pterodactylus/sone/web/notification/StartupHandler.kt new file mode 100644 index 0000000..06acf94 --- /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(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..924d395 --- /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(webOfTrustAppeared: WebOfTrustAppeared) { + notificationManager.removeNotification(notification) + } + + @Subscribe + fun webOfTrustDisappeared(webOfTrustDisappeared: WebOfTrustDisappeared) { + notificationManager.addNotification(notification) + } + +} diff --git a/src/main/resources/i18n/sone.de.properties b/src/main/resources/i18n/sone.de.properties index 6c91fcd..a448377 100644 --- a/src/main/resources/i18n/sone.de.properties +++ b/src/main/resources/i18n/sone.de.properties @@ -461,4 +461,4 @@ Notification.Mention.Text=Sie wurden in diesen Nachrichten erwähnt: Notification.SoneIsInserting.Text=Ihre Sone sone://{0} wird jetzt hoch geladen. Notification.SoneIsInserted.Text=Ihre Sone sone://{0} wurde in {1,number} {1,choice,0#Sekunden|1#Sekunde|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(webOfTrustAppeared: WebOfTrustAppeared) { + received() + } +} + +private class WebOfTrustDisappearedCatcher(private val received: () -> Unit) { + @Subscribe + fun webOfTrustDisappeared(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..1a71f4b 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.* @@ -20,6 +19,7 @@ import org.hamcrest.MatcherAssert.* import org.hamcrest.Matchers.* import org.mockito.Mockito.* import java.io.* +import java.util.concurrent.* import java.util.concurrent.atomic.* import kotlin.test.* @@ -190,9 +190,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 +207,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..e658fe2 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.* @@ -14,6 +16,8 @@ import net.pterodactylus.sone.web.notification.* import org.hamcrest.MatcherAssert.* import org.hamcrest.Matchers.* import org.mockito.Mockito.* +import java.io.* +import java.util.concurrent.atomic.* import kotlin.test.* /** @@ -22,8 +26,7 @@ import kotlin.test.* @Dirty 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 +74,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 +95,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(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(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(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(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/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/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..d435101 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/notification/LocalPostHandlerTest.kt @@ -0,0 +1,115 @@ +/** + * 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 java.io.* +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 adds notification to manager`() { + eventBus.post(NewPostFoundEvent(remotePost)) + assertThat(notificationManager.notifications, contains(notification)) + } + + @Test + fun `handler does not add notification during first start`() { + notificationManager.addNotification(object : AbstractNotification("first-start-notification") { + override fun render(writer: Writer?) = Unit + }) + eventBus.post(NewPostFoundEvent(remotePost)) + 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/MarkPostKnownDuringFirstStartHandlerTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/notification/MarkPostKnownDuringFirstStartHandlerTest.kt new file mode 100644 index 0000000..28a1719 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/notification/MarkPostKnownDuringFirstStartHandlerTest.kt @@ -0,0 +1,62 @@ +/** + * 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.io.* +import java.util.function.Consumer +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.addNotification(object : AbstractNotification("first-start-notification") { + override fun render(writer: Writer?) = Unit + }) + 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/NewRemotePostHandlerTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/notification/NewRemotePostHandlerTest.kt new file mode 100644 index 0000000..5e4e5fc --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/notification/NewRemotePostHandlerTest.kt @@ -0,0 +1,93 @@ +/** + * 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 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.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 java.io.* +import kotlin.test.* + +/** + * Unit test for [NewRemotePostHandler]. + */ +@Suppress("UnstableApiUsage") +class NewRemotePostHandlerTest { + + private val eventBus = EventBus() + private val notificationManager = NotificationManager() + private val notification = ListNotification("", "", Template()) + private val handler = NewRemotePostHandler(notificationManager, notification) + + init { + eventBus.register(handler) + } + + @Test + fun `handler adds remote post to new-post notification`() { + eventBus.post(NewPostFoundEvent(remotePost)) + assertThat(notification.elements, contains(remotePost)) + } + + @Test + fun `handler does not add local post to new-post notification`() { + eventBus.post(NewPostFoundEvent(localPost)) + assertThat(notification.elements, emptyIterable()) + } + + @Test + fun `handler adds notification for remote post to notification manager`() { + eventBus.post(NewPostFoundEvent(remotePost)) + assertThat(notificationManager.notifications, contains(notification)) + } + + @Test + fun `handler does not add notification for local post to notification manager`() { + eventBus.post(NewPostFoundEvent(localPost)) + assertThat(notificationManager.notifications, emptyIterable()) + } + + @Test + fun `handler does not add notification to notification manager during first start`() { + notificationManager.addNotification(object : AbstractNotification("first-start-notification") { + override fun render(writer: Writer?) = Unit + }) + eventBus.post(NewPostFoundEvent(remotePost)) + assertThat(notificationManager.notifications, not(hasItem(notification))) + } + +} + +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..ff0947d --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/notification/NewSoneHandlerTest.kt @@ -0,0 +1,81 @@ +/** + * 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 java.io.* +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.addNotification(object : AbstractNotification("first-start-notification") { + override fun render(writer: Writer) = Unit + }) + eventBus.post(NewSoneFoundEvent(sone)) + assertThat(notificationManager.notifications.single().id, equalTo("first-start-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..ca52c1f --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/notification/NotificationHandlerModuleTest.kt @@ -0,0 +1,460 @@ +/** + * 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.freenet.wot.* +import net.pterodactylus.sone.main.* +import net.pterodactylus.sone.notify.* +import net.pterodactylus.sone.test.* +import net.pterodactylus.sone.utils.* +import net.pterodactylus.util.notify.* +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers +import org.hamcrest.Matchers.* +import org.mockito.* +import org.mockito.Mockito.* +import java.io.* +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"), + 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.addNotification(object : AbstractNotification("first-start-notification") { + override fun render(writer: Writer?) = Unit + }) + val handler = injector.getInstance() + val post = mock() + handler.newPostFound(NewPostFoundEvent(post)) + verify(core).markPostKnown(post) + } + + @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 `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 `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)) + } + +} 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/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/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..12088d0 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/notification/Testing.kt @@ -0,0 +1,37 @@ +/** + * 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 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) } + +} 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()) + } + +}