♻️ Use handler for Sone insertion notifications
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Sat, 8 Feb 2020 23:55:09 +0000 (00:55 +0100)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Sun, 9 Feb 2020 00:14:30 +0000 (01:14 +0100)
src/main/java/net/pterodactylus/sone/web/WebInterface.java
src/main/kotlin/net/pterodactylus/sone/web/notification/NotificationHandler.kt
src/main/kotlin/net/pterodactylus/sone/web/notification/NotificationHandlerModule.kt
src/main/kotlin/net/pterodactylus/sone/web/notification/SoneInsertHandler.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/test/Mocks.kt
src/test/kotlin/net/pterodactylus/sone/web/notification/NotificationHandlerModuleTest.kt
src/test/kotlin/net/pterodactylus/sone/web/notification/SoneInsertHandlerTest.kt [new file with mode: 0644]

index 1e6e6fb..2a32241 100644 (file)
@@ -21,8 +21,6 @@ import static com.google.common.collect.FluentIterable.from;
 import static java.util.logging.Logger.getLogger;
 
 import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
 import java.util.Set;
 import java.util.TimeZone;
 import java.util.UUID;
@@ -85,7 +83,6 @@ import net.pterodactylus.sone.web.page.TemplateRenderer;
 import net.pterodactylus.sone.web.pages.*;
 import net.pterodactylus.util.notify.Notification;
 import net.pterodactylus.util.notify.NotificationManager;
-import net.pterodactylus.util.notify.TemplateNotification;
 import net.pterodactylus.util.template.Template;
 import net.pterodactylus.util.template.TemplateContextFactory;
 import net.pterodactylus.util.web.RedirectPage;
