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;
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;
/** 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("localPost") ListNotification<Post> localPostNotification) {
this.sonePlugin = sonePlugin;
this.loaders = loaders;
this.listNotificationFilter = listNotificationFilter;
this.l10nFilter = l10nFilter;
this.translation = translation;
this.notificationManager = notificationManager;
+ this.newPostNotification = newPostNotification;
+ this.localPostNotification = localPostNotification;
formPassword = sonePlugin.pluginRespirator().getToadletContainer().getFormPassword();
soneTextParser = new SoneTextParser(getCore(), getCore());
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 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(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
//
*/
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();
}
//
//
/**
- * 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
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 {@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 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);
}
}
/**
- * 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
}
}
- /**
- * 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 - 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
--- /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(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
+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(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(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 - 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 - 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)
+ }
+ }
+ }
+
+}
--- /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 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,
+ newSoneHandler: NewSoneHandler,
+ newRemotePostHandler: NewRemotePostHandler,
+ soneLockedOnStartupHandler: SoneLockedOnStartupHandler,
+ soneLockedHandler: SoneLockedHandler,
+ newVersionHandler: NewVersionHandler,
+ imageInsertHandler: ImageInsertHandler,
+ firstStartHandler: FirstStartHandler,
+ configNotReadHandler: ConfigNotReadHandler,
+ startupHandler: StartupHandler,
+ webOfTrustPinger: WebOfTrustPinger,
+ webOfTrustHandler: WebOfTrustHandler
+)
--- /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.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<SoneLockedOnStartupHandler>().asSingleton()
+ bind<NewSoneHandler>().asSingleton()
+ bind<NewRemotePostHandler>().asSingleton()
+ bind<SoneLockedHandler>().asSingleton()
+ bind<LocalPostHandler>().asSingleton()
+ bind<NewVersionHandler>().asSingleton()
+ bind<ImageInsertHandler>().asSingleton()
+ bind<FirstStartHandler>().asSingleton()
+ bind<ConfigNotReadHandler>().asSingleton()
+ bind<StartupHandler>().asSingleton()
+ bind<WebOfTrustHandler>().asSingleton()
+ }
+
+ @Provides
+ fun getMarkPostKnownHandler(core: Core): Consumer<Post> = Consumer { core.markPostKnown(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("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("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) }
+
+ private inline fun <reified T> bind(): AnnotatedBindingBuilder<T> = bind(T::class.java)
+ private fun ScopedBindingBuilder.asSingleton() = `in`(Singleton::class.java)
+
+}
--- /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 - 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(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(webOfTrustAppeared: WebOfTrustAppeared) {
+ notificationManager.removeNotification(notification)
+ }
+
+ @Subscribe
+ fun webOfTrustDisappeared(webOfTrustDisappeared: WebOfTrustDisappeared) {
+ notificationManager.addNotification(notification)
+ }
+
+}
Notification.SoneIsInserting.Text=Ihre Sone sone://{0} wird jetzt hoch geladen.
Notification.SoneIsInserted.Text=Ihre Sone sone://{0} wurde in {1,number} {1,choice,0#Sekunden|1#Sekunde|1<Sekunden} hoch geladen.
Notification.SoneInsertAborted.Text=Ihre Sone sone://{0} konnte nicht hoch geladen werden.
-Notification.SoneLockedOnStartup.Text=Einige Sones wurden beim Starten gesperrt, weil sie keine Inhalte enthielten. Versionen vor v81 hatten einen Fehler, der Sones ohne Inhalte zur Folge hatte. Um zu verhindern, dass solchermaßen defekte Sones hoch geladen werden, wurden sie automatisch gesperrt. Bitte überprüfen Sie Ihre Sones, benutzen Sie den Sonerettungsmodus und entsperren Sie die Sones manuell, wenn Sie mit den Inhalten Ihrer Sones zufrieden sind. Die gesperrten Sones sind:
+Notification.SoneLockedOnStartup.Text=Versionen vor v81 hatten einen Fehler, der Sones ohne Inhalte zur Folge hatte. Um zu verhindern, dass solchermaßen defekte Sones hoch geladen werden, wurden sie automatisch gesperrt. Bitte überprüfen Sie Ihre Sones, benutzen Sie den Sonerettungsmodus und entsperren Sie die Sones manuell, wenn Sie mit den Inhalten Ihrer Sones zufrieden sind. Die gesperrten Sones sind:
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
--- /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(webOfTrustAppeared: WebOfTrustAppeared) {
+ received()
+ }
+}
+
+private class WebOfTrustDisappearedCatcher(private val received: () -> Unit) {
+ @Subscribe
+ fun webOfTrustDisappeared(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 org.hamcrest.Matchers.*
import org.mockito.Mockito.*
import java.io.*
+import java.util.concurrent.*
import java.util.concurrent.atomic.*
import kotlin.test.*
@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 org.hamcrest.MatcherAssert.*
import org.hamcrest.Matchers.*
import org.mockito.Mockito.*
+import java.io.*
+import java.util.concurrent.atomic.*
import kotlin.test.*
/**
@Dirty
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(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(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(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(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
+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
+
+}
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 java.io.*
+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 adds notification to manager`() {
+ eventBus.post(NewPostFoundEvent(remotePost))
+ assertThat(notificationManager.notifications, contains<Notification>(notification))
+ }
+
+ @Test
+ fun `handler does not add notification during first start`() {
+ notificationManager.addNotification(object : AbstractNotification("first-start-notification") {
+ override fun render(writer: Writer?) = Unit
+ })
+ eventBus.post(NewPostFoundEvent(remotePost))
+ 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 - 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.io.*
+import java.util.function.Consumer
+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.addNotification(object : AbstractNotification("first-start-notification") {
+ override fun render(writer: Writer?) = Unit
+ })
+ eventBus.post(NewPostFoundEvent(post))
+ assertThat(markedPosts, contains(post))
+ }
+
+}
+
+private val post: Post = Post.EmptyPost("post")
--- /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 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.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 java.io.*
+import kotlin.test.*
+
+/**
+ * Unit test for [NewRemotePostHandler].
+ */
+@Suppress("UnstableApiUsage")
+class NewRemotePostHandlerTest {
+
+ private val eventBus = EventBus()
+ private val notificationManager = NotificationManager()
+ private val notification = ListNotification<Post>("", "", Template())
+ private val handler = NewRemotePostHandler(notificationManager, notification)
+
+ init {
+ eventBus.register(handler)
+ }
+
+ @Test
+ fun `handler adds remote post to new-post notification`() {
+ eventBus.post(NewPostFoundEvent(remotePost))
+ assertThat(notification.elements, contains(remotePost))
+ }
+
+ @Test
+ fun `handler does not add local post to new-post notification`() {
+ eventBus.post(NewPostFoundEvent(localPost))
+ assertThat(notification.elements, emptyIterable())
+ }
+
+ @Test
+ fun `handler adds notification for remote post to notification manager`() {
+ eventBus.post(NewPostFoundEvent(remotePost))
+ assertThat(notificationManager.notifications, contains<Notification>(notification))
+ }
+
+ @Test
+ fun `handler does not add notification for local post to notification manager`() {
+ eventBus.post(NewPostFoundEvent(localPost))
+ assertThat(notificationManager.notifications, emptyIterable())
+ }
+
+ @Test
+ fun `handler does not add notification to notification manager during first start`() {
+ notificationManager.addNotification(object : AbstractNotification("first-start-notification") {
+ override fun render(writer: Writer?) = Unit
+ })
+ eventBus.post(NewPostFoundEvent(remotePost))
+ assertThat(notificationManager.notifications, not(hasItem(notification)))
+ }
+
+}
+
+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 java.io.*
+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.addNotification(object : AbstractNotification("first-start-notification") {
+ override fun render(writer: Writer) = Unit
+ })
+ eventBus.post(NewSoneFoundEvent(sone))
+ assertThat(notificationManager.notifications.single().id, equalTo("first-start-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.freenet.wot.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.notify.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.util.notify.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers
+import org.hamcrest.Matchers.*
+import org.mockito.*
+import org.mockito.Mockito.*
+import java.io.*
+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"),
+ 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.addNotification(object : AbstractNotification("first-start-notification") {
+ override fun render(writer: Writer?) = Unit
+ })
+ val handler = injector.getInstance<MarkPostKnownDuringFirstStartHandler>()
+ val post = mock<Post>()
+ handler.newPostFound(NewPostFoundEvent(post))
+ verify(core).markPostKnown(post)
+ }
+
+ @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 `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 `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))
+ }
+
+}
+++ /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 - 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 - 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 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) }
+
+}
--- /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())
+ }
+
+}