2 * Sone - WebInterface.java - Copyright © 2010–2019 David Roden
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 package net.pterodactylus.sone.web;
20 import static com.google.common.collect.FluentIterable.from;
21 import static java.util.logging.Logger.getLogger;
23 import java.util.Collection;
24 import java.util.HashMap;
25 import java.util.HashSet;
28 import java.util.TimeZone;
29 import java.util.UUID;
30 import java.util.concurrent.Executors;
31 import java.util.concurrent.ScheduledExecutorService;
32 import java.util.concurrent.TimeUnit;
33 import java.util.logging.Logger;
34 import javax.annotation.Nonnull;
35 import javax.annotation.Nullable;
36 import javax.inject.Named;
38 import net.pterodactylus.sone.core.Core;
39 import net.pterodactylus.sone.core.ElementLoader;
40 import net.pterodactylus.sone.core.event.*;
41 import net.pterodactylus.sone.data.Post;
42 import net.pterodactylus.sone.data.PostReply;
43 import net.pterodactylus.sone.data.Sone;
44 import net.pterodactylus.sone.freenet.L10nFilter;
45 import net.pterodactylus.sone.freenet.Translation;
46 import net.pterodactylus.sone.main.Loaders;
47 import net.pterodactylus.sone.main.PluginHomepage;
48 import net.pterodactylus.sone.main.PluginVersion;
49 import net.pterodactylus.sone.main.PluginYear;
50 import net.pterodactylus.sone.main.SonePlugin;
51 import net.pterodactylus.sone.notify.ListNotification;
52 import net.pterodactylus.sone.notify.ListNotificationFilter;
53 import net.pterodactylus.sone.notify.PostVisibilityFilter;
54 import net.pterodactylus.sone.notify.ReplyVisibilityFilter;
55 import net.pterodactylus.sone.template.LinkedElementRenderFilter;
56 import net.pterodactylus.sone.template.ParserFilter;
57 import net.pterodactylus.sone.template.RenderFilter;
58 import net.pterodactylus.sone.template.ShortenFilter;
59 import net.pterodactylus.sone.text.Part;
60 import net.pterodactylus.sone.text.SonePart;
61 import net.pterodactylus.sone.text.SoneTextParser;
62 import net.pterodactylus.sone.text.TimeTextConverter;
63 import net.pterodactylus.sone.web.ajax.BookmarkAjaxPage;
64 import net.pterodactylus.sone.web.ajax.CreatePostAjaxPage;
65 import net.pterodactylus.sone.web.ajax.CreateReplyAjaxPage;
66 import net.pterodactylus.sone.web.ajax.DeletePostAjaxPage;
67 import net.pterodactylus.sone.web.ajax.DeleteProfileFieldAjaxPage;
68 import net.pterodactylus.sone.web.ajax.DeleteReplyAjaxPage;
69 import net.pterodactylus.sone.web.ajax.DismissNotificationAjaxPage;
70 import net.pterodactylus.sone.web.ajax.EditAlbumAjaxPage;
71 import net.pterodactylus.sone.web.ajax.EditImageAjaxPage;
72 import net.pterodactylus.sone.web.ajax.EditProfileFieldAjaxPage;
73 import net.pterodactylus.sone.web.ajax.FollowSoneAjaxPage;
74 import net.pterodactylus.sone.web.ajax.GetLikesAjaxPage;
75 import net.pterodactylus.sone.web.ajax.GetLinkedElementAjaxPage;
76 import net.pterodactylus.sone.web.ajax.GetNotificationsAjaxPage;
77 import net.pterodactylus.sone.web.ajax.GetPostAjaxPage;
78 import net.pterodactylus.sone.web.ajax.GetReplyAjaxPage;
79 import net.pterodactylus.sone.web.ajax.GetStatusAjaxPage;
80 import net.pterodactylus.sone.web.ajax.GetTimesAjaxPage;
81 import net.pterodactylus.sone.web.ajax.GetTranslationAjaxPage;
82 import net.pterodactylus.sone.web.ajax.LikeAjaxPage;
83 import net.pterodactylus.sone.web.ajax.LockSoneAjaxPage;
84 import net.pterodactylus.sone.web.ajax.MarkAsKnownAjaxPage;
85 import net.pterodactylus.sone.web.ajax.MoveProfileFieldAjaxPage;
86 import net.pterodactylus.sone.web.ajax.UnbookmarkAjaxPage;
87 import net.pterodactylus.sone.web.ajax.UnfollowSoneAjaxPage;
88 import net.pterodactylus.sone.web.ajax.UnlikeAjaxPage;
89 import net.pterodactylus.sone.web.ajax.UnlockSoneAjaxPage;
90 import net.pterodactylus.sone.web.page.FreenetRequest;
91 import net.pterodactylus.sone.web.page.TemplateRenderer;
92 import net.pterodactylus.sone.web.pages.*;
93 import net.pterodactylus.util.notify.Notification;
94 import net.pterodactylus.util.notify.NotificationManager;
95 import net.pterodactylus.util.notify.TemplateNotification;
96 import net.pterodactylus.util.template.Template;
97 import net.pterodactylus.util.template.TemplateContextFactory;
98 import net.pterodactylus.util.web.RedirectPage;
99 import net.pterodactylus.util.web.TemplatePage;
101 import freenet.clients.http.SessionManager;
102 import freenet.clients.http.SessionManager.Session;
103 import freenet.clients.http.ToadletContext;
105 import com.codahale.metrics.*;
106 import com.google.common.base.Optional;
107 import com.google.common.collect.Collections2;
108 import com.google.common.collect.ImmutableSet;
109 import com.google.common.eventbus.Subscribe;
110 import com.google.inject.Inject;
113 * Bundles functionality that a web interface of a Freenet plugin needs, e.g.
114 * references to l10n helpers.
116 public class WebInterface implements SessionProvider {
119 private static final Logger logger = getLogger(WebInterface.class.getName());
121 /** The loaders for templates, pages, and classpath providers. */
122 private final Loaders loaders;
124 /** The notification manager. */
125 private final NotificationManager notificationManager;
127 /** The Sone plugin. */
128 private final SonePlugin sonePlugin;
130 /** The form password. */
131 private final String formPassword;
133 /** The template context factory. */
134 private final TemplateContextFactory templateContextFactory;
135 private final TemplateRenderer templateRenderer;
137 /** The Sone text parser. */
138 private final SoneTextParser soneTextParser;
140 /** The parser filter. */
141 private final ParserFilter parserFilter;
142 private final ShortenFilter shortenFilter;
143 private final RenderFilter renderFilter;
145 private final ListNotificationFilter listNotificationFilter;
146 private final PostVisibilityFilter postVisibilityFilter;
147 private final ReplyVisibilityFilter replyVisibilityFilter;
149 private final ElementLoader elementLoader;
150 private final LinkedElementRenderFilter linkedElementRenderFilter;
151 private final TimeTextConverter timeTextConverter = new TimeTextConverter();
152 private final L10nFilter l10nFilter;
154 private final PageToadletRegistry pageToadletRegistry;
155 private final MetricRegistry metricRegistry;
156 private final Translation translation;
158 /** The “new post” notification. */
159 private final ListNotification<Post> newPostNotification;
161 /** The “new reply” notification. */
162 private final ListNotification<PostReply> newReplyNotification;
164 /** The invisible “local post” notification. */
165 private final ListNotification<Post> localPostNotification;
167 /** The invisible “local reply” notification. */
168 private final ListNotification<PostReply> localReplyNotification;
170 /** The “you have been mentioned” notification. */
171 private final ListNotification<Post> mentionNotification;
173 /** Notifications for sone inserts. */
174 private final Map<Sone, TemplateNotification> soneInsertNotifications = new HashMap<>();
176 /** Scheduled executor for time-based notifications. */
177 private final ScheduledExecutorService ticker = Executors.newScheduledThreadPool(1);
180 public WebInterface(SonePlugin sonePlugin, Loaders loaders, ListNotificationFilter listNotificationFilter,
181 PostVisibilityFilter postVisibilityFilter, ReplyVisibilityFilter replyVisibilityFilter,
182 ElementLoader elementLoader, TemplateContextFactory templateContextFactory,
183 TemplateRenderer templateRenderer,
184 ParserFilter parserFilter, ShortenFilter shortenFilter,
185 RenderFilter renderFilter,
186 LinkedElementRenderFilter linkedElementRenderFilter,
187 PageToadletRegistry pageToadletRegistry, MetricRegistry metricRegistry, Translation translation, L10nFilter l10nFilter,
188 NotificationManager notificationManager, @Named("newRemotePost") ListNotification<Post> newPostNotification,
189 @Named("localPost") ListNotification<Post> localPostNotification) {
190 this.sonePlugin = sonePlugin;
191 this.loaders = loaders;
192 this.listNotificationFilter = listNotificationFilter;
193 this.postVisibilityFilter = postVisibilityFilter;
194 this.replyVisibilityFilter = replyVisibilityFilter;
195 this.elementLoader = elementLoader;
196 this.templateRenderer = templateRenderer;
197 this.parserFilter = parserFilter;
198 this.shortenFilter = shortenFilter;
199 this.renderFilter = renderFilter;
200 this.linkedElementRenderFilter = linkedElementRenderFilter;
201 this.pageToadletRegistry = pageToadletRegistry;
202 this.metricRegistry = metricRegistry;
203 this.l10nFilter = l10nFilter;
204 this.translation = translation;
205 this.notificationManager = notificationManager;
206 this.newPostNotification = newPostNotification;
207 this.localPostNotification = localPostNotification;
208 formPassword = sonePlugin.pluginRespirator().getToadletContainer().getFormPassword();
209 soneTextParser = new SoneTextParser(getCore(), getCore());
211 this.templateContextFactory = templateContextFactory;
212 templateContextFactory.addTemplateObject("webInterface", this);
213 templateContextFactory.addTemplateObject("formPassword", formPassword);
215 /* create notifications. */
216 Template newReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
217 newReplyNotification = new ListNotification<>("new-reply-notification", "replies", newReplyNotificationTemplate, false);
219 Template localReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
220 localReplyNotification = new ListNotification<>("local-reply-notification", "replies", localReplyNotificationTemplate, false);
222 Template mentionNotificationTemplate = loaders.loadTemplate("/templates/notify/mentionNotification.html");
223 mentionNotification = new ListNotification<>("mention-notification", "posts", mentionNotificationTemplate, false);
231 * Returns the Sone core used by the Sone plugin.
233 * @return The Sone core
236 public Core getCore() {
237 return sonePlugin.core();
241 * Returns the template context factory of the web interface.
243 * @return The template context factory
245 public TemplateContextFactory getTemplateContextFactory() {
246 return templateContextFactory;
249 private Session getCurrentSessionWithoutCreation(ToadletContext toadletContenxt) {
250 return getSessionManager().useSession(toadletContenxt);
253 private Session getOrCreateCurrentSession(ToadletContext toadletContenxt) {
254 Session session = getCurrentSessionWithoutCreation(toadletContenxt);
255 if (session == null) {
256 session = getSessionManager().createSession(UUID.randomUUID().toString(), toadletContenxt);
261 public Sone getCurrentSoneCreatingSession(ToadletContext toadletContext) {
262 Collection<Sone> localSones = getCore().getLocalSones();
263 if (localSones.size() == 1) {
264 return localSones.iterator().next();
266 return getCurrentSone(getOrCreateCurrentSession(toadletContext));
269 public Sone getCurrentSoneWithoutCreatingSession(ToadletContext toadletContext) {
270 Collection<Sone> localSones = getCore().getLocalSones();
271 if (localSones.size() == 1) {
272 return localSones.iterator().next();
274 return getCurrentSone(getCurrentSessionWithoutCreation(toadletContext));
278 * Returns the currently logged in Sone.
282 * @return The currently logged in Sone, or {@code null} if no Sone is
283 * currently logged in
285 private Sone getCurrentSone(Session session) {
286 if (session == null) {
289 String soneId = (String) session.getAttribute("Sone.CurrentSone");
290 if (soneId == null) {
293 return getCore().getLocalSone(soneId);
298 public Sone getCurrentSone(@Nonnull ToadletContext toadletContext, boolean createSession) {
299 return createSession ? getCurrentSoneCreatingSession(toadletContext) : getCurrentSoneWithoutCreatingSession(toadletContext);
303 * Sets the currently logged in Sone.
305 * @param toadletContext
306 * The toadlet context
308 * The Sone to set as currently logged in
311 public void setCurrentSone(@Nonnull ToadletContext toadletContext, @Nullable Sone sone) {
312 Session session = getOrCreateCurrentSession(toadletContext);
314 session.removeAttribute("Sone.CurrentSone");
316 session.setAttribute("Sone.CurrentSone", sone.getId());
321 * Returns the notification manager.
323 * @return The notification manager
325 public NotificationManager getNotifications() {
326 return notificationManager;
330 public Optional<Notification> getNotification(@Nonnull String notificationId) {
331 return Optional.fromNullable(notificationManager.getNotification(notificationId));
335 public Collection<Notification> getNotifications(@Nullable Sone currentSone) {
336 return listNotificationFilter.filterNotifications(notificationManager.getNotifications(), currentSone);
339 public Translation getTranslation() {
344 * Returns the session manager of the node.
346 * @return The node’s session manager
348 public SessionManager getSessionManager() {
349 return sonePlugin.pluginRespirator().getSessionManager("Sone");
353 * Returns the node’s form password.
355 * @return The form password
357 public String getFormPassword() {
362 public Collection<Post> getNewPosts(@Nullable Sone currentSone) {
363 Set<Post> allNewPosts = ImmutableSet.<Post> builder()
364 .addAll(newPostNotification.getElements())
365 .addAll(localPostNotification.getElements())
367 return from(allNewPosts).filter(postVisibilityFilter.isVisible(currentSone)).toSet();
371 * Returns the replies that have been announced as new in the
372 * {@link #newReplyNotification}.
374 * @return The new replies
376 public Set<PostReply> getNewReplies() {
377 return ImmutableSet.<PostReply> builder().addAll(newReplyNotification.getElements()).addAll(localReplyNotification.getElements()).build();
381 public Collection<PostReply> getNewReplies(@Nullable Sone currentSone) {
382 Set<PostReply> allNewReplies = ImmutableSet.<PostReply>builder()
383 .addAll(newReplyNotification.getElements())
384 .addAll(localReplyNotification.getElements())
386 return from(allNewReplies).filter(replyVisibilityFilter.isVisible(currentSone)).toSet();
390 * Sets whether the current start of the plugin is the first start. It is
391 * considered a first start if the configuration file does not exist.
394 * {@code true} if no configuration file existed when Sone was
395 * loaded, {@code false} otherwise
397 public void setFirstStart(boolean firstStart) {
399 Template firstStartNotificationTemplate = loaders.loadTemplate("/templates/notify/firstStartNotification.html");
400 Notification firstStartNotification = new TemplateNotification("first-start-notification", firstStartNotificationTemplate);
401 notificationManager.addNotification(firstStartNotification);
406 * Sets whether Sone was started with a fresh configuration file.
409 * {@code true} if Sone was started with a fresh configuration,
410 * {@code false} if the existing configuration could be read
412 public void setNewConfig(boolean newConfig) {
413 if (newConfig && !hasFirstStartNotification()) {
414 Template configNotReadNotificationTemplate = loaders.loadTemplate("/templates/notify/configNotReadNotification.html");
415 Notification configNotReadNotification = new TemplateNotification("config-not-read-notification", configNotReadNotificationTemplate);
416 notificationManager.addNotification(configNotReadNotification);
425 * Returns whether the first start notification is currently displayed.
427 * @return {@code true} if the first-start notification is currently
428 * displayed, {@code false} otherwise
430 private boolean hasFirstStartNotification() {
431 return notificationManager.getNotification("first-start-notification") != null;
439 * Starts the web interface and registers all toadlets.
441 public void start() {
444 /* notification templates. */
445 Template startupNotificationTemplate = loaders.loadTemplate("/templates/notify/startupNotification.html");
447 final TemplateNotification startupNotification = new TemplateNotification("startup-notification", startupNotificationTemplate);
448 notificationManager.addNotification(startupNotification);
450 ticker.schedule(new Runnable() {
454 startupNotification.dismiss();
456 }, 2, TimeUnit.MINUTES);
458 Template wotMissingNotificationTemplate = loaders.loadTemplate("/templates/notify/wotMissingNotification.html");
459 final TemplateNotification wotMissingNotification = new TemplateNotification("wot-missing-notification", wotMissingNotificationTemplate);
460 ticker.scheduleAtFixedRate(new Runnable() {
463 @SuppressWarnings("synthetic-access")
465 if (getCore().getIdentityManager().isConnected()) {
466 wotMissingNotification.dismiss();
468 notificationManager.addNotification(wotMissingNotification);
472 }, 15, 15, TimeUnit.SECONDS);
476 * Stops the web interface and unregisters all toadlets.
479 pageToadletRegistry.unregisterToadlets();
480 ticker.shutdownNow();
488 * Register all toadlets.
490 private void registerToadlets() {
491 Template postTemplate = loaders.loadTemplate("/templates/include/viewPost.html");
492 Template replyTemplate = loaders.loadTemplate("/templates/include/viewReply.html");
493 Template openSearchTemplate = loaders.loadTemplate("/templates/xml/OpenSearch.xml");
495 pageToadletRegistry.addPage(new RedirectPage<FreenetRequest>("", "index.html"));
496 pageToadletRegistry.addPage(new IndexPage(this, loaders, templateRenderer, postVisibilityFilter));
497 pageToadletRegistry.addPage(new NewPage(this, loaders, templateRenderer));
498 pageToadletRegistry.addPage(new CreateSonePage(this, loaders, templateRenderer));
499 pageToadletRegistry.addPage(new KnownSonesPage(this, loaders, templateRenderer));
500 pageToadletRegistry.addPage(new EditProfilePage(this, loaders, templateRenderer));
501 pageToadletRegistry.addPage(new EditProfileFieldPage(this, loaders, templateRenderer));
502 pageToadletRegistry.addPage(new DeleteProfileFieldPage(this, loaders, templateRenderer));
503 pageToadletRegistry.addPage(new CreatePostPage(this, loaders, templateRenderer));
504 pageToadletRegistry.addPage(new CreateReplyPage(this, loaders, templateRenderer));
505 pageToadletRegistry.addPage(new ViewSonePage(this, loaders, templateRenderer));
506 pageToadletRegistry.addPage(new ViewPostPage(this, loaders, templateRenderer));
507 pageToadletRegistry.addPage(new LikePage(this, loaders, templateRenderer));
508 pageToadletRegistry.addPage(new UnlikePage(this, loaders, templateRenderer));
509 pageToadletRegistry.addPage(new DeletePostPage(this, loaders, templateRenderer));
510 pageToadletRegistry.addPage(new DeleteReplyPage(this, loaders, templateRenderer));
511 pageToadletRegistry.addPage(new LockSonePage(this, loaders, templateRenderer));
512 pageToadletRegistry.addPage(new UnlockSonePage(this, loaders, templateRenderer));
513 pageToadletRegistry.addPage(new FollowSonePage(this, loaders, templateRenderer));
514 pageToadletRegistry.addPage(new UnfollowSonePage(this, loaders, templateRenderer));
515 pageToadletRegistry.addPage(new ImageBrowserPage(this, loaders, templateRenderer));
516 pageToadletRegistry.addPage(new CreateAlbumPage(this, loaders, templateRenderer));
517 pageToadletRegistry.addPage(new EditAlbumPage(this, loaders, templateRenderer));
518 pageToadletRegistry.addPage(new DeleteAlbumPage(this, loaders, templateRenderer));
519 pageToadletRegistry.addPage(new UploadImagePage(this, loaders, templateRenderer));
520 pageToadletRegistry.addPage(new EditImagePage(this, loaders, templateRenderer));
521 pageToadletRegistry.addPage(new DeleteImagePage(this, loaders, templateRenderer));
522 pageToadletRegistry.addPage(new MarkAsKnownPage(this, loaders, templateRenderer));
523 pageToadletRegistry.addPage(new BookmarkPage(this, loaders, templateRenderer));
524 pageToadletRegistry.addPage(new UnbookmarkPage(this, loaders, templateRenderer));
525 pageToadletRegistry.addPage(new BookmarksPage(this, loaders, templateRenderer));
526 pageToadletRegistry.addPage(new SearchPage(this, loaders, templateRenderer));
527 pageToadletRegistry.addPage(new DeleteSonePage(this, loaders, templateRenderer));
528 pageToadletRegistry.addPage(new LoginPage(this, loaders, templateRenderer));
529 pageToadletRegistry.addPage(new LogoutPage(this, loaders, templateRenderer));
530 pageToadletRegistry.addPage(new OptionsPage(this, loaders, templateRenderer));
531 pageToadletRegistry.addPage(new RescuePage(this, loaders, templateRenderer));
532 pageToadletRegistry.addPage(new AboutPage(this, loaders, templateRenderer, new PluginVersion(SonePlugin.getPluginVersion()), new PluginYear(sonePlugin.getYear()), new PluginHomepage(sonePlugin.getHomepage())));
533 pageToadletRegistry.addPage(new InvalidPage(this, loaders, templateRenderer));
534 pageToadletRegistry.addPage(new NoPermissionPage(this, loaders, templateRenderer));
535 pageToadletRegistry.addPage(new EmptyImageTitlePage(this, loaders, templateRenderer));
536 pageToadletRegistry.addPage(new EmptyAlbumTitlePage(this, loaders, templateRenderer));
537 pageToadletRegistry.addPage(new DismissNotificationPage(this, loaders, templateRenderer));
538 pageToadletRegistry.addPage(new DebugPage(this, loaders, templateRenderer));
539 pageToadletRegistry.addDebugPage(new MetricsPage(this, loaders, templateRenderer, metricRegistry));
540 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("css/", "/static/css/", "text/css"));
541 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("javascript/", "/static/javascript/", "text/javascript"));
542 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("images/", "/static/images/", "image/png"));
543 pageToadletRegistry.addPage(new TemplatePage<FreenetRequest>("OpenSearch.xml", "application/opensearchdescription+xml", templateContextFactory, openSearchTemplate));
544 pageToadletRegistry.addPage(new GetImagePage(this));
545 pageToadletRegistry.addPage(new GetTranslationAjaxPage(this));
546 pageToadletRegistry.addPage(new GetStatusAjaxPage(this, elementLoader, timeTextConverter, l10nFilter, TimeZone.getDefault()));
547 pageToadletRegistry.addPage(new GetNotificationsAjaxPage(this));
548 pageToadletRegistry.addPage(new DismissNotificationAjaxPage(this));
549 pageToadletRegistry.addPage(new CreatePostAjaxPage(this));
550 pageToadletRegistry.addPage(new CreateReplyAjaxPage(this));
551 pageToadletRegistry.addPage(new GetReplyAjaxPage(this, replyTemplate));
552 pageToadletRegistry.addPage(new GetPostAjaxPage(this, postTemplate));
553 pageToadletRegistry.addPage(new GetLinkedElementAjaxPage(this, elementLoader, linkedElementRenderFilter));
554 pageToadletRegistry.addPage(new GetTimesAjaxPage(this, timeTextConverter, l10nFilter, TimeZone.getDefault()));
555 pageToadletRegistry.addPage(new MarkAsKnownAjaxPage(this));
556 pageToadletRegistry.addPage(new DeletePostAjaxPage(this));
557 pageToadletRegistry.addPage(new DeleteReplyAjaxPage(this));
558 pageToadletRegistry.addPage(new LockSoneAjaxPage(this));
559 pageToadletRegistry.addPage(new UnlockSoneAjaxPage(this));
560 pageToadletRegistry.addPage(new FollowSoneAjaxPage(this));
561 pageToadletRegistry.addPage(new UnfollowSoneAjaxPage(this));
562 pageToadletRegistry.addPage(new EditAlbumAjaxPage(this));
563 pageToadletRegistry.addPage(new EditImageAjaxPage(this, parserFilter, shortenFilter, renderFilter));
564 pageToadletRegistry.addPage(new LikeAjaxPage(this));
565 pageToadletRegistry.addPage(new UnlikeAjaxPage(this));
566 pageToadletRegistry.addPage(new GetLikesAjaxPage(this));
567 pageToadletRegistry.addPage(new BookmarkAjaxPage(this));
568 pageToadletRegistry.addPage(new UnbookmarkAjaxPage(this));
569 pageToadletRegistry.addPage(new EditProfileFieldAjaxPage(this));
570 pageToadletRegistry.addPage(new DeleteProfileFieldAjaxPage(this));
571 pageToadletRegistry.addPage(new MoveProfileFieldAjaxPage(this));
573 pageToadletRegistry.registerToadlets();
577 * Returns all {@link Sone#isLocal() local Sone}s that are referenced by
578 * {@link SonePart}s in the given text (after parsing it using
579 * {@link SoneTextParser}).
583 * @return All mentioned local Sones
585 private Collection<Sone> getMentionedSones(String text) {
586 /* we need no context to find mentioned Sones. */
587 Set<Sone> mentionedSones = new HashSet<>();
588 for (Part part : soneTextParser.parse(text, null)) {
589 if (part instanceof SonePart) {
590 mentionedSones.add(((SonePart) part).getSone());
593 return Collections2.filter(mentionedSones, Sone.LOCAL_SONE_FILTER);
597 * Returns the Sone insert notification for the given Sone. If no
598 * notification for the given Sone exists, a new notification is created and
602 * The Sone to get the insert notification for
603 * @return The Sone insert notification
605 private TemplateNotification getSoneInsertNotification(Sone sone) {
606 synchronized (soneInsertNotifications) {
607 TemplateNotification templateNotification = soneInsertNotifications.get(sone);
608 if (templateNotification == null) {
609 templateNotification = new TemplateNotification(loaders.loadTemplate("/templates/notify/soneInsertNotification.html"));
610 templateNotification.set("insertSone", sone);
611 soneInsertNotifications.put(sone, templateNotification);
613 return templateNotification;
617 private boolean localSoneMentionedInNewPostOrReply(Post post) {
618 if (!post.getSone().isLocal()) {
619 if (!getMentionedSones(post.getText()).isEmpty() && !post.isKnown()) {
623 for (PostReply postReply : getCore().getReplies(post.getId())) {
624 if (postReply.getSone().isLocal()) {
627 if (!getMentionedSones(postReply.getText()).isEmpty() && !postReply.isKnown()) {
639 * Notifies the web interface that a new {@link Post} was found.
641 * @param newPostFoundEvent
645 public void newPostFound(NewPostFoundEvent newPostFoundEvent) {
646 Post post = newPostFoundEvent.getPost();
647 boolean isLocal = post.getSone().isLocal();
648 if (!hasFirstStartNotification()) {
649 if (!getMentionedSones(post.getText()).isEmpty() && !isLocal) {
650 mentionNotification.add(post);
651 notificationManager.addNotification(mentionNotification);
657 * Notifies the web interface that a new {@link PostReply} was found.
659 * @param newPostReplyFoundEvent
663 public void newReplyFound(NewPostReplyFoundEvent newPostReplyFoundEvent) {
664 PostReply reply = newPostReplyFoundEvent.getPostReply();
665 boolean isLocal = reply.getSone().isLocal();
667 localReplyNotification.add(reply);
669 newReplyNotification.add(reply);
671 if (!hasFirstStartNotification()) {
672 notificationManager.addNotification(isLocal ? localReplyNotification : newReplyNotification);
673 if (reply.getPost().isPresent() && localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
674 mentionNotification.add(reply.getPost().get());
675 notificationManager.addNotification(mentionNotification);
678 getCore().markReplyKnown(reply);
683 public void markPostKnown(MarkPostKnownEvent markPostKnownEvent) {
684 removePost(markPostKnownEvent.getPost());
688 public void markReplyKnown(MarkPostReplyKnownEvent markPostReplyKnownEvent) {
689 removeReply(markPostReplyKnownEvent.getPostReply());
693 public void postRemoved(PostRemovedEvent postRemovedEvent) {
694 removePost(postRemovedEvent.getPost());
697 private void removePost(Post post) {
698 newPostNotification.remove(post);
699 if (!localSoneMentionedInNewPostOrReply(post)) {
700 mentionNotification.remove(post);
705 public void replyRemoved(PostReplyRemovedEvent postReplyRemovedEvent) {
706 removeReply(postReplyRemovedEvent.getPostReply());
709 private void removeReply(PostReply reply) {
710 newReplyNotification.remove(reply);
711 localReplyNotification.remove(reply);
712 if (reply.getPost().isPresent() && !localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
713 mentionNotification.remove(reply.getPost().get());
718 * Notifies the web interface that a {@link Sone} is being inserted.
720 * @param soneInsertingEvent
724 public void soneInserting(SoneInsertingEvent soneInsertingEvent) {
725 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertingEvent.getSone());
726 soneInsertNotification.set("soneStatus", "inserting");
727 if (soneInsertingEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
728 notificationManager.addNotification(soneInsertNotification);
733 * Notifies the web interface that a {@link Sone} was inserted.
735 * @param soneInsertedEvent
739 public void soneInserted(SoneInsertedEvent soneInsertedEvent) {
740 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertedEvent.getSone());
741 soneInsertNotification.set("soneStatus", "inserted");
742 soneInsertNotification.set("insertDuration", soneInsertedEvent.getInsertDuration() / 1000);
743 if (soneInsertedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
744 notificationManager.addNotification(soneInsertNotification);
749 * Notifies the web interface that a {@link Sone} insert was aborted.
751 * @param soneInsertAbortedEvent
755 public void soneInsertAborted(SoneInsertAbortedEvent soneInsertAbortedEvent) {
756 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertAbortedEvent.getSone());
757 soneInsertNotification.set("soneStatus", "insert-aborted");
758 soneInsertNotification.set("insert-error", soneInsertAbortedEvent.getCause());
759 if (soneInsertAbortedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
760 notificationManager.addNotification(soneInsertNotification);
765 public void debugActivated(@Nonnull DebugActivatedEvent debugActivatedEvent) {
766 pageToadletRegistry.activateDebugMode();