♻️ Use handler for sone-mentioned notifications
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Sun, 5 Jan 2020 21:31:54 +0000 (22:31 +0100)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Sun, 5 Jan 2020 21:31:54 +0000 (22:31 +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/SoneMentionedHandler.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/notification/NotificationHandlerModuleTest.kt
src/test/kotlin/net/pterodactylus/sone/web/notification/SoneMentionedHandlerTest.kt [new file with mode: 0644]

index 50c63ee..5bb4e3a 100644 (file)
@@ -22,7 +22,6 @@ import static java.util.logging.Logger.getLogger;
 
 import java.util.Collection;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 import java.util.TimeZone;
@@ -53,9 +52,6 @@ import net.pterodactylus.sone.template.LinkedElementRenderFilter;
 import net.pterodactylus.sone.template.ParserFilter;
 import net.pterodactylus.sone.template.RenderFilter;
 import net.pterodactylus.sone.template.ShortenFilter;
-import net.pterodactylus.sone.text.Part;
-import net.pterodactylus.sone.text.SonePart;
-import net.pterodactylus.sone.text.SoneTextParser;
 import net.pterodactylus.sone.text.TimeTextConverter;
 import net.pterodactylus.sone.web.ajax.BookmarkAjaxPage;
 import net.pterodactylus.sone.web.ajax.CreatePostAjaxPage;
@@ -101,7 +97,6 @@ import freenet.clients.http.ToadletContext;
 
 import com.codahale.metrics.*;
 import com.google.common.base.Optional;
-import com.google.common.collect.Collections2;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.eventbus.Subscribe;
 import com.google.inject.Inject;
@@ -131,9 +126,6 @@ public class WebInterface implements SessionProvider {
        private final TemplateContextFactory templateContextFactory;
        private final TemplateRenderer templateRenderer;
 
-       /** The Sone text parser. */
-       private final SoneTextParser soneTextParser;
-
        /** The parser filter. */
        private final ParserFilter parserFilter;
        private final ShortenFilter shortenFilter;
@@ -164,9 +156,6 @@ public class WebInterface implements SessionProvider {
        /** The invisible “local reply” notification. */
        private final ListNotification<PostReply> localReplyNotification;
 
-       /** The “you have been mentioned” notification. */
-       private final ListNotification<Post> mentionNotification;
-
        /** Notifications for sone inserts. */
        private final Map<Sone, TemplateNotification> soneInsertNotifications = new HashMap<>();
 
@@ -200,7 +189,6 @@ public class WebInterface implements SessionProvider {
                this.newPostNotification = newPostNotification;
                this.localPostNotification = localPostNotification;
                formPassword = sonePlugin.pluginRespirator().getToadletContainer().getFormPassword();
-               soneTextParser = new SoneTextParser(getCore(), getCore());
 
                this.templateContextFactory = templateContextFactory;
                templateContextFactory.addTemplateObject("webInterface", this);
@@ -212,9 +200,6 @@ public class WebInterface implements SessionProvider {
 
                Template localReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
                localReplyNotification = new ListNotification<>("local-reply-notification", "replies", localReplyNotificationTemplate, false);
-
-               Template mentionNotificationTemplate = loaders.loadTemplate("/templates/notify/mentionNotification.html");
-               mentionNotification = new ListNotification<>("mention-notification", "posts", mentionNotificationTemplate, false);
        }
 
        //
@@ -506,26 +491,6 @@ public class WebInterface implements SessionProvider {
        }
 
        /**
-        * Returns all {@link Sone#isLocal() local Sone}s that are referenced by
-        * {@link SonePart}s in the given text (after parsing it using
-        * {@link SoneTextParser}).
-        *
-        * @param text
-        *            The text to parse
-        * @return All mentioned local Sones
-        */
-       private Collection<Sone> getMentionedSones(String text) {
-               /* we need no context to find mentioned Sones. */
-               Set<Sone> mentionedSones = new HashSet<>();
-               for (Part part : soneTextParser.parse(text, null)) {
-                       if (part instanceof SonePart) {
-                               mentionedSones.add(((SonePart) part).getSone());
-                       }
-               }
-               return Collections2.filter(mentionedSones, Sone.LOCAL_SONE_FILTER);
-       }
-
-       /**
         * Returns the Sone insert notification for the given Sone. If no
         * notification for the given Sone exists, a new notification is created and
         * cached.
@@ -546,46 +511,11 @@ public class WebInterface implements SessionProvider {
                }
        }
 
-       private boolean localSoneMentionedInNewPostOrReply(Post post) {
-               if (!post.getSone().isLocal()) {
-                       if (!getMentionedSones(post.getText()).isEmpty() && !post.isKnown()) {
-                               return true;
-                       }
-               }
-               for (PostReply postReply : getCore().getReplies(post.getId())) {
-                       if (postReply.getSone().isLocal()) {
-                               continue;
-                       }
-                       if (!getMentionedSones(postReply.getText()).isEmpty() && !postReply.isKnown()) {
-                               return true;
-                       }
-               }
-               return false;
-       }
-
        //
        // EVENT HANDLERS
        //
 
        /**
-        * Notifies the web interface that a new {@link Post} was found.
-        *
-        * @param newPostFoundEvent
-        *            The event
-        */
-       @Subscribe
-       public void newPostFound(NewPostFoundEvent newPostFoundEvent) {
-               Post post = newPostFoundEvent.getPost();
-               boolean isLocal = post.getSone().isLocal();
-               if (!hasFirstStartNotification()) {
-                       if (!getMentionedSones(post.getText()).isEmpty() && !isLocal) {
-                               mentionNotification.add(post);
-                               notificationManager.addNotification(mentionNotification);
-                       }
-               }
-       }
-
-       /**
         * Notifies the web interface that a new {@link PostReply} was found.
         *
         * @param newPostReplyFoundEvent
@@ -602,10 +532,6 @@ public class WebInterface implements SessionProvider {
                }
                if (!hasFirstStartNotification()) {
                        notificationManager.addNotification(isLocal ? localReplyNotification : newReplyNotification);
-                       if (reply.getPost().isPresent() && localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
-                               mentionNotification.add(reply.getPost().get());
-                               notificationManager.addNotification(mentionNotification);
-                       }
                } else {
                        getCore().markReplyKnown(reply);
                }
@@ -628,9 +554,6 @@ public class WebInterface implements SessionProvider {
 
        private void removePost(Post post) {
                newPostNotification.remove(post);
-               if (!localSoneMentionedInNewPostOrReply(post)) {
-                       mentionNotification.remove(post);
-               }
        }
 
        @Subscribe
@@ -641,9 +564,6 @@ public class WebInterface implements SessionProvider {
        private void removeReply(PostReply reply) {
                newReplyNotification.remove(reply);
                localReplyNotification.remove(reply);
-               if (reply.getPost().isPresent() && !localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
-                       mentionNotification.remove(reply.getPost().get());
-               }
        }
 
        /**
index cc2a2b3..aba74ae 100644 (file)
@@ -39,5 +39,6 @@ class NotificationHandler @Inject constructor(
                startupHandler: StartupHandler,
                webOfTrustPinger: WebOfTrustPinger,
                webOfTrustHandler: WebOfTrustHandler,
-               soneMentionDetector: SoneMentionDetector
+               soneMentionDetector: SoneMentionDetector,
+               soneMentionedHandler: SoneMentionedHandler
 )
index 96a7cdf..cfe5db3 100644 (file)
@@ -52,6 +52,7 @@ class NotificationHandlerModule : AbstractModule() {
                bind<StartupHandler>().asSingleton()
                bind<WebOfTrustHandler>().asSingleton()
                bind<SoneMentionDetector>().asSingleton()
+               bind<SoneMentionedHandler>().asSingleton()
        }
 
        @Provides
@@ -146,6 +147,12 @@ class NotificationHandlerModule : AbstractModule() {
        fun getWebOfTrustReschedule(@Named("notification") ticker: ScheduledExecutorService) =
                        Consumer<Runnable> { ticker.schedule(it, 15, SECONDS) }
 
+       @Provides
+       @Singleton
+       @Named("soneMentioned")
+       fun getSoneMentionedNotification(loaders: Loaders) =
+                       ListNotification<Post>("mention-notification", "posts", loaders.loadTemplate("/templates/notify/mentionNotification.html"), dismissable = false)
+
        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/SoneMentionedHandler.kt b/src/main/kotlin/net/pterodactylus/sone/web/notification/SoneMentionedHandler.kt
new file mode 100644 (file)
index 0000000..a03e490
--- /dev/null
@@ -0,0 +1,47 @@
+/**
+ * Sone - SoneMentionedHandler.kt - Copyright © 2020 David ‘Bombe’ Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <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.sone.notify.*
+import net.pterodactylus.util.notify.*
+import javax.inject.*
+
+/**
+ * Handler for the [MentionOfLocalSoneFoundEvent] and
+ * [MentionOfLocalSoneRemovedEvent] events that add the corresponding
+ * notification to the notification manager.
+ */
+class SoneMentionedHandler @Inject constructor(private val notificationManager: NotificationManager, @Named("soneMentioned") private val notification: ListNotification<Post>) {
+
+       @Subscribe
+       fun mentionOfLocalSoneFound(event: MentionOfLocalSoneFoundEvent) {
+               if (!notificationManager.hasFirstStartNotification()) {
+                       notification.add(event.post)
+                       notificationManager.addNotification(notification)
+               }
+       }
+
+       @Subscribe
+       fun mentionOfLocalSoneRemoved(event: MentionOfLocalSoneRemovedEvent) {
+               notification.remove(event.post)
+       }
+
+}
index 2606040..497ae1d 100644 (file)
@@ -463,4 +463,32 @@ class NotificationHandlerModuleTest {
                assertThat(injector.getInstance<SoneMentionDetector>(), notNullValue())
        }
 
+       @Test
+       fun `sone-mentioned notification is created as singleton`() {
+               injector.verifySingletonInstance<ListNotification<Post>>(named("soneMentioned"))
+       }
+
+       @Test
+       fun `sone-mentioned notification has correct ID`() {
+               assertThat(injector.getInstance<ListNotification<Post>>(named("soneMentioned")).id, equalTo("mention-notification"))
+       }
+
+       @Test
+       fun `sone-mentioned notification is not dismissable`() {
+               assertThat(injector.getInstance<ListNotification<Post>>(named("soneMentioned")).isDismissable, equalTo(false))
+       }
+
+       @Test
+       fun `sone-mentioned notification loads correct template`() {
+               loaders.templates += "/templates/notify/mentionNotification.html" to "<% posts>".asTemplate()
+               val notification = injector.getInstance<ListNotification<Post>>(named("soneMentioned"))
+               val posts = listOf(EmptyPost("1"), EmptyPost("2")).onEach(notification::add)
+               assertThat(notification.render(), equalTo(posts.toString()))
+       }
+
+       @Test
+       fun `sone-mentioned handler is created as singleton`() {
+               injector.verifySingletonInstance<SoneMentionedHandler>()
+       }
+
 }
diff --git a/src/test/kotlin/net/pterodactylus/sone/web/notification/SoneMentionedHandlerTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/notification/SoneMentionedHandlerTest.kt
new file mode 100644 (file)
index 0000000..4e91aa5
--- /dev/null
@@ -0,0 +1,87 @@
+/**
+ * Sone - SoneMentionedHandlerTest.kt - Copyright © 2020 David ‘Bombe’ Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <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.sone.data.Post.*
+import net.pterodactylus.sone.notify.*
+import net.pterodactylus.util.notify.*
+import net.pterodactylus.util.template.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import kotlin.test.*
+
+/**
+ * Unit test for [SoneMentionedHandler].
+ */
+@Suppress("UnstableApiUsage")
+class SoneMentionedHandlerTest {
+
+       private val notificationManager = NotificationManager()
+       private val notification = ListNotification<Post>("", "", Template())
+       private val eventBus = EventBus()
+
+       init {
+               eventBus.register(SoneMentionedHandler(notificationManager, notification))
+       }
+
+       @Test
+       fun `handler adds notification to manager on event`() {
+               eventBus.post(MentionOfLocalSoneFoundEvent(post))
+               assertThat(notificationManager.notifications, contains<Notification>(notification))
+       }
+
+       @Test
+       fun `handler adds post to notification on event`() {
+               eventBus.post(MentionOfLocalSoneFoundEvent(post))
+               assertThat(notification.elements, contains<Post>(post))
+       }
+
+       @Test
+       fun `handler does not add notification during first start`() {
+               notificationManager.firstStart()
+               eventBus.post(MentionOfLocalSoneFoundEvent(post))
+               assertThat(notificationManager.notifications, not(hasItem<Notification>(notification)))
+       }
+
+       @Test
+       fun `handler does not add post to notification during first start`() {
+               notificationManager.firstStart()
+               eventBus.post(MentionOfLocalSoneFoundEvent(post))
+               assertThat(notification.elements, not(hasItem<Post>(post)))
+       }
+
+       @Test
+       fun `handler removes post from notification`() {
+               notification.add(post)
+               eventBus.post(MentionOfLocalSoneRemovedEvent(post))
+               assertThat(notification.elements, not(hasItem(post)))
+       }
+
+       @Test
+       fun `handler removes notification from manager`() {
+               notificationManager.addNotification(notification)
+               eventBus.post(MentionOfLocalSoneRemovedEvent(post))
+               assertThat(notificationManager.notifications, not(hasItem<Notification>(notification)))
+       }
+
+}
+
+private val post = EmptyPost("")