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 Sone was started with a fresh configuration file.
393 * {@code true} if Sone was started with a fresh configuration,
394 * {@code false} if the existing configuration could be read
396 public void setNewConfig(boolean newConfig) {
397 if (newConfig && !hasFirstStartNotification()) {
398 Template configNotReadNotificationTemplate = loaders.loadTemplate("/templates/notify/configNotReadNotification.html");
399 Notification configNotReadNotification = new TemplateNotification("config-not-read-notification", configNotReadNotificationTemplate);
400 notificationManager.addNotification(configNotReadNotification);
409 * Returns whether the first start notification is currently displayed.
411 * @return {@code true} if the first-start notification is currently
412 * displayed, {@code false} otherwise
414 private boolean hasFirstStartNotification() {
415 return notificationManager.getNotification("first-start-notification") != null;
423 * Starts the web interface and registers all toadlets.
425 public void start() {
428 /* notification templates. */
429 Template startupNotificationTemplate = loaders.loadTemplate("/templates/notify/startupNotification.html");
431 final TemplateNotification startupNotification = new TemplateNotification("startup-notification", startupNotificationTemplate);
432 notificationManager.addNotification(startupNotification);
434 ticker.schedule(new Runnable() {
438 startupNotification.dismiss();
440 }, 2, TimeUnit.MINUTES);
442 Template wotMissingNotificationTemplate = loaders.loadTemplate("/templates/notify/wotMissingNotification.html");
443 final TemplateNotification wotMissingNotification = new TemplateNotification("wot-missing-notification", wotMissingNotificationTemplate);
444 ticker.scheduleAtFixedRate(new Runnable() {
447 @SuppressWarnings("synthetic-access")
449 if (getCore().getIdentityManager().isConnected()) {
450 wotMissingNotification.dismiss();
452 notificationManager.addNotification(wotMissingNotification);
456 }, 15, 15, TimeUnit.SECONDS);
460 * Stops the web interface and unregisters all toadlets.
463 pageToadletRegistry.unregisterToadlets();
464 ticker.shutdownNow();
472 * Register all toadlets.
474 private void registerToadlets() {
475 Template postTemplate = loaders.loadTemplate("/templates/include/viewPost.html");
476 Template replyTemplate = loaders.loadTemplate("/templates/include/viewReply.html");
477 Template openSearchTemplate = loaders.loadTemplate("/templates/xml/OpenSearch.xml");
479 pageToadletRegistry.addPage(new RedirectPage<FreenetRequest>("", "index.html"));
480 pageToadletRegistry.addPage(new IndexPage(this, loaders, templateRenderer, postVisibilityFilter));
481 pageToadletRegistry.addPage(new NewPage(this, loaders, templateRenderer));
482 pageToadletRegistry.addPage(new CreateSonePage(this, loaders, templateRenderer));
483 pageToadletRegistry.addPage(new KnownSonesPage(this, loaders, templateRenderer));
484 pageToadletRegistry.addPage(new EditProfilePage(this, loaders, templateRenderer));
485 pageToadletRegistry.addPage(new EditProfileFieldPage(this, loaders, templateRenderer));
486 pageToadletRegistry.addPage(new DeleteProfileFieldPage(this, loaders, templateRenderer));
487 pageToadletRegistry.addPage(new CreatePostPage(this, loaders, templateRenderer));
488 pageToadletRegistry.addPage(new CreateReplyPage(this, loaders, templateRenderer));
489 pageToadletRegistry.addPage(new ViewSonePage(this, loaders, templateRenderer));
490 pageToadletRegistry.addPage(new ViewPostPage(this, loaders, templateRenderer));
491 pageToadletRegistry.addPage(new LikePage(this, loaders, templateRenderer));
492 pageToadletRegistry.addPage(new UnlikePage(this, loaders, templateRenderer));
493 pageToadletRegistry.addPage(new DeletePostPage(this, loaders, templateRenderer));
494 pageToadletRegistry.addPage(new DeleteReplyPage(this, loaders, templateRenderer));
495 pageToadletRegistry.addPage(new LockSonePage(this, loaders, templateRenderer));
496 pageToadletRegistry.addPage(new UnlockSonePage(this, loaders, templateRenderer));
497 pageToadletRegistry.addPage(new FollowSonePage(this, loaders, templateRenderer));
498 pageToadletRegistry.addPage(new UnfollowSonePage(this, loaders, templateRenderer));
499 pageToadletRegistry.addPage(new ImageBrowserPage(this, loaders, templateRenderer));
500 pageToadletRegistry.addPage(new CreateAlbumPage(this, loaders, templateRenderer));
501 pageToadletRegistry.addPage(new EditAlbumPage(this, loaders, templateRenderer));
502 pageToadletRegistry.addPage(new DeleteAlbumPage(this, loaders, templateRenderer));
503 pageToadletRegistry.addPage(new UploadImagePage(this, loaders, templateRenderer));
504 pageToadletRegistry.addPage(new EditImagePage(this, loaders, templateRenderer));
505 pageToadletRegistry.addPage(new DeleteImagePage(this, loaders, templateRenderer));
506 pageToadletRegistry.addPage(new MarkAsKnownPage(this, loaders, templateRenderer));
507 pageToadletRegistry.addPage(new BookmarkPage(this, loaders, templateRenderer));
508 pageToadletRegistry.addPage(new UnbookmarkPage(this, loaders, templateRenderer));
509 pageToadletRegistry.addPage(new BookmarksPage(this, loaders, templateRenderer));
510 pageToadletRegistry.addPage(new SearchPage(this, loaders, templateRenderer));
511 pageToadletRegistry.addPage(new DeleteSonePage(this, loaders, templateRenderer));
512 pageToadletRegistry.addPage(new LoginPage(this, loaders, templateRenderer));
513 pageToadletRegistry.addPage(new LogoutPage(this, loaders, templateRenderer));
514 pageToadletRegistry.addPage(new OptionsPage(this, loaders, templateRenderer));
515 pageToadletRegistry.addPage(new RescuePage(this, loaders, templateRenderer));
516 pageToadletRegistry.addPage(new AboutPage(this, loaders, templateRenderer, new PluginVersion(SonePlugin.getPluginVersion()), new PluginYear(sonePlugin.getYear()), new PluginHomepage(sonePlugin.getHomepage())));
517 pageToadletRegistry.addPage(new InvalidPage(this, loaders, templateRenderer));
518 pageToadletRegistry.addPage(new NoPermissionPage(this, loaders, templateRenderer));
519 pageToadletRegistry.addPage(new EmptyImageTitlePage(this, loaders, templateRenderer));
520 pageToadletRegistry.addPage(new EmptyAlbumTitlePage(this, loaders, templateRenderer));
521 pageToadletRegistry.addPage(new DismissNotificationPage(this, loaders, templateRenderer));
522 pageToadletRegistry.addPage(new DebugPage(this, loaders, templateRenderer));
523 pageToadletRegistry.addDebugPage(new MetricsPage(this, loaders, templateRenderer, metricRegistry));
524 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("css/", "/static/css/", "text/css"));
525 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("javascript/", "/static/javascript/", "text/javascript"));
526 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("images/", "/static/images/", "image/png"));
527 pageToadletRegistry.addPage(new TemplatePage<FreenetRequest>("OpenSearch.xml", "application/opensearchdescription+xml", templateContextFactory, openSearchTemplate));
528 pageToadletRegistry.addPage(new GetImagePage(this));
529 pageToadletRegistry.addPage(new GetTranslationAjaxPage(this));
530 pageToadletRegistry.addPage(new GetStatusAjaxPage(this, elementLoader, timeTextConverter, l10nFilter, TimeZone.getDefault()));
531 pageToadletRegistry.addPage(new GetNotificationsAjaxPage(this));
532 pageToadletRegistry.addPage(new DismissNotificationAjaxPage(this));
533 pageToadletRegistry.addPage(new CreatePostAjaxPage(this));
534 pageToadletRegistry.addPage(new CreateReplyAjaxPage(this));
535 pageToadletRegistry.addPage(new GetReplyAjaxPage(this, replyTemplate));
536 pageToadletRegistry.addPage(new GetPostAjaxPage(this, postTemplate));
537 pageToadletRegistry.addPage(new GetLinkedElementAjaxPage(this, elementLoader, linkedElementRenderFilter));
538 pageToadletRegistry.addPage(new GetTimesAjaxPage(this, timeTextConverter, l10nFilter, TimeZone.getDefault()));
539 pageToadletRegistry.addPage(new MarkAsKnownAjaxPage(this));
540 pageToadletRegistry.addPage(new DeletePostAjaxPage(this));
541 pageToadletRegistry.addPage(new DeleteReplyAjaxPage(this));
542 pageToadletRegistry.addPage(new LockSoneAjaxPage(this));
543 pageToadletRegistry.addPage(new UnlockSoneAjaxPage(this));
544 pageToadletRegistry.addPage(new FollowSoneAjaxPage(this));
545 pageToadletRegistry.addPage(new UnfollowSoneAjaxPage(this));
546 pageToadletRegistry.addPage(new EditAlbumAjaxPage(this));
547 pageToadletRegistry.addPage(new EditImageAjaxPage(this, parserFilter, shortenFilter, renderFilter));
548 pageToadletRegistry.addPage(new LikeAjaxPage(this));
549 pageToadletRegistry.addPage(new UnlikeAjaxPage(this));
550 pageToadletRegistry.addPage(new GetLikesAjaxPage(this));
551 pageToadletRegistry.addPage(new BookmarkAjaxPage(this));
552 pageToadletRegistry.addPage(new UnbookmarkAjaxPage(this));
553 pageToadletRegistry.addPage(new EditProfileFieldAjaxPage(this));
554 pageToadletRegistry.addPage(new DeleteProfileFieldAjaxPage(this));
555 pageToadletRegistry.addPage(new MoveProfileFieldAjaxPage(this));
557 pageToadletRegistry.registerToadlets();
561 * Returns all {@link Sone#isLocal() local Sone}s that are referenced by
562 * {@link SonePart}s in the given text (after parsing it using
563 * {@link SoneTextParser}).
567 * @return All mentioned local Sones
569 private Collection<Sone> getMentionedSones(String text) {
570 /* we need no context to find mentioned Sones. */
571 Set<Sone> mentionedSones = new HashSet<>();
572 for (Part part : soneTextParser.parse(text, null)) {
573 if (part instanceof SonePart) {
574 mentionedSones.add(((SonePart) part).getSone());
577 return Collections2.filter(mentionedSones, Sone.LOCAL_SONE_FILTER);
581 * Returns the Sone insert notification for the given Sone. If no
582 * notification for the given Sone exists, a new notification is created and
586 * The Sone to get the insert notification for
587 * @return The Sone insert notification
589 private TemplateNotification getSoneInsertNotification(Sone sone) {
590 synchronized (soneInsertNotifications) {
591 TemplateNotification templateNotification = soneInsertNotifications.get(sone);
592 if (templateNotification == null) {
593 templateNotification = new TemplateNotification(loaders.loadTemplate("/templates/notify/soneInsertNotification.html"));
594 templateNotification.set("insertSone", sone);
595 soneInsertNotifications.put(sone, templateNotification);
597 return templateNotification;
601 private boolean localSoneMentionedInNewPostOrReply(Post post) {
602 if (!post.getSone().isLocal()) {
603 if (!getMentionedSones(post.getText()).isEmpty() && !post.isKnown()) {
607 for (PostReply postReply : getCore().getReplies(post.getId())) {
608 if (postReply.getSone().isLocal()) {
611 if (!getMentionedSones(postReply.getText()).isEmpty() && !postReply.isKnown()) {
623 * Notifies the web interface that a new {@link Post} was found.
625 * @param newPostFoundEvent
629 public void newPostFound(NewPostFoundEvent newPostFoundEvent) {
630 Post post = newPostFoundEvent.getPost();
631 boolean isLocal = post.getSone().isLocal();
632 if (!hasFirstStartNotification()) {
633 if (!getMentionedSones(post.getText()).isEmpty() && !isLocal) {
634 mentionNotification.add(post);
635 notificationManager.addNotification(mentionNotification);
641 * Notifies the web interface that a new {@link PostReply} was found.
643 * @param newPostReplyFoundEvent
647 public void newReplyFound(NewPostReplyFoundEvent newPostReplyFoundEvent) {
648 PostReply reply = newPostReplyFoundEvent.getPostReply();
649 boolean isLocal = reply.getSone().isLocal();
651 localReplyNotification.add(reply);
653 newReplyNotification.add(reply);
655 if (!hasFirstStartNotification()) {
656 notificationManager.addNotification(isLocal ? localReplyNotification : newReplyNotification);
657 if (reply.getPost().isPresent() && localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
658 mentionNotification.add(reply.getPost().get());
659 notificationManager.addNotification(mentionNotification);
662 getCore().markReplyKnown(reply);
667 public void markPostKnown(MarkPostKnownEvent markPostKnownEvent) {
668 removePost(markPostKnownEvent.getPost());
672 public void markReplyKnown(MarkPostReplyKnownEvent markPostReplyKnownEvent) {
673 removeReply(markPostReplyKnownEvent.getPostReply());
677 public void postRemoved(PostRemovedEvent postRemovedEvent) {
678 removePost(postRemovedEvent.getPost());
681 private void removePost(Post post) {
682 newPostNotification.remove(post);
683 if (!localSoneMentionedInNewPostOrReply(post)) {
684 mentionNotification.remove(post);
689 public void replyRemoved(PostReplyRemovedEvent postReplyRemovedEvent) {
690 removeReply(postReplyRemovedEvent.getPostReply());
693 private void removeReply(PostReply reply) {
694 newReplyNotification.remove(reply);
695 localReplyNotification.remove(reply);
696 if (reply.getPost().isPresent() && !localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
697 mentionNotification.remove(reply.getPost().get());
702 * Notifies the web interface that a {@link Sone} is being inserted.
704 * @param soneInsertingEvent
708 public void soneInserting(SoneInsertingEvent soneInsertingEvent) {
709 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertingEvent.getSone());
710 soneInsertNotification.set("soneStatus", "inserting");
711 if (soneInsertingEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
712 notificationManager.addNotification(soneInsertNotification);
717 * Notifies the web interface that a {@link Sone} was inserted.
719 * @param soneInsertedEvent
723 public void soneInserted(SoneInsertedEvent soneInsertedEvent) {
724 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertedEvent.getSone());
725 soneInsertNotification.set("soneStatus", "inserted");
726 soneInsertNotification.set("insertDuration", soneInsertedEvent.getInsertDuration() / 1000);
727 if (soneInsertedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
728 notificationManager.addNotification(soneInsertNotification);
733 * Notifies the web interface that a {@link Sone} insert was aborted.
735 * @param soneInsertAbortedEvent
739 public void soneInsertAborted(SoneInsertAbortedEvent soneInsertAbortedEvent) {
740 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertAbortedEvent.getSone());
741 soneInsertNotification.set("soneStatus", "insert-aborted");
742 soneInsertNotification.set("insert-error", soneInsertAbortedEvent.getCause());
743 if (soneInsertAbortedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
744 notificationManager.addNotification(soneInsertNotification);
749 public void debugActivated(@Nonnull DebugActivatedEvent debugActivatedEvent) {
750 pageToadletRegistry.activateDebugMode();