From: David ‘Bombe’ Roden Date: Sat, 8 Feb 2020 23:55:09 +0000 (+0100) Subject: ♻️ Use handler for Sone insertion notifications X-Git-Tag: v81^2~5^2~2 X-Git-Url: https://git.pterodactylus.net/?p=Sone.git;a=commitdiff_plain;h=a1fa131a44204b2b42de3b5d467373cd113dab28 ♻️ Use handler for Sone insertion notifications --- diff --git a/src/main/java/net/pterodactylus/sone/web/WebInterface.java b/src/main/java/net/pterodactylus/sone/web/WebInterface.java index 1e6e6fb..2a32241 100644 --- a/src/main/java/net/pterodactylus/sone/web/WebInterface.java +++ b/src/main/java/net/pterodactylus/sone/web/WebInterface.java @@ -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 localReplyNotification; - /** Notifications for sone inserts. */ - private final Map 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(); 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 c55308e..28b82c0 100644 --- a/src/main/kotlin/net/pterodactylus/sone/web/notification/NotificationHandler.kt +++ b/src/main/kotlin/net/pterodactylus/sone/web/notification/NotificationHandler.kt @@ -44,5 +44,6 @@ class NotificationHandler @Inject constructor( webOfTrustPinger: WebOfTrustPinger, webOfTrustHandler: WebOfTrustHandler, soneMentionDetector: SoneMentionDetector, - soneMentionedHandler: SoneMentionedHandler + soneMentionedHandler: SoneMentionedHandler, + soneInsertHandler: SoneInsertHandler ) diff --git a/src/main/kotlin/net/pterodactylus/sone/web/notification/NotificationHandlerModule.kt b/src/main/kotlin/net/pterodactylus/sone/web/notification/NotificationHandlerModule.kt index bb37e09..bdbbc32 100644 --- a/src/main/kotlin/net/pterodactylus/sone/web/notification/NotificationHandlerModule.kt +++ b/src/main/kotlin/net/pterodactylus/sone/web/notification/NotificationHandlerModule.kt @@ -56,6 +56,7 @@ class NotificationHandlerModule : AbstractModule() { bind().asSingleton() bind().asSingleton() bind().asSingleton() + bind().asSingleton() } @Provides @@ -171,6 +172,20 @@ class NotificationHandlerModule : AbstractModule() { fun getSoneMentionedNotification(loaders: Loaders) = ListNotification("mention-notification", "posts", loaders.loadTemplate("/templates/notify/mentionNotification.html"), dismissable = false) + @Provides + @Singleton + fun getSoneNotificationSupplier(loaders: Loaders): SoneInsertNotificationSupplier = + mutableMapOf() + .let { cache -> + { sone -> + cache.computeIfAbsent(sone) { + loaders.loadTemplate("/templates/notify/soneInsertNotification.html") + .let(::TemplateNotification) + .also { it["insertSone"] = sone } + } + } + } + private inline fun bind(): AnnotatedBindingBuilder = bind(T::class.java) private fun ScopedBindingBuilder.asSingleton() = `in`(Singleton::class.java) diff --git a/src/main/kotlin/net/pterodactylus/sone/web/notification/SoneInsertHandler.kt b/src/main/kotlin/net/pterodactylus/sone/web/notification/SoneInsertHandler.kt new file mode 100644 index 0000000..b12acb4 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/notification/SoneInsertHandler.kt @@ -0,0 +1,58 @@ +/** + * Sone - SoneInsertHandler.kt - Copyright © 2020 David ‘Bombe’ Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.notification + +import com.google.common.eventbus.* +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.sone.data.* +import net.pterodactylus.util.notify.* +import javax.inject.* + +/** + * Handler for all notifications concerning Sone-insert events. + */ +class SoneInsertHandler @Inject constructor(private val notificationManager: NotificationManager, private val soneNotifications: SoneInsertNotificationSupplier) { + + @Subscribe + fun soneInserting(event: SoneInsertingEvent) { + showNotification(event.sone, "inserting") + } + + @Subscribe + fun soneInserted(event: SoneInsertedEvent) { + showNotification(event.sone, "inserted", "insertDuration" to event.insertDuration / 1000) + } + + @Subscribe + fun soneInsertAborted(event: SoneInsertAbortedEvent) { + showNotification(event.sone, "insert-aborted") + } + + private fun showNotification(sone: Sone, status: String, vararg templateVariables: Pair) { + if (sone.options.isSoneInsertNotificationEnabled) { + soneNotifications(sone).let { notification -> + notification["soneStatus"] = status + templateVariables.forEach { notification[it.first] = it.second } + notificationManager.addNotification(notification) + } + } + } + +} + +typealias SoneInsertNotificationSupplier = (@JvmSuppressWildcards Sone) -> @JvmSuppressWildcards TemplateNotification diff --git a/src/test/kotlin/net/pterodactylus/sone/test/Mocks.kt b/src/test/kotlin/net/pterodactylus/sone/test/Mocks.kt index 39756f6..3b2c716 100644 --- a/src/test/kotlin/net/pterodactylus/sone/test/Mocks.kt +++ b/src/test/kotlin/net/pterodactylus/sone/test/Mocks.kt @@ -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) diff --git a/src/test/kotlin/net/pterodactylus/sone/web/notification/NotificationHandlerModuleTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/notification/NotificationHandlerModuleTest.kt index 27f396f..40e8f4d 100644 --- a/src/test/kotlin/net/pterodactylus/sone/web/notification/NotificationHandlerModuleTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/web/notification/NotificationHandlerModuleTest.kt @@ -562,4 +562,50 @@ class NotificationHandlerModuleTest { injector.verifySingletonInstance() } + @Test + fun `sone insert notification supplier is created as singleton`() { + injector.verifySingletonInstance() + } + + @Test + fun `sone insert notification template is loaded correctly`() { + loaders.templates += "/templates/notify/soneInsertNotification.html" to "foo".asTemplate() + injector.getInstance() + .invoke(createRemoteSone()) + .render() + .let { assertThat(it, equalTo("foo")) } + } + + @Test + fun `sone notification supplier returns different notifications for different sones`() { + val supplier = injector.getInstance() + listOf(createRemoteSone(), createRemoteSone(), createRemoteSone()) + .map(supplier) + .distinct() + .let { assertThat(it, hasSize(3)) } + } + + @Test + fun `sone notification supplier caches notifications for a sone`() { + val supplier = injector.getInstance() + val sone = createRemoteSone() + listOf(sone, sone, sone) + .map(supplier) + .distinct() + .let { assertThat(it, hasSize(1)) } + } + + @Test + fun `sone notification supplier sets sone in notification template`() { + val supplier = injector.getInstance() + val sone = createRemoteSone() + val templateNotification = supplier(sone) + assertThat(templateNotification["insertSone"], sameInstance(sone)) + } + + @Test + fun `sone insert handler is created as singleton`() { + injector.verifySingletonInstance() + } + } diff --git a/src/test/kotlin/net/pterodactylus/sone/web/notification/SoneInsertHandlerTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/notification/SoneInsertHandlerTest.kt new file mode 100644 index 0000000..6a11a90 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/notification/SoneInsertHandlerTest.kt @@ -0,0 +1,112 @@ +/** + * Sone - SoneInsertHandlerTest.kt - Copyright © 2020 David ‘Bombe’ Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.notification + +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.sone.test.* +import net.pterodactylus.util.notify.* +import net.pterodactylus.util.template.* +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.* +import kotlin.test.* + +/** + * Unit test for [SoneInsertHandler]. + */ +class SoneInsertHandlerTest { + + private val localSone = createLocalSone() + private val notification1 = TemplateNotification(Template()) + private val notification2 = TemplateNotification(Template()) + private val soneInsertHandlerTester = NotificationHandlerTester { + SoneInsertHandler(it) { sone -> + if (sone == localSone) notification1 else notification2 + } + } + + @Test + fun `handler adds notification to manager when sone insert starts`() { + localSone.options.isSoneInsertNotificationEnabled = true + soneInsertHandlerTester.sendEvent(SoneInsertingEvent(localSone)) + assertThat(soneInsertHandlerTester.notifications, hasItem(notification1)) + } + + @Test + fun `handler sets sone status in notification when sone insert starts`() { + localSone.options.isSoneInsertNotificationEnabled = true + soneInsertHandlerTester.sendEvent(SoneInsertingEvent(localSone)) + assertThat(notification1.get("soneStatus"), equalTo("inserting")) + } + + @Test + fun `handler does not add notification to manager if option is disabled`() { + localSone.options.isSoneInsertNotificationEnabled = false + soneInsertHandlerTester.sendEvent(SoneInsertingEvent(localSone)) + assertThat(soneInsertHandlerTester.notifications, not(hasItem(notification1))) + } + + @Test + fun `handler adds notification to manager when sone insert finishes`() { + localSone.options.isSoneInsertNotificationEnabled = true + soneInsertHandlerTester.sendEvent(SoneInsertedEvent(localSone, 123456, "")) + assertThat(soneInsertHandlerTester.notifications, hasItem(notification1)) + } + + @Test + fun `handler sets sone status in notification when sone insert finishes`() { + localSone.options.isSoneInsertNotificationEnabled = true + soneInsertHandlerTester.sendEvent(SoneInsertedEvent(localSone, 123456, "")) + assertThat(notification1.get("soneStatus"), equalTo("inserted")) + } + + @Test + fun `handler sets insert duration in notification when sone insert finishes`() { + localSone.options.isSoneInsertNotificationEnabled = true + soneInsertHandlerTester.sendEvent(SoneInsertedEvent(localSone, 123456, "")) + assertThat(notification1.get("insertDuration"), equalTo(123L)) + } + + @Test + fun `handler does not add notification for finished insert to manager if option is disabled`() { + localSone.options.isSoneInsertNotificationEnabled = false + soneInsertHandlerTester.sendEvent(SoneInsertedEvent(localSone, 123456, "")) + assertThat(soneInsertHandlerTester.notifications, not(hasItem(notification1))) + } + + @Test + fun `handler adds notification to manager when sone insert aborts`() { + localSone.options.isSoneInsertNotificationEnabled = true + soneInsertHandlerTester.sendEvent(SoneInsertAbortedEvent(localSone, Exception())) + assertThat(soneInsertHandlerTester.notifications, hasItem(notification1)) + } + + @Test + fun `handler sets sone status in notification when sone insert aborts`() { + localSone.options.isSoneInsertNotificationEnabled = true + soneInsertHandlerTester.sendEvent(SoneInsertAbortedEvent(localSone, Exception())) + assertThat(notification1.get("soneStatus"), equalTo("insert-aborted")) + } + + @Test + fun `handler does not add notification for aborted insert to manager if option is disabled`() { + localSone.options.isSoneInsertNotificationEnabled = false + soneInsertHandlerTester.sendEvent(SoneInsertAbortedEvent(localSone, Exception())) + assertThat(soneInsertHandlerTester.notifications, not(hasItem(notification1))) + } + +}