@@ -156,9 +153,6 @@ public class WebInterface implements SessionProvider {
        /** The invisible “local reply” notification. */
        private final ListNotification<PostReply> localReplyNotification;
 
-       /** Notifications for sone inserts. */
-       private final Map<Sone, TemplateNotification> soneInsertNotifications = new HashMap<>();
-
        @Inject
        public WebInterface(SonePlugin sonePlugin, Loaders loaders, ListNotificationFilter listNotificationFilter,
                        PostVisibilityFilter postVisibilityFilter, ReplyVisibilityFilter replyVisibilityFilter,
@@ -487,74 +481,6 @@ public class WebInterface implements SessionProvider {
                pageToadletRegistry.registerToadlets();
        }
 
-       /**
-        * Returns the Sone insert notification for the given Sone. If no
-        * notification for the given Sone exists, a new notification is created and
-        * cached.
-        *
-        * @param sone
-        *            The Sone to get the insert notification for
-        * @return The Sone insert notification
-        */
-       private TemplateNotification getSoneInsertNotification(Sone sone) {
-               synchronized (soneInsertNotifications) {
-                       TemplateNotification templateNotification = soneInsertNotifications.get(sone);
-                       if (templateNotification == null) {
-                               templateNotification = new TemplateNotification(loaders.loadTemplate("/templates/notify/soneInsertNotification.html"));
-                               templateNotification.set("insertSone", sone);
-                               soneInsertNotifications.put(sone, templateNotification);
-                       }
-                       return templateNotification;
-               }
-       }
-
-       /**
-        * Notifies the web interface that a {@link Sone} is being inserted.
-        *
-        * @param soneInsertingEvent
-        *            The event
-        */
-       @Subscribe
-       public void soneInserting(SoneInsertingEvent soneInsertingEvent) {
-               TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertingEvent.getSone());
-               soneInsertNotification.set("soneStatus", "inserting");
-               if (soneInsertingEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
-                       notificationManager.addNotification(soneInsertNotification);
-               }
-       }
-
-       /**
-        * Notifies the web interface that a {@link Sone} was inserted.
-        *
-        * @param soneInsertedEvent
-        *            The event
-        */
-       @Subscribe
-       public void soneInserted(SoneInsertedEvent soneInsertedEvent) {
-               TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertedEvent.getSone());
-               soneInsertNotification.set("soneStatus", "inserted");
-               soneInsertNotification.set("insertDuration", soneInsertedEvent.getInsertDuration() / 1000);
-               if (soneInsertedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
-                       notificationManager.addNotification(soneInsertNotification);
-               }
-       }
-
-       /**
-        * Notifies the web interface that a {@link Sone} insert was aborted.
-        *
-        * @param soneInsertAbortedEvent
-        *            The event
-        */
-       @Subscribe
-       public void soneInsertAborted(SoneInsertAbortedEvent soneInsertAbortedEvent) {
-               TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertAbortedEvent.getSone());
-               soneInsertNotification.set("soneStatus", "insert-aborted");
-               soneInsertNotification.set("insert-error", soneInsertAbortedEvent.getCause());
-               if (soneInsertAbortedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
-                       notificationManager.addNotification(soneInsertNotification);
-               }
-       }
-
        @Subscribe
        public void debugActivated(@Nonnull DebugActivatedEvent debugActivatedEvent) {
                pageToadletRegistry.activateDebugMode();
index c55308e..28b82c0 100644 (file)
@@ -44,5 +44,6 @@ class NotificationHandler @Inject constructor(
                webOfTrustPinger: WebOfTrustPinger,
                webOfTrustHandler: WebOfTrustHandler,
                soneMentionDetector: SoneMentionDetector,
-               soneMentionedHandler: SoneMentionedHandler
+               soneMentionedHandler: SoneMentionedHandler,
+               soneInsertHandler: SoneInsertHandler
 )
index bb37e09..bdbbc32 100644 (file)
@@ -56,6 +56,7 @@ class NotificationHandlerModule : AbstractModule() {
                bind<WebOfTrustHandler>().asSingleton()
                bind<SoneMentionDetector>().asSingleton()
                bind<SoneMentionedHandler>().asSingleton()
+               bind<SoneInsertHandler>().asSingleton()
        }
 
        @Provides
@@ -171,6 +172,20 @@ class NotificationHandlerModule : AbstractModule() {
        fun getSoneMentionedNotification(loaders: Loaders) =
                        ListNotification<Post>("mention-notification", "posts", loaders.loadTemplate("/templates/notify/mentionNotification.html"), dismissable = false)
 
+       @Provides
+       @Singleton
+       fun getSoneNotificationSupplier(loaders: Loaders): SoneInsertNotificationSupplier =
+                       mutableMapOf<Sone, TemplateNotification>()
+                                       .let { cache ->
+                                               { sone ->
+                                                       cache.computeIfAbsent(sone) {
+                                                               loaders.loadTemplate("/templates/notify/soneInsertNotification.html")
+                                                                               .let(::TemplateNotification)
+                                                                               .also { it["insertSone"] = sone }
+                                                       }
+                                               }
+                                       }
+
        private inline fun <reified T> bind(): AnnotatedBindingBuilder<T> = bind(T::class.java)
        private fun ScopedBindingBuilder.asSingleton() = `in`(Singleton::class.java)
 
diff --git a/src/main/kotlin/net/pterodactylus/sone/web/notification/SoneInsertHandler.kt b/src/main/kotlin/net/pterodactylus/sone/web/notification/SoneInsertHandler.kt
new file mode 100644 (file)
index 0000000..b12acb4
--- /dev/null
@@ -0,0 +1,58 @@
+/**
+ * Sone - SoneInsertHandler.kt - Copyright © 2020 David ‘Bombe’ Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.web.notification
+
+import com.google.common.eventbus.*
+import net.pterodactylus.sone.core.event.*
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.util.notify.*
+import javax.inject.*
+
+/**
+ * Handler for all notifications concerning Sone-insert events.
+ */
+class SoneInsertHandler @Inject constructor(private val notificationManager: NotificationManager, private val soneNotifications: SoneInsertNotificationSupplier) {
+
+       @Subscribe
+       fun soneInserting(event: SoneInsertingEvent) {
+               showNotification(event.sone, "inserting")
+       }
+
+       @Subscribe
+       fun soneInserted(event: SoneInsertedEvent) {
+               showNotification(event.sone, "inserted", "insertDuration" to event.insertDuration / 1000)
+       }
+
+       @Subscribe
+       fun soneInsertAborted(event: SoneInsertAbortedEvent) {
+               showNotification(event.sone, "insert-aborted")
+       }
+
+       private fun showNotification(sone: Sone, status: String, vararg templateVariables: Pair<String, Any>) {
+               if (sone.options.isSoneInsertNotificationEnabled) {
+                       soneNotifications(sone).let { notification ->
+                               notification["soneStatus"] = status
+                               templateVariables.forEach { notification[it.first] = it.second }
+                               notificationManager.addNotification(notification)
+                       }
+               }
+       }
+
+}
+
+typealias SoneInsertNotificationSupplier = (@JvmSuppressWildcards Sone) -> @JvmSuppressWildcards TemplateNotification
index 39756f6..3b2c716 100644 (file)
@@ -21,6 +21,7 @@ import com.google.common.base.*
 import freenet.crypt.*
 import freenet.keys.*
 import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.data.SoneOptions.*
 import net.pterodactylus.sone.data.impl.*
 import net.pterodactylus.sone.text.*
 import net.pterodactylus.sone.utils.*
@@ -34,6 +35,8 @@ val localSone2 = createLocalSone()
 fun createId() = InsertableClientSSK.createRandom(DummyRandomSource(), "").uri.routingKey.asFreenetBase64
 
 fun createLocalSone(id: String? = createId()) = object : IdOnlySone(id) {
+       private val options = DefaultSoneOptions()
+       override fun getOptions() = options
        override fun isLocal() = true
 }
 fun createRemoteSone(id: String? = createId()) = IdOnlySone(id)
index 27f396f..40e8f4d 100644 (file)
@@ -562,4 +562,50 @@ class NotificationHandlerModuleTest {
                injector.verifySingletonInstance<SoneMentionedHandler>()
        }
 
+       @Test
+       fun `sone insert notification supplier is created as singleton`() {
+               injector.verifySingletonInstance<SoneInsertNotificationSupplier>()
+       }
+
+       @Test
+       fun `sone insert notification template is loaded correctly`() {
+               loaders.templates += "/templates/notify/soneInsertNotification.html" to "foo".asTemplate()
+               injector.getInstance<SoneInsertNotificationSupplier>()
+                               .invoke(createRemoteSone())
+                               .render()
+                               .let { assertThat(it, equalTo("foo")) }
+       }
+
+       @Test
+       fun `sone notification supplier returns different notifications for different sones`() {
+               val supplier = injector.getInstance<SoneInsertNotificationSupplier>()
+               listOf(createRemoteSone(), createRemoteSone(), createRemoteSone())
+                               .map(supplier)
+                               .distinct()
+                               .let { assertThat(it, hasSize(3)) }
+       }
+
+       @Test
+       fun `sone notification supplier caches notifications for a sone`() {
+               val supplier = injector.getInstance<SoneInsertNotificationSupplier>()
+               val sone = createRemoteSone()
+               listOf(sone, sone, sone)
+                               .map(supplier)
+                               .distinct()
+                               .let { assertThat(it, hasSize(1)) }
+       }
+
+       @Test
+       fun `sone notification supplier sets sone in notification template`() {
+               val supplier = injector.getInstance<SoneInsertNotificationSupplier>()
+               val sone = createRemoteSone()
+               val templateNotification = supplier(sone)
+               assertThat(templateNotification["insertSone"], sameInstance<Any>(sone))
+       }
+
+       @Test
+       fun `sone insert handler is created as singleton`() {
+               injector.verifySingletonInstance<SoneInsertHandler>()
+       }
+
 }
diff --git a/src/test/kotlin/net/pterodactylus/sone/web/notification/SoneInsertHandlerTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/notification/SoneInsertHandlerTest.kt
new file mode 100644 (file)
index 0000000..6a11a90
--- /dev/null
@@ -0,0 +1,112 @@
+/**
+ * Sone - SoneInsertHandlerTest.kt - Copyright © 2020 David ‘Bombe’ Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.web.notification
+
+import net.pterodactylus.sone.core.event.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.util.notify.*
+import net.pterodactylus.util.template.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import kotlin.test.*
+
+/**
+ * Unit test for [SoneInsertHandler].
+ */
+class SoneInsertHandlerTest {
+
+       private val localSone = createLocalSone()
+       private val notification1 = TemplateNotification(Template())
+       private val notification2 = TemplateNotification(Template())
+       private val soneInsertHandlerTester = NotificationHandlerTester {
+               SoneInsertHandler(it) { sone ->
+                       if (sone == localSone) notification1 else notification2
+               }
+       }
+
+       @Test
+       fun `handler adds notification to manager when sone insert starts`() {
+               localSone.options.isSoneInsertNotificationEnabled = true
+               soneInsertHandlerTester.sendEvent(SoneInsertingEvent(localSone))
+               assertThat(soneInsertHandlerTester.notifications, hasItem(notification1))
+       }
+
+       @Test
+       fun `handler sets sone status in notification when sone insert starts`() {
+               localSone.options.isSoneInsertNotificationEnabled = true
+               soneInsertHandlerTester.sendEvent(SoneInsertingEvent(localSone))
+               assertThat(notification1.get("soneStatus"), equalTo<Any>("inserting"))
+       }
+
+       @Test
+       fun `handler does not add notification to manager if option is disabled`() {
+               localSone.options.isSoneInsertNotificationEnabled = false
+               soneInsertHandlerTester.sendEvent(SoneInsertingEvent(localSone))
+               assertThat(soneInsertHandlerTester.notifications, not(hasItem(notification1)))
+       }
+
+       @Test
+       fun `handler adds notification to manager when sone insert finishes`() {
+               localSone.options.isSoneInsertNotificationEnabled = true
+               soneInsertHandlerTester.sendEvent(SoneInsertedEvent(localSone, 123456, ""))
+               assertThat(soneInsertHandlerTester.notifications, hasItem(notification1))
+       }
+
+       @Test
+       fun `handler sets sone status in notification when sone insert finishes`() {
+               localSone.options.isSoneInsertNotificationEnabled = true
+               soneInsertHandlerTester.sendEvent(SoneInsertedEvent(localSone, 123456, ""))
+               assertThat(notification1.get("soneStatus"), equalTo<Any>("inserted"))
+       }
+
+       @Test
+       fun `handler sets insert duration in notification when sone insert finishes`() {
+               localSone.options.isSoneInsertNotificationEnabled = true
+               soneInsertHandlerTester.sendEvent(SoneInsertedEvent(localSone, 123456, ""))
+               assertThat(notification1.get("insertDuration"), equalTo<Any>(123L))
+       }
+
+       @Test
+       fun `handler does not add notification for finished insert to manager if option is disabled`() {
+               localSone.options.isSoneInsertNotificationEnabled = false
+               soneInsertHandlerTester.sendEvent(SoneInsertedEvent(localSone, 123456, ""))
+               assertThat(soneInsertHandlerTester.notifications, not(hasItem(notification1)))
+       }
+
+       @Test
+       fun `handler adds notification to manager when sone insert aborts`() {
+               localSone.options.isSoneInsertNotificationEnabled = true
+               soneInsertHandlerTester.sendEvent(SoneInsertAbortedEvent(localSone, Exception()))
+               assertThat(soneInsertHandlerTester.notifications, hasItem(notification1))
+       }
+
+       @Test
+       fun `handler sets sone status in notification when sone insert aborts`() {
+               localSone.options.isSoneInsertNotificationEnabled = true
+               soneInsertHandlerTester.sendEvent(SoneInsertAbortedEvent(localSone, Exception()))
+               assertThat(notification1.get("soneStatus"), equalTo<Any>("insert-aborted"))
+       }
+
+       @Test
+       fun `handler does not add notification for aborted insert to manager if option is disabled`() {
+               localSone.options.isSoneInsertNotificationEnabled = false
+               soneInsertHandlerTester.sendEvent(SoneInsertAbortedEvent(localSone, Exception()))
+               assertThat(soneInsertHandlerTester.notifications, not(hasItem(notification1)))
+       }
+
+}