import java.util.Collection;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
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;
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;
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;
/** 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<>();
this.newPostNotification = newPostNotification;
this.localPostNotification = localPostNotification;
formPassword = sonePlugin.pluginRespirator().getToadletContainer().getFormPassword();
- soneTextParser = new SoneTextParser(getCore(), getCore());
this.templateContextFactory = templateContextFactory;
templateContextFactory.addTemplateObject("webInterface", this);
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);
}
//
}
/**
- * 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.
}
}
- 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
}
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);
}
private void removePost(Post post) {
newPostNotification.remove(post);
- if (!localSoneMentionedInNewPostOrReply(post)) {
- mentionNotification.remove(post);
- }
}
@Subscribe
private void removeReply(PostReply reply) {
newReplyNotification.remove(reply);
localReplyNotification.remove(reply);
- if (reply.getPost().isPresent() && !localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
- mentionNotification.remove(reply.getPost().get());
- }
}
/**
startupHandler: StartupHandler,
webOfTrustPinger: WebOfTrustPinger,
webOfTrustHandler: WebOfTrustHandler,
- soneMentionDetector: SoneMentionDetector
+ soneMentionDetector: SoneMentionDetector,
+ soneMentionedHandler: SoneMentionedHandler
)
bind<StartupHandler>().asSingleton()
bind<WebOfTrustHandler>().asSingleton()
bind<SoneMentionDetector>().asSingleton()
+ bind<SoneMentionedHandler>().asSingleton()
}
@Provides
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)
--- /dev/null
+/**
+ * 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)
+ }
+
+}
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>()
+ }
+
}
--- /dev/null
+/**
+ * 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("")