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 “new version” notification. */
183 private final TemplateNotification newVersionNotification;
185 /** The “inserting images” notification. */
186 private final ListNotification<Image> insertingImagesNotification;
188 /** The “inserted images” notification. */
189 private final ListNotification<Image> insertedImagesNotification;
191 /** The “image insert failed” notification. */
192 private final ListNotification<Image> imageInsertFailedNotification;
194 /** Scheduled executor for time-based notifications. */
195 private final ScheduledExecutorService ticker = Executors.newScheduledThreadPool(1);
198 public WebInterface(SonePlugin sonePlugin, Loaders loaders, ListNotificationFilter listNotificationFilter,
199 PostVisibilityFilter postVisibilityFilter, ReplyVisibilityFilter replyVisibilityFilter,
200 ElementLoader elementLoader, TemplateContextFactory templateContextFactory,
201 TemplateRenderer templateRenderer,
202 ParserFilter parserFilter, ShortenFilter shortenFilter,
203 RenderFilter renderFilter,
204 LinkedElementRenderFilter linkedElementRenderFilter,
205 PageToadletRegistry pageToadletRegistry, MetricRegistry metricRegistry, Translation translation, L10nFilter l10nFilter,
206 NotificationManager notificationManager, @Named("newRemotePost") ListNotification<Post> newPostNotification,
207 @Named("localPost") ListNotification<Post> localPostNotification) {
208 this.sonePlugin = sonePlugin;
209 this.loaders = loaders;
210 this.listNotificationFilter = listNotificationFilter;
211 this.postVisibilityFilter = postVisibilityFilter;
212 this.replyVisibilityFilter = replyVisibilityFilter;
213 this.elementLoader = elementLoader;
214 this.templateRenderer = templateRenderer;
215 this.parserFilter = parserFilter;
216 this.shortenFilter = shortenFilter;
217 this.renderFilter = renderFilter;
218 this.linkedElementRenderFilter = linkedElementRenderFilter;
219 this.pageToadletRegistry = pageToadletRegistry;
220 this.metricRegistry = metricRegistry;
221 this.l10nFilter = l10nFilter;
222 this.translation = translation;
223 this.notificationManager = notificationManager;
224 this.newPostNotification = newPostNotification;
225 this.localPostNotification = localPostNotification;
226 formPassword = sonePlugin.pluginRespirator().getToadletContainer().getFormPassword();
227 soneTextParser = new SoneTextParser(getCore(), getCore());
229 this.templateContextFactory = templateContextFactory;
230 templateContextFactory.addTemplateObject("webInterface", this);
231 templateContextFactory.addTemplateObject("formPassword", formPassword);
233 /* create notifications. */
234 Template newReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
235 newReplyNotification = new ListNotification<>("new-reply-notification", "replies", newReplyNotificationTemplate, false);
237 Template localReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
238 localReplyNotification = new ListNotification<>("local-reply-notification", "replies", localReplyNotificationTemplate, false);
240 Template mentionNotificationTemplate = loaders.loadTemplate("/templates/notify/mentionNotification.html");
241 mentionNotification = new ListNotification<>("mention-notification", "posts", mentionNotificationTemplate, false);
243 Template newVersionTemplate = loaders.loadTemplate("/templates/notify/newVersionNotification.html");
244 newVersionNotification = new TemplateNotification("new-version-notification", newVersionTemplate);
246 Template insertingImagesTemplate = loaders.loadTemplate("/templates/notify/inserting-images-notification.html");
247 insertingImagesNotification = new ListNotification<>("inserting-images-notification", "images", insertingImagesTemplate);
249 Template insertedImagesTemplate = loaders.loadTemplate("/templates/notify/inserted-images-notification.html");
250 insertedImagesNotification = new ListNotification<>("inserted-images-notification", "images", insertedImagesTemplate);
252 Template imageInsertFailedTemplate = loaders.loadTemplate("/templates/notify/image-insert-failed-notification.html");
253 imageInsertFailedNotification = new ListNotification<>("image-insert-failed-notification", "images", imageInsertFailedTemplate);
261 * Returns the Sone core used by the Sone plugin.
263 * @return The Sone core
266 public Core getCore() {
267 return sonePlugin.core();
271 * Returns the template context factory of the web interface.
273 * @return The template context factory
275 public TemplateContextFactory getTemplateContextFactory() {
276 return templateContextFactory;
279 private Session getCurrentSessionWithoutCreation(ToadletContext toadletContenxt) {
280 return getSessionManager().useSession(toadletContenxt);
283 private Session getOrCreateCurrentSession(ToadletContext toadletContenxt) {
284 Session session = getCurrentSessionWithoutCreation(toadletContenxt);
285 if (session == null) {
286 session = getSessionManager().createSession(UUID.randomUUID().toString(), toadletContenxt);
291 public Sone getCurrentSoneCreatingSession(ToadletContext toadletContext) {
292 Collection<Sone> localSones = getCore().getLocalSones();
293 if (localSones.size() == 1) {
294 return localSones.iterator().next();
296 return getCurrentSone(getOrCreateCurrentSession(toadletContext));
299 public Sone getCurrentSoneWithoutCreatingSession(ToadletContext toadletContext) {
300 Collection<Sone> localSones = getCore().getLocalSones();
301 if (localSones.size() == 1) {
302 return localSones.iterator().next();
304 return getCurrentSone(getCurrentSessionWithoutCreation(toadletContext));
308 * Returns the currently logged in Sone.
312 * @return The currently logged in Sone, or {@code null} if no Sone is
313 * currently logged in
315 private Sone getCurrentSone(Session session) {
316 if (session == null) {
319 String soneId = (String) session.getAttribute("Sone.CurrentSone");
320 if (soneId == null) {
323 return getCore().getLocalSone(soneId);
328 public Sone getCurrentSone(@Nonnull ToadletContext toadletContext, boolean createSession) {
329 return createSession ? getCurrentSoneCreatingSession(toadletContext) : getCurrentSoneWithoutCreatingSession(toadletContext);
333 * Sets the currently logged in Sone.
335 * @param toadletContext
336 * The toadlet context
338 * The Sone to set as currently logged in
341 public void setCurrentSone(@Nonnull ToadletContext toadletContext, @Nullable Sone sone) {
342 Session session = getOrCreateCurrentSession(toadletContext);
344 session.removeAttribute("Sone.CurrentSone");
346 session.setAttribute("Sone.CurrentSone", sone.getId());
351 * Returns the notification manager.
353 * @return The notification manager
355 public NotificationManager getNotifications() {
356 return notificationManager;
360 public Optional<Notification> getNotification(@Nonnull String notificationId) {
361 return Optional.fromNullable(notificationManager.getNotification(notificationId));
365 public Collection<Notification> getNotifications(@Nullable Sone currentSone) {
366 return listNotificationFilter.filterNotifications(notificationManager.getNotifications(), currentSone);
369 public Translation getTranslation() {
374 * Returns the session manager of the node.
376 * @return The node’s session manager
378 public SessionManager getSessionManager() {
379 return sonePlugin.pluginRespirator().getSessionManager("Sone");
383 * Returns the node’s form password.
385 * @return The form password
387 public String getFormPassword() {
392 public Collection<Post> getNewPosts(@Nullable Sone currentSone) {
393 Set<Post> allNewPosts = ImmutableSet.<Post> builder()
394 .addAll(newPostNotification.getElements())
395 .addAll(localPostNotification.getElements())
397 return from(allNewPosts).filter(postVisibilityFilter.isVisible(currentSone)).toSet();
401 * Returns the replies that have been announced as new in the
402 * {@link #newReplyNotification}.
404 * @return The new replies
406 public Set<PostReply> getNewReplies() {
407 return ImmutableSet.<PostReply> builder().addAll(newReplyNotification.getElements()).addAll(localReplyNotification.getElements()).build();
411 public Collection<PostReply> getNewReplies(@Nullable Sone currentSone) {
412 Set<PostReply> allNewReplies = ImmutableSet.<PostReply>builder()
413 .addAll(newReplyNotification.getElements())
414 .addAll(localReplyNotification.getElements())
416 return from(allNewReplies).filter(replyVisibilityFilter.isVisible(currentSone)).toSet();
420 * Sets whether the current start of the plugin is the first start. It is
421 * considered a first start if the configuration file does not exist.
424 * {@code true} if no configuration file existed when Sone was
425 * loaded, {@code false} otherwise
427 public void setFirstStart(boolean firstStart) {
429 Template firstStartNotificationTemplate = loaders.loadTemplate("/templates/notify/firstStartNotification.html");
430 Notification firstStartNotification = new TemplateNotification("first-start-notification", firstStartNotificationTemplate);
431 notificationManager.addNotification(firstStartNotification);
436 * Sets whether Sone was started with a fresh configuration file.
439 * {@code true} if Sone was started with a fresh configuration,
440 * {@code false} if the existing configuration could be read
442 public void setNewConfig(boolean newConfig) {
443 if (newConfig && !hasFirstStartNotification()) {
444 Template configNotReadNotificationTemplate = loaders.loadTemplate("/templates/notify/configNotReadNotification.html");
445 Notification configNotReadNotification = new TemplateNotification("config-not-read-notification", configNotReadNotificationTemplate);
446 notificationManager.addNotification(configNotReadNotification);
455 * Returns whether the first start notification is currently displayed.
457 * @return {@code true} if the first-start notification is currently
458 * displayed, {@code false} otherwise
460 private boolean hasFirstStartNotification() {
461 return notificationManager.getNotification("first-start-notification") != null;
469 * Starts the web interface and registers all toadlets.
471 public void start() {
474 /* notification templates. */
475 Template startupNotificationTemplate = loaders.loadTemplate("/templates/notify/startupNotification.html");
477 final TemplateNotification startupNotification = new TemplateNotification("startup-notification", startupNotificationTemplate);
478 notificationManager.addNotification(startupNotification);
480 ticker.schedule(new Runnable() {
484 startupNotification.dismiss();
486 }, 2, TimeUnit.MINUTES);
488 Template wotMissingNotificationTemplate = loaders.loadTemplate("/templates/notify/wotMissingNotification.html");
489 final TemplateNotification wotMissingNotification = new TemplateNotification("wot-missing-notification", wotMissingNotificationTemplate);
490 ticker.scheduleAtFixedRate(new Runnable() {
493 @SuppressWarnings("synthetic-access")
495 if (getCore().getIdentityManager().isConnected()) {
496 wotMissingNotification.dismiss();
498 notificationManager.addNotification(wotMissingNotification);
502 }, 15, 15, TimeUnit.SECONDS);
506 * Stops the web interface and unregisters all toadlets.
509 pageToadletRegistry.unregisterToadlets();
510 ticker.shutdownNow();
518 * Register all toadlets.
520 private void registerToadlets() {
521 Template postTemplate = loaders.loadTemplate("/templates/include/viewPost.html");
522 Template replyTemplate = loaders.loadTemplate("/templates/include/viewReply.html");
523 Template openSearchTemplate = loaders.loadTemplate("/templates/xml/OpenSearch.xml");
525 pageToadletRegistry.addPage(new RedirectPage<FreenetRequest>("", "index.html"));
526 pageToadletRegistry.addPage(new IndexPage(this, loaders, templateRenderer, postVisibilityFilter));
527 pageToadletRegistry.addPage(new NewPage(this, loaders, templateRenderer));
528 pageToadletRegistry.addPage(new CreateSonePage(this, loaders, templateRenderer));
529 pageToadletRegistry.addPage(new KnownSonesPage(this, loaders, templateRenderer));
530 pageToadletRegistry.addPage(new EditProfilePage(this, loaders, templateRenderer));
531 pageToadletRegistry.addPage(new EditProfileFieldPage(this, loaders, templateRenderer));
532 pageToadletRegistry.addPage(new DeleteProfileFieldPage(this, loaders, templateRenderer));
533 pageToadletRegistry.addPage(new CreatePostPage(this, loaders, templateRenderer));
534 pageToadletRegistry.addPage(new CreateReplyPage(this, loaders, templateRenderer));
535 pageToadletRegistry.addPage(new ViewSonePage(this, loaders, templateRenderer));
536 pageToadletRegistry.addPage(new ViewPostPage(this, loaders, templateRenderer));
537 pageToadletRegistry.addPage(new LikePage(this, loaders, templateRenderer));
538 pageToadletRegistry.addPage(new UnlikePage(this, loaders, templateRenderer));
539 pageToadletRegistry.addPage(new DeletePostPage(this, loaders, templateRenderer));
540 pageToadletRegistry.addPage(new DeleteReplyPage(this, loaders, templateRenderer));
541 pageToadletRegistry.addPage(new LockSonePage(this, loaders, templateRenderer));
542 pageToadletRegistry.addPage(new UnlockSonePage(this, loaders, templateRenderer));
543 pageToadletRegistry.addPage(new FollowSonePage(this, loaders, templateRenderer));
544 pageToadletRegistry.addPage(new UnfollowSonePage(this, loaders, templateRenderer));
545 pageToadletRegistry.addPage(new ImageBrowserPage(this, loaders, templateRenderer));
546 pageToadletRegistry.addPage(new CreateAlbumPage(this, loaders, templateRenderer));
547 pageToadletRegistry.addPage(new EditAlbumPage(this, loaders, templateRenderer));
548 pageToadletRegistry.addPage(new DeleteAlbumPage(this, loaders, templateRenderer));
549 pageToadletRegistry.addPage(new UploadImagePage(this, loaders, templateRenderer));
550 pageToadletRegistry.addPage(new EditImagePage(this, loaders, templateRenderer));
551 pageToadletRegistry.addPage(new DeleteImagePage(this, loaders, templateRenderer));
552 pageToadletRegistry.addPage(new MarkAsKnownPage(this, loaders, templateRenderer));
553 pageToadletRegistry.addPage(new BookmarkPage(this, loaders, templateRenderer));
554 pageToadletRegistry.addPage(new UnbookmarkPage(this, loaders, templateRenderer));
555 pageToadletRegistry.addPage(new BookmarksPage(this, loaders, templateRenderer));
556 pageToadletRegistry.addPage(new SearchPage(this, loaders, templateRenderer));
557 pageToadletRegistry.addPage(new DeleteSonePage(this, loaders, templateRenderer));
558 pageToadletRegistry.addPage(new LoginPage(this, loaders, templateRenderer));
559 pageToadletRegistry.addPage(new LogoutPage(this, loaders, templateRenderer));
560 pageToadletRegistry.addPage(new OptionsPage(this, loaders, templateRenderer));
561 pageToadletRegistry.addPage(new RescuePage(this, loaders, templateRenderer));
562 pageToadletRegistry.addPage(new AboutPage(this, loaders, templateRenderer, new PluginVersion(SonePlugin.getPluginVersion()), new PluginYear(sonePlugin.getYear()), new PluginHomepage(sonePlugin.getHomepage())));
563 pageToadletRegistry.addPage(new InvalidPage(this, loaders, templateRenderer));
564 pageToadletRegistry.addPage(new NoPermissionPage(this, loaders, templateRenderer));
565 pageToadletRegistry.addPage(new EmptyImageTitlePage(this, loaders, templateRenderer));
566 pageToadletRegistry.addPage(new EmptyAlbumTitlePage(this, loaders, templateRenderer));
567 pageToadletRegistry.addPage(new DismissNotificationPage(this, loaders, templateRenderer));
568 pageToadletRegistry.addPage(new DebugPage(this, loaders, templateRenderer));
569 pageToadletRegistry.addDebugPage(new MetricsPage(this, loaders, templateRenderer, metricRegistry));
570 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("css/", "/static/css/", "text/css"));
571 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("javascript/", "/static/javascript/", "text/javascript"));
572 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("images/", "/static/images/", "image/png"));
573 pageToadletRegistry.addPage(new TemplatePage<FreenetRequest>("OpenSearch.xml", "application/opensearchdescription+xml", templateContextFactory, openSearchTemplate));
574 pageToadletRegistry.addPage(new GetImagePage(this));
575 pageToadletRegistry.addPage(new GetTranslationAjaxPage(this));
576 pageToadletRegistry.addPage(new GetStatusAjaxPage(this, elementLoader, timeTextConverter, l10nFilter, TimeZone.getDefault()));
577 pageToadletRegistry.addPage(new GetNotificationsAjaxPage(this));
578 pageToadletRegistry.addPage(new DismissNotificationAjaxPage(this));
579 pageToadletRegistry.addPage(new CreatePostAjaxPage(this));
580 pageToadletRegistry.addPage(new CreateReplyAjaxPage(this));
581 pageToadletRegistry.addPage(new GetReplyAjaxPage(this, replyTemplate));
582 pageToadletRegistry.addPage(new GetPostAjaxPage(this, postTemplate));
583 pageToadletRegistry.addPage(new GetLinkedElementAjaxPage(this, elementLoader, linkedElementRenderFilter));
584 pageToadletRegistry.addPage(new GetTimesAjaxPage(this, timeTextConverter, l10nFilter, TimeZone.getDefault()));
585 pageToadletRegistry.addPage(new MarkAsKnownAjaxPage(this));
586 pageToadletRegistry.addPage(new DeletePostAjaxPage(this));
587 pageToadletRegistry.addPage(new DeleteReplyAjaxPage(this));
588 pageToadletRegistry.addPage(new LockSoneAjaxPage(this));
589 pageToadletRegistry.addPage(new UnlockSoneAjaxPage(this));
590 pageToadletRegistry.addPage(new FollowSoneAjaxPage(this));
591 pageToadletRegistry.addPage(new UnfollowSoneAjaxPage(this));
592 pageToadletRegistry.addPage(new EditAlbumAjaxPage(this));
593 pageToadletRegistry.addPage(new EditImageAjaxPage(this, parserFilter, shortenFilter, renderFilter));
594 pageToadletRegistry.addPage(new LikeAjaxPage(this));
595 pageToadletRegistry.addPage(new UnlikeAjaxPage(this));
596 pageToadletRegistry.addPage(new GetLikesAjaxPage(this));
597 pageToadletRegistry.addPage(new BookmarkAjaxPage(this));
598 pageToadletRegistry.addPage(new UnbookmarkAjaxPage(this));
599 pageToadletRegistry.addPage(new EditProfileFieldAjaxPage(this));
600 pageToadletRegistry.addPage(new DeleteProfileFieldAjaxPage(this));
601 pageToadletRegistry.addPage(new MoveProfileFieldAjaxPage(this));
603 pageToadletRegistry.registerToadlets();
607 * Returns all {@link Sone#isLocal() local Sone}s that are referenced by
608 * {@link SonePart}s in the given text (after parsing it using
609 * {@link SoneTextParser}).
613 * @return All mentioned local Sones
615 private Collection<Sone> getMentionedSones(String text) {
616 /* we need no context to find mentioned Sones. */
617 Set<Sone> mentionedSones = new HashSet<>();
618 for (Part part : soneTextParser.parse(text, null)) {
619 if (part instanceof SonePart) {
620 mentionedSones.add(((SonePart) part).getSone());
623 return Collections2.filter(mentionedSones, Sone.LOCAL_SONE_FILTER);
627 * Returns the Sone insert notification for the given Sone. If no
628 * notification for the given Sone exists, a new notification is created and
632 * The Sone to get the insert notification for
633 * @return The Sone insert notification
635 private TemplateNotification getSoneInsertNotification(Sone sone) {
636 synchronized (soneInsertNotifications) {
637 TemplateNotification templateNotification = soneInsertNotifications.get(sone);
638 if (templateNotification == null) {
639 templateNotification = new TemplateNotification(loaders.loadTemplate("/templates/notify/soneInsertNotification.html"));
640 templateNotification.set("insertSone", sone);
641 soneInsertNotifications.put(sone, templateNotification);
643 return templateNotification;
647 private boolean localSoneMentionedInNewPostOrReply(Post post) {
648 if (!post.getSone().isLocal()) {
649 if (!getMentionedSones(post.getText()).isEmpty() && !post.isKnown()) {
653 for (PostReply postReply : getCore().getReplies(post.getId())) {
654 if (postReply.getSone().isLocal()) {
657 if (!getMentionedSones(postReply.getText()).isEmpty() && !postReply.isKnown()) {
669 * Notifies the web interface that a new {@link Post} was found.
671 * @param newPostFoundEvent
675 public void newPostFound(NewPostFoundEvent newPostFoundEvent) {
676 Post post = newPostFoundEvent.getPost();
677 boolean isLocal = post.getSone().isLocal();
678 if (!hasFirstStartNotification()) {
679 if (!getMentionedSones(post.getText()).isEmpty() && !isLocal) {
680 mentionNotification.add(post);
681 notificationManager.addNotification(mentionNotification);
687 * Notifies the web interface that a new {@link PostReply} was found.
689 * @param newPostReplyFoundEvent
693 public void newReplyFound(NewPostReplyFoundEvent newPostReplyFoundEvent) {
694 PostReply reply = newPostReplyFoundEvent.getPostReply();
695 boolean isLocal = reply.getSone().isLocal();
697 localReplyNotification.add(reply);
699 newReplyNotification.add(reply);
701 if (!hasFirstStartNotification()) {
702 notificationManager.addNotification(isLocal ? localReplyNotification : newReplyNotification);
703 if (reply.getPost().isPresent() && localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
704 mentionNotification.add(reply.getPost().get());
705 notificationManager.addNotification(mentionNotification);
708 getCore().markReplyKnown(reply);
713 public void markPostKnown(MarkPostKnownEvent markPostKnownEvent) {
714 removePost(markPostKnownEvent.getPost());
718 public void markReplyKnown(MarkPostReplyKnownEvent markPostReplyKnownEvent) {
719 removeReply(markPostReplyKnownEvent.getPostReply());
723 public void postRemoved(PostRemovedEvent postRemovedEvent) {
724 removePost(postRemovedEvent.getPost());
727 private void removePost(Post post) {
728 newPostNotification.remove(post);
729 if (!localSoneMentionedInNewPostOrReply(post)) {
730 mentionNotification.remove(post);
735 public void replyRemoved(PostReplyRemovedEvent postReplyRemovedEvent) {
736 removeReply(postReplyRemovedEvent.getPostReply());
739 private void removeReply(PostReply reply) {
740 newReplyNotification.remove(reply);
741 localReplyNotification.remove(reply);
742 if (reply.getPost().isPresent() && !localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
743 mentionNotification.remove(reply.getPost().get());
748 * Notifies the web interface that a {@link Sone} is being inserted.
750 * @param soneInsertingEvent
754 public void soneInserting(SoneInsertingEvent soneInsertingEvent) {
755 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertingEvent.getSone());
756 soneInsertNotification.set("soneStatus", "inserting");
757 if (soneInsertingEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
758 notificationManager.addNotification(soneInsertNotification);
763 * Notifies the web interface that a {@link Sone} was inserted.
765 * @param soneInsertedEvent
769 public void soneInserted(SoneInsertedEvent soneInsertedEvent) {
770 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertedEvent.getSone());
771 soneInsertNotification.set("soneStatus", "inserted");
772 soneInsertNotification.set("insertDuration", soneInsertedEvent.getInsertDuration() / 1000);
773 if (soneInsertedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
774 notificationManager.addNotification(soneInsertNotification);
779 * Notifies the web interface that a {@link Sone} insert was aborted.
781 * @param soneInsertAbortedEvent
785 public void soneInsertAborted(SoneInsertAbortedEvent soneInsertAbortedEvent) {
786 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertAbortedEvent.getSone());
787 soneInsertNotification.set("soneStatus", "insert-aborted");
788 soneInsertNotification.set("insert-error", soneInsertAbortedEvent.getCause());
789 if (soneInsertAbortedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
790 notificationManager.addNotification(soneInsertNotification);
795 * Notifies the web interface that a new Sone version was found.
797 * @param updateFoundEvent
801 public void updateFound(UpdateFoundEvent updateFoundEvent) {
802 newVersionNotification.set("latestVersion", updateFoundEvent.getVersion());
803 newVersionNotification.set("latestEdition", updateFoundEvent.getLatestEdition());
804 newVersionNotification.set("releaseTime", updateFoundEvent.getReleaseTime());
805 newVersionNotification.set("disruptive", updateFoundEvent.isDisruptive());
806 notificationManager.addNotification(newVersionNotification);
810 * Notifies the web interface that an image insert was started
812 * @param imageInsertStartedEvent
816 public void imageInsertStarted(ImageInsertStartedEvent imageInsertStartedEvent) {
817 insertingImagesNotification.add(imageInsertStartedEvent.getImage());
818 notificationManager.addNotification(insertingImagesNotification);
822 * Notifies the web interface that an {@link Image} insert was aborted.
824 * @param imageInsertAbortedEvent
828 public void imageInsertAborted(ImageInsertAbortedEvent imageInsertAbortedEvent) {
829 insertingImagesNotification.remove(imageInsertAbortedEvent.getImage());
833 * Notifies the web interface that an {@link Image} insert is finished.
835 * @param imageInsertFinishedEvent
839 public void imageInsertFinished(ImageInsertFinishedEvent imageInsertFinishedEvent) {
840 insertingImagesNotification.remove(imageInsertFinishedEvent.getImage());
841 insertedImagesNotification.add(imageInsertFinishedEvent.getImage());
842 notificationManager.addNotification(insertedImagesNotification);
846 * Notifies the web interface that an {@link Image} insert has failed.
848 * @param imageInsertFailedEvent
852 public void imageInsertFailed(ImageInsertFailedEvent imageInsertFailedEvent) {
853 insertingImagesNotification.remove(imageInsertFailedEvent.getImage());
854 imageInsertFailedNotification.add(imageInsertFailedEvent.getImage());
855 notificationManager.addNotification(imageInsertFailedNotification);
859 public void debugActivated(@Nonnull DebugActivatedEvent debugActivatedEvent) {
860 pageToadletRegistry.activateDebugMode();