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.Image;
42 import net.pterodactylus.sone.data.Post;
43 import net.pterodactylus.sone.data.PostReply;
44 import net.pterodactylus.sone.data.Sone;
45 import net.pterodactylus.sone.freenet.L10nFilter;
46 import net.pterodactylus.sone.freenet.Translation;
47 import net.pterodactylus.sone.main.Loaders;
48 import net.pterodactylus.sone.main.PluginHomepage;
49 import net.pterodactylus.sone.main.PluginVersion;
50 import net.pterodactylus.sone.main.PluginYear;
51 import net.pterodactylus.sone.main.SonePlugin;
52 import net.pterodactylus.sone.notify.ListNotification;
53 import net.pterodactylus.sone.notify.ListNotificationFilter;
54 import net.pterodactylus.sone.notify.PostVisibilityFilter;
55 import net.pterodactylus.sone.notify.ReplyVisibilityFilter;
56 import net.pterodactylus.sone.template.LinkedElementRenderFilter;
57 import net.pterodactylus.sone.template.ParserFilter;
58 import net.pterodactylus.sone.template.RenderFilter;
59 import net.pterodactylus.sone.template.ShortenFilter;
60 import net.pterodactylus.sone.text.Part;
61 import net.pterodactylus.sone.text.SonePart;
62 import net.pterodactylus.sone.text.SoneTextParser;
63 import net.pterodactylus.sone.text.TimeTextConverter;
64 import net.pterodactylus.sone.web.ajax.BookmarkAjaxPage;
65 import net.pterodactylus.sone.web.ajax.CreatePostAjaxPage;
66 import net.pterodactylus.sone.web.ajax.CreateReplyAjaxPage;
67 import net.pterodactylus.sone.web.ajax.DeletePostAjaxPage;
68 import net.pterodactylus.sone.web.ajax.DeleteProfileFieldAjaxPage;
69 import net.pterodactylus.sone.web.ajax.DeleteReplyAjaxPage;
70 import net.pterodactylus.sone.web.ajax.DismissNotificationAjaxPage;
71 import net.pterodactylus.sone.web.ajax.EditAlbumAjaxPage;
72 import net.pterodactylus.sone.web.ajax.EditImageAjaxPage;
73 import net.pterodactylus.sone.web.ajax.EditProfileFieldAjaxPage;
74 import net.pterodactylus.sone.web.ajax.FollowSoneAjaxPage;
75 import net.pterodactylus.sone.web.ajax.GetLikesAjaxPage;
76 import net.pterodactylus.sone.web.ajax.GetLinkedElementAjaxPage;
77 import net.pterodactylus.sone.web.ajax.GetNotificationsAjaxPage;
78 import net.pterodactylus.sone.web.ajax.GetPostAjaxPage;
79 import net.pterodactylus.sone.web.ajax.GetReplyAjaxPage;
80 import net.pterodactylus.sone.web.ajax.GetStatusAjaxPage;
81 import net.pterodactylus.sone.web.ajax.GetTimesAjaxPage;
82 import net.pterodactylus.sone.web.ajax.GetTranslationAjaxPage;
83 import net.pterodactylus.sone.web.ajax.LikeAjaxPage;
84 import net.pterodactylus.sone.web.ajax.LockSoneAjaxPage;
85 import net.pterodactylus.sone.web.ajax.MarkAsKnownAjaxPage;
86 import net.pterodactylus.sone.web.ajax.MoveProfileFieldAjaxPage;
87 import net.pterodactylus.sone.web.ajax.UnbookmarkAjaxPage;
88 import net.pterodactylus.sone.web.ajax.UnfollowSoneAjaxPage;
89 import net.pterodactylus.sone.web.ajax.UnlikeAjaxPage;
90 import net.pterodactylus.sone.web.ajax.UnlockSoneAjaxPage;
91 import net.pterodactylus.sone.web.page.FreenetRequest;
92 import net.pterodactylus.sone.web.page.TemplateRenderer;
93 import net.pterodactylus.sone.web.pages.*;
94 import net.pterodactylus.util.notify.Notification;
95 import net.pterodactylus.util.notify.NotificationManager;
96 import net.pterodactylus.util.notify.TemplateNotification;
97 import net.pterodactylus.util.template.Template;
98 import net.pterodactylus.util.template.TemplateContextFactory;
99 import net.pterodactylus.util.web.RedirectPage;
100 import net.pterodactylus.util.web.TemplatePage;
102 import freenet.clients.http.SessionManager;
103 import freenet.clients.http.SessionManager.Session;
104 import freenet.clients.http.ToadletContext;
106 import com.codahale.metrics.*;
107 import com.google.common.base.Optional;
108 import com.google.common.collect.Collections2;
109 import com.google.common.collect.ImmutableSet;
110 import com.google.common.eventbus.Subscribe;
111 import com.google.inject.Inject;
114 * Bundles functionality that a web interface of a Freenet plugin needs, e.g.
115 * references to l10n helpers.
117 public class WebInterface implements SessionProvider {
120 private static final Logger logger = getLogger(WebInterface.class.getName());
122 /** The loaders for templates, pages, and classpath providers. */
123 private final Loaders loaders;
125 /** The notification manager. */
126 private final NotificationManager notificationManager;
128 /** The Sone plugin. */
129 private final SonePlugin sonePlugin;
131 /** The form password. */
132 private final String formPassword;
134 /** The template context factory. */
135 private final TemplateContextFactory templateContextFactory;
136 private final TemplateRenderer templateRenderer;
138 /** The Sone text parser. */
139 private final SoneTextParser soneTextParser;
141 /** The parser filter. */
142 private final ParserFilter parserFilter;
143 private final ShortenFilter shortenFilter;
144 private final RenderFilter renderFilter;
146 private final ListNotificationFilter listNotificationFilter;
147 private final PostVisibilityFilter postVisibilityFilter;
148 private final ReplyVisibilityFilter replyVisibilityFilter;
150 private final ElementLoader elementLoader;
151 private final LinkedElementRenderFilter linkedElementRenderFilter;
152 private final TimeTextConverter timeTextConverter = new TimeTextConverter();
153 private final L10nFilter l10nFilter;
155 private final PageToadletRegistry pageToadletRegistry;
156 private final MetricRegistry metricRegistry;
157 private final Translation translation;
159 /** The “new post” notification. */
160 private final ListNotification<Post> newPostNotification;
162 /** The “new reply” notification. */
163 private final ListNotification<PostReply> newReplyNotification;
165 /** The invisible “local post” notification. */
166 private final ListNotification<Post> localPostNotification;
168 /** The invisible “local reply” notification. */
169 private final ListNotification<PostReply> localReplyNotification;
171 /** The “you have been mentioned” notification. */
172 private final ListNotification<Post> mentionNotification;
174 /** Notifications for sone inserts. */
175 private final Map<Sone, TemplateNotification> soneInsertNotifications = new HashMap<>();
177 /** The “inserting images” notification. */
178 private final ListNotification<Image> insertingImagesNotification;
180 /** The “inserted images” notification. */
181 private final ListNotification<Image> insertedImagesNotification;
183 /** The “image insert failed” notification. */
184 private final ListNotification<Image> imageInsertFailedNotification;
186 /** Scheduled executor for time-based notifications. */
187 private final ScheduledExecutorService ticker = Executors.newScheduledThreadPool(1);
190 public WebInterface(SonePlugin sonePlugin, Loaders loaders, ListNotificationFilter listNotificationFilter,
191 PostVisibilityFilter postVisibilityFilter, ReplyVisibilityFilter replyVisibilityFilter,
192 ElementLoader elementLoader, TemplateContextFactory templateContextFactory,
193 TemplateRenderer templateRenderer,
194 ParserFilter parserFilter, ShortenFilter shortenFilter,
195 RenderFilter renderFilter,
196 LinkedElementRenderFilter linkedElementRenderFilter,
197 PageToadletRegistry pageToadletRegistry, MetricRegistry metricRegistry, Translation translation, L10nFilter l10nFilter,
198 NotificationManager notificationManager, @Named("newRemotePost") ListNotification<Post> newPostNotification,
199 @Named("localPost") ListNotification<Post> localPostNotification) {
200 this.sonePlugin = sonePlugin;
201 this.loaders = loaders;
202 this.listNotificationFilter = listNotificationFilter;
203 this.postVisibilityFilter = postVisibilityFilter;
204 this.replyVisibilityFilter = replyVisibilityFilter;
205 this.elementLoader = elementLoader;
206 this.templateRenderer = templateRenderer;
207 this.parserFilter = parserFilter;
208 this.shortenFilter = shortenFilter;
209 this.renderFilter = renderFilter;
210 this.linkedElementRenderFilter = linkedElementRenderFilter;
211 this.pageToadletRegistry = pageToadletRegistry;
212 this.metricRegistry = metricRegistry;
213 this.l10nFilter = l10nFilter;
214 this.translation = translation;
215 this.notificationManager = notificationManager;
216 this.newPostNotification = newPostNotification;
217 this.localPostNotification = localPostNotification;
218 formPassword = sonePlugin.pluginRespirator().getToadletContainer().getFormPassword();
219 soneTextParser = new SoneTextParser(getCore(), getCore());
221 this.templateContextFactory = templateContextFactory;
222 templateContextFactory.addTemplateObject("webInterface", this);
223 templateContextFactory.addTemplateObject("formPassword", formPassword);
225 /* create notifications. */
226 Template newReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
227 newReplyNotification = new ListNotification<>("new-reply-notification", "replies", newReplyNotificationTemplate, false);
229 Template localReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
230 localReplyNotification = new ListNotification<>("local-reply-notification", "replies", localReplyNotificationTemplate, false);
232 Template mentionNotificationTemplate = loaders.loadTemplate("/templates/notify/mentionNotification.html");
233 mentionNotification = new ListNotification<>("mention-notification", "posts", mentionNotificationTemplate, false);
235 Template insertingImagesTemplate = loaders.loadTemplate("/templates/notify/inserting-images-notification.html");
236 insertingImagesNotification = new ListNotification<>("inserting-images-notification", "images", insertingImagesTemplate);
238 Template insertedImagesTemplate = loaders.loadTemplate("/templates/notify/inserted-images-notification.html");
239 insertedImagesNotification = new ListNotification<>("inserted-images-notification", "images", insertedImagesTemplate);
241 Template imageInsertFailedTemplate = loaders.loadTemplate("/templates/notify/image-insert-failed-notification.html");
242 imageInsertFailedNotification = new ListNotification<>("image-insert-failed-notification", "images", imageInsertFailedTemplate);
250 * Returns the Sone core used by the Sone plugin.
252 * @return The Sone core
255 public Core getCore() {
256 return sonePlugin.core();
260 * Returns the template context factory of the web interface.
262 * @return The template context factory
264 public TemplateContextFactory getTemplateContextFactory() {
265 return templateContextFactory;
268 private Session getCurrentSessionWithoutCreation(ToadletContext toadletContenxt) {
269 return getSessionManager().useSession(toadletContenxt);
272 private Session getOrCreateCurrentSession(ToadletContext toadletContenxt) {
273 Session session = getCurrentSessionWithoutCreation(toadletContenxt);
274 if (session == null) {
275 session = getSessionManager().createSession(UUID.randomUUID().toString(), toadletContenxt);
280 public Sone getCurrentSoneCreatingSession(ToadletContext toadletContext) {
281 Collection<Sone> localSones = getCore().getLocalSones();
282 if (localSones.size() == 1) {
283 return localSones.iterator().next();
285 return getCurrentSone(getOrCreateCurrentSession(toadletContext));
288 public Sone getCurrentSoneWithoutCreatingSession(ToadletContext toadletContext) {
289 Collection<Sone> localSones = getCore().getLocalSones();
290 if (localSones.size() == 1) {
291 return localSones.iterator().next();
293 return getCurrentSone(getCurrentSessionWithoutCreation(toadletContext));
297 * Returns the currently logged in Sone.
301 * @return The currently logged in Sone, or {@code null} if no Sone is
302 * currently logged in
304 private Sone getCurrentSone(Session session) {
305 if (session == null) {
308 String soneId = (String) session.getAttribute("Sone.CurrentSone");
309 if (soneId == null) {
312 return getCore().getLocalSone(soneId);
317 public Sone getCurrentSone(@Nonnull ToadletContext toadletContext, boolean createSession) {
318 return createSession ? getCurrentSoneCreatingSession(toadletContext) : getCurrentSoneWithoutCreatingSession(toadletContext);
322 * Sets the currently logged in Sone.
324 * @param toadletContext
325 * The toadlet context
327 * The Sone to set as currently logged in
330 public void setCurrentSone(@Nonnull ToadletContext toadletContext, @Nullable Sone sone) {
331 Session session = getOrCreateCurrentSession(toadletContext);
333 session.removeAttribute("Sone.CurrentSone");
335 session.setAttribute("Sone.CurrentSone", sone.getId());
340 * Returns the notification manager.
342 * @return The notification manager
344 public NotificationManager getNotifications() {
345 return notificationManager;
349 public Optional<Notification> getNotification(@Nonnull String notificationId) {
350 return Optional.fromNullable(notificationManager.getNotification(notificationId));
354 public Collection<Notification> getNotifications(@Nullable Sone currentSone) {
355 return listNotificationFilter.filterNotifications(notificationManager.getNotifications(), currentSone);
358 public Translation getTranslation() {
363 * Returns the session manager of the node.
365 * @return The node’s session manager
367 public SessionManager getSessionManager() {
368 return sonePlugin.pluginRespirator().getSessionManager("Sone");
372 * Returns the node’s form password.
374 * @return The form password
376 public String getFormPassword() {
381 public Collection<Post> getNewPosts(@Nullable Sone currentSone) {
382 Set<Post> allNewPosts = ImmutableSet.<Post> builder()
383 .addAll(newPostNotification.getElements())
384 .addAll(localPostNotification.getElements())
386 return from(allNewPosts).filter(postVisibilityFilter.isVisible(currentSone)).toSet();
390 * Returns the replies that have been announced as new in the
391 * {@link #newReplyNotification}.
393 * @return The new replies
395 public Set<PostReply> getNewReplies() {
396 return ImmutableSet.<PostReply> builder().addAll(newReplyNotification.getElements()).addAll(localReplyNotification.getElements()).build();
400 public Collection<PostReply> getNewReplies(@Nullable Sone currentSone) {
401 Set<PostReply> allNewReplies = ImmutableSet.<PostReply>builder()
402 .addAll(newReplyNotification.getElements())
403 .addAll(localReplyNotification.getElements())
405 return from(allNewReplies).filter(replyVisibilityFilter.isVisible(currentSone)).toSet();
409 * Sets whether the current start of the plugin is the first start. It is
410 * considered a first start if the configuration file does not exist.
413 * {@code true} if no configuration file existed when Sone was
414 * loaded, {@code false} otherwise
416 public void setFirstStart(boolean firstStart) {
418 Template firstStartNotificationTemplate = loaders.loadTemplate("/templates/notify/firstStartNotification.html");
419 Notification firstStartNotification = new TemplateNotification("first-start-notification", firstStartNotificationTemplate);
420 notificationManager.addNotification(firstStartNotification);
425 * Sets whether Sone was started with a fresh configuration file.
428 * {@code true} if Sone was started with a fresh configuration,
429 * {@code false} if the existing configuration could be read
431 public void setNewConfig(boolean newConfig) {
432 if (newConfig && !hasFirstStartNotification()) {
433 Template configNotReadNotificationTemplate = loaders.loadTemplate("/templates/notify/configNotReadNotification.html");
434 Notification configNotReadNotification = new TemplateNotification("config-not-read-notification", configNotReadNotificationTemplate);
435 notificationManager.addNotification(configNotReadNotification);
444 * Returns whether the first start notification is currently displayed.
446 * @return {@code true} if the first-start notification is currently
447 * displayed, {@code false} otherwise
449 private boolean hasFirstStartNotification() {
450 return notificationManager.getNotification("first-start-notification") != null;
458 * Starts the web interface and registers all toadlets.
460 public void start() {
463 /* notification templates. */
464 Template startupNotificationTemplate = loaders.loadTemplate("/templates/notify/startupNotification.html");
466 final TemplateNotification startupNotification = new TemplateNotification("startup-notification", startupNotificationTemplate);
467 notificationManager.addNotification(startupNotification);
469 ticker.schedule(new Runnable() {
473 startupNotification.dismiss();
475 }, 2, TimeUnit.MINUTES);
477 Template wotMissingNotificationTemplate = loaders.loadTemplate("/templates/notify/wotMissingNotification.html");
478 final TemplateNotification wotMissingNotification = new TemplateNotification("wot-missing-notification", wotMissingNotificationTemplate);
479 ticker.scheduleAtFixedRate(new Runnable() {
482 @SuppressWarnings("synthetic-access")
484 if (getCore().getIdentityManager().isConnected()) {
485 wotMissingNotification.dismiss();
487 notificationManager.addNotification(wotMissingNotification);
491 }, 15, 15, TimeUnit.SECONDS);
495 * Stops the web interface and unregisters all toadlets.
498 pageToadletRegistry.unregisterToadlets();
499 ticker.shutdownNow();
507 * Register all toadlets.
509 private void registerToadlets() {
510 Template postTemplate = loaders.loadTemplate("/templates/include/viewPost.html");
511 Template replyTemplate = loaders.loadTemplate("/templates/include/viewReply.html");
512 Template openSearchTemplate = loaders.loadTemplate("/templates/xml/OpenSearch.xml");
514 pageToadletRegistry.addPage(new RedirectPage<FreenetRequest>("", "index.html"));
515 pageToadletRegistry.addPage(new IndexPage(this, loaders, templateRenderer, postVisibilityFilter));
516 pageToadletRegistry.addPage(new NewPage(this, loaders, templateRenderer));
517 pageToadletRegistry.addPage(new CreateSonePage(this, loaders, templateRenderer));
518 pageToadletRegistry.addPage(new KnownSonesPage(this, loaders, templateRenderer));
519 pageToadletRegistry.addPage(new EditProfilePage(this, loaders, templateRenderer));
520 pageToadletRegistry.addPage(new EditProfileFieldPage(this, loaders, templateRenderer));
521 pageToadletRegistry.addPage(new DeleteProfileFieldPage(this, loaders, templateRenderer));
522 pageToadletRegistry.addPage(new CreatePostPage(this, loaders, templateRenderer));
523 pageToadletRegistry.addPage(new CreateReplyPage(this, loaders, templateRenderer));
524 pageToadletRegistry.addPage(new ViewSonePage(this, loaders, templateRenderer));
525 pageToadletRegistry.addPage(new ViewPostPage(this, loaders, templateRenderer));
526 pageToadletRegistry.addPage(new LikePage(this, loaders, templateRenderer));
527 pageToadletRegistry.addPage(new UnlikePage(this, loaders, templateRenderer));
528 pageToadletRegistry.addPage(new DeletePostPage(this, loaders, templateRenderer));
529 pageToadletRegistry.addPage(new DeleteReplyPage(this, loaders, templateRenderer));
530 pageToadletRegistry.addPage(new LockSonePage(this, loaders, templateRenderer));
531 pageToadletRegistry.addPage(new UnlockSonePage(this, loaders, templateRenderer));
532 pageToadletRegistry.addPage(new FollowSonePage(this, loaders, templateRenderer));
533 pageToadletRegistry.addPage(new UnfollowSonePage(this, loaders, templateRenderer));
534 pageToadletRegistry.addPage(new ImageBrowserPage(this, loaders, templateRenderer));
535 pageToadletRegistry.addPage(new CreateAlbumPage(this, loaders, templateRenderer));
536 pageToadletRegistry.addPage(new EditAlbumPage(this, loaders, templateRenderer));
537 pageToadletRegistry.addPage(new DeleteAlbumPage(this, loaders, templateRenderer));
538 pageToadletRegistry.addPage(new UploadImagePage(this, loaders, templateRenderer));
539 pageToadletRegistry.addPage(new EditImagePage(this, loaders, templateRenderer));
540 pageToadletRegistry.addPage(new DeleteImagePage(this, loaders, templateRenderer));
541 pageToadletRegistry.addPage(new MarkAsKnownPage(this, loaders, templateRenderer));
542 pageToadletRegistry.addPage(new BookmarkPage(this, loaders, templateRenderer));
543 pageToadletRegistry.addPage(new UnbookmarkPage(this, loaders, templateRenderer));
544 pageToadletRegistry.addPage(new BookmarksPage(this, loaders, templateRenderer));
545 pageToadletRegistry.addPage(new SearchPage(this, loaders, templateRenderer));
546 pageToadletRegistry.addPage(new DeleteSonePage(this, loaders, templateRenderer));
547 pageToadletRegistry.addPage(new LoginPage(this, loaders, templateRenderer));
548 pageToadletRegistry.addPage(new LogoutPage(this, loaders, templateRenderer));
549 pageToadletRegistry.addPage(new OptionsPage(this, loaders, templateRenderer));
550 pageToadletRegistry.addPage(new RescuePage(this, loaders, templateRenderer));
551 pageToadletRegistry.addPage(new AboutPage(this, loaders, templateRenderer, new PluginVersion(SonePlugin.getPluginVersion()), new PluginYear(sonePlugin.getYear()), new PluginHomepage(sonePlugin.getHomepage())));
552 pageToadletRegistry.addPage(new InvalidPage(this, loaders, templateRenderer));
553 pageToadletRegistry.addPage(new NoPermissionPage(this, loaders, templateRenderer));
554 pageToadletRegistry.addPage(new EmptyImageTitlePage(this, loaders, templateRenderer));
555 pageToadletRegistry.addPage(new EmptyAlbumTitlePage(this, loaders, templateRenderer));
556 pageToadletRegistry.addPage(new DismissNotificationPage(this, loaders, templateRenderer));
557 pageToadletRegistry.addPage(new DebugPage(this, loaders, templateRenderer));
558 pageToadletRegistry.addDebugPage(new MetricsPage(this, loaders, templateRenderer, metricRegistry));
559 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("css/", "/static/css/", "text/css"));
560 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("javascript/", "/static/javascript/", "text/javascript"));
561 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("images/", "/static/images/", "image/png"));
562 pageToadletRegistry.addPage(new TemplatePage<FreenetRequest>("OpenSearch.xml", "application/opensearchdescription+xml", templateContextFactory, openSearchTemplate));
563 pageToadletRegistry.addPage(new GetImagePage(this));
564 pageToadletRegistry.addPage(new GetTranslationAjaxPage(this));
565 pageToadletRegistry.addPage(new GetStatusAjaxPage(this, elementLoader, timeTextConverter, l10nFilter, TimeZone.getDefault()));
566 pageToadletRegistry.addPage(new GetNotificationsAjaxPage(this));
567 pageToadletRegistry.addPage(new DismissNotificationAjaxPage(this));
568 pageToadletRegistry.addPage(new CreatePostAjaxPage(this));
569 pageToadletRegistry.addPage(new CreateReplyAjaxPage(this));
570 pageToadletRegistry.addPage(new GetReplyAjaxPage(this, replyTemplate));
571 pageToadletRegistry.addPage(new GetPostAjaxPage(this, postTemplate));
572 pageToadletRegistry.addPage(new GetLinkedElementAjaxPage(this, elementLoader, linkedElementRenderFilter));
573 pageToadletRegistry.addPage(new GetTimesAjaxPage(this, timeTextConverter, l10nFilter, TimeZone.getDefault()));
574 pageToadletRegistry.addPage(new MarkAsKnownAjaxPage(this));
575 pageToadletRegistry.addPage(new DeletePostAjaxPage(this));
576 pageToadletRegistry.addPage(new DeleteReplyAjaxPage(this));
577 pageToadletRegistry.addPage(new LockSoneAjaxPage(this));
578 pageToadletRegistry.addPage(new UnlockSoneAjaxPage(this));
579 pageToadletRegistry.addPage(new FollowSoneAjaxPage(this));
580 pageToadletRegistry.addPage(new UnfollowSoneAjaxPage(this));
581 pageToadletRegistry.addPage(new EditAlbumAjaxPage(this));
582 pageToadletRegistry.addPage(new EditImageAjaxPage(this, parserFilter, shortenFilter, renderFilter));
583 pageToadletRegistry.addPage(new LikeAjaxPage(this));
584 pageToadletRegistry.addPage(new UnlikeAjaxPage(this));
585 pageToadletRegistry.addPage(new GetLikesAjaxPage(this));
586 pageToadletRegistry.addPage(new BookmarkAjaxPage(this));
587 pageToadletRegistry.addPage(new UnbookmarkAjaxPage(this));
588 pageToadletRegistry.addPage(new EditProfileFieldAjaxPage(this));
589 pageToadletRegistry.addPage(new DeleteProfileFieldAjaxPage(this));
590 pageToadletRegistry.addPage(new MoveProfileFieldAjaxPage(this));
592 pageToadletRegistry.registerToadlets();
596 * Returns all {@link Sone#isLocal() local Sone}s that are referenced by
597 * {@link SonePart}s in the given text (after parsing it using
598 * {@link SoneTextParser}).
602 * @return All mentioned local Sones
604 private Collection<Sone> getMentionedSones(String text) {
605 /* we need no context to find mentioned Sones. */
606 Set<Sone> mentionedSones = new HashSet<>();
607 for (Part part : soneTextParser.parse(text, null)) {
608 if (part instanceof SonePart) {
609 mentionedSones.add(((SonePart) part).getSone());
612 return Collections2.filter(mentionedSones, Sone.LOCAL_SONE_FILTER);
616 * Returns the Sone insert notification for the given Sone. If no
617 * notification for the given Sone exists, a new notification is created and
621 * The Sone to get the insert notification for
622 * @return The Sone insert notification
624 private TemplateNotification getSoneInsertNotification(Sone sone) {
625 synchronized (soneInsertNotifications) {
626 TemplateNotification templateNotification = soneInsertNotifications.get(sone);
627 if (templateNotification == null) {
628 templateNotification = new TemplateNotification(loaders.loadTemplate("/templates/notify/soneInsertNotification.html"));
629 templateNotification.set("insertSone", sone);
630 soneInsertNotifications.put(sone, templateNotification);
632 return templateNotification;
636 private boolean localSoneMentionedInNewPostOrReply(Post post) {
637 if (!post.getSone().isLocal()) {
638 if (!getMentionedSones(post.getText()).isEmpty() && !post.isKnown()) {
642 for (PostReply postReply : getCore().getReplies(post.getId())) {
643 if (postReply.getSone().isLocal()) {
646 if (!getMentionedSones(postReply.getText()).isEmpty() && !postReply.isKnown()) {
658 * Notifies the web interface that a new {@link Post} was found.
660 * @param newPostFoundEvent
664 public void newPostFound(NewPostFoundEvent newPostFoundEvent) {
665 Post post = newPostFoundEvent.getPost();
666 boolean isLocal = post.getSone().isLocal();
667 if (!hasFirstStartNotification()) {
668 if (!getMentionedSones(post.getText()).isEmpty() && !isLocal) {
669 mentionNotification.add(post);
670 notificationManager.addNotification(mentionNotification);
676 * Notifies the web interface that a new {@link PostReply} was found.
678 * @param newPostReplyFoundEvent
682 public void newReplyFound(NewPostReplyFoundEvent newPostReplyFoundEvent) {
683 PostReply reply = newPostReplyFoundEvent.getPostReply();
684 boolean isLocal = reply.getSone().isLocal();
686 localReplyNotification.add(reply);
688 newReplyNotification.add(reply);
690 if (!hasFirstStartNotification()) {
691 notificationManager.addNotification(isLocal ? localReplyNotification : newReplyNotification);
692 if (reply.getPost().isPresent() && localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
693 mentionNotification.add(reply.getPost().get());
694 notificationManager.addNotification(mentionNotification);
697 getCore().markReplyKnown(reply);
702 public void markPostKnown(MarkPostKnownEvent markPostKnownEvent) {
703 removePost(markPostKnownEvent.getPost());
707 public void markReplyKnown(MarkPostReplyKnownEvent markPostReplyKnownEvent) {
708 removeReply(markPostReplyKnownEvent.getPostReply());
712 public void postRemoved(PostRemovedEvent postRemovedEvent) {
713 removePost(postRemovedEvent.getPost());
716 private void removePost(Post post) {
717 newPostNotification.remove(post);
718 if (!localSoneMentionedInNewPostOrReply(post)) {
719 mentionNotification.remove(post);
724 public void replyRemoved(PostReplyRemovedEvent postReplyRemovedEvent) {
725 removeReply(postReplyRemovedEvent.getPostReply());
728 private void removeReply(PostReply reply) {
729 newReplyNotification.remove(reply);
730 localReplyNotification.remove(reply);
731 if (reply.getPost().isPresent() && !localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
732 mentionNotification.remove(reply.getPost().get());
737 * Notifies the web interface that a {@link Sone} is being inserted.
739 * @param soneInsertingEvent
743 public void soneInserting(SoneInsertingEvent soneInsertingEvent) {
744 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertingEvent.getSone());
745 soneInsertNotification.set("soneStatus", "inserting");
746 if (soneInsertingEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
747 notificationManager.addNotification(soneInsertNotification);
752 * Notifies the web interface that a {@link Sone} was inserted.
754 * @param soneInsertedEvent
758 public void soneInserted(SoneInsertedEvent soneInsertedEvent) {
759 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertedEvent.getSone());
760 soneInsertNotification.set("soneStatus", "inserted");
761 soneInsertNotification.set("insertDuration", soneInsertedEvent.getInsertDuration() / 1000);
762 if (soneInsertedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
763 notificationManager.addNotification(soneInsertNotification);
768 * Notifies the web interface that a {@link Sone} insert was aborted.
770 * @param soneInsertAbortedEvent
774 public void soneInsertAborted(SoneInsertAbortedEvent soneInsertAbortedEvent) {
775 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertAbortedEvent.getSone());
776 soneInsertNotification.set("soneStatus", "insert-aborted");
777 soneInsertNotification.set("insert-error", soneInsertAbortedEvent.getCause());
778 if (soneInsertAbortedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
779 notificationManager.addNotification(soneInsertNotification);
784 * Notifies the web interface that an image insert was started
786 * @param imageInsertStartedEvent
790 public void imageInsertStarted(ImageInsertStartedEvent imageInsertStartedEvent) {
791 insertingImagesNotification.add(imageInsertStartedEvent.getImage());
792 notificationManager.addNotification(insertingImagesNotification);
796 * Notifies the web interface that an {@link Image} insert was aborted.
798 * @param imageInsertAbortedEvent
802 public void imageInsertAborted(ImageInsertAbortedEvent imageInsertAbortedEvent) {
803 insertingImagesNotification.remove(imageInsertAbortedEvent.getImage());
807 * Notifies the web interface that an {@link Image} insert is finished.
809 * @param imageInsertFinishedEvent
813 public void imageInsertFinished(ImageInsertFinishedEvent imageInsertFinishedEvent) {
814 insertingImagesNotification.remove(imageInsertFinishedEvent.getImage());
815 insertedImagesNotification.add(imageInsertFinishedEvent.getImage());
816 notificationManager.addNotification(insertedImagesNotification);
820 * Notifies the web interface that an {@link Image} insert has failed.
822 * @param imageInsertFailedEvent
826 public void imageInsertFailed(ImageInsertFailedEvent imageInsertFailedEvent) {
827 insertingImagesNotification.remove(imageInsertFailedEvent.getImage());
828 imageInsertFailedNotification.add(imageInsertFailedEvent.getImage());
829 notificationManager.addNotification(imageInsertFailedNotification);
833 public void debugActivated(@Nonnull DebugActivatedEvent debugActivatedEvent) {
834 pageToadletRegistry.activateDebugMode();