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.Collections;
25 import java.util.HashMap;
26 import java.util.HashSet;
29 import java.util.TimeZone;
30 import java.util.UUID;
31 import java.util.concurrent.Executors;
32 import java.util.concurrent.ScheduledExecutorService;
33 import java.util.concurrent.ScheduledFuture;
34 import java.util.concurrent.TimeUnit;
35 import java.util.logging.Logger;
36 import javax.annotation.Nonnull;
37 import javax.annotation.Nullable;
38 import javax.inject.Named;
40 import net.pterodactylus.sone.core.Core;
41 import net.pterodactylus.sone.core.ElementLoader;
42 import net.pterodactylus.sone.core.event.*;
43 import net.pterodactylus.sone.data.Image;
44 import net.pterodactylus.sone.data.Post;
45 import net.pterodactylus.sone.data.PostReply;
46 import net.pterodactylus.sone.data.Sone;
47 import net.pterodactylus.sone.freenet.L10nFilter;
48 import net.pterodactylus.sone.freenet.Translation;
49 import net.pterodactylus.sone.main.Loaders;
50 import net.pterodactylus.sone.main.PluginHomepage;
51 import net.pterodactylus.sone.main.PluginVersion;
52 import net.pterodactylus.sone.main.PluginYear;
53 import net.pterodactylus.sone.main.SonePlugin;
54 import net.pterodactylus.sone.notify.ListNotification;
55 import net.pterodactylus.sone.notify.ListNotificationFilter;
56 import net.pterodactylus.sone.notify.PostVisibilityFilter;
57 import net.pterodactylus.sone.notify.ReplyVisibilityFilter;
58 import net.pterodactylus.sone.template.LinkedElementRenderFilter;
59 import net.pterodactylus.sone.template.ParserFilter;
60 import net.pterodactylus.sone.template.RenderFilter;
61 import net.pterodactylus.sone.template.ShortenFilter;
62 import net.pterodactylus.sone.text.Part;
63 import net.pterodactylus.sone.text.SonePart;
64 import net.pterodactylus.sone.text.SoneTextParser;
65 import net.pterodactylus.sone.text.TimeTextConverter;
66 import net.pterodactylus.sone.web.ajax.BookmarkAjaxPage;
67 import net.pterodactylus.sone.web.ajax.CreatePostAjaxPage;
68 import net.pterodactylus.sone.web.ajax.CreateReplyAjaxPage;
69 import net.pterodactylus.sone.web.ajax.DeletePostAjaxPage;
70 import net.pterodactylus.sone.web.ajax.DeleteProfileFieldAjaxPage;
71 import net.pterodactylus.sone.web.ajax.DeleteReplyAjaxPage;
72 import net.pterodactylus.sone.web.ajax.DismissNotificationAjaxPage;
73 import net.pterodactylus.sone.web.ajax.EditAlbumAjaxPage;
74 import net.pterodactylus.sone.web.ajax.EditImageAjaxPage;
75 import net.pterodactylus.sone.web.ajax.EditProfileFieldAjaxPage;
76 import net.pterodactylus.sone.web.ajax.FollowSoneAjaxPage;
77 import net.pterodactylus.sone.web.ajax.GetLikesAjaxPage;
78 import net.pterodactylus.sone.web.ajax.GetLinkedElementAjaxPage;
79 import net.pterodactylus.sone.web.ajax.GetNotificationsAjaxPage;
80 import net.pterodactylus.sone.web.ajax.GetPostAjaxPage;
81 import net.pterodactylus.sone.web.ajax.GetReplyAjaxPage;
82 import net.pterodactylus.sone.web.ajax.GetStatusAjaxPage;
83 import net.pterodactylus.sone.web.ajax.GetTimesAjaxPage;
84 import net.pterodactylus.sone.web.ajax.GetTranslationAjaxPage;
85 import net.pterodactylus.sone.web.ajax.LikeAjaxPage;
86 import net.pterodactylus.sone.web.ajax.LockSoneAjaxPage;
87 import net.pterodactylus.sone.web.ajax.MarkAsKnownAjaxPage;
88 import net.pterodactylus.sone.web.ajax.MoveProfileFieldAjaxPage;
89 import net.pterodactylus.sone.web.ajax.UnbookmarkAjaxPage;
90 import net.pterodactylus.sone.web.ajax.UnfollowSoneAjaxPage;
91 import net.pterodactylus.sone.web.ajax.UnlikeAjaxPage;
92 import net.pterodactylus.sone.web.ajax.UnlockSoneAjaxPage;
93 import net.pterodactylus.sone.web.page.FreenetRequest;
94 import net.pterodactylus.sone.web.page.TemplateRenderer;
95 import net.pterodactylus.sone.web.pages.*;
96 import net.pterodactylus.util.notify.Notification;
97 import net.pterodactylus.util.notify.NotificationManager;
98 import net.pterodactylus.util.notify.TemplateNotification;
99 import net.pterodactylus.util.template.Template;
100 import net.pterodactylus.util.template.TemplateContextFactory;
101 import net.pterodactylus.util.web.RedirectPage;
102 import net.pterodactylus.util.web.TemplatePage;
104 import freenet.clients.http.SessionManager;
105 import freenet.clients.http.SessionManager.Session;
106 import freenet.clients.http.ToadletContext;
108 import com.codahale.metrics.*;
109 import com.google.common.base.Optional;
110 import com.google.common.collect.Collections2;
111 import com.google.common.collect.ImmutableSet;
112 import com.google.common.eventbus.Subscribe;
113 import com.google.inject.Inject;
116 * Bundles functionality that a web interface of a Freenet plugin needs, e.g.
117 * references to l10n helpers.
119 public class WebInterface implements SessionProvider {
122 private static final Logger logger = getLogger(WebInterface.class.getName());
124 /** The loaders for templates, pages, and classpath providers. */
125 private final Loaders loaders;
127 /** The notification manager. */
128 private final NotificationManager notificationManager;
130 /** The Sone plugin. */
131 private final SonePlugin sonePlugin;
133 /** The form password. */
134 private final String formPassword;
136 /** The template context factory. */
137 private final TemplateContextFactory templateContextFactory;
138 private final TemplateRenderer templateRenderer;
140 /** The Sone text parser. */
141 private final SoneTextParser soneTextParser;
143 /** The parser filter. */
144 private final ParserFilter parserFilter;
145 private final ShortenFilter shortenFilter;
146 private final RenderFilter renderFilter;
148 private final ListNotificationFilter listNotificationFilter;
149 private final PostVisibilityFilter postVisibilityFilter;
150 private final ReplyVisibilityFilter replyVisibilityFilter;
152 private final ElementLoader elementLoader;
153 private final LinkedElementRenderFilter linkedElementRenderFilter;
154 private final TimeTextConverter timeTextConverter = new TimeTextConverter();
155 private final L10nFilter l10nFilter;
157 private final PageToadletRegistry pageToadletRegistry;
158 private final MetricRegistry metricRegistry;
159 private final Translation translation;
161 /** The “new post” notification. */
162 private final ListNotification<Post> newPostNotification;
164 /** The “new reply” notification. */
165 private final ListNotification<PostReply> newReplyNotification;
167 /** The invisible “local post” notification. */
168 private final ListNotification<Post> localPostNotification;
170 /** The invisible “local reply” notification. */
171 private final ListNotification<PostReply> localReplyNotification;
173 /** The “you have been mentioned” notification. */
174 private final ListNotification<Post> mentionNotification;
176 /** Notifications for sone inserts. */
177 private final Map<Sone, TemplateNotification> soneInsertNotifications = new HashMap<>();
179 /** Sone locked notification ticker objects. */
180 private final Map<Sone, ScheduledFuture<?>> lockedSonesTickerObjects = Collections.synchronizedMap(new HashMap<Sone, ScheduledFuture<?>>());
182 /** The “inserting images” notification. */
183 private final ListNotification<Image> insertingImagesNotification;
185 /** The “inserted images” notification. */
186 private final ListNotification<Image> insertedImagesNotification;
188 /** The “image insert failed” notification. */
189 private final ListNotification<Image> imageInsertFailedNotification;
191 /** Scheduled executor for time-based notifications. */
192 private final ScheduledExecutorService ticker = Executors.newScheduledThreadPool(1);
195 public WebInterface(SonePlugin sonePlugin, Loaders loaders, ListNotificationFilter listNotificationFilter,
196 PostVisibilityFilter postVisibilityFilter, ReplyVisibilityFilter replyVisibilityFilter,
197 ElementLoader elementLoader, TemplateContextFactory templateContextFactory,
198 TemplateRenderer templateRenderer,
199 ParserFilter parserFilter, ShortenFilter shortenFilter,
200 RenderFilter renderFilter,
201 LinkedElementRenderFilter linkedElementRenderFilter,
202 PageToadletRegistry pageToadletRegistry, MetricRegistry metricRegistry, Translation translation, L10nFilter l10nFilter,
203 NotificationManager notificationManager, @Named("newRemotePost") ListNotification<Post> newPostNotification,
204 @Named("localPost") ListNotification<Post> localPostNotification) {
205 this.sonePlugin = sonePlugin;
206 this.loaders = loaders;
207 this.listNotificationFilter = listNotificationFilter;
208 this.postVisibilityFilter = postVisibilityFilter;
209 this.replyVisibilityFilter = replyVisibilityFilter;
210 this.elementLoader = elementLoader;
211 this.templateRenderer = templateRenderer;
212 this.parserFilter = parserFilter;
213 this.shortenFilter = shortenFilter;
214 this.renderFilter = renderFilter;
215 this.linkedElementRenderFilter = linkedElementRenderFilter;
216 this.pageToadletRegistry = pageToadletRegistry;
217 this.metricRegistry = metricRegistry;
218 this.l10nFilter = l10nFilter;
219 this.translation = translation;
220 this.notificationManager = notificationManager;
221 this.newPostNotification = newPostNotification;
222 this.localPostNotification = localPostNotification;
223 formPassword = sonePlugin.pluginRespirator().getToadletContainer().getFormPassword();
224 soneTextParser = new SoneTextParser(getCore(), getCore());
226 this.templateContextFactory = templateContextFactory;
227 templateContextFactory.addTemplateObject("webInterface", this);
228 templateContextFactory.addTemplateObject("formPassword", formPassword);
230 /* create notifications. */
231 Template newReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
232 newReplyNotification = new ListNotification<>("new-reply-notification", "replies", newReplyNotificationTemplate, false);
234 Template localReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
235 localReplyNotification = new ListNotification<>("local-reply-notification", "replies", localReplyNotificationTemplate, false);
237 Template mentionNotificationTemplate = loaders.loadTemplate("/templates/notify/mentionNotification.html");
238 mentionNotification = new ListNotification<>("mention-notification", "posts", mentionNotificationTemplate, false);
240 Template insertingImagesTemplate = loaders.loadTemplate("/templates/notify/inserting-images-notification.html");
241 insertingImagesNotification = new ListNotification<>("inserting-images-notification", "images", insertingImagesTemplate);
243 Template insertedImagesTemplate = loaders.loadTemplate("/templates/notify/inserted-images-notification.html");
244 insertedImagesNotification = new ListNotification<>("inserted-images-notification", "images", insertedImagesTemplate);
246 Template imageInsertFailedTemplate = loaders.loadTemplate("/templates/notify/image-insert-failed-notification.html");
247 imageInsertFailedNotification = new ListNotification<>("image-insert-failed-notification", "images", imageInsertFailedTemplate);
255 * Returns the Sone core used by the Sone plugin.
257 * @return The Sone core
260 public Core getCore() {
261 return sonePlugin.core();
265 * Returns the template context factory of the web interface.
267 * @return The template context factory
269 public TemplateContextFactory getTemplateContextFactory() {
270 return templateContextFactory;
273 private Session getCurrentSessionWithoutCreation(ToadletContext toadletContenxt) {
274 return getSessionManager().useSession(toadletContenxt);
277 private Session getOrCreateCurrentSession(ToadletContext toadletContenxt) {
278 Session session = getCurrentSessionWithoutCreation(toadletContenxt);
279 if (session == null) {
280 session = getSessionManager().createSession(UUID.randomUUID().toString(), toadletContenxt);
285 public Sone getCurrentSoneCreatingSession(ToadletContext toadletContext) {
286 Collection<Sone> localSones = getCore().getLocalSones();
287 if (localSones.size() == 1) {
288 return localSones.iterator().next();
290 return getCurrentSone(getOrCreateCurrentSession(toadletContext));
293 public Sone getCurrentSoneWithoutCreatingSession(ToadletContext toadletContext) {
294 Collection<Sone> localSones = getCore().getLocalSones();
295 if (localSones.size() == 1) {
296 return localSones.iterator().next();
298 return getCurrentSone(getCurrentSessionWithoutCreation(toadletContext));
302 * Returns the currently logged in Sone.
306 * @return The currently logged in Sone, or {@code null} if no Sone is
307 * currently logged in
309 private Sone getCurrentSone(Session session) {
310 if (session == null) {
313 String soneId = (String) session.getAttribute("Sone.CurrentSone");
314 if (soneId == null) {
317 return getCore().getLocalSone(soneId);
322 public Sone getCurrentSone(@Nonnull ToadletContext toadletContext, boolean createSession) {
323 return createSession ? getCurrentSoneCreatingSession(toadletContext) : getCurrentSoneWithoutCreatingSession(toadletContext);
327 * Sets the currently logged in Sone.
329 * @param toadletContext
330 * The toadlet context
332 * The Sone to set as currently logged in
335 public void setCurrentSone(@Nonnull ToadletContext toadletContext, @Nullable Sone sone) {
336 Session session = getOrCreateCurrentSession(toadletContext);
338 session.removeAttribute("Sone.CurrentSone");
340 session.setAttribute("Sone.CurrentSone", sone.getId());
345 * Returns the notification manager.
347 * @return The notification manager
349 public NotificationManager getNotifications() {
350 return notificationManager;
354 public Optional<Notification> getNotification(@Nonnull String notificationId) {
355 return Optional.fromNullable(notificationManager.getNotification(notificationId));
359 public Collection<Notification> getNotifications(@Nullable Sone currentSone) {
360 return listNotificationFilter.filterNotifications(notificationManager.getNotifications(), currentSone);
363 public Translation getTranslation() {
368 * Returns the session manager of the node.
370 * @return The node’s session manager
372 public SessionManager getSessionManager() {
373 return sonePlugin.pluginRespirator().getSessionManager("Sone");
377 * Returns the node’s form password.
379 * @return The form password
381 public String getFormPassword() {
386 public Collection<Post> getNewPosts(@Nullable Sone currentSone) {
387 Set<Post> allNewPosts = ImmutableSet.<Post> builder()
388 .addAll(newPostNotification.getElements())
389 .addAll(localPostNotification.getElements())
391 return from(allNewPosts).filter(postVisibilityFilter.isVisible(currentSone)).toSet();
395 * Returns the replies that have been announced as new in the
396 * {@link #newReplyNotification}.
398 * @return The new replies
400 public Set<PostReply> getNewReplies() {
401 return ImmutableSet.<PostReply> builder().addAll(newReplyNotification.getElements()).addAll(localReplyNotification.getElements()).build();
405 public Collection<PostReply> getNewReplies(@Nullable Sone currentSone) {
406 Set<PostReply> allNewReplies = ImmutableSet.<PostReply>builder()
407 .addAll(newReplyNotification.getElements())
408 .addAll(localReplyNotification.getElements())
410 return from(allNewReplies).filter(replyVisibilityFilter.isVisible(currentSone)).toSet();
414 * Sets whether the current start of the plugin is the first start. It is
415 * considered a first start if the configuration file does not exist.
418 * {@code true} if no configuration file existed when Sone was
419 * loaded, {@code false} otherwise
421 public void setFirstStart(boolean firstStart) {
423 Template firstStartNotificationTemplate = loaders.loadTemplate("/templates/notify/firstStartNotification.html");
424 Notification firstStartNotification = new TemplateNotification("first-start-notification", firstStartNotificationTemplate);
425 notificationManager.addNotification(firstStartNotification);
430 * Sets whether Sone was started with a fresh configuration file.
433 * {@code true} if Sone was started with a fresh configuration,
434 * {@code false} if the existing configuration could be read
436 public void setNewConfig(boolean newConfig) {
437 if (newConfig && !hasFirstStartNotification()) {
438 Template configNotReadNotificationTemplate = loaders.loadTemplate("/templates/notify/configNotReadNotification.html");
439 Notification configNotReadNotification = new TemplateNotification("config-not-read-notification", configNotReadNotificationTemplate);
440 notificationManager.addNotification(configNotReadNotification);
449 * Returns whether the first start notification is currently displayed.
451 * @return {@code true} if the first-start notification is currently
452 * displayed, {@code false} otherwise
454 private boolean hasFirstStartNotification() {
455 return notificationManager.getNotification("first-start-notification") != null;
463 * Starts the web interface and registers all toadlets.
465 public void start() {
468 /* notification templates. */
469 Template startupNotificationTemplate = loaders.loadTemplate("/templates/notify/startupNotification.html");
471 final TemplateNotification startupNotification = new TemplateNotification("startup-notification", startupNotificationTemplate);
472 notificationManager.addNotification(startupNotification);
474 ticker.schedule(new Runnable() {
478 startupNotification.dismiss();
480 }, 2, TimeUnit.MINUTES);
482 Template wotMissingNotificationTemplate = loaders.loadTemplate("/templates/notify/wotMissingNotification.html");
483 final TemplateNotification wotMissingNotification = new TemplateNotification("wot-missing-notification", wotMissingNotificationTemplate);
484 ticker.scheduleAtFixedRate(new Runnable() {
487 @SuppressWarnings("synthetic-access")
489 if (getCore().getIdentityManager().isConnected()) {
490 wotMissingNotification.dismiss();
492 notificationManager.addNotification(wotMissingNotification);
496 }, 15, 15, TimeUnit.SECONDS);
500 * Stops the web interface and unregisters all toadlets.
503 pageToadletRegistry.unregisterToadlets();
504 ticker.shutdownNow();
512 * Register all toadlets.
514 private void registerToadlets() {
515 Template postTemplate = loaders.loadTemplate("/templates/include/viewPost.html");
516 Template replyTemplate = loaders.loadTemplate("/templates/include/viewReply.html");
517 Template openSearchTemplate = loaders.loadTemplate("/templates/xml/OpenSearch.xml");
519 pageToadletRegistry.addPage(new RedirectPage<FreenetRequest>("", "index.html"));
520 pageToadletRegistry.addPage(new IndexPage(this, loaders, templateRenderer, postVisibilityFilter));
521 pageToadletRegistry.addPage(new NewPage(this, loaders, templateRenderer));
522 pageToadletRegistry.addPage(new CreateSonePage(this, loaders, templateRenderer));
523 pageToadletRegistry.addPage(new KnownSonesPage(this, loaders, templateRenderer));
524 pageToadletRegistry.addPage(new EditProfilePage(this, loaders, templateRenderer));
525 pageToadletRegistry.addPage(new EditProfileFieldPage(this, loaders, templateRenderer));
526 pageToadletRegistry.addPage(new DeleteProfileFieldPage(this, loaders, templateRenderer));
527 pageToadletRegistry.addPage(new CreatePostPage(this, loaders, templateRenderer));
528 pageToadletRegistry.addPage(new CreateReplyPage(this, loaders, templateRenderer));
529 pageToadletRegistry.addPage(new ViewSonePage(this, loaders, templateRenderer));
530 pageToadletRegistry.addPage(new ViewPostPage(this, loaders, templateRenderer));
531 pageToadletRegistry.addPage(new LikePage(this, loaders, templateRenderer));
532 pageToadletRegistry.addPage(new UnlikePage(this, loaders, templateRenderer));
533 pageToadletRegistry.addPage(new DeletePostPage(this, loaders, templateRenderer));
534 pageToadletRegistry.addPage(new DeleteReplyPage(this, loaders, templateRenderer));
535 pageToadletRegistry.addPage(new LockSonePage(this, loaders, templateRenderer));
536 pageToadletRegistry.addPage(new UnlockSonePage(this, loaders, templateRenderer));
537 pageToadletRegistry.addPage(new FollowSonePage(this, loaders, templateRenderer));
538 pageToadletRegistry.addPage(new UnfollowSonePage(this, loaders, templateRenderer));
539 pageToadletRegistry.addPage(new ImageBrowserPage(this, loaders, templateRenderer));
540 pageToadletRegistry.addPage(new CreateAlbumPage(this, loaders, templateRenderer));
541 pageToadletRegistry.addPage(new EditAlbumPage(this, loaders, templateRenderer));
542 pageToadletRegistry.addPage(new DeleteAlbumPage(this, loaders, templateRenderer));
543 pageToadletRegistry.addPage(new UploadImagePage(this, loaders, templateRenderer));
544 pageToadletRegistry.addPage(new EditImagePage(this, loaders, templateRenderer));
545 pageToadletRegistry.addPage(new DeleteImagePage(this, loaders, templateRenderer));
546 pageToadletRegistry.addPage(new MarkAsKnownPage(this, loaders, templateRenderer));
547 pageToadletRegistry.addPage(new BookmarkPage(this, loaders, templateRenderer));
548 pageToadletRegistry.addPage(new UnbookmarkPage(this, loaders, templateRenderer));
549 pageToadletRegistry.addPage(new BookmarksPage(this, loaders, templateRenderer));
550 pageToadletRegistry.addPage(new SearchPage(this, loaders, templateRenderer));
551 pageToadletRegistry.addPage(new DeleteSonePage(this, loaders, templateRenderer));
552 pageToadletRegistry.addPage(new LoginPage(this, loaders, templateRenderer));
553 pageToadletRegistry.addPage(new LogoutPage(this, loaders, templateRenderer));
554 pageToadletRegistry.addPage(new OptionsPage(this, loaders, templateRenderer));
555 pageToadletRegistry.addPage(new RescuePage(this, loaders, templateRenderer));
556 pageToadletRegistry.addPage(new AboutPage(this, loaders, templateRenderer, new PluginVersion(SonePlugin.getPluginVersion()), new PluginYear(sonePlugin.getYear()), new PluginHomepage(sonePlugin.getHomepage())));
557 pageToadletRegistry.addPage(new InvalidPage(this, loaders, templateRenderer));
558 pageToadletRegistry.addPage(new NoPermissionPage(this, loaders, templateRenderer));
559 pageToadletRegistry.addPage(new EmptyImageTitlePage(this, loaders, templateRenderer));
560 pageToadletRegistry.addPage(new EmptyAlbumTitlePage(this, loaders, templateRenderer));
561 pageToadletRegistry.addPage(new DismissNotificationPage(this, loaders, templateRenderer));
562 pageToadletRegistry.addPage(new DebugPage(this, loaders, templateRenderer));
563 pageToadletRegistry.addDebugPage(new MetricsPage(this, loaders, templateRenderer, metricRegistry));
564 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("css/", "/static/css/", "text/css"));
565 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("javascript/", "/static/javascript/", "text/javascript"));
566 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("images/", "/static/images/", "image/png"));
567 pageToadletRegistry.addPage(new TemplatePage<FreenetRequest>("OpenSearch.xml", "application/opensearchdescription+xml", templateContextFactory, openSearchTemplate));
568 pageToadletRegistry.addPage(new GetImagePage(this));
569 pageToadletRegistry.addPage(new GetTranslationAjaxPage(this));
570 pageToadletRegistry.addPage(new GetStatusAjaxPage(this, elementLoader, timeTextConverter, l10nFilter, TimeZone.getDefault()));
571 pageToadletRegistry.addPage(new GetNotificationsAjaxPage(this));
572 pageToadletRegistry.addPage(new DismissNotificationAjaxPage(this));
573 pageToadletRegistry.addPage(new CreatePostAjaxPage(this));
574 pageToadletRegistry.addPage(new CreateReplyAjaxPage(this));
575 pageToadletRegistry.addPage(new GetReplyAjaxPage(this, replyTemplate));
576 pageToadletRegistry.addPage(new GetPostAjaxPage(this, postTemplate));
577 pageToadletRegistry.addPage(new GetLinkedElementAjaxPage(this, elementLoader, linkedElementRenderFilter));
578 pageToadletRegistry.addPage(new GetTimesAjaxPage(this, timeTextConverter, l10nFilter, TimeZone.getDefault()));
579 pageToadletRegistry.addPage(new MarkAsKnownAjaxPage(this));
580 pageToadletRegistry.addPage(new DeletePostAjaxPage(this));
581 pageToadletRegistry.addPage(new DeleteReplyAjaxPage(this));
582 pageToadletRegistry.addPage(new LockSoneAjaxPage(this));
583 pageToadletRegistry.addPage(new UnlockSoneAjaxPage(this));
584 pageToadletRegistry.addPage(new FollowSoneAjaxPage(this));
585 pageToadletRegistry.addPage(new UnfollowSoneAjaxPage(this));
586 pageToadletRegistry.addPage(new EditAlbumAjaxPage(this));
587 pageToadletRegistry.addPage(new EditImageAjaxPage(this, parserFilter, shortenFilter, renderFilter));
588 pageToadletRegistry.addPage(new LikeAjaxPage(this));
589 pageToadletRegistry.addPage(new UnlikeAjaxPage(this));
590 pageToadletRegistry.addPage(new GetLikesAjaxPage(this));
591 pageToadletRegistry.addPage(new BookmarkAjaxPage(this));
592 pageToadletRegistry.addPage(new UnbookmarkAjaxPage(this));
593 pageToadletRegistry.addPage(new EditProfileFieldAjaxPage(this));
594 pageToadletRegistry.addPage(new DeleteProfileFieldAjaxPage(this));
595 pageToadletRegistry.addPage(new MoveProfileFieldAjaxPage(this));
597 pageToadletRegistry.registerToadlets();
601 * Returns all {@link Sone#isLocal() local Sone}s that are referenced by
602 * {@link SonePart}s in the given text (after parsing it using
603 * {@link SoneTextParser}).
607 * @return All mentioned local Sones
609 private Collection<Sone> getMentionedSones(String text) {
610 /* we need no context to find mentioned Sones. */
611 Set<Sone> mentionedSones = new HashSet<>();
612 for (Part part : soneTextParser.parse(text, null)) {
613 if (part instanceof SonePart) {
614 mentionedSones.add(((SonePart) part).getSone());
617 return Collections2.filter(mentionedSones, Sone.LOCAL_SONE_FILTER);
621 * Returns the Sone insert notification for the given Sone. If no
622 * notification for the given Sone exists, a new notification is created and
626 * The Sone to get the insert notification for
627 * @return The Sone insert notification
629 private TemplateNotification getSoneInsertNotification(Sone sone) {
630 synchronized (soneInsertNotifications) {
631 TemplateNotification templateNotification = soneInsertNotifications.get(sone);
632 if (templateNotification == null) {
633 templateNotification = new TemplateNotification(loaders.loadTemplate("/templates/notify/soneInsertNotification.html"));
634 templateNotification.set("insertSone", sone);
635 soneInsertNotifications.put(sone, templateNotification);
637 return templateNotification;
641 private boolean localSoneMentionedInNewPostOrReply(Post post) {
642 if (!post.getSone().isLocal()) {
643 if (!getMentionedSones(post.getText()).isEmpty() && !post.isKnown()) {
647 for (PostReply postReply : getCore().getReplies(post.getId())) {
648 if (postReply.getSone().isLocal()) {
651 if (!getMentionedSones(postReply.getText()).isEmpty() && !postReply.isKnown()) {
663 * Notifies the web interface that a new {@link Post} was found.
665 * @param newPostFoundEvent
669 public void newPostFound(NewPostFoundEvent newPostFoundEvent) {
670 Post post = newPostFoundEvent.getPost();
671 boolean isLocal = post.getSone().isLocal();
672 if (!hasFirstStartNotification()) {
673 if (!getMentionedSones(post.getText()).isEmpty() && !isLocal) {
674 mentionNotification.add(post);
675 notificationManager.addNotification(mentionNotification);
681 * Notifies the web interface that a new {@link PostReply} was found.
683 * @param newPostReplyFoundEvent
687 public void newReplyFound(NewPostReplyFoundEvent newPostReplyFoundEvent) {
688 PostReply reply = newPostReplyFoundEvent.getPostReply();
689 boolean isLocal = reply.getSone().isLocal();
691 localReplyNotification.add(reply);
693 newReplyNotification.add(reply);
695 if (!hasFirstStartNotification()) {
696 notificationManager.addNotification(isLocal ? localReplyNotification : newReplyNotification);
697 if (reply.getPost().isPresent() && localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
698 mentionNotification.add(reply.getPost().get());
699 notificationManager.addNotification(mentionNotification);
702 getCore().markReplyKnown(reply);
707 public void markPostKnown(MarkPostKnownEvent markPostKnownEvent) {
708 removePost(markPostKnownEvent.getPost());
712 public void markReplyKnown(MarkPostReplyKnownEvent markPostReplyKnownEvent) {
713 removeReply(markPostReplyKnownEvent.getPostReply());
717 public void postRemoved(PostRemovedEvent postRemovedEvent) {
718 removePost(postRemovedEvent.getPost());
721 private void removePost(Post post) {
722 newPostNotification.remove(post);
723 if (!localSoneMentionedInNewPostOrReply(post)) {
724 mentionNotification.remove(post);
729 public void replyRemoved(PostReplyRemovedEvent postReplyRemovedEvent) {
730 removeReply(postReplyRemovedEvent.getPostReply());
733 private void removeReply(PostReply reply) {
734 newReplyNotification.remove(reply);
735 localReplyNotification.remove(reply);
736 if (reply.getPost().isPresent() && !localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
737 mentionNotification.remove(reply.getPost().get());
742 * Notifies the web interface that a {@link Sone} is being inserted.
744 * @param soneInsertingEvent
748 public void soneInserting(SoneInsertingEvent soneInsertingEvent) {
749 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertingEvent.getSone());
750 soneInsertNotification.set("soneStatus", "inserting");
751 if (soneInsertingEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
752 notificationManager.addNotification(soneInsertNotification);
757 * Notifies the web interface that a {@link Sone} was inserted.
759 * @param soneInsertedEvent
763 public void soneInserted(SoneInsertedEvent soneInsertedEvent) {
764 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertedEvent.getSone());
765 soneInsertNotification.set("soneStatus", "inserted");
766 soneInsertNotification.set("insertDuration", soneInsertedEvent.getInsertDuration() / 1000);
767 if (soneInsertedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
768 notificationManager.addNotification(soneInsertNotification);
773 * Notifies the web interface that a {@link Sone} insert was aborted.
775 * @param soneInsertAbortedEvent
779 public void soneInsertAborted(SoneInsertAbortedEvent soneInsertAbortedEvent) {
780 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertAbortedEvent.getSone());
781 soneInsertNotification.set("soneStatus", "insert-aborted");
782 soneInsertNotification.set("insert-error", soneInsertAbortedEvent.getCause());
783 if (soneInsertAbortedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
784 notificationManager.addNotification(soneInsertNotification);
789 * Notifies the web interface that an image insert was started
791 * @param imageInsertStartedEvent
795 public void imageInsertStarted(ImageInsertStartedEvent imageInsertStartedEvent) {
796 insertingImagesNotification.add(imageInsertStartedEvent.getImage());
797 notificationManager.addNotification(insertingImagesNotification);
801 * Notifies the web interface that an {@link Image} insert was aborted.
803 * @param imageInsertAbortedEvent
807 public void imageInsertAborted(ImageInsertAbortedEvent imageInsertAbortedEvent) {
808 insertingImagesNotification.remove(imageInsertAbortedEvent.getImage());
812 * Notifies the web interface that an {@link Image} insert is finished.
814 * @param imageInsertFinishedEvent
818 public void imageInsertFinished(ImageInsertFinishedEvent imageInsertFinishedEvent) {
819 insertingImagesNotification.remove(imageInsertFinishedEvent.getImage());
820 insertedImagesNotification.add(imageInsertFinishedEvent.getImage());
821 notificationManager.addNotification(insertedImagesNotification);
825 * Notifies the web interface that an {@link Image} insert has failed.
827 * @param imageInsertFailedEvent
831 public void imageInsertFailed(ImageInsertFailedEvent imageInsertFailedEvent) {
832 insertingImagesNotification.remove(imageInsertFailedEvent.getImage());
833 imageInsertFailedNotification.add(imageInsertFailedEvent.getImage());
834 notificationManager.addNotification(imageInsertFailedNotification);
838 public void debugActivated(@Nonnull DebugActivatedEvent debugActivatedEvent) {
839 pageToadletRegistry.activateDebugMode();