apply from: 'version.gradle'
-test {
+task parallelTest(type: Test) {
maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1
+ useJUnit {
+ excludeCategories 'net.pterodactylus.sone.test.NotParallel'
+ }
+}
+
+task notParallelTest(type: Test) {
+ maxParallelForks = 1
+ useJUnit {
+ includeCategories 'net.pterodactylus.sone.test.NotParallel'
+ }
+}
+
+test {
+ exclude '**'
+ dependsOn parallelTest, notParallelTest
}
task fatJar(type: Jar) {
import java.util.logging.Logger;
import java.util.logging.*;
+import javax.annotation.Nonnull;
+
import net.pterodactylus.sone.core.*;
+import net.pterodactylus.sone.core.event.*;
import net.pterodactylus.sone.fcp.*;
import net.pterodactylus.sone.freenet.wot.*;
import net.pterodactylus.sone.web.*;
import net.pterodactylus.sone.web.notification.NotificationHandler;
+import net.pterodactylus.sone.web.notification.NotificationHandlerModule;
import freenet.l10n.BaseL10n.*;
import freenet.l10n.*;
private final LoadingCache<String, Class<?>> classCache = CacheBuilder.newBuilder()
.build(new CacheLoader<String, Class<?>>() {
@Override
- public Class<?> load(String key) throws Exception {
+ public Class<?> load(@Nonnull String key) throws Exception {
return SonePlugin.class.getClassLoader().loadClass(key);
}
});
/** The core. */
private Core core;
+ /** The event bus. */
+ private EventBus eventBus;
+
/** The web interface. */
private WebInterface webInterface;
/* create the web interface. */
webInterface = injector.getInstance(WebInterface.class);
- NotificationHandler notificationHandler = injector.getInstance(NotificationHandler.class);
+
+ /* we need to request this to install all notification handlers. */
+ injector.getInstance(NotificationHandler.class);
+
+ /* and this is required to shutdown all tickers. */
+ injector.getInstance(TickerShutdown.class);
/* start core! */
core.start();
/* start the web interface! */
webInterface.start();
- webInterface.setFirstStart(injector.getInstance(Key.get(Boolean.class, Names.named("FirstStart"))));
- webInterface.setNewConfig(injector.getInstance(Key.get(Boolean.class, Names.named("NewConfig"))));
- notificationHandler.start();
+
+ /* send some events on startup */
+ eventBus = injector.getInstance(EventBus.class);
+
+ /* first start? */
+ if (injector.getInstance(Key.get(Boolean.class, Names.named("FirstStart")))) {
+ eventBus.post(new FirstStart());
+ } else {
+ /* new config? */
+ if (injector.getInstance(Key.get(Boolean.class, Names.named("NewConfig")))) {
+ eventBus.post(new ConfigNotRead());
+ }
+ }
+
+ eventBus.post(new Startup());
}
@VisibleForTesting
FreenetModule freenetModule = new FreenetModule(pluginRespirator);
AbstractModule soneModule = new SoneModule(this, new EventBus());
Module webInterfaceModule = new WebInterfaceModule();
+ Module notificationHandlerModule = new NotificationHandlerModule();
- return createInjector(freenetModule, soneModule, webInterfaceModule);
+ return createInjector(freenetModule, soneModule, webInterfaceModule, notificationHandlerModule);
}
@VisibleForTesting
*/
@Override
public void terminate() {
+ /* send shutdown event. */
+ eventBus.post(new Shutdown());
+
try {
/* stop the web interface. */
webInterface.stop();
import static java.util.logging.Logger.getLogger;
import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.UUID;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import javax.inject.Named;
import net.pterodactylus.sone.core.Core;
import net.pterodactylus.sone.core.ElementLoader;
import net.pterodactylus.sone.core.event.*;
-import net.pterodactylus.sone.data.Image;
import net.pterodactylus.sone.data.Post;
import net.pterodactylus.sone.data.PostReply;
import net.pterodactylus.sone.data.Sone;
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 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;
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;
private final MetricRegistry metricRegistry;
private final Translation translation;
- /** The “new Sone” notification. */
- private final ListNotification<Sone> newSoneNotification;
-
/** The “new post” notification. */
private final ListNotification<Post> newPostNotification;
/** 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<>();
-
- /** Sone locked notification ticker objects. */
- private final Map<Sone, ScheduledFuture<?>> lockedSonesTickerObjects = Collections.synchronizedMap(new HashMap<Sone, ScheduledFuture<?>>());
-
- /** The “Sone locked” notification. */
- private final ListNotification<Sone> lockedSonesNotification;
-
- /** The “new version” notification. */
- private final TemplateNotification newVersionNotification;
-
- /** The “inserting images” notification. */
- private final ListNotification<Image> insertingImagesNotification;
-
- /** The “inserted images” notification. */
- private final ListNotification<Image> insertedImagesNotification;
-
- /** The “image insert failed” notification. */
- private final ListNotification<Image> imageInsertFailedNotification;
-
- /** Scheduled executor for time-based notifications. */
- private final ScheduledExecutorService ticker = Executors.newScheduledThreadPool(1);
-
@Inject
public WebInterface(SonePlugin sonePlugin, Loaders loaders, ListNotificationFilter listNotificationFilter,
PostVisibilityFilter postVisibilityFilter, ReplyVisibilityFilter replyVisibilityFilter,
RenderFilter renderFilter,
LinkedElementRenderFilter linkedElementRenderFilter,
PageToadletRegistry pageToadletRegistry, MetricRegistry metricRegistry, Translation translation, L10nFilter l10nFilter,
- NotificationManager notificationManager) {
+ NotificationManager notificationManager, @Named("newRemotePost") ListNotification<Post> newPostNotification,
+ @Named("newRemotePostReply") ListNotification<PostReply> newReplyNotification,
+ @Named("localPost") ListNotification<Post> localPostNotification,
+ @Named("localReply") ListNotification<PostReply> localReplyNotification) {
this.sonePlugin = sonePlugin;
this.loaders = loaders;
this.listNotificationFilter = listNotificationFilter;
this.l10nFilter = l10nFilter;
this.translation = translation;
this.notificationManager = notificationManager;
+ this.newPostNotification = newPostNotification;
+ this.newReplyNotification = newReplyNotification;
+ this.localPostNotification = localPostNotification;
+ this.localReplyNotification = localReplyNotification;
formPassword = sonePlugin.pluginRespirator().getToadletContainer().getFormPassword();
- soneTextParser = new SoneTextParser(getCore(), getCore());
this.templateContextFactory = templateContextFactory;
templateContextFactory.addTemplateObject("webInterface", this);
templateContextFactory.addTemplateObject("formPassword", formPassword);
-
- /* create notifications. */
- Template newSoneNotificationTemplate = loaders.loadTemplate("/templates/notify/newSoneNotification.html");
- newSoneNotification = new ListNotification<>("new-sone-notification", "sones", newSoneNotificationTemplate, false);
-
- Template newPostNotificationTemplate = loaders.loadTemplate("/templates/notify/newPostNotification.html");
- newPostNotification = new ListNotification<>("new-post-notification", "posts", newPostNotificationTemplate, false);
-
- Template localPostNotificationTemplate = loaders.loadTemplate("/templates/notify/newPostNotification.html");
- localPostNotification = new ListNotification<>("local-post-notification", "posts", localPostNotificationTemplate, false);
-
- Template newReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
- newReplyNotification = new ListNotification<>("new-reply-notification", "replies", newReplyNotificationTemplate, false);
-
- 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);
-
- Template lockedSonesTemplate = loaders.loadTemplate("/templates/notify/lockedSonesNotification.html");
- lockedSonesNotification = new ListNotification<>("sones-locked-notification", "sones", lockedSonesTemplate);
-
- Template newVersionTemplate = loaders.loadTemplate("/templates/notify/newVersionNotification.html");
- newVersionNotification = new TemplateNotification("new-version-notification", newVersionTemplate);
-
- Template insertingImagesTemplate = loaders.loadTemplate("/templates/notify/inserting-images-notification.html");
- insertingImagesNotification = new ListNotification<>("inserting-images-notification", "images", insertingImagesTemplate);
-
- Template insertedImagesTemplate = loaders.loadTemplate("/templates/notify/inserted-images-notification.html");
- insertedImagesNotification = new ListNotification<>("inserted-images-notification", "images", insertedImagesTemplate);
-
- Template imageInsertFailedTemplate = loaders.loadTemplate("/templates/notify/image-insert-failed-notification.html");
- imageInsertFailedNotification = new ListNotification<>("image-insert-failed-notification", "images", imageInsertFailedTemplate);
}
//
return formPassword;
}
- /**
- * Returns the posts that have been announced as new in the
- * {@link #newPostNotification}.
- *
- * @return The new posts
- */
- public Set<Post> getNewPosts() {
- return ImmutableSet.<Post> builder().addAll(newPostNotification.getElements()).addAll(localPostNotification.getElements()).build();
- }
-
@Nonnull
public Collection<Post> getNewPosts(@Nullable Sone currentSone) {
Set<Post> allNewPosts = ImmutableSet.<Post> builder()
return from(allNewPosts).filter(postVisibilityFilter.isVisible(currentSone)).toSet();
}
- /**
- * Returns the replies that have been announced as new in the
- * {@link #newReplyNotification}.
- *
- * @return The new replies
- */
- public Set<PostReply> getNewReplies() {
- return ImmutableSet.<PostReply> builder().addAll(newReplyNotification.getElements()).addAll(localReplyNotification.getElements()).build();
- }
-
@Nonnull
public Collection<PostReply> getNewReplies(@Nullable Sone currentSone) {
Set<PostReply> allNewReplies = ImmutableSet.<PostReply>builder()
return from(allNewReplies).filter(replyVisibilityFilter.isVisible(currentSone)).toSet();
}
- /**
- * Sets whether the current start of the plugin is the first start. It is
- * considered a first start if the configuration file does not exist.
- *
- * @param firstStart
- * {@code true} if no configuration file existed when Sone was
- * loaded, {@code false} otherwise
- */
- public void setFirstStart(boolean firstStart) {
- if (firstStart) {
- Template firstStartNotificationTemplate = loaders.loadTemplate("/templates/notify/firstStartNotification.html");
- Notification firstStartNotification = new TemplateNotification("first-start-notification", firstStartNotificationTemplate);
- notificationManager.addNotification(firstStartNotification);
- }
- }
-
- /**
- * Sets whether Sone was started with a fresh configuration file.
- *
- * @param newConfig
- * {@code true} if Sone was started with a fresh configuration,
- * {@code false} if the existing configuration could be read
- */
- public void setNewConfig(boolean newConfig) {
- if (newConfig && !hasFirstStartNotification()) {
- Template configNotReadNotificationTemplate = loaders.loadTemplate("/templates/notify/configNotReadNotification.html");
- Notification configNotReadNotification = new TemplateNotification("config-not-read-notification", configNotReadNotificationTemplate);
- notificationManager.addNotification(configNotReadNotification);
- }
- }
-
- //
- // PRIVATE ACCESSORS
- //
-
- /**
- * Returns whether the first start notification is currently displayed.
- *
- * @return {@code true} if the first-start notification is currently
- * displayed, {@code false} otherwise
- */
- private boolean hasFirstStartNotification() {
- return notificationManager.getNotification("first-start-notification") != null;
- }
-
//
// ACTIONS
//
*/
public void start() {
registerToadlets();
-
- /* notification templates. */
- Template startupNotificationTemplate = loaders.loadTemplate("/templates/notify/startupNotification.html");
-
- final TemplateNotification startupNotification = new TemplateNotification("startup-notification", startupNotificationTemplate);
- notificationManager.addNotification(startupNotification);
-
- ticker.schedule(new Runnable() {
-
- @Override
- public void run() {
- startupNotification.dismiss();
- }
- }, 2, TimeUnit.MINUTES);
-
- Template wotMissingNotificationTemplate = loaders.loadTemplate("/templates/notify/wotMissingNotification.html");
- final TemplateNotification wotMissingNotification = new TemplateNotification("wot-missing-notification", wotMissingNotificationTemplate);
- ticker.scheduleAtFixedRate(new Runnable() {
-
- @Override
- @SuppressWarnings("synthetic-access")
- public void run() {
- if (getCore().getIdentityManager().isConnected()) {
- wotMissingNotification.dismiss();
- } else {
- notificationManager.addNotification(wotMissingNotification);
- }
- }
-
- }, 15, 15, TimeUnit.SECONDS);
}
/**
*/
public void stop() {
pageToadletRegistry.unregisterToadlets();
- ticker.shutdownNow();
}
//
pageToadletRegistry.registerToadlets();
}
- /**
- * 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.
- *
- * @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;
- }
- }
-
- 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 Sone} was found.
- *
- * @param newSoneFoundEvent
- * The event
- */
- @Subscribe
- public void newSoneFound(NewSoneFoundEvent newSoneFoundEvent) {
- newSoneNotification.add(newSoneFoundEvent.getSone());
- if (!hasFirstStartNotification()) {
- notificationManager.addNotification(newSoneNotification);
- }
- }
-
- /**
- * 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 (isLocal) {
- localPostNotification.add(post);
- } else {
- newPostNotification.add(post);
- }
- if (!hasFirstStartNotification()) {
- notificationManager.addNotification(isLocal ? localPostNotification : newPostNotification);
- if (!getMentionedSones(post.getText()).isEmpty() && !isLocal) {
- mentionNotification.add(post);
- notificationManager.addNotification(mentionNotification);
- }
- } else {
- getCore().markPostKnown(post);
- }
- }
-
- /**
- * Notifies the web interface that a new {@link PostReply} was found.
- *
- * @param newPostReplyFoundEvent
- * The event
- */
- @Subscribe
- public void newReplyFound(NewPostReplyFoundEvent newPostReplyFoundEvent) {
- PostReply reply = newPostReplyFoundEvent.getPostReply();
- boolean isLocal = reply.getSone().isLocal();
- if (isLocal) {
- localReplyNotification.add(reply);
- } else {
- newReplyNotification.add(reply);
- }
- 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);
- }
- }
-
- /**
- * Notifies the web interface that a {@link Sone} was marked as known.
- *
- * @param markSoneKnownEvent
- * The event
- */
- @Subscribe
- public void markSoneKnown(MarkSoneKnownEvent markSoneKnownEvent) {
- newSoneNotification.remove(markSoneKnownEvent.getSone());
- }
-
- @Subscribe
- public void markPostKnown(MarkPostKnownEvent markPostKnownEvent) {
- removePost(markPostKnownEvent.getPost());
- }
-
- @Subscribe
- public void markReplyKnown(MarkPostReplyKnownEvent markPostReplyKnownEvent) {
- removeReply(markPostReplyKnownEvent.getPostReply());
- }
-
- @Subscribe
- public void soneRemoved(SoneRemovedEvent soneRemovedEvent) {
- newSoneNotification.remove(soneRemovedEvent.getSone());
- }
-
- @Subscribe
- public void postRemoved(PostRemovedEvent postRemovedEvent) {
- removePost(postRemovedEvent.getPost());
- }
-
- private void removePost(Post post) {
- newPostNotification.remove(post);
- localPostNotification.remove(post);
- if (!localSoneMentionedInNewPostOrReply(post)) {
- mentionNotification.remove(post);
- }
- }
-
- @Subscribe
- public void replyRemoved(PostReplyRemovedEvent postReplyRemovedEvent) {
- removeReply(postReplyRemovedEvent.getPostReply());
- }
-
- private void removeReply(PostReply reply) {
- newReplyNotification.remove(reply);
- localReplyNotification.remove(reply);
- if (reply.getPost().isPresent() && !localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
- mentionNotification.remove(reply.getPost().get());
- }
- }
-
- /**
- * Notifies the web interface that a Sone was locked.
- *
- * @param soneLockedEvent
- * The event
- */
- @Subscribe
- public void soneLocked(SoneLockedEvent soneLockedEvent) {
- final Sone sone = soneLockedEvent.getSone();
- ScheduledFuture<?> tickerObject = ticker.schedule(new Runnable() {
-
- @Override
- @SuppressWarnings("synthetic-access")
- public void run() {
- lockedSonesNotification.add(sone);
- notificationManager.addNotification(lockedSonesNotification);
- }
- }, 5, TimeUnit.MINUTES);
- lockedSonesTickerObjects.put(sone, tickerObject);
- }
-
- /**
- * Notifies the web interface that a Sone was unlocked.
- *
- * @param soneUnlockedEvent
- * The event
- */
- @Subscribe
- public void soneUnlocked(SoneUnlockedEvent soneUnlockedEvent) {
- lockedSonesNotification.remove(soneUnlockedEvent.getSone());
- lockedSonesTickerObjects.remove(soneUnlockedEvent.getSone()).cancel(false);
- }
-
- /**
- * 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);
- }
- }
-
- /**
- * Notifies the web interface that a new Sone version was found.
- *
- * @param updateFoundEvent
- * The event
- */
- @Subscribe
- public void updateFound(UpdateFoundEvent updateFoundEvent) {
- newVersionNotification.set("latestVersion", updateFoundEvent.getVersion());
- newVersionNotification.set("latestEdition", updateFoundEvent.getLatestEdition());
- newVersionNotification.set("releaseTime", updateFoundEvent.getReleaseTime());
- newVersionNotification.set("disruptive", updateFoundEvent.isDisruptive());
- notificationManager.addNotification(newVersionNotification);
- }
-
- /**
- * Notifies the web interface that an image insert was started
- *
- * @param imageInsertStartedEvent
- * The event
- */
- @Subscribe
- public void imageInsertStarted(ImageInsertStartedEvent imageInsertStartedEvent) {
- insertingImagesNotification.add(imageInsertStartedEvent.getImage());
- notificationManager.addNotification(insertingImagesNotification);
- }
-
- /**
- * Notifies the web interface that an {@link Image} insert was aborted.
- *
- * @param imageInsertAbortedEvent
- * The event
- */
- @Subscribe
- public void imageInsertAborted(ImageInsertAbortedEvent imageInsertAbortedEvent) {
- insertingImagesNotification.remove(imageInsertAbortedEvent.getImage());
- }
-
- /**
- * Notifies the web interface that an {@link Image} insert is finished.
- *
- * @param imageInsertFinishedEvent
- * The event
- */
- @Subscribe
- public void imageInsertFinished(ImageInsertFinishedEvent imageInsertFinishedEvent) {
- insertingImagesNotification.remove(imageInsertFinishedEvent.getImage());
- insertedImagesNotification.add(imageInsertFinishedEvent.getImage());
- notificationManager.addNotification(insertedImagesNotification);
- }
-
- /**
- * Notifies the web interface that an {@link Image} insert has failed.
- *
- * @param imageInsertFailedEvent
- * The event
- */
- @Subscribe
- public void imageInsertFailed(ImageInsertFailedEvent imageInsertFailedEvent) {
- insertingImagesNotification.remove(imageInsertFailedEvent.getImage());
- imageInsertFailedNotification.add(imageInsertFailedEvent.getImage());
- notificationManager.addNotification(imageInsertFailedNotification);
- }
-
@Subscribe
public void debugActivated(@Nonnull DebugActivatedEvent debugActivatedEvent) {
pageToadletRegistry.activateDebugMode();
--- /dev/null
+/**
+ * Sone - ConfigNotRead.kt - Copyright © 2019 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.core.event
+
+/**
+ * Event that signals that Sone could not read an existing configuration
+ * successfully, and a new configuration was created. This is different from
+ * [FirstStart] in that `FirstStart` signals that there *was* no existing
+ * configuration to be read.
+ */
+class ConfigNotRead
--- /dev/null
+/**
+ * Sone - FirstStart.kt - Copyright © 2019 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.core.event
+
+/**
+ * Event that signals that Sone was started for the first time. This event
+ * will only be triggered once, on startup.
+ */
+class FirstStart
--- /dev/null
+/**
+ * Sone - MentionOfLocalSoneFoundEvent.kt - Copyright © 2019 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.core.event
+
+import net.pterodactylus.sone.data.*
+
+/**
+ * Event that signals that a new post or reply was found that mentioned a local
+ * Sone, which happens if the [SoneTextParser] locates a [SonePart] in a post
+ * or reply.
+ */
+data class MentionOfLocalSoneFoundEvent(val post: Post)
--- /dev/null
+/**
+ * Sone - MentionOfLocalSoneRemovedEvent.kt - Copyright © 2019 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.core.event
+
+import net.pterodactylus.sone.data.*
+
+/**
+ * Event that signals that a post or reply that mentioned a local Sone was
+ * removed so that the given post and its replies do not contain a mention of
+ * a local Sone anymore.
+ */
+data class MentionOfLocalSoneRemovedEvent(val post: Post)
--- /dev/null
+/**
+ * Sone - Shutdown.kt - Copyright © 2019 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.core.event
+
+/**
+ * Event that signals the shutdown of Sone.
+ */
+class Shutdown
--- /dev/null
+/**
+ * Sone - Startup.kt - Copyright © 2019 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.core.event
+
+/**
+ * Event that signals the startup of Sone.
+ */
+class Startup
--- /dev/null
+/**
+ * Sone - WebOfTrustAppeared.kt - Copyright © 2019 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.core.event
+
+/**
+ * Event that signals that the web of trust is reachable.
+ */
+class WebOfTrustAppeared
--- /dev/null
+/**
+ * Sone - WebOfTrustDisappeared.kt - Copyright © 2019 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.core.event
+
+/**
+ * Event that signals that the web of trust is not reachable.
+ */
+class WebOfTrustDisappeared
package net.pterodactylus.sone.database
+import com.google.inject.*
import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.database.memory.*
/**
* Interface for objects that can provide [PostReply]s.
*/
+@ImplementedBy(MemoryDatabase::class)
interface PostReplyProvider {
fun getPostReply(id: String): PostReply?
package net.pterodactylus.sone.freenet
import freenet.keys.*
-import freenet.support.Base64.*
+import net.pterodactylus.sone.utils.*
-val FreenetURI.routingKeyString: String get() = encode(routingKey)
+val FreenetURI.routingKeyString: String get() = routingKey.asFreenetBase64
--- /dev/null
+/**
+ * Sone - WebOfTrustPinger.kt - Copyright © 2019 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.freenet.wot
+
+import com.google.common.eventbus.*
+import net.pterodactylus.sone.core.event.*
+import net.pterodactylus.sone.freenet.plugin.*
+import net.pterodactylus.sone.utils.*
+import java.util.concurrent.atomic.*
+import java.util.function.*
+import javax.inject.*
+
+/**
+ * [Runnable] that is scheduled via an [Executor][java.util.concurrent.Executor],
+ * checks whether the web of trust plugin can be communicated with, sends
+ * events if its status changes and reschedules itself.
+ */
+class WebOfTrustPinger @Inject constructor(
+ private val eventBus: EventBus,
+ @Named("webOfTrustReacher") private val webOfTrustReacher: Runnable,
+ @Named("webOfTrustReschedule") private val reschedule: Consumer<Runnable>) : Runnable {
+
+ private val lastState = AtomicBoolean(false)
+
+ override fun run() {
+ try {
+ webOfTrustReacher()
+ if (!lastState.get()) {
+ eventBus.post(WebOfTrustAppeared())
+ lastState.set(true)
+ }
+ } catch (e: PluginException) {
+ if (lastState.get()) {
+ eventBus.post(WebOfTrustDisappeared())
+ lastState.set(false)
+ }
+ }
+ reschedule(this)
+ }
+
+}
import com.google.inject.matcher.*
import com.google.inject.name.Names.*
import com.google.inject.spi.*
-import freenet.l10n.*
import net.pterodactylus.sone.database.*
import net.pterodactylus.sone.database.memory.*
import net.pterodactylus.sone.freenet.*
import net.pterodactylus.sone.freenet.wot.*
import net.pterodactylus.util.config.*
import net.pterodactylus.util.config.ConfigurationException
+import net.pterodactylus.util.logging.*
import net.pterodactylus.util.version.Version
import java.io.*
+import java.util.concurrent.*
+import java.util.concurrent.Executors.*
+import java.util.logging.*
+import javax.inject.*
+import javax.inject.Singleton
open class SoneModule(private val sonePlugin: SonePlugin, private val eventBus: EventBus) : AbstractModule() {
loaders?.let { bind(Loaders::class.java).toInstance(it) }
bind(MetricRegistry::class.java).`in`(Singleton::class.java)
bind(WebOfTrustConnector::class.java).to(PluginWebOfTrustConnector::class.java).`in`(Singleton::class.java)
+ bind(TickerShutdown::class.java).`in`(Singleton::class.java)
bindListener(Matchers.any(), object : TypeListener {
override fun <I> hear(typeLiteral: TypeLiteral<I>, typeEncounter: TypeEncounter<I>) {
- typeEncounter.register(InjectionListener { injectee -> eventBus.register(injectee) })
+ typeEncounter.register(InjectionListener { injectee ->
+ logger.fine { "Injecting $injectee..." }
+ eventBus.register(injectee)
+ })
}
})
}
+ @Provides
+ @Singleton
+ @Named("notification")
+ fun getNotificationTicker(): ScheduledExecutorService =
+ newSingleThreadScheduledExecutor()
+
+ private val logger: Logger = Logging.getLogger(javaClass)
+
}
private fun String.parseVersion(): Version = Version.parse(this)
--- /dev/null
+/**
+ * Sone - TickerShutdown.kt - Copyright © 2019 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.main
+
+import com.google.common.eventbus.*
+import net.pterodactylus.sone.core.event.*
+import java.util.concurrent.*
+import javax.inject.*
+
+/**
+ * Wrapper around all [tickers][ScheduledExecutorService] used in Sone,
+ * ensuring proper shutdown.
+ */
+class TickerShutdown @Inject constructor(@Named("notification") private val notificationTicker: ScheduledExecutorService) {
+
+ @Subscribe
+ fun shutdown(@Suppress("UNUSED_PARAMETER") shutdown: Shutdown) {
+ notificationTicker.shutdown()
+ }
+
+}
--- /dev/null
+/**
+ * Sone - Notifications.kt - Copyright © 2019 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.notify
+
+import net.pterodactylus.util.notify.*
+
+/**
+ * Returns whether the notification manager contains a notification with the given ID.
+ */
+operator fun NotificationManager.contains(id: String) =
+ getNotification(id) != null
+
+/**
+ * Returns whether the notification manager currently has a “first start” notification.
+ */
+fun NotificationManager.hasFirstStartNotification() =
+ "first-start-notification" in this
--- /dev/null
+/**
+ * Sone - SoneMentionDetector.kt - Copyright © 2019 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.text
+
+import com.google.common.eventbus.*
+import net.pterodactylus.sone.core.event.*
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.database.*
+import net.pterodactylus.sone.utils.*
+import javax.inject.*
+
+/**
+ * Listens to [NewPostFoundEvent]s and [NewPostReplyFoundEvent], parses the
+ * texts and emits a [MentionOfLocalSoneFoundEvent] if a [SoneTextParser]
+ * finds a [SonePart] that points to a local [Sone].
+ */
+class SoneMentionDetector @Inject constructor(private val eventBus: EventBus, private val soneTextParser: SoneTextParser, private val postReplyProvider: PostReplyProvider) {
+
+ @Subscribe
+ fun onNewPost(newPostFoundEvent: NewPostFoundEvent) {
+ newPostFoundEvent.post.let { post ->
+ post.sone.isLocal.onFalse {
+ if (post.text.hasLinksToLocalSones()) {
+ mentionedPosts += post
+ eventBus.post(MentionOfLocalSoneFoundEvent(post))
+ }
+ }
+ }
+ }
+
+ @Subscribe
+ fun onNewPostReply(event: NewPostReplyFoundEvent) {
+ event.postReply.let { postReply ->
+ postReply.sone.isLocal.onFalse {
+ if (postReply.text.hasLinksToLocalSones()) {
+ postReply.post
+ .also { mentionedPosts += it }
+ .let(::MentionOfLocalSoneFoundEvent)
+ ?.also(eventBus::post)
+ }
+ }
+ }
+ }
+
+ @Subscribe
+ fun onPostRemoved(event: PostRemovedEvent) {
+ unmentionPost(event.post)
+ }
+
+ @Subscribe
+ fun onPostMarkedKnown(event: MarkPostKnownEvent) {
+ unmentionPost(event.post)
+ }
+
+ @Subscribe
+ fun onReplyRemoved(event: PostReplyRemovedEvent) {
+ event.postReply.post.let {
+ if ((!it.text.hasLinksToLocalSones() || it.isKnown) && (it.replies.filterNot { it == event.postReply }.none { it.text.hasLinksToLocalSones() && !it.isKnown })) {
+ unmentionPost(it)
+ }
+ }
+ }
+
+ private fun unmentionPost(post: Post) {
+ if (post in mentionedPosts) {
+ eventBus.post(MentionOfLocalSoneRemovedEvent(post))
+ mentionedPosts -= post
+ }
+ }
+
+ private val mentionedPosts = mutableSetOf<Post>()
+
+ private fun String.hasLinksToLocalSones() = soneTextParser.parse(this, null)
+ .filterIsInstance<SonePart>()
+ .any { it.sone.isLocal }
+
+ private val Post.replies get() = postReplyProvider.getReplies(id)
+
+}
import net.pterodactylus.sone.database.*
import net.pterodactylus.sone.text.LinkType.*
import net.pterodactylus.sone.text.LinkType.USK
+import net.pterodactylus.sone.utils.*
import org.bitpedia.util.*
import java.net.*
import javax.inject.*
?.takeIf { (it.size > 1) || ((it.size == 1) && (it.single() != "")) }
?.lastOrNull()
?: uri.docName
- ?: "${uri.keyType}@${uri.routingKey.freenetBase64}"
+ ?: "${uri.keyType}@${uri.routingKey.asFreenetBase64}"
}.let { FreenetLinkPart(linkWithoutBacklink.removeSuffix("/"), it, trusted = context?.routingKey?.contentEquals(FreenetURI(linkWithoutBacklink).routingKey) == true) }
} catch (e: MalformedURLException) {
PlainTextPart(linkWithoutBacklink)
private fun List<Part>.removeEmptyPlainTextParts() = filterNot { it == PlainTextPart("") }
-private val String.decodedId: String get() = Base64.encode(Base32.decode(this))
+private val String.decodedId: String get() = Base32.decode(this).asFreenetBase64
private val String.withoutProtocol get() = substring(indexOf("//") + 2)
private val String.withoutUrlParameters get() = split('?').first()
}
private val String.withoutTrailingSlash get() = if (endsWith("/")) substring(0, length - 1) else this
private val SoneTextParserContext.routingKey: ByteArray? get() = postingSone?.routingKey
-private val Sone.routingKey: ByteArray get() = Base64.decode(id)
+private val Sone.routingKey: ByteArray get() = id.fromFreenetBase64
private enum class LinkType(private val scheme: String, private val freenetLink: Boolean) {
private val whitespace = Regex("[\\u000a\u0020\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u200c\u200d\u202f\u205f\u2060\u2800\u3000]")
private data class NextLink(val position: Int, val linkType: LinkType, val link: String, val remainder: String)
-
-private val ByteArray.freenetBase64 get() = Base64.encode(this)!!
fun <R> Boolean.ifFalse(block: () -> R): R? = if (!this) block() else null
/**
+ * Returns `this` but runs the given block if `this` is `true`.
+ *
+ * @param block The block to run if `this` is `true`
+ * @return `this`
+ */
+fun Boolean.onTrue(block: () -> Unit): Boolean = also { if (this) block() }
+
+/**
* Returns `this` but runs the given block if `this` is `false`.
*
* @param block The block to run if `this` is `false`
--- /dev/null
+/**
+ * Sone - Freenet.kt - Copyright © 2019 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.utils
+
+import freenet.support.*
+
+val ByteArray.asFreenetBase64: String get() = Base64.encode(this)
+val String.fromFreenetBase64: ByteArray get() = Base64.decode(this)
--- /dev/null
+package net.pterodactylus.sone.utils
+
+import java.util.function.*
+
+/** Allows easy invocation of Java Consumers. */
+operator fun <T> Consumer<T>.invoke(t: T) = accept(t)
+
+/** Allows easy invocation of Java Runnables. */
+operator fun Runnable.invoke() = run()
package net.pterodactylus.sone.web
-import com.google.common.eventbus.*
import com.google.inject.*
import freenet.support.api.*
import net.pterodactylus.sone.core.*
import net.pterodactylus.sone.main.*
import net.pterodactylus.sone.template.*
import net.pterodactylus.sone.text.*
-import net.pterodactylus.sone.web.notification.*
import net.pterodactylus.util.notify.*
import net.pterodactylus.util.template.*
import javax.inject.*
fun getNotificationManager() =
NotificationManager()
- @Provides
- @Singleton
- fun getNotificationHandler(eventBus: EventBus, loaders: Loaders, notificationManager: NotificationManager) =
- NotificationHandler(eventBus, loaders, notificationManager)
-
}
--- /dev/null
+/**
+ * Sone - ConfigNotReadHandler.kt - Copyright © 2019 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.util.notify.*
+import javax.inject.*
+
+/**
+ * Handler for [ConfigNotRead] events.
+ */
+class ConfigNotReadHandler @Inject constructor(private val notificationManager: NotificationManager, @Named("configNotRead") private val notification: TemplateNotification) {
+
+ @Subscribe
+ fun configNotRead(@Suppress("UNUSED_PARAMETER") configNotRead: ConfigNotRead) {
+ notificationManager.addNotification(notification)
+ }
+
+}
--- /dev/null
+/**
+ * Sone - FirstStartHandler.kt - Copyright © 2019 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.util.notify.*
+import javax.inject.*
+
+/**
+ * Handles the notification shown on first start of Sone.
+ */
+class FirstStartHandler @Inject constructor(private val notificationManager: NotificationManager, @Named("firstStart") private val notification: TemplateNotification) {
+
+ @Subscribe
+ fun firstStart(@Suppress("UNUSED_PARAMETER") firstStart: FirstStart) {
+ notificationManager.addNotification(notification)
+ }
+
+}
--- /dev/null
+/**
+ * Sone - ImageInsertHandler.kt - Copyright © 2019 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.*
+
+/**
+ * Notification handler for the various image-insert-related events.
+ *
+ * @see ImageInsertStartedEvent
+ * @see ImageInsertAbortedEvent
+ * @see ImageInsertFailedEvent
+ * @see ImageInsertFinishedEvent
+ */
+class ImageInsertHandler @Inject constructor(
+ private val notificationManager: NotificationManager,
+ @Named("imageInserting") private val imageInsertingNotification: ListNotification<Image>,
+ @Named("imageFailed") private val imageFailedNotification: ListNotification<Image>,
+ @Named("imageInserted") private val imageInsertedNotification: ListNotification<Image>) {
+
+ @Subscribe
+ fun imageInsertStarted(imageInsertStartedEvent: ImageInsertStartedEvent) {
+ imageInsertingNotification.add(imageInsertStartedEvent.image)
+ notificationManager.addNotification(imageInsertingNotification)
+ }
+
+ @Subscribe
+ fun imageInsertAborted(imageInsertAbortedEvent: ImageInsertAbortedEvent) {
+ imageInsertingNotification.remove(imageInsertAbortedEvent.image)
+ }
+
+ @Subscribe
+ fun imageInsertFailed(imageInsertFailedEvent: ImageInsertFailedEvent) {
+ imageInsertingNotification.remove(imageInsertFailedEvent.image)
+ imageFailedNotification.add(imageInsertFailedEvent.image)
+ notificationManager.addNotification(imageFailedNotification)
+ }
+
+ @Subscribe
+ fun imageInsertFinished(imageInsertFinishedEvent: ImageInsertFinishedEvent) {
+ imageInsertingNotification.remove(imageInsertFinishedEvent.image)
+ imageInsertedNotification.add(imageInsertFinishedEvent.image)
+ notificationManager.addNotification(imageInsertedNotification)
+ }
+
+}
--- /dev/null
+/**
+ * Sone - NewLocalPostHandler.kt - Copyright © 2019 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 local posts.
+ */
+class LocalPostHandler @Inject constructor(private val notificationManager: NotificationManager, @Named("localPost") private val notification: ListNotification<Post>) {
+
+ @Subscribe
+ fun newPostFound(newPostFoundEvent: NewPostFoundEvent) {
+ newPostFoundEvent.post.onLocal { post ->
+ notification.add(post)
+ if (!notificationManager.hasFirstStartNotification()) {
+ notificationManager.addNotification(notification)
+ }
+ }
+ }
+
+ @Subscribe
+ fun postRemoved(postRemovedEvent: PostRemovedEvent) {
+ postRemovedEvent.post.onLocal { post ->
+ notification.remove(post)
+ }
+ }
+
+ @Subscribe
+ fun postMarkedAsKnown(markPostKnownEvent: MarkPostKnownEvent) {
+ markPostKnownEvent.post.onLocal { post ->
+ notification.remove(post)
+ }
+ }
+
+}
+
+private fun Post.onLocal(action: (Post) -> Unit) =
+ if (sone.isLocal) action(this) else Unit
--- /dev/null
+/**
+ * Sone - LocalReplyHandler.kt - Copyright © 2019 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 local replies.
+ */
+class LocalReplyHandler @Inject constructor(private val notificationManager: NotificationManager, @Named("localReply") private val notification: ListNotification<PostReply>) {
+
+ @Subscribe
+ fun newReplyFound(event: NewPostReplyFoundEvent) =
+ event.postReply.onLocal {
+ notification.add(it)
+ if (!notificationManager.hasFirstStartNotification()) {
+ notificationManager.addNotification(notification)
+ }
+ }
+
+ @Subscribe
+ fun replyRemoved(event: PostReplyRemovedEvent) {
+ notification.remove(event.postReply)
+ }
+
+ @Subscribe
+ fun replyMarkedAsKnown(event: MarkPostReplyKnownEvent) {
+ notification.remove(event.postReply)
+ }
+
+}
+
+private fun PostReply.onLocal(action: (PostReply) -> Unit) =
+ if (sone.isLocal) action(this) else Unit
--- /dev/null
+/**
+ * Sone - MarkPostKnownDuringFirstStartHandler.kt - Copyright © 2019 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.sone.utils.*
+import net.pterodactylus.util.notify.*
+import java.util.function.*
+import javax.inject.*
+
+/**
+ * Handler that marks a [new][NewPostFoundEvent] [post][Post] as known while
+ * the [notification manager][NotificationManager] shows a [first start notification]
+ * [NotificationManager.hasFirstStartNotification].
+ */
+class MarkPostKnownDuringFirstStartHandler @Inject constructor(private val notificationManager: NotificationManager, private val markPostAsKnown: Consumer<Post>) {
+
+ @Subscribe
+ fun newPostFound(newPostFoundEvent: NewPostFoundEvent) {
+ if (notificationManager.hasFirstStartNotification()) {
+ markPostAsKnown(newPostFoundEvent.post)
+ }
+ }
+
+}
--- /dev/null
+/**
+ * Sone - MarkPostReplyKnownDuringFirstStartHandlerTest.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.sone.utils.*
+import net.pterodactylus.util.notify.*
+import java.util.function.*
+import javax.inject.*
+
+/**
+ * Handler that marks post replies [as known][net.pterodactylus.sone.core.Core.markReplyKnown]
+ * while the [first start notification][net.pterodactylus.util.notify.NotificationManager.hasFirstStartNotification]
+ * is shown.
+ */
+class MarkPostReplyKnownDuringFirstStartHandler @Inject constructor(private val notificationManager: NotificationManager, private val markAsKnown: Consumer<PostReply>) {
+
+ @Subscribe
+ fun newPostReply(event: NewPostReplyFoundEvent) {
+ if (notificationManager.hasFirstStartNotification()) {
+ markAsKnown(event.postReply)
+ }
+ }
+
+}
--- /dev/null
+/**
+ * Sone - NewRemotePostHandler.kt - Copyright © 2019 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 [NewPostFoundEvent]s that adds the new post to the “new posts” notification and
+ * displays the notification if the “first start” notification is not being shown.
+ */
+class NewRemotePostHandler @Inject constructor(private val notificationManager: NotificationManager, @Named("newRemotePost") private val notification: ListNotification<Post>) {
+
+ @Subscribe
+ fun newPostFound(newPostFoundEvent: NewPostFoundEvent) {
+ if (!newPostFoundEvent.post.sone.isLocal) {
+ notification.add(newPostFoundEvent.post)
+ if (!notificationManager.hasFirstStartNotification()) {
+ notificationManager.addNotification(notification)
+ }
+ }
+ }
+
+ @Subscribe
+ fun postRemoved(event: PostRemovedEvent) {
+ notification.remove(event.post)
+ }
+
+ @Subscribe
+ fun postMarkedKnown(event: MarkPostKnownEvent) {
+ notification.remove(event.post)
+ }
+
+}
--- /dev/null
+/**
+ * Sone - NewSoneHandler.kt - Copyright © 2019 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.*
+
+/**
+ * Notification handler for “new Sone discovered” events.
+ */
+class NewSoneHandler @Inject constructor(private val notificationManager: NotificationManager, @Named("newSone") private val notification: ListNotification<Sone>) {
+
+ @Subscribe
+ fun newSoneFound(newSoneFoundEvent: NewSoneFoundEvent) {
+ if (!notificationManager.hasFirstStartNotification()) {
+ notification.add(newSoneFoundEvent.sone)
+ notificationManager.addNotification(notification)
+ }
+ }
+
+ @Subscribe
+ fun markedSoneKnown(markSoneKnownEvent: MarkSoneKnownEvent) {
+ notification.remove(markSoneKnownEvent.sone)
+ }
+
+ @Subscribe
+ fun soneRemoved(soneRemovedEvent: SoneRemovedEvent) {
+ notification.remove(soneRemovedEvent.sone)
+ }
+
+}
--- /dev/null
+/**
+ * Sone - NewVersionHandler.kt - Copyright © 2019 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.util.notify.*
+import javax.inject.*
+
+/**
+ * Handler for the “new version” notification.
+ */
+class NewVersionHandler @Inject constructor(private val notificationManager: NotificationManager, @Named("newVersion") private val notification: TemplateNotification) {
+
+ @Subscribe
+ fun newVersionFound(updateFoundEvent: UpdateFoundEvent) {
+ notification.set("latestVersion", updateFoundEvent.version)
+ notification.set("releaseTime", updateFoundEvent.releaseTime)
+ notification.set("latestEdition", updateFoundEvent.latestEdition)
+ notification.set("disruptive", updateFoundEvent.isDisruptive)
+ notificationManager.addNotification(notification)
+ }
+
+}
package net.pterodactylus.sone.web.notification
-import com.google.common.eventbus.*
-import net.pterodactylus.sone.main.*
-import net.pterodactylus.util.notify.*
+import net.pterodactylus.sone.freenet.wot.*
+import net.pterodactylus.sone.text.*
import javax.inject.*
/**
- * Handler for notifications that can create notifications and register them with an event bus.
+ * Container that causes notification handlers to be created and (more importantly) registered
+ * on creation with the event bus.
*/
-@Suppress("UnstableApiUsage")
-class NotificationHandler @Inject constructor(private val eventBus: EventBus, private val loaders: Loaders, private val notificationManager: NotificationManager) {
-
- fun start() {
- SoneLockedOnStartupHandler(notificationManager, loaders.loadTemplate("/templates/notify/soneLockedOnStartupNotification.html"))
- .also(eventBus::register)
- }
-
-}
+@Suppress("UNUSED_PARAMETER")
+class NotificationHandler @Inject constructor(
+ markPostKnownDuringFirstStartHandler: MarkPostKnownDuringFirstStartHandler,
+ markPostReplyKnownDuringFirstStartHandler: MarkPostReplyKnownDuringFirstStartHandler,
+ newSoneHandler: NewSoneHandler,
+ newRemotePostHandler: NewRemotePostHandler,
+ remotePostReplyHandler: RemotePostReplyHandler,
+ soneLockedOnStartupHandler: SoneLockedOnStartupHandler,
+ soneLockedHandler: SoneLockedHandler,
+ localPostHandler: LocalPostHandler,
+ localReplyHandler: LocalReplyHandler,
+ newVersionHandler: NewVersionHandler,
+ imageInsertHandler: ImageInsertHandler,
+ firstStartHandler: FirstStartHandler,
+ configNotReadHandler: ConfigNotReadHandler,
+ startupHandler: StartupHandler,
+ webOfTrustPinger: WebOfTrustPinger,
+ webOfTrustHandler: WebOfTrustHandler,
+ soneMentionDetector: SoneMentionDetector,
+ soneMentionedHandler: SoneMentionedHandler,
+ soneInsertHandler: SoneInsertHandler
+)
--- /dev/null
+/**
+ * Sone - NotificationHandlerModuleTest.kt - Copyright © 2019 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.inject.*
+import com.google.inject.binder.*
+import net.pterodactylus.sone.core.*
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.freenet.wot.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.notify.*
+import net.pterodactylus.sone.text.*
+import net.pterodactylus.util.notify.*
+import java.util.concurrent.*
+import java.util.concurrent.TimeUnit.*
+import java.util.function.*
+import javax.inject.*
+import javax.inject.Singleton
+
+/**
+ * Guice module for creating all notification handlers.
+ */
+class NotificationHandlerModule : AbstractModule() {
+
+ override fun configure() {
+ bind(NotificationHandler::class.java).`in`(Singleton::class.java)
+ bind<MarkPostKnownDuringFirstStartHandler>().asSingleton()
+ bind<MarkPostReplyKnownDuringFirstStartHandler>().asSingleton()
+ bind<SoneLockedOnStartupHandler>().asSingleton()
+ bind<NewSoneHandler>().asSingleton()
+ bind<NewRemotePostHandler>().asSingleton()
+ bind<RemotePostReplyHandler>().asSingleton()
+ bind<SoneLockedHandler>().asSingleton()
+ bind<LocalPostHandler>().asSingleton()
+ bind<LocalReplyHandler>().asSingleton()
+ bind<NewVersionHandler>().asSingleton()
+ bind<ImageInsertHandler>().asSingleton()
+ bind<FirstStartHandler>().asSingleton()
+ bind<ConfigNotReadHandler>().asSingleton()
+ bind<StartupHandler>().asSingleton()
+ bind<WebOfTrustHandler>().asSingleton()
+ bind<SoneMentionDetector>().asSingleton()
+ bind<SoneMentionedHandler>().asSingleton()
+ bind<SoneInsertHandler>().asSingleton()
+ }
+
+ @Provides
+ fun getMarkPostKnownHandler(core: Core): Consumer<Post> = Consumer { core.markPostKnown(it) }
+
+ @Provides
+ fun getMarkPostReplyKnownHandler(core: Core): Consumer<PostReply> = Consumer { core.markReplyKnown(it) }
+
+ @Provides
+ @Singleton
+ @Named("soneLockedOnStartup")
+ fun getSoneLockedOnStartupNotification(loaders: Loaders) =
+ ListNotification<Sone>("sone-locked-on-startup", "sones", loaders.loadTemplate("/templates/notify/soneLockedOnStartupNotification.html"))
+
+ @Provides
+ @Named("newSone")
+ fun getNewSoneNotification(loaders: Loaders) =
+ ListNotification<Sone>("new-sone-notification", "sones", loaders.loadTemplate("/templates/notify/newSoneNotification.html"), dismissable = false)
+
+ @Provides
+ @Singleton
+ @Named("newRemotePost")
+ fun getNewPostNotification(loaders: Loaders) =
+ ListNotification<Post>("new-post-notification", "posts", loaders.loadTemplate("/templates/notify/newPostNotification.html"), dismissable = false)
+
+ @Provides
+ @Singleton
+ @Named("newRemotePostReply")
+ fun getNewRemotePostReplyNotification(loaders: Loaders) =
+ ListNotification<PostReply>("new-reply-notification", "replies", loaders.loadTemplate("/templates/notify/newReplyNotification.html"), dismissable = false)
+
+ @Provides
+ @Singleton
+ @Named("soneLocked")
+ fun getSoneLockedNotification(loaders: Loaders) =
+ ListNotification<Sone>("sones-locked-notification", "sones", loaders.loadTemplate("/templates/notify/lockedSonesNotification.html"), dismissable = true)
+
+ @Provides
+ @Singleton
+ @Named("localPost")
+ fun getLocalPostNotification(loaders: Loaders) =
+ ListNotification<Post>("local-post-notification", "posts", loaders.loadTemplate("/templates/notify/newPostNotification.html"), dismissable = false)
+
+ @Provides
+ @Singleton
+ @Named("localReply")
+ fun getLocalReplyNotification(loaders: Loaders) =
+ ListNotification<PostReply>("local-reply-notification", "replies", loaders.loadTemplate("/templates/notify/newReplyNotification.html"), dismissable = false)
+
+ @Provides
+ @Singleton
+ @Named("newVersion")
+ fun getNewVersionNotification(loaders: Loaders) =
+ TemplateNotification("new-version-notification", loaders.loadTemplate("/templates/notify/newVersionNotification.html"))
+
+ @Provides
+ @Singleton
+ @Named("imageInserting")
+ fun getImageInsertingNotification(loaders: Loaders) =
+ ListNotification<Image>("inserting-images-notification", "images", loaders.loadTemplate("/templates/notify/inserting-images-notification.html"), dismissable = true)
+
+ @Provides
+ @Singleton
+ @Named("imageFailed")
+ fun getImageInsertingFailedNotification(loaders: Loaders) =
+ ListNotification<Image>("image-insert-failed-notification", "images", loaders.loadTemplate("/templates/notify/image-insert-failed-notification.html"), dismissable = true)
+
+ @Provides
+ @Singleton
+ @Named("imageInserted")
+ fun getImageInsertedNotification(loaders: Loaders) =
+ ListNotification<Image>("inserted-images-notification", "images", loaders.loadTemplate("/templates/notify/inserted-images-notification.html"), dismissable = true)
+
+ @Provides
+ @Singleton
+ @Named("firstStart")
+ fun getFirstStartNotification(loaders: Loaders) =
+ TemplateNotification("first-start-notification", loaders.loadTemplate("/templates/notify/firstStartNotification.html"))
+
+ @Provides
+ @Singleton
+ @Named("configNotRead")
+ fun getConfigNotReadNotification(loaders: Loaders) =
+ TemplateNotification("config-not-read-notification", loaders.loadTemplate("/templates/notify/configNotReadNotification.html"))
+
+ @Provides
+ @Singleton
+ @Named("startup")
+ fun getStartupNotification(loaders: Loaders) =
+ TemplateNotification("startup-notification", loaders.loadTemplate("/templates/notify/startupNotification.html"))
+
+ @Provides
+ @Singleton
+ @Named("webOfTrust")
+ fun getWebOfTrustNotification(loaders: Loaders) =
+ TemplateNotification("wot-missing-notification", loaders.loadTemplate("/templates/notify/wotMissingNotification.html"))
+
+ @Provides
+ @Singleton
+ @Named("webOfTrustReacher")
+ fun getWebOfTrustReacher(webOfTrustConnector: WebOfTrustConnector): Runnable =
+ Runnable { webOfTrustConnector.ping() }
+
+ @Provides
+ @Singleton
+ @Named("webOfTrustReschedule")
+ 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)
+
+ @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 - RemotePostReplyHandler.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.sone.utils.*
+import net.pterodactylus.util.notify.*
+import javax.inject.*
+
+/**
+ * Handler for remote replies.
+ */
+class RemotePostReplyHandler @Inject constructor(private val notificationManager: NotificationManager, @Named("newRemotePostReply") private val notification: ListNotification<PostReply>) {
+
+ @Subscribe
+ fun newPostReplyFound(event: NewPostReplyFoundEvent) {
+ event.postReply.let { postReply ->
+ postReply.sone.isLocal.onFalse {
+ if (!notificationManager.hasFirstStartNotification()) {
+ notification.add(event.postReply)
+ notificationManager.addNotification(notification)
+ }
+ }
+ }
+ }
+
+ @Subscribe
+ fun postReplyRemoved(event: PostReplyRemovedEvent) {
+ notification.remove(event.postReply)
+ }
+
+ @Subscribe
+ fun postReplyMarkedAsKnown(event: MarkPostReplyKnownEvent) {
+ notification.remove(event.postReply)
+ }
+
+}
--- /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
--- /dev/null
+/**
+ * Sone - SoneLockedHandler.kt - Copyright © 2019 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 java.util.concurrent.*
+import java.util.concurrent.atomic.*
+import javax.inject.*
+
+/**
+ * Handler for [SoneLockedEvent]s and [SoneUnlockedEvent]s that can schedule notifications after
+ * a certain timeout.
+ */
+class SoneLockedHandler @Inject constructor(
+ private val notificationManager: NotificationManager,
+ @Named("soneLocked") private val notification: ListNotification<Sone>,
+ @Named("notification") private val executor: ScheduledExecutorService) {
+
+ private val future: AtomicReference<ScheduledFuture<*>> = AtomicReference()
+
+ @Subscribe
+ fun soneLocked(soneLockedEvent: SoneLockedEvent) {
+ synchronized(future) {
+ notification.add(soneLockedEvent.sone)
+ future.get()?.also(this::cancelPreviousFuture)
+ future.set(executor.schedule(::showNotification, 5, TimeUnit.MINUTES))
+ }
+ }
+
+ @Subscribe
+ fun soneUnlocked(soneUnlockedEvent: SoneUnlockedEvent) {
+ synchronized(future) {
+ notification.remove(soneUnlockedEvent.sone)
+ future.get()?.also(::cancelPreviousFuture)
+ }
+ }
+
+ private fun cancelPreviousFuture(future: ScheduledFuture<*>) {
+ future.cancel(true)
+ }
+
+ private fun showNotification() {
+ notificationManager.addNotification(notification)
+ }
+
+}
import net.pterodactylus.sone.data.*
import net.pterodactylus.sone.notify.*
import net.pterodactylus.util.notify.*
-import net.pterodactylus.util.template.*
+import javax.inject.*
/**
* Handler for [SoneLockedOnStartup][net.pterodactylus.sone.core.event.SoneLockedOnStartup] events
* that adds the appropriate notification to the [NotificationManager].
*/
-class SoneLockedOnStartupHandler(private val notificationManager: NotificationManager, template: Template) {
-
- private val notification = ListNotification<Sone>("sone-locked-on-startup", "sones", template)
+class SoneLockedOnStartupHandler @Inject constructor(private val notificationManager: NotificationManager, @Named("soneLockedOnStartup") private val notification: ListNotification<Sone>) {
@Subscribe
@Suppress("UnstableApiUsage")
--- /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)
+ }
+
+}
--- /dev/null
+/**
+ * Sone - StartupHandler.kt - Copyright © 2019 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.util.notify.*
+import java.util.concurrent.*
+import java.util.concurrent.TimeUnit.*
+import javax.inject.*
+
+/**
+ * Handler for the [Startup] event notification.
+ */
+class StartupHandler @Inject constructor(
+ private val notificationManager: NotificationManager,
+ @Named("startup") private val notification: TemplateNotification,
+ @Named("notification") private val ticker: ScheduledExecutorService) {
+
+ @Subscribe
+ fun startup(@Suppress("UNUSED_PARAMETER") startup: Startup) {
+ notificationManager.addNotification(notification)
+ ticker.schedule({ notificationManager.removeNotification(notification) }, 2, MINUTES)
+ }
+
+}
--- /dev/null
+/**
+ * Sone - WebOfTrustHandler.kt - Copyright © 2019 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.util.notify.*
+import javax.inject.*
+
+/**
+ * Handler for web of trust-related notifications and the [WebOfTrustAppeared]
+ * and [WebOfTrustDisappeared] events.
+ */
+class WebOfTrustHandler @Inject constructor(private val notificationManager: NotificationManager, @Named("webOfTrust") private val notification: TemplateNotification) {
+
+ @Subscribe
+ fun webOfTrustAppeared(@Suppress("UNUSED_PARAMETER") webOfTrustAppeared: WebOfTrustAppeared) {
+ notificationManager.removeNotification(notification)
+ }
+
+ @Subscribe
+ fun webOfTrustDisappeared(@Suppress("UNUSED_PARAMETER") webOfTrustDisappeared: WebOfTrustDisappeared) {
+ notificationManager.addNotification(notification)
+ }
+
+}
Notification.SoneIsInserting.Text=Your Sone sone://{0} is now being inserted.
Notification.SoneIsInserted.Text=Your Sone sone://{0} has been inserted in {1,number} {1,choice,0#seconds|1#second|1<seconds}.
Notification.SoneInsertAborted.Text=Your Sone sone://{0} could not be inserted.
-Notification.SoneLockedOnStartup.Text=Some Sones were locked on startup because they don’t contain anything. Versions prior to v81 had a bug that resulted in empty Sones. To prevent buggy Sones from being inserted they have been automatically locked. Please check your Sones, use the Rescue Mode if necessary, and unlock your Sones once you are satisfied with the results. Locked Sones are:
+Notification.SoneLockedOnStartup.Text=Versions prior to v81 had a bug that resulted in empty Sones. To prevent buggy Sones from being inserted they have automatically been locked. Please check your Sones, use the Rescue Mode if necessary, and unlock your Sones once you are satisfied with the results. Locked Sones are:
Notification.SoneIsInserting.Text=Tu Sone sone://{0} está siendo insertado.
Notification.SoneIsInserted.Text=Tu Sone sone://{0} ha sido insertado en {1,number} {1,choice,0#segundos|1#segundo|1<segundos}.
Notification.SoneInsertAborted.Text=Tu Sone sone://{0} no pudo ser insertado.
-Notification.SoneLockedOnStartup.Text=Some Sones were locked on startup because they don’t contain anything. Versions prior to v81 had a bug that resulted in empty Sones. To prevent buggy Sones from being inserted they have been automatically locked. Please check your Sones, use the Rescue Mode if necessary, and unlock your Sones once you are satisfied with the results. Locked Sones are:
+Notification.SoneLockedOnStartup.Text=Versions prior to v81 had a bug that resulted in empty Sones. To prevent buggy Sones from being inserted they have been automatically locked. Please check your Sones, use the Rescue Mode if necessary, and unlock your Sones once you are satisfied with the results. Locked Sones are:
# 55-61, 324–328, 360, 464
Notification.SoneIsInserted.Text=votre Sone sone://{0} a été inséré dans {1,number} {1,choice,0#seconds|1#second|1<seconds}.
Notification.SoneInsertAborted.Text=Votre Sone sone://{0} ne peut pas être inséré.
Notification.SoneLockedOnStartup.Text=Les versions antérieures à v81 avaient un bug vidant les Sones. Pour éviter d'insérer des Sones vides ils ont été automatiquement vérouillés. Vérifiez vos Sones, utilisez le mode récupération si nécessaire puis dévérouillez vos Sones lorsque vous êtes satisfait du résultat. Les Sones vérouillés sont:
-#
+# 464
Notification.SoneIsInserting.Text=あなたのSone sone://{0}は現在インサート中です。
Notification.SoneIsInserted.Text=あなたのSone sone://{0}は{1,number}{1,choice,0#秒|1#秒|1<秒}でインサートされました。
Notification.SoneInsertAborted.Text=あなたのSone sone://{0}のインサートに失敗しました。
-Notification.SoneLockedOnStartup.Text=Some Sones were locked on startup because they don’t contain anything. Versions prior to v81 had a bug that resulted in empty Sones. To prevent buggy Sones from being inserted they have been automatically locked. Please check your Sones, use the Rescue Mode if necessary, and unlock your Sones once you are satisfied with the results. Locked Sones are:
+Notification.SoneLockedOnStartup.Text=Versions prior to v81 had a bug that resulted in empty Sones. To prevent buggy Sones from being inserted they have been automatically locked. Please check your Sones, use the Rescue Mode if necessary, and unlock your Sones once you are satisfied with the results. Locked Sones are:
# 55-51, 67, 103, 324–328, 360, 455, 464
Notification.SoneIsInserting.Text=Your Sone sone://{0} is now being inserted.
Notification.SoneIsInserted.Text=Your Sone sone://{0} has been inserted in {1,number} {1,choice,0#seconds|1#second|1<seconds}.
Notification.SoneInsertAborted.Text=Your Sone sone://{0} could not be inserted.
-Notification.SoneLockedOnStartup.Text=Some Sones were locked on startup because they don’t contain anything. Versions prior to v81 had a bug that resulted in empty Sones. To prevent buggy Sones from being inserted they have been automatically locked. Please check your Sones, use the Rescue Mode if necessary, and unlock your Sones once you are satisfied with the results. Locked Sones are:
+Notification.SoneLockedOnStartup.Text=Versions prior to v81 had a bug that resulted in empty Sones. To prevent buggy Sones from being inserted they have been automatically locked. Please check your Sones, use the Rescue Mode if necessary, and unlock your Sones once you are satisfied with the results. Locked Sones are:
# 55-61, 67, 103, 123-124, 305-307, 309-311, 324–328, 360, 455, 461-464
Notification.SoneIsInserting.Text=Twoje Sone sone://{0} jest w tej chili wysyłane.
Notification.SoneIsInserted.Text=Twoje sone://{0} zostało wysłane w {1,number} {1,choice,0#seconds|1#second|1<seconds}.
Notification.SoneInsertAborted.Text=Twoje Sone sone://{0} nie mogło zostać wysłane.
-Notification.SoneLockedOnStartup.Text=Some Sones were locked on startup because they don’t contain anything. Versions prior to v81 had a bug that resulted in empty Sones. To prevent buggy Sones from being inserted they have been automatically locked. Please check your Sones, use the Rescue Mode if necessary, and unlock your Sones once you are satisfied with the results. Locked Sones are:
+Notification.SoneLockedOnStartup.Text=Versions prior to v81 had a bug that resulted in empty Sones. To prevent buggy Sones from being inserted they have been automatically locked. Please check your Sones, use the Rescue Mode if necessary, and unlock your Sones once you are satisfied with the results. Locked Sones are:
# 55-61, 324–328, 360, 455, 464
Notification.SoneIsInserting.Text=Your Sone sone://{0} is now being inserted.
Notification.SoneIsInserted.Text=Your Sone sone://{0} has been inserted in {1,number} {1,choice,0#seconds|1#second|1<seconds}.
Notification.SoneInsertAborted.Text=Your Sone sone://{0} could not be inserted.
-Notification.SoneLockedOnStartup.Text=Some Sones were locked on startup because they don’t contain anything. Versions prior to v81 had a bug that resulted in empty Sones. To prevent buggy Sones from being inserted they have been automatically locked. Please check your Sones, use the Rescue Mode if necessary, and unlock your Sones once you are satisfied with the results. Locked Sones are:
+Notification.SoneLockedOnStartup.Text=Versions prior to v81 had a bug that resulted in empty Sones. To prevent buggy Sones from being inserted they have been automatically locked. Please check your Sones, use the Rescue Mode if necessary, and unlock your Sones once you are satisfied with the results. Locked Sones are:
# 55-61, 67, 103, 123-124, 305-307, 309-311, 324–328, 360, 455, 461-464
import freenet.keys.InsertableClientSSK.*
import freenet.node.*
import freenet.node.RequestStarter.*
-import freenet.support.Base64
import freenet.support.api.*
import freenet.support.io.*
import net.pterodactylus.sone.core.FreenetInterface.*
import net.pterodactylus.sone.test.*
import net.pterodactylus.sone.test.Matchers.*
import net.pterodactylus.sone.test.TestUtil.*
+import net.pterodactylus.sone.utils.*
import org.hamcrest.MatcherAssert.*
import org.hamcrest.Matchers.equalTo
import org.hamcrest.Matchers.notNullValue
@Before
fun setupSone() {
val insertSsk = createRandom(randomSource, "test-0")
- whenever(sone.id).thenReturn(Base64.encode(insertSsk.uri.routingKey))
+ whenever(sone.id).thenReturn(insertSsk.uri.routingKey.asFreenetBase64)
whenever(sone.requestUri).thenReturn(insertSsk.uri.uskForSSK())
}
--- /dev/null
+/**
+ * Sone - WebOfTrustPinger.kt - Copyright © 2019 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.freenet.wot
+
+import com.google.common.eventbus.*
+import net.pterodactylus.sone.core.event.*
+import net.pterodactylus.sone.freenet.plugin.*
+import net.pterodactylus.sone.utils.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import java.util.concurrent.atomic.*
+import java.util.function.*
+import kotlin.test.*
+
+/**
+ * Unit test for [WebOfTrustPinger].
+ */
+class WebOfTrustPingerTest {
+
+ private val eventBus = EventBus()
+ private val webOfTrustReachable = AtomicBoolean()
+ private val webOfTrustReacher = Runnable { webOfTrustReachable.get().onFalse { throw PluginException() } }
+ private val rescheduled = AtomicBoolean()
+ private val reschedule: Consumer<Runnable> = Consumer { if (it == pinger) rescheduled.set(true) }
+ private val pinger = WebOfTrustPinger(eventBus, webOfTrustReacher, reschedule)
+
+ @Test
+ fun `pinger sends wot appeared event when run first and wot is reachable`() {
+ webOfTrustReachable.set(true)
+ val appearedReceived = AtomicBoolean()
+ eventBus.register(WebOfTrustAppearedCatcher { appearedReceived.set(true) })
+ pinger()
+ assertThat(appearedReceived.get(), equalTo(true))
+ }
+
+ @Test
+ fun `pinger reschedules when wot is reachable`() {
+ webOfTrustReachable.set(true)
+ pinger()
+ assertThat(rescheduled.get(), equalTo(true))
+ }
+
+ @Test
+ fun `pinger sends wot disappeared event when run first and wot is not reachable`() {
+ val appearedReceived = AtomicBoolean()
+ eventBus.register(WebOfTrustAppearedCatcher { appearedReceived.set(true) })
+ pinger()
+ assertThat(appearedReceived.get(), equalTo(false))
+ }
+
+ @Test
+ fun `pinger reschedules when wot is not reachable`() {
+ pinger()
+ assertThat(rescheduled.get(), equalTo(true))
+ }
+
+ @Test
+ fun `pinger sends wot disappeared event when run twice and wot is not reachable on second call`() {
+ val disappearedReceived = AtomicBoolean()
+ eventBus.register(WebOfTrustDisappearedCatcher { disappearedReceived.set(true) })
+ webOfTrustReachable.set(true)
+ pinger()
+ webOfTrustReachable.set(false)
+ pinger()
+ assertThat(disappearedReceived.get(), equalTo(true))
+ }
+
+ @Test
+ fun `pinger sends wot appeared event only once`() {
+ webOfTrustReachable.set(true)
+ val appearedReceived = AtomicBoolean()
+ eventBus.register(WebOfTrustAppearedCatcher { appearedReceived.set(true) })
+ pinger()
+ appearedReceived.set(false)
+ pinger()
+ assertThat(appearedReceived.get(), equalTo(false))
+ }
+
+ @Test
+ fun `pinger sends wot disappeared event only once`() {
+ val disappearedReceived = AtomicBoolean()
+ eventBus.register(WebOfTrustDisappearedCatcher { disappearedReceived.set(true) })
+ pinger()
+ disappearedReceived.set(false)
+ pinger()
+ assertThat(disappearedReceived.get(), equalTo(false))
+ }
+
+}
+
+private class WebOfTrustAppearedCatcher(private val received: () -> Unit) {
+ @Subscribe
+ fun webOfTrustAppeared(@Suppress("UNUSED_PARAMETER") webOfTrustAppeared: WebOfTrustAppeared) {
+ received()
+ }
+}
+
+private class WebOfTrustDisappearedCatcher(private val received: () -> Unit) {
+ @Subscribe
+ fun webOfTrustDisappeared(@Suppress("UNUSED_PARAMETER") webOfTrustDisappeared: WebOfTrustDisappeared) {
+ received()
+ }
+}
private val module = FreenetModule(pluginRespirator)
private val injector = Guice.createInjector(module)
- private inline fun <reified T : Any> verifySingletonInstance() {
- val firstInstance = injector.getInstance<T>()
- val secondInstance = injector.getInstance<T>()
- assertThat(firstInstance, sameInstance(secondInstance))
- }
-
@Test
fun `plugin respirator is not bound`() {
expectedException.expect(Exception::class.java)
@Test
fun `node is returned as singleton`() {
- verifySingletonInstance<Node>()
+ injector.verifySingletonInstance<Node>()
}
@Test
@Test
fun `high level simply client is returned as singleton`() {
- verifySingletonInstance<HighLevelSimpleClient>()
+ injector.verifySingletonInstance<HighLevelSimpleClient>()
}
@Test
@Test
fun `session manager is returned as singleton`() {
- verifySingletonInstance<SessionManager>()
+ injector.verifySingletonInstance<SessionManager>()
verify(pluginRespirator).getSessionManager("Sone")
}
@Test
fun `toadlet container is returned as singleten`() {
- verifySingletonInstance<ToadletContainer>()
+ injector.verifySingletonInstance<ToadletContainer>()
}
@Test
@Test
fun `page maker is returned as singleton`() {
- verifySingletonInstance<PageMaker>()
+ injector.verifySingletonInstance<PageMaker>()
}
@Test
@Test
fun `plugin respirator facade is returned as singleton`() {
- verifySingletonInstance<PluginRespiratorFacade>()
+ injector.verifySingletonInstance<PluginRespiratorFacade>()
}
@Test
@Test
fun `plugin connector facade is returned as singleton`() {
- verifySingletonInstance<PluginConnector>()
+ injector.verifySingletonInstance<PluginConnector>()
}
}
import com.google.inject.Guice.*
import com.google.inject.name.Names.*
import freenet.l10n.*
-import freenet.pluginmanager.*
import net.pterodactylus.sone.core.*
import net.pterodactylus.sone.database.*
import net.pterodactylus.sone.database.memory.*
import net.pterodactylus.util.version.Version
import org.hamcrest.MatcherAssert.*
import org.hamcrest.Matchers.*
+import org.junit.experimental.categories.*
import org.mockito.Mockito.*
import java.io.*
+import java.util.concurrent.*
import java.util.concurrent.atomic.*
import kotlin.test.*
const val versionString = "v80"
+@Category(NotParallel::class)
class SoneModuleTest {
private val currentDir: File = File(".")
@Test
fun `core is created as singleton`() {
- val firstCore = injector.getInstance<Core>()
- val secondCore = injector.getInstance<Core>()
- assertThat(secondCore, sameInstance(firstCore))
+ injector.verifySingletonInstance<Core>()
}
@Test
}
@Test
- fun `metrics registry can be created`() {
- assertThat(injector.getInstance<MetricRegistry>(), notNullValue())
+ fun `metrics registry is created as singleton`() {
+ injector.verifySingletonInstance<MetricRegistry>()
}
@Test
- fun `metrics registry is created as singleton`() {
- val firstMetricRegistry = injector.getInstance<MetricRegistry>()
- val secondMetricRegistry = injector.getInstance<MetricRegistry>()
- assertThat(firstMetricRegistry, sameInstance(secondMetricRegistry))
+ fun `wot connector is created as singleton`() {
+ injector.verifySingletonInstance<WebOfTrustConnector>()
}
@Test
- fun `wot connector can be created`() {
- assertThat(injector.getInstance<WebOfTrustConnector>(), notNullValue())
+ fun `notification ticker is created as singleton`() {
+ injector.verifySingletonInstance<ScheduledExecutorService>(named("notification"))
}
@Test
- fun `wot connector is created as singleton`() {
- val firstWebOfTrustConnector = injector.getInstance<WebOfTrustConnector>()
- val secondWebOfTrustConnector = injector.getInstance<WebOfTrustConnector>()
- assertThat(firstWebOfTrustConnector, sameInstance(secondWebOfTrustConnector))
+ fun `ticker shutdown is created as singleton`() {
+ injector.verifySingletonInstance<TickerShutdown>()
}
}
package net.pterodactylus.sone.main
+import com.google.common.eventbus.*
import com.google.inject.*
import freenet.client.async.*
import freenet.l10n.BaseL10n.LANGUAGE.*
import freenet.node.*
import freenet.pluginmanager.*
import net.pterodactylus.sone.core.*
+import net.pterodactylus.sone.core.event.*
import net.pterodactylus.sone.fcp.*
import net.pterodactylus.sone.freenet.wot.*
import net.pterodactylus.sone.test.*
import net.pterodactylus.sone.web.notification.*
import org.hamcrest.MatcherAssert.*
import org.hamcrest.Matchers.*
+import org.junit.experimental.categories.*
import org.mockito.Mockito.*
+import java.io.*
+import java.util.concurrent.atomic.*
import kotlin.test.*
/**
* Unit test for [SonePlugin].
*/
@Dirty
+@Category(NotParallel::class)
class SonePluginTest {
- private var injector = mockInjector()
- private val sonePlugin by lazy { SonePlugin { injector } }
+ private var sonePlugin = SonePlugin { injector }
private val pluginRespirator = deepMock<PluginRespirator>()
private val node = deepMock<Node>()
private val clientCore = deepMock<NodeClientCore>()
assertThat(injector.getInstance<NotificationHandler>(), notNullValue())
}
- private fun runSonePluginWithRealInjector(): Injector {
+ private fun runSonePluginWithRealInjector(injectorConsumer: (Injector) -> Unit = {}): Injector {
lateinit var injector: Injector
- val sonePlugin = SonePlugin {
+ sonePlugin = SonePlugin {
Guice.createInjector(*it).also {
injector = it
+ injectorConsumer(it)
}
}
sonePlugin.setLanguage(ENGLISH)
}
@Test
- fun `notification handler is being started`() {
+ fun `notification handler is being requested`() {
sonePlugin.runPlugin(pluginRespirator)
- val notificationHandler = injector.getInstance<NotificationHandler>()
- verify(notificationHandler).start()
+ assertThat(getInjected(NotificationHandler::class.java), notNullValue())
}
-}
+ @Test
+ fun `ticker shutdown is being requested`() {
+ sonePlugin.runPlugin(pluginRespirator)
+ assertThat(getInjected(TickerShutdown::class.java), notNullValue())
+ }
+
+ private class FirstStartListener(private val firstStartReceived: AtomicBoolean) {
+ @Subscribe
+ fun firstStart(@Suppress("UNUSED_PARAMETER") firstStart: FirstStart) {
+ firstStartReceived.set(true)
+ }
+ }
-private fun mockInjector() = mock<Injector>().apply {
- val injected = mutableMapOf<Pair<TypeLiteral<*>, Annotation?>, Any>()
- fun mockValue(clazz: Class<*>) = false.takeIf { clazz.name == java.lang.Boolean::class.java.name } ?: mock(clazz)
- whenever(getInstance(any<Key<*>>())).then {
- injected.getOrPut((it.getArgument(0) as Key<*>).let { it.typeLiteral to it.annotation }) {
- it.getArgument<Key<*>>(0).typeLiteral.type.typeName.toClass().let(::mockValue)
+ @Test
+ fun `first-start event is sent to event bus when first start is true`() {
+ File("sone.properties").delete()
+ val firstStartReceived = AtomicBoolean()
+ runSonePluginWithRealInjector {
+ val eventBus = it.getInstance(EventBus::class.java)
+ eventBus.register(FirstStartListener(firstStartReceived))
}
+ assertThat(firstStartReceived.get(), equalTo(true))
}
- whenever(getInstance(any<Class<*>>())).then {
- injected.getOrPut(TypeLiteral.get(it.getArgument(0) as Class<*>) to null) {
- it.getArgument<Class<*>>(0).let(::mockValue)
+
+ @Test
+ fun `first-start event is not sent to event bus when first start is false`() {
+ File("sone.properties").deleteAfter {
+ writeText("# empty")
+ val firstStartReceived = AtomicBoolean()
+ runSonePluginWithRealInjector {
+ val eventBus = it.getInstance(EventBus::class.java)
+ eventBus.register(FirstStartListener(firstStartReceived))
+ }
+ assertThat(firstStartReceived.get(), equalTo(false))
+ }
+ }
+
+ private class ConfigNotReadListener(private val configNotReadReceiver: AtomicBoolean) {
+ @Subscribe
+ fun configNotRead(@Suppress("UNUSED_PARAMETER") configNotRead: ConfigNotRead) {
+ configNotReadReceiver.set(true)
}
}
+
+ @Test
+ fun `config-not-read event is sent to event bus when new config is true`() {
+ File("sone.properties").deleteAfter {
+ writeText("Invalid")
+ val configNotReadReceived = AtomicBoolean()
+ runSonePluginWithRealInjector {
+ val eventBus = it.getInstance(EventBus::class.java)
+ eventBus.register(ConfigNotReadListener(configNotReadReceived))
+ }
+ assertThat(configNotReadReceived.get(), equalTo(true))
+ }
+ }
+
+ @Test
+ fun `config-not-read event is not sent to event bus when first start is true`() {
+ File("sone.properties").delete()
+ val configNotReadReceived = AtomicBoolean()
+ runSonePluginWithRealInjector {
+ val eventBus = it.getInstance(EventBus::class.java)
+ eventBus.register(ConfigNotReadListener(configNotReadReceived))
+ }
+ assertThat(configNotReadReceived.get(), equalTo(false))
+ }
+
+ @Test
+ fun `config-not-read event is not sent to event bus when new config is false`() {
+ File("sone.properties").deleteAfter {
+ writeText("# comment")
+ val configNotReadReceived = AtomicBoolean()
+ runSonePluginWithRealInjector {
+ val eventBus = it.getInstance(EventBus::class.java)
+ eventBus.register(ConfigNotReadListener(configNotReadReceived))
+ }
+ assertThat(configNotReadReceived.get(), equalTo(false))
+ }
+ }
+
+ private class StartupListener(private val startupReceived: () -> Unit) {
+ @Subscribe
+ fun startup(@Suppress("UNUSED_PARAMETER") startup: Startup) {
+ startupReceived()
+ }
+ }
+
+ @Test
+ fun `startup event is sent to event bus`() {
+ val startupReceived = AtomicBoolean()
+ runSonePluginWithRealInjector {
+ val eventBus = it.getInstance(EventBus::class.java)
+ eventBus.register(StartupListener { startupReceived.set(true) })
+ }
+ assertThat(startupReceived.get(), equalTo(true))
+ }
+
+ private class ShutdownListener(private val shutdownReceived: () -> Unit) {
+ @Subscribe
+ fun shutdown(@Suppress("UNUSED_PARAMETER") shutdown: Shutdown) {
+ shutdownReceived()
+ }
+ }
+
+ @Test
+ fun `shutdown event is sent to event bus on terminate`() {
+ val shutdownReceived = AtomicBoolean()
+ runSonePluginWithRealInjector {
+ val eventBus = it.getInstance(EventBus::class.java)
+ eventBus.register(ShutdownListener { shutdownReceived.set(true) })
+ }
+ sonePlugin.terminate()
+ assertThat(shutdownReceived.get(), equalTo(true))
+ }
+
+ private fun <T> getInjected(clazz: Class<T>, annotation: Annotation? = null): T? =
+ injected[TypeLiteral.get(clazz) to annotation] as? T
+
+ private val injected =
+ mutableMapOf<Pair<TypeLiteral<*>, Annotation?>, Any>()
+
+ private val injector = mock<Injector>().apply {
+ fun mockValue(clazz: Class<*>) = false.takeIf { clazz.name == java.lang.Boolean::class.java.name } ?: mock(clazz)
+ whenever(getInstance(any<Key<*>>())).then {
+ injected.getOrPut((it.getArgument(0) as Key<*>).let { it.typeLiteral to it.annotation }) {
+ it.getArgument<Key<*>>(0).typeLiteral.type.typeName.toClass().let(::mockValue)
+ }
+ }
+ whenever(getInstance(any<Class<*>>())).then {
+ injected.getOrPut(TypeLiteral.get(it.getArgument(0) as Class<*>) to null) {
+ it.getArgument<Class<*>>(0).let(::mockValue)
+ }
+ }
+ }
+
}
private fun String.toClass(): Class<*> = SonePlugin::class.java.classLoader.loadClass(this)
+
+private fun File.deleteAfter(action: File.() -> Unit) = try {
+ action(this)
+} finally {
+ this.delete()
+}
--- /dev/null
+/**
+ * Sone - TickerShutdownTest.kt - Copyright © 2019 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.main
+
+import com.google.common.eventbus.*
+import net.pterodactylus.sone.core.event.*
+import net.pterodactylus.sone.test.*
+import org.mockito.Mockito.*
+import java.util.concurrent.*
+import kotlin.test.*
+
+/**
+ * Unit test for [TickerShutdown].
+ */
+@Suppress("UnstableApiUsage")
+class TickerShutdownTest {
+
+ private val eventBus = EventBus()
+ private val notificationTicker = mock<ScheduledExecutorService>()
+
+ init {
+ eventBus.register(TickerShutdown(notificationTicker))
+ }
+
+ @Test
+ fun `ticker is shutdown on shutdown`() {
+ eventBus.post(Shutdown())
+ verify(notificationTicker).shutdown()
+ }
+
+}
import com.google.inject.*
import com.google.inject.name.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
import org.mockito.*
import javax.inject.Provider
import kotlin.reflect.*
?.let { getInstance(Key.get(object : TypeLiteral<T>() {}, it)) }
?: getInstance(Key.get(object : TypeLiteral<T>() {}))
+
+inline fun <reified T : Any> Injector.verifySingletonInstance(annotation: Annotation? = null) {
+ val firstInstance = getInstance<T>(annotation)
+ val secondInstance = getInstance<T>(annotation)
+ assertThat(firstInstance, sameInstance(secondInstance))
+}
+
fun <T : Any> supply(javaClass: Class<T>): Source<T> = object : Source<T> {
override fun fromInstance(instance: T) = Module { it.bind(javaClass).toInstance(instance) }
override fun byInstance(instance: T) = Module { it.bind(javaClass).toProvider(Provider<T> { instance }) }
import net.pterodactylus.sone.utils.*
import net.pterodactylus.util.web.*
import org.hamcrest.*
+import org.hamcrest.Matchers
import org.hamcrest.Matchers.*
+/**
+ * Returns a [hamcrest matcher][Matcher] constructed from the given predicate.
+ */
+fun <T> matches(description: String? = null, predicate: (T) -> Boolean) = object : TypeSafeDiagnosingMatcher<T>() {
+
+ override fun matchesSafely(item: T, mismatchDescription: Description) =
+ predicate(item).onFalse {
+ mismatchDescription.appendValue(item).appendText(" did not match predicate")
+ }
+
+ override fun describeTo(description: Description) {
+ description.appendText("matches predicate ").appendValue(predicate)
+ }
+
+}.let { matcher ->
+ description?.let { describedAs(description, matcher) } ?: matcher
+}
+
fun hasHeader(name: String, value: String) = object : TypeSafeDiagnosingMatcher<Header>() {
override fun matchesSafely(item: Header, mismatchDescription: Description) =
compare(item.name, { it.equals(name, ignoreCase = true) }) { mismatchDescription.appendText("name is ").appendValue(it) }
--- /dev/null
+/**
+ * Sone - Mocks.kt - Copyright © 2019 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.test
+
+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.*
+
+val remoteSone1 = createRemoteSone()
+val remoteSone2 = createRemoteSone()
+
+val localSone1 = createLocalSone()
+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)
+
+fun createPost(text: String = "", sone: Sone = remoteSone1, known: Boolean = false): Post.EmptyPost {
+ return object : Post.EmptyPost("post-id") {
+ override fun getSone() = sone
+ override fun getText() = text
+ override fun isKnown() = known
+ }
+}
+
+fun emptyPostReply(text: String = "", post: Post? = createPost(), sone: Sone = remoteSone1, known: Boolean = false) = object : PostReply {
+ override val id = "reply-id"
+ override fun getSone() = sone
+ override fun getPostId() = post!!.id
+ override fun getPost(): Optional<Post> = Optional.fromNullable(post)
+ override fun getTime() = 1L
+ override fun getText() = text
+ override fun isKnown() = known
+ override fun setKnown(known: Boolean): PostReply = this
+}
--- /dev/null
+package net.pterodactylus.sone.test
+
+/**
+ * Marker class for a JUnit [org.junit.experimental.categories.Category], to
+ * mark tests that should not be run parallel to other tests.
+ */
+class NotParallel
+
--- /dev/null
+package net.pterodactylus.sone.test
+
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.util.template.*
+import net.pterodactylus.util.web.*
+
+/**
+ * [Loaders] implementation for use in tests. Use [templates] to control what templates are
+ * returned by the [loadTemplate] method.
+ */
+class TestLoaders : Loaders {
+
+ val templates = mutableMapOf<String, Template>()
+
+ override fun loadTemplate(path: String) = templates[path] ?: Template()
+
+ override fun <REQ : Request> loadStaticPage(basePath: String, prefix: String, mimeType: String) = TestPage<REQ>()
+
+ override fun getTemplateProvider() = TemplateProvider { _, _ -> Template() }
+
+}
--- /dev/null
+package net.pterodactylus.sone.test
+
+import net.pterodactylus.util.web.*
+
+/**
+ * Dummy implementation of a [Page].
+ */
+class TestPage<REQ : Request> : Page<REQ> {
+
+ override fun getPath() = ""
+ override fun isPrefixPage() = false
+ override fun handleRequest(freenetRequest: REQ, response: Response) = response
+
+}
--- /dev/null
+/**
+ * Sone - SoneMentionDetectorTest.kt - Copyright © 2019 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.text
+
+import com.google.common.eventbus.*
+import net.pterodactylus.sone.core.event.*
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.database.*
+import net.pterodactylus.sone.test.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import kotlin.test.*
+
+/**
+ * Unit test for [SoneMentionDetector].
+ */
+@Suppress("UnstableApiUsage")
+class SoneMentionDetectorTest {
+
+ private val caughtExceptions = mutableListOf<Throwable>()
+ private val eventBus = EventBus { exception, _ -> caughtExceptions += exception }
+ private val soneProvider = TestSoneProvider()
+ private val postProvider = TestPostProvider()
+ private val soneTextParser = SoneTextParser(soneProvider, postProvider)
+ private val capturedFoundEvents = mutableListOf<MentionOfLocalSoneFoundEvent>()
+ private val capturedRemovedEvents = mutableListOf<MentionOfLocalSoneRemovedEvent>()
+ private val postReplyProvider = TestPostReplyProvider()
+
+ init {
+ eventBus.register(SoneMentionDetector(eventBus, soneTextParser, postReplyProvider))
+ eventBus.register(object : Any() {
+ @Subscribe
+ fun captureFoundEvent(mentionOfLocalSoneFoundEvent: MentionOfLocalSoneFoundEvent) {
+ capturedFoundEvents += mentionOfLocalSoneFoundEvent
+ }
+
+ @Subscribe
+ fun captureRemovedEvent(event: MentionOfLocalSoneRemovedEvent) {
+ capturedRemovedEvents += event
+ }
+ })
+ }
+
+ @Test
+ fun `detector does not emit event on post that does not contain any sones`() {
+ val post = createPost()
+ eventBus.post(NewPostFoundEvent(post))
+ assertThat(capturedFoundEvents, emptyIterable())
+ }
+
+ @Test
+ fun `detector does not emit event on post that does contain two remote sones`() {
+ val post = createPost("text mentions sone://${remoteSone1.id} and sone://${remoteSone2.id}.")
+ eventBus.post(NewPostFoundEvent(post))
+ assertThat(capturedFoundEvents, emptyIterable())
+ }
+
+ @Test
+ fun `detector emits event on post that contains links to a remote and a local sone`() {
+ val post = createPost("text mentions sone://${localSone1.id} and sone://${remoteSone2.id}.")
+ eventBus.post(NewPostFoundEvent(post))
+ assertThat(capturedFoundEvents, contains(MentionOfLocalSoneFoundEvent(post)))
+ }
+
+ @Test
+ fun `detector emits one event on post that contains two links to the same local sone`() {
+ val post = createPost("text mentions sone://${localSone1.id} and sone://${localSone1.id}.")
+ eventBus.post(NewPostFoundEvent(post))
+ assertThat(capturedFoundEvents, contains(MentionOfLocalSoneFoundEvent(post)))
+ }
+
+ @Test
+ fun `detector emits one event on post that contains links to two local sones`() {
+ val post = createPost("text mentions sone://${localSone1.id} and sone://${localSone2.id}.")
+ eventBus.post(NewPostFoundEvent(post))
+ assertThat(capturedFoundEvents, contains(MentionOfLocalSoneFoundEvent(post)))
+ }
+
+ @Test
+ fun `detector does not emit event for post by local sone`() {
+ val post = createPost("text mentions sone://${localSone1.id} and sone://${localSone2.id}.", localSone1)
+ eventBus.post(NewPostFoundEvent(post))
+ assertThat(capturedFoundEvents, emptyIterable())
+ }
+
+ @Test
+ fun `detector does not emit event for reply that contains no sones`() {
+ val reply = emptyPostReply()
+ eventBus.post(NewPostReplyFoundEvent(reply))
+ assertThat(capturedFoundEvents, emptyIterable())
+ }
+
+ @Test
+ fun `detector does not emit event for reply that contains two links to remote sones`() {
+ val reply = emptyPostReply("text mentions sone://${remoteSone1.id} and sone://${remoteSone2.id}.")
+ eventBus.post(NewPostReplyFoundEvent(reply))
+ assertThat(capturedFoundEvents, emptyIterable())
+ }
+
+ @Test
+ fun `detector emits event on reply that contains links to a remote and a local sone`() {
+ val post = createPost()
+ val reply = emptyPostReply("text mentions sone://${remoteSone1.id} and sone://${localSone1.id}.", post)
+ eventBus.post(NewPostReplyFoundEvent(reply))
+ assertThat(capturedFoundEvents, contains(MentionOfLocalSoneFoundEvent(post)))
+ }
+
+ @Test
+ fun `detector emits one event on reply that contains two links to the same local sone`() {
+ val post = createPost()
+ val reply = emptyPostReply("text mentions sone://${localSone1.id} and sone://${localSone1.id}.", post)
+ eventBus.post(NewPostReplyFoundEvent(reply))
+ assertThat(capturedFoundEvents, contains(MentionOfLocalSoneFoundEvent(post)))
+ }
+
+ @Test
+ fun `detector emits one event on reply that contains two links to local sones`() {
+ val post = createPost()
+ val reply = emptyPostReply("text mentions sone://${localSone1.id} and sone://${localSone2.id}.", post)
+ eventBus.post(NewPostReplyFoundEvent(reply))
+ assertThat(capturedFoundEvents, contains(MentionOfLocalSoneFoundEvent(post)))
+ }
+
+ @Test
+ fun `detector does not emit event for reply by local sone`() {
+ val reply = emptyPostReply("text mentions sone://${localSone1.id} and sone://${localSone2.id}.", sone = localSone1)
+ eventBus.post(NewPostReplyFoundEvent(reply))
+ assertThat(capturedFoundEvents, emptyIterable())
+ }
+
+ @Test
+ fun `detector does not emit event for reply without post`() {
+ val reply = emptyPostReply("text mentions sone://${localSone1.id} and sone://${localSone2.id}.", post = null)
+ eventBus.post(NewPostReplyFoundEvent(reply))
+ assertThat(caughtExceptions, emptyIterable())
+ assertThat(capturedFoundEvents, emptyIterable())
+ }
+
+ @Test
+ fun `detector does not emit removed event when a post without mention is removed`() {
+ val post = createPost()
+ eventBus.post(PostRemovedEvent(post))
+ assertThat(capturedRemovedEvents, emptyIterable())
+ }
+
+ @Test
+ fun `detector does emit removed event when post with mention is removed`() {
+ val post = createPost("sone://${localSone1.id}")
+ eventBus.post(NewPostFoundEvent(post))
+ eventBus.post(PostRemovedEvent(post))
+ assertThat(capturedRemovedEvents, contains(MentionOfLocalSoneRemovedEvent(post)))
+ }
+
+ @Test
+ fun `detector does not emit removed event when a post without mention is marked as known`() {
+ val post = createPost()
+ eventBus.post(MarkPostKnownEvent(post))
+ assertThat(capturedRemovedEvents, emptyIterable())
+ }
+
+ @Test
+ fun `detector does emit removed event when post with mention is marked as known`() {
+ val post = createPost("sone://${localSone1.id}")
+ eventBus.post(NewPostFoundEvent(post))
+ eventBus.post(MarkPostKnownEvent(post))
+ assertThat(capturedRemovedEvents, contains(MentionOfLocalSoneRemovedEvent(post)))
+ }
+
+ @Test
+ fun `detector does emit removed event when reply with mention is removed and no more mentions in that post exist`() {
+ val post = createPost()
+ val reply = emptyPostReply("sone://${localSone1.id}", post)
+ postReplyProvider.postReplies[post.id] = listOf(reply)
+ eventBus.post(NewPostReplyFoundEvent(reply))
+ eventBus.post(PostReplyRemovedEvent(reply))
+ assertThat(capturedRemovedEvents, contains(MentionOfLocalSoneRemovedEvent(post)))
+ }
+
+ @Test
+ fun `detector does not emit removed event when reply with mention is removed and post mentions local sone`() {
+ val post = createPost("sone://${localSone1.id}")
+ val reply = emptyPostReply("sone://${localSone1.id}", post)
+ eventBus.post(NewPostReplyFoundEvent(reply))
+ eventBus.post(PostReplyRemovedEvent(reply))
+ assertThat(capturedRemovedEvents, emptyIterable())
+ }
+
+ @Test
+ fun `detector does emit removed event when reply with mention is removed and post mentions local sone but is known`() {
+ val post = createPost("sone://${localSone1.id}", known = true)
+ val reply = emptyPostReply("sone://${localSone1.id}", post)
+ eventBus.post(NewPostReplyFoundEvent(reply))
+ eventBus.post(PostReplyRemovedEvent(reply))
+ assertThat(capturedRemovedEvents, contains(MentionOfLocalSoneRemovedEvent(post)))
+ }
+
+ @Test
+ fun `detector does not emit removed event when reply with mention is removed and post has other replies with mentions`() {
+ val post = createPost()
+ val reply1 = emptyPostReply("sone://${localSone1.id}", post)
+ val reply2 = emptyPostReply("sone://${localSone1.id}", post)
+ postReplyProvider.postReplies[post.id] = listOf(reply1, reply2)
+ eventBus.post(NewPostReplyFoundEvent(reply1))
+ eventBus.post(PostReplyRemovedEvent(reply1))
+ assertThat(capturedRemovedEvents, emptyIterable())
+ }
+
+ @Test
+ fun `detector does emit removed event when reply with mention is removed and post has other replies with mentions which are known`() {
+ val post = createPost()
+ val reply1 = emptyPostReply("sone://${localSone1.id}", post)
+ val reply2 = emptyPostReply("sone://${localSone1.id}", post, known = true)
+ postReplyProvider.postReplies[post.id] = listOf(reply1, reply2)
+ eventBus.post(NewPostReplyFoundEvent(reply1))
+ eventBus.post(PostReplyRemovedEvent(reply1))
+ assertThat(capturedRemovedEvents, contains(MentionOfLocalSoneRemovedEvent(post)))
+ }
+
+}
+
+private class TestSoneProvider : SoneProvider {
+
+ override val sones: Collection<Sone> get() = remoteSones + localSones
+ override val localSones: Collection<Sone> get() = setOf(localSone1, localSone2)
+ override val remoteSones: Collection<Sone> get() = setOf(remoteSone1, remoteSone2)
+ override val soneLoader: (String) -> Sone? get() = this::getSone
+ override fun getSone(soneId: String): Sone? =
+ localSones.firstOrNull { it.id == soneId } ?: remoteSones.firstOrNull { it.id == soneId }
+
+}
+
+private class TestPostProvider : PostProvider {
+
+ override fun getPost(postId: String): Post? = null
+ override fun getPosts(soneId: String): Collection<Post> = emptyList()
+ override fun getDirectedPosts(recipientId: String): Collection<Post> = emptyList()
+
+}
+
+private class TestPostReplyProvider : PostReplyProvider {
+
+ val replies = mutableMapOf<String, PostReply>()
+ val postReplies = mutableMapOf<String, List<PostReply>>()
+
+ override fun getPostReply(id: String) = replies[id]
+ override fun getReplies(postId: String) = postReplies[postId] ?: emptyList()
+
+}
}
@Test
+ fun `onTrue returns true on true`() {
+ assertThat(true.onTrue {}, equalTo(true))
+ }
+
+ @Test
+ fun `onTrue returns false on false`() {
+ assertThat(false.onTrue {}, equalTo(false))
+ }
+
+ @Test
+ fun `onTrue is not executed on false`() {
+ assertThat(false.onTrue { throw RuntimeException() }, equalTo(false))
+ }
+
+ @Test(expected = RuntimeException::class)
+ fun `onTrue is executed on true`() {
+ true.onTrue { throw RuntimeException() }
+ }
+
+ @Test
fun `onFalse returns true on true`() {
assertThat(true.onFalse {}, equalTo(true))
}
import net.pterodactylus.sone.main.*
import net.pterodactylus.sone.test.*
import net.pterodactylus.sone.web.page.*
-import net.pterodactylus.util.web.*
import org.junit.*
import org.junit.rules.*
import org.mockito.Mockito.*
verify(pageMaker).addNavigationCategory("/Sone/index.html", "Navigation.Menu.Sone.Name", "Navigation.Menu.Sone.Tooltip", sonePlugin)
}
- private val page = TestPage()
+ private val page = TestPage<FreenetRequest>()
@Test
fun `adding a page without menuname will add it correctly`() {
whenever(this.menuName).thenReturn(menuName)
}
- private class TestPage : Page<FreenetRequest> {
- override fun getPath() = ""
- override fun isPrefixPage() = false
- override fun handleRequest(freenetRequest: FreenetRequest, response: Response) = response
- }
-
}
@Test
fun `template context factory is created as singleton`() {
- val factory1 = injector.getInstance<TemplateContextFactory>()
- val factory2 = injector.getInstance<TemplateContextFactory>()
- assertThat(factory1, sameInstance(factory2))
+ injector.verifySingletonInstance<TemplateContextFactory>()
}
@Test
@Test
fun `page toadlet factory is created with correct prefix`() {
val page = mock<Page<FreenetRequest>>()
- assertThat(injector.getInstance<PageToadletFactory>().createPageToadlet(page).path(), startsWith("/Sone/"))
+ assertThat(injector.getInstance<PageToadletFactory>().createPageToadlet(page).path(), startsWith("/Sone/"))
}
@Test
fun `notification manager is created as singleton`() {
- val firstNotificationManager = injector.getInstance<NotificationManager>()
- val secondNotificationManager = injector.getInstance<NotificationManager>()
- assertThat(firstNotificationManager, sameInstance(secondNotificationManager))
- }
-
- @Test
- fun `notification handler can be created`() {
- assertThat(injector.getInstance<NotificationHandler>(), notNullValue())
- }
-
- @Test
- fun `notification handler is created as singleton`() {
- val firstNotificationHandler = injector.getInstance<NotificationHandler>()
- val secondNotificationHandler = injector.getInstance<NotificationHandler>()
- assertThat(firstNotificationHandler, sameInstance(secondNotificationHandler))
+ injector.verifySingletonInstance<NotificationManager>()
}
}
--- /dev/null
+/**
+ * Sone - ConfigNotReadHandler.kt - Copyright © 2019 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.util.notify.*
+import net.pterodactylus.util.template.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+
+/**
+ * Unit test for [ConfigNotReadHandler].
+ */
+@Suppress("UnstableApiUsage")
+class ConfigNotReadHandlerTest {
+
+ private val eventBus = EventBus()
+ private val notificationManager = NotificationManager()
+ private val notification = TemplateNotification("", Template())
+
+ init {
+ eventBus.register(ConfigNotReadHandler(notificationManager, notification))
+ }
+
+ @Test
+ fun `handler adds notification to manager when config was not read`() {
+ eventBus.post(ConfigNotRead())
+ assertThat(notificationManager.notifications, contains<Notification>(notification))
+ }
+
+}
--- /dev/null
+/**
+ * Sone - FirstStartHandlerTest.kt - Copyright © 2019 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.util.notify.*
+import net.pterodactylus.util.template.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import kotlin.test.*
+
+/**
+ * Unit test for [FirstStartHandler].
+ */
+@Suppress("UnstableApiUsage")
+class FirstStartHandlerTest {
+
+ private val eventBus = EventBus()
+ private val notificationManager = NotificationManager()
+ private val notification = TemplateNotification(Template())
+
+ init {
+ eventBus.register(FirstStartHandler(notificationManager, notification))
+ }
+
+ @Test
+ fun `handler can be created`() {
+ FirstStartHandler(notificationManager, notification)
+ }
+
+ @Test
+ fun `handler adds notification to manager on first start event`() {
+ eventBus.post(FirstStart())
+ assertThat(notificationManager.notifications, contains<Notification>(notification))
+ }
+
+}
--- /dev/null
+/**
+ * Sone - ImageInsertHandler.kt - Copyright © 2019 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 freenet.keys.FreenetURI.*
+import net.pterodactylus.sone.core.event.*
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.data.impl.*
+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 [ImageInsertHandler].
+ */
+@Suppress("UnstableApiUsage")
+class ImageInsertHandlerTest {
+
+ private val eventBus = EventBus()
+ private val notificationManager = NotificationManager()
+ private val imageInsertingNotification = ListNotification<Image>("", "", Template())
+ private val imageFailedNotification = ListNotification<Image>("", "", Template())
+ private val imageInsertedNotification = ListNotification<Image>("", "", Template())
+
+ init {
+ eventBus.register(ImageInsertHandler(notificationManager, imageInsertingNotification, imageFailedNotification, imageInsertedNotification))
+ }
+
+ @Test
+ fun `handler adds notification when image insert starts`() {
+ eventBus.post(ImageInsertStartedEvent(image))
+ assertThat(notificationManager.notifications, contains<Notification>(imageInsertingNotification))
+ }
+
+ @Test
+ fun `handler adds image to notification when image insert starts`() {
+ eventBus.post(ImageInsertStartedEvent(image))
+ assertThat(imageInsertingNotification.elements, contains(image))
+ }
+
+ @Test
+ fun `handler removes image from inserting notification when insert is aborted`() {
+ eventBus.post(ImageInsertStartedEvent(image))
+ eventBus.post(ImageInsertAbortedEvent(image))
+ assertThat(imageInsertingNotification.elements, emptyIterable())
+ }
+
+ @Test
+ fun `handler removes image from inserting notification when insert fails`() {
+ eventBus.post(ImageInsertStartedEvent(image))
+ eventBus.post(ImageInsertFailedEvent(image, Throwable()))
+ assertThat(imageInsertingNotification.elements, emptyIterable())
+ }
+
+ @Test
+ fun `handler adds image to insert-failed notification when insert fails`() {
+ eventBus.post(ImageInsertFailedEvent(image, Throwable()))
+ assertThat(imageFailedNotification.elements, contains(image))
+ }
+
+ @Test
+ fun `handler adds insert-failed notification to manager when insert fails`() {
+ eventBus.post(ImageInsertFailedEvent(image, Throwable()))
+ assertThat(notificationManager.notifications, contains<Notification>(imageFailedNotification))
+ }
+
+ @Test
+ fun `handler removes image from inserting notification when insert succeeds`() {
+ eventBus.post(ImageInsertStartedEvent(image))
+ eventBus.post(ImageInsertFinishedEvent(image, EMPTY_CHK_URI))
+ assertThat(imageInsertingNotification.elements, emptyIterable())
+ }
+
+ @Test
+ fun `handler adds image to inserted notification when insert succeeds`() {
+ eventBus.post(ImageInsertFinishedEvent(image, EMPTY_CHK_URI))
+ assertThat(imageInsertedNotification.elements, contains(image))
+ }
+
+ @Test
+ fun `handler adds inserted notification to manager when insert succeeds`() {
+ eventBus.post(ImageInsertFinishedEvent(image, EMPTY_CHK_URI))
+ assertThat(notificationManager.notifications, contains<Notification>(imageInsertedNotification))
+ }
+
+}
+
+private val image: Image = ImageImpl()
--- /dev/null
+/**
+ * Sone - NewLocalPostHandlerTest.kt - Copyright © 2019 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.impl.*
+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 [LocalPostHandler].
+ */
+class LocalPostHandlerTest {
+
+ private val eventBus = EventBus()
+ private val notificationManager = NotificationManager()
+ private val notification = ListNotification<Post>("", "", Template())
+
+ init {
+ eventBus.register(LocalPostHandler(notificationManager, notification))
+ }
+
+ @Test
+ fun `handler adds post by local sone to notification`() {
+ eventBus.post(NewPostFoundEvent(localPost))
+ assertThat(notification.elements, contains<Post>(localPost))
+ }
+
+ @Test
+ fun `handler does not add post by remote sone to notification`() {
+ eventBus.post(NewPostFoundEvent(remotePost))
+ assertThat(notification.elements, emptyIterable())
+ }
+
+ @Test
+ fun `handler does not add notification to manager for post by remote sone`() {
+ eventBus.post(NewPostFoundEvent(remotePost))
+ assertThat(notificationManager.notifications, not(hasItem<Notification>(notification)))
+ }
+
+ @Test
+ fun `handler adds notification to manager`() {
+ eventBus.post(NewPostFoundEvent(localPost))
+ assertThat(notificationManager.notifications, contains<Notification>(notification))
+ }
+
+ @Test
+ fun `handler does not add notification during first start`() {
+ notificationManager.firstStart()
+ eventBus.post(NewPostFoundEvent(localPost))
+ assertThat(notificationManager.notifications, not(hasItem<Notification>(notification)))
+ }
+
+ @Test
+ fun `handler removes post from notification when post is removed`() {
+ notification.add(localPost)
+ notificationManager.addNotification(notification)
+ eventBus.post(PostRemovedEvent(localPost))
+ assertThat(notification.elements, emptyIterable())
+ }
+
+ @Test
+ fun `handler does not remove remote post from notification when post is removed`() {
+ notification.add(remotePost)
+ notificationManager.addNotification(notification)
+ eventBus.post(PostRemovedEvent(remotePost))
+ assertThat(notification.elements, contains(remotePost))
+ }
+
+ @Test
+ fun `handler removes post from notification when post is marked as known`() {
+ notification.add(localPost)
+ notificationManager.addNotification(notification)
+ eventBus.post(MarkPostKnownEvent(localPost))
+ assertThat(notification.elements, emptyIterable())
+ }
+
+ @Test
+ fun `handler does not remove remote post from notification when post is marked as known`() {
+ notification.add(remotePost)
+ notificationManager.addNotification(notification)
+ eventBus.post(MarkPostKnownEvent(remotePost))
+ assertThat(notification.elements, contains(remotePost))
+ }
+
+}
+
+private val localSone: Sone = object : IdOnlySone("local-sone") {
+ override fun isLocal() = true
+}
+private val localPost: Post = object : Post.EmptyPost("local-post") {
+ override fun getSone() = localSone
+}
+private val remoteSone: Sone = IdOnlySone("remote-sone")
+private val remotePost: Post = object : Post.EmptyPost("remote-post") {
+ override fun getSone() = remoteSone
+}
--- /dev/null
+/**
+ * Sone - LocalReplyHandlerTest.kt - Copyright © 2019 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.data.*
+import net.pterodactylus.sone.notify.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.util.template.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import kotlin.test.*
+
+/**
+ * Unit test for [LocalReplyHandler].
+ */
+class LocalReplyHandlerTest {
+
+ private val notification = ListNotification<PostReply>("", "", Template())
+ private val localReplyHandlerTester = NotificationHandlerTester { LocalReplyHandler(it, notification) }
+
+ @Test
+ fun `handler does not add reply to notification`() {
+ localReplyHandlerTester.sendEvent(NewPostReplyFoundEvent(remoteReply))
+ assertThat(notification.elements, emptyIterable())
+ }
+
+ @Test
+ fun `handler does add local reply to notification`() {
+ localReplyHandlerTester.sendEvent(NewPostReplyFoundEvent(localReply))
+ assertThat(notification.elements, contains(localReply))
+ }
+
+ @Test
+ fun `handler adds notification to manager`() {
+ localReplyHandlerTester.sendEvent(NewPostReplyFoundEvent(localReply))
+ assertThat(localReplyHandlerTester.notifications, hasItem(notification))
+ }
+
+ @Test
+ fun `handler does not add notification to manager for remote reply`() {
+ localReplyHandlerTester.sendEvent(NewPostReplyFoundEvent(remoteReply))
+ assertThat(localReplyHandlerTester.notifications, not(hasItem(notification)))
+ }
+
+ @Test
+ fun `handler does not add notification to manager during first start`() {
+ localReplyHandlerTester.firstStart()
+ localReplyHandlerTester.sendEvent(NewPostReplyFoundEvent(localReply))
+ assertThat(localReplyHandlerTester.notifications, not(hasItem(notification)))
+ }
+
+ @Test
+ fun `handler removes reply from notification if reply is removed`() {
+ notification.add(localReply)
+ localReplyHandlerTester.sendEvent(PostReplyRemovedEvent(localReply))
+ assertThat(notification.elements, not(hasItem(localReply)))
+ }
+
+ @Test
+ fun `handler removes reply from notification if reply is marked as known`() {
+ notification.add(localReply)
+ localReplyHandlerTester.sendEvent(MarkPostReplyKnownEvent(localReply))
+ assertThat(notification.elements, not(hasItem(localReply)))
+ }
+
+}
+
+private val localReply = emptyPostReply(sone = localSone1)
+private val remoteReply = emptyPostReply()
--- /dev/null
+/**
+ * Sone - MarkPostKnownDuringFirstStartHandlerTest.kt - Copyright © 2019 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 org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import java.util.function.*
+import kotlin.test.*
+
+/**
+ * Unit test for [MarkPostKnownDuringFirstStartHandler].
+ */
+@Suppress("UnstableApiUsage")
+class MarkPostKnownDuringFirstStartHandlerTest {
+
+ private val eventBus = EventBus()
+ private val notificationManager = NotificationManager()
+ private val markedPosts = mutableListOf<Post>()
+ private val handler = MarkPostKnownDuringFirstStartHandler(notificationManager, Consumer { markedPosts += it })
+
+ init {
+ eventBus.register(handler)
+ }
+
+ @Test
+ fun `post is not marked as known if not during first start`() {
+ eventBus.post(NewPostFoundEvent(post))
+ assertThat(markedPosts, emptyIterable())
+ }
+
+ @Test
+ fun `new post is marked as known during first start`() {
+ notificationManager.firstStart()
+ eventBus.post(NewPostFoundEvent(post))
+ assertThat(markedPosts, contains(post))
+ }
+
+}
+
+private val post: Post = Post.EmptyPost("post")
--- /dev/null
+/**
+ * Sone - MarkPostReplyKnownDuringFirstStartHandlerTest.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.data.*
+import net.pterodactylus.sone.test.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import java.util.function.*
+import kotlin.test.*
+
+/**
+ * Unit test for [MarkPostReplyKnownDuringFirstStartHandler].
+ */
+class MarkPostReplyKnownDuringFirstStartHandlerTest {
+
+ private val markedAsKnown = mutableListOf<PostReply>()
+ private val notificationTester = NotificationHandlerTester { MarkPostReplyKnownDuringFirstStartHandler(it, Consumer { markedAsKnown += it }) }
+ private val postReply = emptyPostReply()
+
+ @Test
+ fun `post reply is marked as known on new reply during first start`() {
+ notificationTester.firstStart()
+ notificationTester.sendEvent(NewPostReplyFoundEvent(postReply))
+ assertThat(markedAsKnown, contains(postReply))
+ }
+
+ @Test
+ fun `post reply is not marked as known on new reply if not during first start`() {
+ notificationTester.sendEvent(NewPostReplyFoundEvent(postReply))
+ assertThat(markedAsKnown, not(hasItem(postReply)))
+ }
+
+}
--- /dev/null
+/**
+ * Sone - NewRemotePostHandlerTest.kt - Copyright © 2019 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.data.*
+import net.pterodactylus.sone.data.Post.*
+import net.pterodactylus.sone.data.impl.*
+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 [NewRemotePostHandler].
+ */
+@Suppress("UnstableApiUsage")
+class NewRemotePostHandlerTest {
+
+ private val notification = ListNotification<Post>("", "", Template())
+ private val remotePostHandlerTest = NotificationHandlerTester { NewRemotePostHandler(it, notification) }
+
+ @Test
+ fun `handler adds remote post to new-post notification`() {
+ remotePostHandlerTest.sendEvent(NewPostFoundEvent(remotePost))
+ assertThat(notification.elements, contains(remotePost))
+ }
+
+ @Test
+ fun `handler does not add local post to new-post notification`() {
+ remotePostHandlerTest.sendEvent(NewPostFoundEvent(localPost))
+ assertThat(notification.elements, emptyIterable())
+ }
+
+ @Test
+ fun `handler adds notification for remote post to notification manager`() {
+ remotePostHandlerTest.sendEvent(NewPostFoundEvent(remotePost))
+ assertThat(remotePostHandlerTest.notifications, contains<Notification>(notification))
+ }
+
+ @Test
+ fun `handler does not add notification for local post to notification manager`() {
+ remotePostHandlerTest.sendEvent(NewPostFoundEvent(localPost))
+ assertThat(remotePostHandlerTest.notifications, emptyIterable())
+ }
+
+ @Test
+ fun `handler does not add notification to notification manager during first start`() {
+ remotePostHandlerTest.firstStart()
+ remotePostHandlerTest.sendEvent(NewPostFoundEvent(remotePost))
+ assertThat(remotePostHandlerTest.notifications, not(hasItem(notification)))
+ }
+
+ @Test
+ fun `handler removes post from notification if post is removed`() {
+ notification.add(remotePost)
+ remotePostHandlerTest.sendEvent(PostRemovedEvent(remotePost))
+ assertThat(notification.elements, not(hasItem(remotePost)))
+ }
+
+ @Test
+ fun `handler removes post from notification if post is marked as known`() {
+ notification.add(remotePost)
+ remotePostHandlerTest.sendEvent(MarkPostKnownEvent(remotePost))
+ assertThat(notification.elements, not(hasItem(remotePost)))
+ }
+
+}
+
+private val remoteSone: Sone = IdOnlySone("remote-sone")
+private val remotePost: Post = object : EmptyPost("remote-post") {
+ override fun getSone() = remoteSone
+}
+
+private val localSone: Sone = object : IdOnlySone("local-sone") {
+ override fun isLocal() = true
+}
+private val localPost: Post = object : EmptyPost("local-post") {
+ override fun getSone() = localSone
+}
--- /dev/null
+/**
+ * Sone - NewSoneHandlerTest.kt - Copyright © 2019 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.impl.*
+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.*
+
+class NewSoneHandlerTest {
+
+ @Suppress("UnstableApiUsage")
+ private val eventBus = EventBus()
+ private val notificationManager = NotificationManager()
+ private val notification = ListNotification<Sone>("", "", Template())
+ private val handler = NewSoneHandler(notificationManager, notification)
+
+ init {
+ eventBus.register(handler)
+ }
+
+ @Test
+ fun `handler adds notification if new sone event is fired`() {
+ eventBus.post(NewSoneFoundEvent(sone))
+ assertThat(notificationManager.notifications, contains<Notification>(notification))
+ }
+
+ @Test
+ fun `handler adds sone to notification`() {
+ eventBus.post(NewSoneFoundEvent(sone))
+ assertThat(notification.elements, contains(sone))
+ }
+
+ @Test
+ fun `handler does not add notification on new sone event if first-start notification is present`() {
+ notificationManager.firstStart()
+ eventBus.post(NewSoneFoundEvent(sone))
+ assertThat(notificationManager.notifications, not(contains<Notification>(notification)))
+ }
+
+ @Test
+ fun `handler removes sone from notification if sone is marked as known`() {
+ notification.add(sone)
+ eventBus.post(MarkSoneKnownEvent(sone))
+ assertThat(notification.elements, emptyIterable())
+ }
+
+ @Test
+ fun `handler removes sone from notification if sone is removed`() {
+ notification.add(sone)
+ eventBus.post(SoneRemovedEvent(sone))
+ assertThat(notification.elements, emptyIterable())
+ }
+
+}
+
+private val sone: Sone = IdOnlySone("sone-id")
--- /dev/null
+/**
+ * Sone - NewVersionHandlerTest.kt - Copyright © 2019 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.util.notify.*
+import net.pterodactylus.util.template.*
+import net.pterodactylus.util.version.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import kotlin.test.*
+
+/**
+ * Unit test for [NewVersionHandler].
+ */
+@Suppress("UnstableApiUsage")
+class NewVersionHandlerTest {
+
+ private val eventBus = EventBus()
+ private val notificationManager = NotificationManager()
+ private val notification = TemplateNotification(Template())
+
+ init {
+ eventBus.register(NewVersionHandler(notificationManager, notification))
+ eventBus.post(UpdateFoundEvent(Version(1, 2, 3), 1000L, 2000L, true))
+ }
+
+ @Test
+ fun `new-version handler adds notification to manager on new version`() {
+ assertThat(notificationManager.notifications, contains<Notification>(notification))
+ }
+
+ @Test
+ fun `handler sets version in notification`() {
+ assertThat(notification.get("latestVersion"), equalTo<Any>(Version(1, 2, 3)))
+ }
+
+ @Test
+ fun `handler sets release time in notification`() {
+ assertThat(notification.get("releaseTime"), equalTo<Any>(1000L))
+ }
+
+ @Test
+ fun `handler sets edition in notification`() {
+ assertThat(notification.get("latestEdition"), equalTo<Any>(2000L))
+ }
+
+ @Test
+ fun `handler sets disruptive flag in notification`() {
+ assertThat(notification.get("disruptive"), equalTo<Any>(true))
+ }
+
+}
--- /dev/null
+/**
+ * Sone - NotificationHandlerModuleTest.kt - Copyright © 2019 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.inject.*
+import com.google.inject.Guice.*
+import com.google.inject.name.Names.*
+import net.pterodactylus.sone.core.*
+import net.pterodactylus.sone.core.event.*
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.data.Post.*
+import net.pterodactylus.sone.data.impl.*
+import net.pterodactylus.sone.database.*
+import net.pterodactylus.sone.freenet.wot.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.notify.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.text.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.util.notify.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.mockito.*
+import org.mockito.Mockito.*
+import java.util.concurrent.*
+import java.util.concurrent.TimeUnit.*
+import java.util.function.*
+import kotlin.test.*
+
+/**
+ * Unit test for [NotificationHandlerModule].
+ */
+class NotificationHandlerModuleTest {
+
+ private val core = mock<Core>()
+ private val webOfTrustConnector = mock<WebOfTrustConnector>()
+ private val ticker = mock<ScheduledExecutorService>()
+ private val notificationManager = NotificationManager()
+ private val loaders = TestLoaders()
+ private val injector: Injector = createInjector(
+ Core::class.isProvidedBy(core),
+ NotificationManager::class.isProvidedBy(notificationManager),
+ Loaders::class.isProvidedBy(loaders),
+ WebOfTrustConnector::class.isProvidedBy(webOfTrustConnector),
+ ScheduledExecutorService::class.withNameIsProvidedBy(ticker, "notification"),
+ SoneTextParser::class.isProvidedByMock(),
+ PostReplyProvider::class.isProvidedByMock(),
+ NotificationHandlerModule()
+ )
+
+ @Test
+ fun `notification handler is created as singleton`() {
+ injector.verifySingletonInstance<NotificationHandler>()
+ }
+
+ @Test
+ fun `mark-post-known-during-first-start handler is created as singleton`() {
+ injector.verifySingletonInstance<MarkPostKnownDuringFirstStartHandler>()
+ }
+
+ @Test
+ fun `mark-post-known-during-first-start handler is created with correct action`() {
+ notificationManager.firstStart()
+ val handler = injector.getInstance<MarkPostKnownDuringFirstStartHandler>()
+ val post = mock<Post>()
+ handler.newPostFound(NewPostFoundEvent(post))
+ verify(core).markPostKnown(post)
+ }
+
+ @Test
+ fun `mark-post-reply-known-during-first-start handler is created as singleton`() {
+ injector.verifySingletonInstance<MarkPostReplyKnownDuringFirstStartHandler>()
+ }
+
+ @Test
+ fun `mark-post-reply-known-during-first-start handler is created with correct action`() {
+ notificationManager.firstStart()
+ val handler = injector.getInstance<MarkPostReplyKnownDuringFirstStartHandler>()
+ val postReply = mock<PostReply>()
+ handler.newPostReply(NewPostReplyFoundEvent(postReply))
+ verify(core).markReplyKnown(postReply)
+ }
+
+ @Test
+ fun `sone-locked-on-startup handler is created as singleton`() {
+ injector.verifySingletonInstance<SoneLockedOnStartupHandler>()
+ }
+
+ @Test
+ fun `module can create sone-locked-on-startup notification with correct id`() {
+ val notification = injector.getInstance<ListNotification<Sone>>(named("soneLockedOnStartup"))
+ assertThat(notification.id, equalTo("sone-locked-on-startup"))
+ }
+
+ @Test
+ fun `sone-locked-on-startup notification is created as singleton`() {
+ injector.verifySingletonInstance<ListNotification<Sone>>(named("soneLockedOnStartup"))
+ }
+
+ @Test
+ fun `module can create sone-locked-on-startup notification with correct template and key`() {
+ loaders.templates += "/templates/notify/soneLockedOnStartupNotification.html" to "<% sones>".asTemplate()
+ val notification = injector.getInstance<ListNotification<Sone>>(named("soneLockedOnStartup"))
+ val sone1 = IdOnlySone("sone1")
+ val sone2 = IdOnlySone("sone2")
+ notification.add(sone1)
+ notification.add(sone2)
+ assertThat(notification.render(), equalTo(listOf(sone1, sone2).toString()))
+ }
+
+ @Test
+ fun `sone-locked-on-startup notification is dismissable`() {
+ assertThat(injector.getInstance<ListNotification<Sone>>(named("soneLockedOnStartup")).isDismissable, equalTo(true))
+ }
+
+ @Test
+ fun `new-sone handler is created as singleton`() {
+ injector.verifySingletonInstance<NewSoneHandler>()
+ }
+
+ @Test
+ fun `new-sone notification has correct ID`() {
+ assertThat(injector.getInstance<ListNotification<Sone>>(named("newSone")).id, equalTo("new-sone-notification"))
+ }
+
+ @Test
+ fun `new-sone notification has correct key and template`() {
+ loaders.templates += "/templates/notify/newSoneNotification.html" to "<% sones>".asTemplate()
+ val notification = injector.getInstance<ListNotification<Sone>>(named("newSone"))
+ val sones = listOf(IdOnlySone("sone1"), IdOnlySone("sone2"))
+ sones.forEach(notification::add)
+ assertThat(notification.render(), equalTo(sones.toString()))
+ }
+
+ @Test
+ fun `new-sone notification is not dismissable`() {
+ assertThat(injector.getInstance<ListNotification<Sone>>(named("newSone")).isDismissable, equalTo(false))
+ }
+
+ @Test
+ fun `new-remote-post handler is created as singleton`() {
+ injector.verifySingletonInstance<NewRemotePostHandler>()
+ }
+
+ @Test
+ fun `new-remote-post notification is created as singleton`() {
+ injector.verifySingletonInstance<ListNotification<Post>>(named("newRemotePost"))
+ }
+
+ @Test
+ fun `new-remote-post notification has correct ID`() {
+ assertThat(injector.getInstance<ListNotification<Post>>(named("newRemotePost")).id, equalTo("new-post-notification"))
+ }
+
+ @Test
+ fun `new-remote-post notification is not dismissable`() {
+ assertThat(injector.getInstance<ListNotification<Post>>(named("newRemotePost")).isDismissable, equalTo(false))
+ }
+
+ @Test
+ fun `new-remote-post notification has correct key and template`() {
+ loaders.templates += "/templates/notify/newPostNotification.html" to "<% posts>".asTemplate()
+ val notification = injector.getInstance<ListNotification<Post>>(named("newRemotePost"))
+ val posts = listOf(EmptyPost("post1"), EmptyPost("post2"))
+ posts.forEach(notification::add)
+ assertThat(notification.render(), equalTo(posts.toString()))
+ }
+
+ @Test
+ fun `remote-post handler is created as singleton`() {
+ injector.verifySingletonInstance<RemotePostReplyHandler>()
+ }
+
+ @Test
+ fun `new-remote-post-reply notification is created as singleton`() {
+ injector.verifySingletonInstance<ListNotification<PostReply>>(named("newRemotePostReply"))
+ }
+
+ @Test
+ fun `new-remote-post-reply notification has correct ID`() {
+ assertThat(injector.getInstance<ListNotification<PostReply>>(named("newRemotePostReply")).id, equalTo("new-reply-notification"))
+ }
+
+ @Test
+ fun `new-remote-post-reply notification is not dismissable`() {
+ assertThat(injector.getInstance<ListNotification<PostReply>>(named("newRemotePostReply")).isDismissable, equalTo(false))
+ }
+
+ @Test
+ fun `new-remote-post-reply notification has correct key and template`() {
+ loaders.templates += "/templates/notify/newReplyNotification.html" to "<% replies>".asTemplate()
+ val notification = injector.getInstance<ListNotification<PostReply>>(named("newRemotePostReply"))
+ val postReplies = listOf(emptyPostReply(), emptyPostReply())
+ postReplies.forEach(notification::add)
+ assertThat(notification.render(), equalTo(postReplies.toString()))
+ }
+
+ @Test
+ fun `sone-locked notification is created as singleton`() {
+ injector.verifySingletonInstance<ListNotification<Sone>>(named("soneLocked"))
+ }
+
+ @Test
+ fun `sone-locked notification is dismissable`() {
+ assertThat(injector.getInstance<ListNotification<Sone>>(named("soneLocked")).isDismissable, equalTo(true))
+ }
+
+ @Test
+ fun `sone-locked notification has correct ID`() {
+ assertThat(injector.getInstance<ListNotification<Sone>>(named("soneLocked")).id, equalTo("sones-locked-notification"))
+ }
+
+ @Test
+ fun `sone-locked notification has correct key and template`() {
+ loaders.templates += "/templates/notify/lockedSonesNotification.html" to "<% sones>".asTemplate()
+ val notification = injector.getInstance<ListNotification<Sone>>(named("soneLocked"))
+ val sones = listOf(IdOnlySone("sone1"), IdOnlySone("sone2"))
+ sones.forEach(notification::add)
+ assertThat(notification.render(), equalTo(sones.toString()))
+ }
+
+ @Test
+ fun `sone-locked handler is created as singleton`() {
+ injector.verifySingletonInstance<SoneLockedHandler>()
+ }
+
+ @Test
+ fun `local-post notification is not dismissable`() {
+ assertThat(injector.getInstance<ListNotification<Post>>(named("localPost")).isDismissable, equalTo(false))
+ }
+
+ @Test
+ fun `local-post notification has correct ID`() {
+ assertThat(injector.getInstance<ListNotification<Post>>(named("localPost")).id, equalTo("local-post-notification"))
+ }
+
+ @Test
+ fun `local-post notification has correct key and template`() {
+ loaders.templates += "/templates/notify/newPostNotification.html" to "<% posts>".asTemplate()
+ val notification = injector.getInstance<ListNotification<Post>>(named("localPost"))
+ val posts = listOf(EmptyPost("post1"), EmptyPost("post2"))
+ posts.forEach(notification::add)
+ assertThat(notification.render(), equalTo(posts.toString()))
+ }
+
+ @Test
+ fun `local-post notification is created as singleton`() {
+ injector.verifySingletonInstance<ListNotification<Post>>(named("localPost"))
+ }
+
+ @Test
+ fun `local-post handler is created as singleton`() {
+ injector.verifySingletonInstance<LocalPostHandler>()
+ }
+
+ @Test
+ fun `local-reply notification is not dismissable`() {
+ assertThat(injector.getInstance<ListNotification<PostReply>>(named("localReply")).isDismissable, equalTo(false))
+ }
+
+ @Test
+ fun `local-reply notification has correct ID`() {
+ assertThat(injector.getInstance<ListNotification<PostReply>>(named("localReply")).id, equalTo("local-reply-notification"))
+ }
+
+ @Test
+ fun `local-reply notification has correct key and template`() {
+ loaders.templates += "/templates/notify/newReplyNotification.html" to "<% replies>".asTemplate()
+ val notification = injector.getInstance<ListNotification<PostReply>>(named("localReply"))
+ val replies = listOf(emptyPostReply("reply1"), emptyPostReply("reply2"))
+ replies.forEach(notification::add)
+ assertThat(notification.render(), equalTo(replies.toString()))
+ }
+
+ @Test
+ fun `local-reply notification is created as singleton`() {
+ injector.verifySingletonInstance<ListNotification<PostReply>>(named("localReply"))
+ }
+
+ @Test
+ fun `local-reply handler is created as singleton`() {
+ injector.verifySingletonInstance<LocalReplyHandler>()
+ }
+
+ @Test
+ fun `new-version notification is created as singleton`() {
+ injector.verifySingletonInstance<TemplateNotification>(named("newVersion"))
+ }
+
+ @Test
+ fun `new-version notification has correct ID`() {
+ assertThat(injector.getInstance<TemplateNotification>(named("newVersion")).id, equalTo("new-version-notification"))
+ }
+
+ @Test
+ fun `new-version notification is dismissable`() {
+ assertThat(injector.getInstance<TemplateNotification>(named("newVersion")).isDismissable, equalTo(true))
+ }
+
+ @Test
+ fun `new-version notification loads correct template`() {
+ loaders.templates += "/templates/notify/newVersionNotification.html" to "1".asTemplate()
+ val notification = injector.getInstance<TemplateNotification>(named("newVersion"))
+ assertThat(notification.render(), equalTo("1"))
+ }
+
+ @Test
+ fun `new-version handler is created as singleton`() {
+ injector.verifySingletonInstance<NewVersionHandler>()
+ }
+
+ @Test
+ fun `inserting-image notification is created as singleton`() {
+ injector.verifySingletonInstance<ListNotification<Image>>(named("imageInserting"))
+ }
+
+ @Test
+ fun `inserting-image notification has correct ID`() {
+ assertThat(injector.getInstance<ListNotification<Image>>(named("imageInserting")).id, equalTo("inserting-images-notification"))
+ }
+
+ @Test
+ fun `inserting-image notification is dismissable`() {
+ assertThat(injector.getInstance<ListNotification<Image>>(named("imageInserting")).isDismissable, equalTo(true))
+ }
+
+ @Test
+ fun `inserting-image notification loads correct template`() {
+ loaders.templates += "/templates/notify/inserting-images-notification.html" to "<% images>".asTemplate()
+ val notification = injector.getInstance<ListNotification<Image>>(named("imageInserting"))
+ val images = listOf(ImageImpl(), ImageImpl()).onEach(notification::add)
+ assertThat(notification.render(), equalTo(images.toString()))
+ }
+
+ @Test
+ fun `inserting-image-failed notification is created as singleton`() {
+ injector.verifySingletonInstance<ListNotification<Image>>(named("imageFailed"))
+ }
+
+ @Test
+ fun `inserting-image-failed notification has correct ID`() {
+ assertThat(injector.getInstance<ListNotification<Image>>(named("imageFailed")).id, equalTo("image-insert-failed-notification"))
+ }
+
+ @Test
+ fun `inserting-image-failed notification is dismissable`() {
+ assertThat(injector.getInstance<ListNotification<Image>>(named("imageFailed")).isDismissable, equalTo(true))
+ }
+
+ @Test
+ fun `inserting-image-failed notification loads correct template`() {
+ loaders.templates += "/templates/notify/image-insert-failed-notification.html" to "<% images>".asTemplate()
+ val notification = injector.getInstance<ListNotification<Image>>(named("imageFailed"))
+ val images = listOf(ImageImpl(), ImageImpl()).onEach(notification::add)
+ assertThat(notification.render(), equalTo(images.toString()))
+ }
+
+ @Test
+ fun `inserted-image notification is created as singleton`() {
+ injector.verifySingletonInstance<ListNotification<Image>>(named("imageInserted"))
+ }
+
+ @Test
+ fun `inserted-image notification has correct ID`() {
+ assertThat(injector.getInstance<ListNotification<Image>>(named("imageInserted")).id, equalTo("inserted-images-notification"))
+ }
+
+ @Test
+ fun `inserted-image notification is dismissable`() {
+ assertThat(injector.getInstance<ListNotification<Image>>(named("imageInserted")).isDismissable, equalTo(true))
+ }
+
+ @Test
+ fun `inserted-image notification loads correct template`() {
+ loaders.templates += "/templates/notify/inserted-images-notification.html" to "<% images>".asTemplate()
+ val notification = injector.getInstance<ListNotification<Image>>(named("imageInserted"))
+ val images = listOf(ImageImpl(), ImageImpl()).onEach(notification::add)
+ assertThat(notification.render(), equalTo(images.toString()))
+ }
+
+ @Test
+ fun `image insert handler is created as singleton`() {
+ injector.verifySingletonInstance<ImageInsertHandler>()
+ }
+
+ @Test
+ fun `first-start notification is created as singleton`() {
+ injector.verifySingletonInstance<TemplateNotification>(named("firstStart"))
+ }
+
+ @Test
+ fun `first-start notification has correct ID`() {
+ assertThat(injector.getInstance<TemplateNotification>(named("firstStart")).id, equalTo("first-start-notification"))
+ }
+
+ @Test
+ fun `first-start notification is dismissable`() {
+ assertThat(injector.getInstance<TemplateNotification>(named("firstStart")).isDismissable, equalTo(true))
+ }
+
+ @Test
+ fun `first-start notification loads correct template`() {
+ loaders.templates += "/templates/notify/firstStartNotification.html" to "1".asTemplate()
+ val notification = injector.getInstance<TemplateNotification>(named("firstStart"))
+ assertThat(notification.render(), equalTo("1"))
+ }
+
+ @Test
+ fun `first-start handler is created as singleton`() {
+ injector.verifySingletonInstance<FirstStartHandler>()
+ }
+
+ @Test
+ fun `config-not-read notification is created as singleton`() {
+ injector.verifySingletonInstance<TemplateNotification>(named("configNotRead"))
+ }
+
+ @Test
+ fun `config-not-read notification has correct ID `() {
+ assertThat(injector.getInstance<TemplateNotification>(named("configNotRead")).id, equalTo("config-not-read-notification"))
+ }
+
+ @Test
+ fun `config-not-read notification is dismissable`() {
+ assertThat(injector.getInstance<TemplateNotification>(named("configNotRead")).isDismissable, equalTo(true))
+ }
+
+ @Test
+ fun `config-not-read notification loads correct template`() {
+ loaders.templates += "/templates/notify/configNotReadNotification.html" to "1".asTemplate()
+ val notification = injector.getInstance<TemplateNotification>(named("configNotRead"))
+ assertThat(notification.render(), equalTo("1"))
+ }
+
+ @Test
+ fun `config-not-read handler is created as singleton`() {
+ injector.verifySingletonInstance<ConfigNotReadHandler>()
+ }
+
+ @Test
+ fun `startup notification can be created`() {
+ injector.verifySingletonInstance<TemplateNotification>(named("startup"))
+ }
+
+ @Test
+ fun `startup notification has correct ID`() {
+ assertThat(injector.getInstance<TemplateNotification>(named("startup")).id, equalTo("startup-notification"))
+ }
+
+ @Test
+ fun `startup notification is dismissable`() {
+ assertThat(injector.getInstance<TemplateNotification>(named("startup")).isDismissable, equalTo(true))
+ }
+
+ @Test
+ fun `startup notification loads correct template`() {
+ loaders.templates += "/templates/notify/startupNotification.html" to "1".asTemplate()
+ val notification = injector.getInstance<TemplateNotification>(named("startup"))
+ assertThat(notification.render(), equalTo("1"))
+ }
+
+ @Test
+ fun `startup handler is created as singleton`() {
+ injector.verifySingletonInstance<StartupHandler>()
+ }
+
+ @Test
+ fun `web-of-trust notification is created as singleton`() {
+ injector.verifySingletonInstance<TemplateNotification>(named("webOfTrust"))
+ }
+
+ @Test
+ fun `web-of-trust notification has correct ID`() {
+ assertThat(injector.getInstance<TemplateNotification>(named("webOfTrust")).id, equalTo("wot-missing-notification"))
+ }
+
+ @Test
+ fun `web-of-trust notification is dismissable`() {
+ assertThat(injector.getInstance<TemplateNotification>(named("webOfTrust")).isDismissable, equalTo(true))
+ }
+
+ @Test
+ fun `web-of-trust notification loads correct template`() {
+ loaders.templates += "/templates/notify/wotMissingNotification.html" to "1".asTemplate()
+ val notification = injector.getInstance<TemplateNotification>(named("webOfTrust"))
+ assertThat(notification.render(), equalTo("1"))
+ }
+
+ @Test
+ fun `web-of-trust handler is created as singleton`() {
+ injector.verifySingletonInstance<TemplateNotification>(named("webOfTrust"))
+ }
+
+ @Test
+ fun `web-of-trust reacher is created as singleton`() {
+ injector.verifySingletonInstance<Runnable>(named("webOfTrustReacher"))
+ }
+
+ @Test
+ fun `web-of-trust reacher access the wot connector`() {
+ injector.getInstance<Runnable>(named("webOfTrustReacher")).run()
+ verify(webOfTrustConnector).ping()
+ }
+
+ @Test
+ fun `web-of-trust reschedule is created as singleton`() {
+ injector.verifySingletonInstance<Consumer<Runnable>>(named("webOfTrustReschedule"))
+ }
+
+ @Test
+ fun `web-of-trust reschedule schedules at the correct delay`() {
+ val webOfTrustPinger = injector.getInstance<WebOfTrustPinger>()
+ injector.getInstance<Consumer<Runnable>>(named("webOfTrustReschedule"))(webOfTrustPinger)
+ verify(ticker).schedule(ArgumentMatchers.eq(webOfTrustPinger), ArgumentMatchers.eq(15L), ArgumentMatchers.eq(SECONDS))
+ }
+
+ @Test
+ fun `sone mention detector is created as singleton`() {
+ 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>()
+ }
+
+ @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 - NotificationHandlerTest.kt - Copyright © 2019 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 com.google.inject.*
-import com.google.inject.Guice.*
-import net.pterodactylus.sone.main.*
-import net.pterodactylus.sone.test.*
-import net.pterodactylus.util.notify.*
-import net.pterodactylus.util.template.*
-import net.pterodactylus.util.web.*
-import org.hamcrest.MatcherAssert.*
-import org.hamcrest.Matchers.*
-import kotlin.test.*
-
-/**
- * Unit test for [NotificationHandler].
- */
-class NotificationHandlerTest {
-
- private val eventBus = TestEventBus()
- private val loaders = TestLoaders()
- private val notificationManager = NotificationManager()
- private val handler = NotificationHandler(eventBus, loaders, notificationManager)
-
- @Test
- fun `notification handler can be created by guice`() {
- val injector = createInjector(
- EventBus::class.isProvidedBy(eventBus),
- NotificationManager::class.isProvidedBy(notificationManager),
- Loaders::class.isProvidedBy(loaders)
- )
- assertThat(injector.getInstance<NotificationHandler>(), notNullValue())
- }
-
- @Test
- fun `notification handler registers handler for sone-locked event`() {
- handler.start()
- assertThat(eventBus.registeredObjects.any { it.javaClass == SoneLockedOnStartupHandler::class.java }, equalTo(true))
- }
-
- @Test
- fun `notification handler loads sone-locked notification template`() {
- handler.start()
- assertThat(loaders.requestedTemplatePaths.any { it == "/templates/notify/soneLockedOnStartupNotification.html" }, equalTo(true))
- }
-
-}
-
-@Suppress("UnstableApiUsage")
-private class TestEventBus : EventBus() {
- private val _registeredObjects = mutableListOf<Any>()
- val registeredObjects: List<Any>
- get() = _registeredObjects
-
- override fun register(`object`: Any) {
- super.register(`object`)
- _registeredObjects += `object`
- }
-
-}
-
-private class TestLoaders : Loaders {
- val requestedTemplatePaths = mutableListOf<String>()
-
- override fun loadTemplate(path: String) =
- Template().also { requestedTemplatePaths += path }
-
- override fun <REQ : Request> loadStaticPage(basePath: String, prefix: String, mimeType: String) = object : Page<REQ> {
-
- override fun getPath() = ""
- override fun isPrefixPage() = false
- override fun handleRequest(request: REQ, response: Response) = response
-
- }
-
- override fun getTemplateProvider() = TemplateProvider { _, _ -> Template() }
-
-}
--- /dev/null
+/**
+ * Sone - NotificationHandlerTester.kt - Copyright © 2019 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.util.notify.*
+
+/**
+ * Helper for testing event handlers that deal with notifications. It contains
+ * a notification manager and an [event bus][EventBus] and automatically
+ * registers the created handler on the event bus.
+ *
+ * ```
+ * val notification = SomeNotification()
+ * val notificationTester = NotificationTester { SomeHandler(it, notification) }
+ *
+ * fun test() {
+ * notificationTester.sendEvent(SomeEvent())
+ * assertThat(notificationTester.elements, hasItem(notification))
+ * }
+ * ```
+ */
+@Suppress("UnstableApiUsage")
+class NotificationHandlerTester(createHandler: (NotificationManager) -> Any) {
+
+ private val eventBus = EventBus()
+ private val notificationManager = NotificationManager()
+
+ /** Returns all notifications of the notification manager. */
+ val notifications: Set<Notification>
+ get() = notificationManager.notifications
+
+ init {
+ eventBus.register(createHandler(notificationManager))
+ }
+
+ /** Sends an event to the event bus. */
+ fun sendEvent(event: Any) = eventBus.post(event)
+
+ /** Sets the first-start notification on the notification manager. */
+ fun firstStart() = notificationManager.firstStart()
+
+}
--- /dev/null
+/**
+ * Sone - RemotePostReplyHandler.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.data.*
+import net.pterodactylus.sone.notify.*
+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 [RemotePostReplyHandler].
+ */
+class RemotePostReplyHandlerTest {
+
+ private val notification = ListNotification<PostReply>("", "", Template())
+ private val notificationHandlerTester = NotificationHandlerTester { RemotePostReplyHandler(it, notification) }
+ private val postReply = emptyPostReply()
+
+ @Test
+ fun `reply is added to notification on new reply`() {
+ notificationHandlerTester.sendEvent(NewPostReplyFoundEvent(postReply))
+ assertThat(notification.elements, hasItem<PostReply>(postReply))
+ }
+
+ @Test
+ fun `notification is added to manager on new reply`() {
+ notificationHandlerTester.sendEvent(NewPostReplyFoundEvent(postReply))
+ assertThat(notificationHandlerTester.notifications, hasItem<Notification>(notification))
+ }
+
+ @Test
+ fun `reply is not added to notification on new reply during first start`() {
+ notificationHandlerTester.firstStart()
+ notificationHandlerTester.sendEvent(NewPostReplyFoundEvent(postReply))
+ assertThat(notification.elements, not(hasItem<PostReply>(postReply)))
+ }
+
+ @Test
+ fun `notification is not added to manager on new reply during first start`() {
+ notificationHandlerTester.firstStart()
+ notificationHandlerTester.sendEvent(NewPostReplyFoundEvent(postReply))
+ assertThat(notificationHandlerTester.notifications, not(hasItem<Notification>(notification)))
+ }
+
+ @Test
+ fun `reply is not added to notification on new local reply`() {
+ val postReply = emptyPostReply(sone = localSone1)
+ notificationHandlerTester.sendEvent(NewPostReplyFoundEvent(postReply))
+ assertThat(notification.elements, not(hasItem<PostReply>(postReply)))
+ }
+
+ @Test
+ fun `notification is not added to manager on new local reply`() {
+ val postReply = emptyPostReply(sone = localSone1)
+ notificationHandlerTester.sendEvent(NewPostReplyFoundEvent(postReply))
+ assertThat(notificationHandlerTester.notifications, not(hasItem<Notification>(notification)))
+ }
+
+ @Test
+ fun `reply is removed from notification when removed`() {
+ notification.add(postReply)
+ notificationHandlerTester.sendEvent(PostReplyRemovedEvent(postReply))
+ assertThat(notification.elements, not(hasItem<PostReply>(postReply)))
+ }
+
+ @Test
+ fun `reply is removed from notification when marked as known`() {
+ notification.add(postReply)
+ notificationHandlerTester.sendEvent(MarkPostReplyKnownEvent(postReply))
+ assertThat(notification.elements, not(hasItem<PostReply>(postReply)))
+ }
+
+}
--- /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)))
+ }
+
+}
--- /dev/null
+/**
+ * Sone - SoneLockedHandlerTest.kt - Copyright © 2019 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.impl.*
+import net.pterodactylus.sone.notify.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.util.notify.*
+import net.pterodactylus.util.template.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import java.util.concurrent.*
+import kotlin.test.*
+
+/**
+ * Unit test for [SoneLockedHandler].
+ */
+@Suppress("UnstableApiUsage")
+class SoneLockedHandlerTest {
+
+ private val eventBus = EventBus()
+ private val notificationManager = NotificationManager()
+ private val notification = ListNotification<Sone>("", "", Template())
+ private val executor = TestScheduledThreadPoolExecutor()
+
+ init {
+ SoneLockedHandler(notificationManager, notification, executor).also(eventBus::register)
+ }
+
+ @AfterTest
+ fun shutdownExecutor() = executor.shutdown()
+
+ @Test
+ fun `notification is not added before the command is run`() {
+ eventBus.post(SoneLockedEvent(sone))
+ assertThat(notificationManager.notifications, emptyIterable())
+ }
+
+ @Test
+ fun `sone is added to notification immediately`() {
+ eventBus.post(SoneLockedEvent(sone))
+ assertThat(notification.elements, contains(sone))
+ }
+
+ @Test
+ fun `notification is added to notification manager from command`() {
+ eventBus.post(SoneLockedEvent(sone))
+ executor.scheduleds.single().command()
+ assertThat(notificationManager.notifications, contains<Any>(notification))
+ }
+
+ @Test
+ fun `command is registered with a delay of five minutes`() {
+ eventBus.post(SoneLockedEvent(sone))
+ with(executor.scheduleds.single()) {
+ assertThat(timeUnit.toNanos(delay), equalTo(TimeUnit.MINUTES.toNanos(5)))
+ }
+ }
+
+ @Test
+ fun `unlocking sone after locking will cancel the future`() {
+ eventBus.post(SoneLockedEvent(sone))
+ eventBus.post(SoneUnlockedEvent(sone))
+ assertThat(executor.scheduleds.first().future.isCancelled, equalTo(true))
+ }
+
+ @Test
+ fun `unlocking sone after locking will remove the sone from the notification`() {
+ eventBus.post(SoneLockedEvent(sone))
+ eventBus.post(SoneUnlockedEvent(sone))
+ assertThat(notification.elements, emptyIterable())
+ }
+
+ @Test
+ fun `unlocking sone after showing the notification will remove the sone from the notification`() {
+ eventBus.post(SoneLockedEvent(sone))
+ executor.scheduleds.single().command()
+ eventBus.post(SoneUnlockedEvent(sone))
+ assertThat(notification.elements, emptyIterable())
+ }
+
+ @Test
+ fun `locking two sones will cancel the first command`() {
+ eventBus.post(SoneLockedEvent(sone))
+ eventBus.post(SoneLockedEvent(sone))
+ assertThat(executor.scheduleds.first().future.isCancelled, equalTo(true))
+ }
+
+ @Test
+ fun `locking two sones will schedule a second command`() {
+ eventBus.post(SoneLockedEvent(sone))
+ eventBus.post(SoneLockedEvent(sone))
+ assertThat(executor.scheduleds[1], notNullValue())
+ }
+
+}
+
+private val sone: Sone = IdOnlySone("sone")
import com.google.common.eventbus.*
import net.pterodactylus.sone.core.event.*
+import net.pterodactylus.sone.data.*
import net.pterodactylus.sone.data.impl.*
import net.pterodactylus.sone.notify.*
-import net.pterodactylus.sone.utils.*
import net.pterodactylus.util.notify.*
+import net.pterodactylus.util.template.*
import org.hamcrest.MatcherAssert.*
import org.hamcrest.Matchers.*
import kotlin.test.*
@Suppress("UnstableApiUsage")
private val eventBus = EventBus()
private val manager = NotificationManager()
- private val notification by lazy { manager.notifications.single() as ListNotification<*> }
+ private val notification = ListNotification<Sone>("", "", Template())
init {
- SoneLockedOnStartupHandler(manager, template).also(eventBus::register)
- eventBus.post(SoneLockedOnStartup(sone))
- }
-
- @Test
- fun `notification has correct id`() {
- assertThat(notification.id, equalTo("sone-locked-on-startup"))
+ SoneLockedOnStartupHandler(manager, notification).also(eventBus::register)
}
@Test
fun `handler adds sone to notification when event is posted`() {
+ eventBus.post(SoneLockedOnStartup(sone))
assertThat(notification.elements, contains<Any>(sone))
}
@Test
- fun `handler creates notification with correct key`() {
- assertThat(notification.render(), equalTo(listOf(sone).toString()))
+ fun `handler adds notification to manager`() {
+ eventBus.post(SoneLockedOnStartup(sone))
+ assertThat(manager.notifications, contains<Notification>(notification))
}
}
private val sone = IdOnlySone("sone-id")
-private val template = "<% sones>".asTemplate()
--- /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("")
--- /dev/null
+/**
+ * Sone - StartupHandlerTest.kt - Copyright © 2019 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.utils.*
+import net.pterodactylus.util.notify.*
+import net.pterodactylus.util.template.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import java.util.concurrent.TimeUnit.*
+import kotlin.test.*
+
+/**
+ * Unit test for [StartupHandler].
+ */
+class StartupHandlerTest {
+
+ private val eventBus = EventBus()
+ private val notificationManager = NotificationManager()
+ private val notification = TemplateNotification("", Template())
+ private val executor = TestScheduledThreadPoolExecutor()
+
+ init {
+ eventBus.register(StartupHandler(notificationManager, notification, executor))
+ }
+
+ @AfterTest
+ fun shutdownExecutor() = executor.shutdown()
+
+ @Test
+ fun `handler adds notification to manager on startup`() {
+ eventBus.post(Startup())
+ assertThat(notificationManager.notifications, contains<Notification>(notification))
+ }
+
+ @Test
+ fun `handler registers command on with 2-minute delay`() {
+ eventBus.post(Startup())
+ assertThat(with(executor.scheduleds.single()) { timeUnit.toNanos(delay) }, equalTo(MINUTES.toNanos(2)))
+ }
+
+ @Test
+ fun `registered command removes notification from manager`() {
+ eventBus.post(Startup())
+ executor.scheduleds.single().command()
+ assertThat(notificationManager.notifications, emptyIterable())
+ }
+
+}
--- /dev/null
+/**
+ * Sone - Testing.kt - Copyright © 2019 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.util.notify.*
+import java.io.*
+import java.util.concurrent.*
+
+/** Information about a scheduled runnable. */
+data class Scheduled(val command: Runnable, val delay: Long, val timeUnit: TimeUnit, val future: ScheduledFuture<*>)
+
+/**
+ * [ScheduledThreadPoolExecutor] extension that stores parameters and return
+ * values for the [ScheduledThreadPoolExecutor.schedule] method.
+ */
+class TestScheduledThreadPoolExecutor : ScheduledThreadPoolExecutor(1) {
+
+ val scheduleds = mutableListOf<Scheduled>()
+
+ override fun schedule(command: Runnable, delay: Long, unit: TimeUnit): ScheduledFuture<*> =
+ super.schedule(command, delay, unit)
+ .also { scheduleds += Scheduled(command, delay, unit, it) }
+
+}
+
+fun NotificationManager.firstStart() {
+ addNotification(object : AbstractNotification("first-start-notification") {
+ override fun render(writer: Writer?) = Unit
+ })
+}
--- /dev/null
+/**
+ * Sone - WebOfTrustHandlerTest.kt - Copyright © 2019 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.util.notify.*
+import net.pterodactylus.util.template.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import kotlin.test.*
+
+/**
+ * Unit test for [WebOfTrustHandler].
+ */
+class WebOfTrustHandlerTest {
+
+ private val eventBus = EventBus()
+ private val notificationManager = NotificationManager()
+ private val notification = TemplateNotification("", Template())
+
+ init {
+ eventBus.register(WebOfTrustHandler(notificationManager, notification))
+ }
+
+ @Test
+ fun `handler adds notification if wot goes down`() {
+ eventBus.post(WebOfTrustDisappeared())
+ assertThat(notificationManager.notifications, contains<Notification>(notification))
+ }
+
+ @Test
+ fun `handler removes notification if wot appears`() {
+ notificationManager.addNotification(notification)
+ eventBus.post(WebOfTrustAppeared())
+ assertThat(notificationManager.notifications, emptyIterable())
+ }
+
+}