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;
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;
/** 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,
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();
webOfTrustPinger: WebOfTrustPinger,
webOfTrustHandler: WebOfTrustHandler,
soneMentionDetector: SoneMentionDetector,
- soneMentionedHandler: SoneMentionedHandler
+ soneMentionedHandler: SoneMentionedHandler,
+ soneInsertHandler: SoneInsertHandler
)
bind<WebOfTrustHandler>().asSingleton()
bind<SoneMentionDetector>().asSingleton()
bind<SoneMentionedHandler>().asSingleton()
+ bind<SoneInsertHandler>().asSingleton()
}
@Provides
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)
--- /dev/null
+/**
+ * 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
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.*
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)
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>()
+ }
+
}
--- /dev/null
+/**
+ * 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)))
+ }
+
+}