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 this.sonePlugin = sonePlugin;
208 this.loaders = loaders;
209 this.listNotificationFilter = listNotificationFilter;
210 this.postVisibilityFilter = postVisibilityFilter;
211 this.replyVisibilityFilter = replyVisibilityFilter;
212 this.elementLoader = elementLoader;
213 this.templateRenderer = templateRenderer;
214 this.parserFilter = parserFilter;
215 this.shortenFilter = shortenFilter;
216 this.renderFilter = renderFilter;
217 this.linkedElementRenderFilter = linkedElementRenderFilter;
218 this.pageToadletRegistry = pageToadletRegistry;
219 this.metricRegistry = metricRegistry;
220 this.l10nFilter = l10nFilter;
221 this.translation = translation;
222 this.notificationManager = notificationManager;
223 this.newPostNotification = newPostNotification;
224 formPassword = sonePlugin.pluginRespirator().getToadletContainer().getFormPassword();
225 soneTextParser = new SoneTextParser(getCore(), getCore());
227 this.templateContextFactory = templateContextFactory;
228 templateContextFactory.addTemplateObject("webInterface", this);
229 templateContextFactory.addTemplateObject("formPassword", formPassword);
231 /* create notifications. */
232 Template localPostNotificationTemplate = loaders.loadTemplate("/templates/notify/newPostNotification.html");
233 localPostNotification = new ListNotification<>("local-post-notification", "posts", localPostNotificationTemplate, false);
235 Template newReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
236 newReplyNotification = new ListNotification<>("new-reply-notification", "replies", newReplyNotificationTemplate, false);
238 Template localReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
239 localReplyNotification = new ListNotification<>("local-reply-notification", "replies", localReplyNotificationTemplate, false);
241 Template mentionNotificationTemplate = loaders.loadTemplate("/templates/notify/mentionNotification.html");
242 mentionNotification = new ListNotification<>("mention-notification", "posts", mentionNotificationTemplate, false);
244 Template newVersionTemplate = loaders.loadTemplate("/templates/notify/newVersionNotification.html");
245 newVersionNotification = new TemplateNotification("new-version-notification", newVersionTemplate);
247 Template insertingImagesTemplate = loaders.loadTemplate("/templates/notify/inserting-images-notification.html");
248 insertingImagesNotification = new ListNotification<>("inserting-images-notification", "images", insertingImagesTemplate);
250 Template insertedImagesTemplate = loaders.loadTemplate("/templates/notify/inserted-images-notification.html");
251 insertedImagesNotification = new ListNotification<>("inserted-images-notification", "images", insertedImagesTemplate);
253 Template imageInsertFailedTemplate = loaders.loadTemplate("/templates/notify/image-insert-failed-notification.html");
254 imageInsertFailedNotification = new ListNotification<>("image-insert-failed-notification", "images", imageInsertFailedTemplate);
262 * Returns the Sone core used by the Sone plugin.
264 * @return The Sone core
267 public Core getCore() {
268 return sonePlugin.core();
272 * Returns the template context factory of the web interface.
274 * @return The template context factory
276 public TemplateContextFactory getTemplateContextFactory() {
277 return templateContextFactory;
280 private Session getCurrentSessionWithoutCreation(ToadletContext toadletContenxt) {
281 return getSessionManager().useSession(toadletContenxt);
284 private Session getOrCreateCurrentSession(ToadletContext toadletContenxt) {
285 Session session = getCurrentSessionWithoutCreation(toadletContenxt);
286 if (session == null) {
287 session = getSessionManager().createSession(UUID.randomUUID().toString(), toadletContenxt);
292 public Sone getCurrentSoneCreatingSession(ToadletContext toadletContext) {
293 Collection<Sone> localSones = getCore().getLocalSones();
294 if (localSones.size() == 1) {
295 return localSones.iterator().next();
297 return getCurrentSone(getOrCreateCurrentSession(toadletContext));
300 public Sone getCurrentSoneWithoutCreatingSession(ToadletContext toadletContext) {
301 Collection<Sone> localSones = getCore().getLocalSones();
302 if (localSones.size() == 1) {
303 return localSones.iterator().next();
305 return getCurrentSone(getCurrentSessionWithoutCreation(toadletContext));
309 * Returns the currently logged in Sone.
313 * @return The currently logged in Sone, or {@code null} if no Sone is
314 * currently logged in
316 private Sone getCurrentSone(Session session) {
317 if (session == null) {
320 String soneId = (String) session.getAttribute("Sone.CurrentSone");
321 if (soneId == null) {
324 return getCore().getLocalSone(soneId);
329 public Sone getCurrentSone(@Nonnull ToadletContext toadletContext, boolean createSession) {
330 return createSession ? getCurrentSoneCreatingSession(toadletContext) : getCurrentSoneWithoutCreatingSession(toadletContext);
334 * Sets the currently logged in Sone.
336 * @param toadletContext
337 * The toadlet context
339 * The Sone to set as currently logged in
342 public void setCurrentSone(@Nonnull ToadletContext toadletContext, @Nullable Sone sone) {
343 Session session = getOrCreateCurrentSession(toadletContext);
345 session.removeAttribute("Sone.CurrentSone");
347 session.setAttribute("Sone.CurrentSone", sone.getId());
352 * Returns the notification manager.
354 * @return The notification manager
356 public NotificationManager getNotifications() {
357 return notificationManager;
361 public Optional<Notification> getNotification(@Nonnull String notificationId) {
362 return Optional.fromNullable(notificationManager.getNotification(notificationId));
366 public Collection<Notification> getNotifications(@Nullable Sone currentSone) {
367 return listNotificationFilter.filterNotifications(notificationManager.getNotifications(), currentSone);
370 public Translation getTranslation() {
375 * Returns the session manager of the node.
377 * @return The node’s session manager
379 public SessionManager getSessionManager() {
380 return sonePlugin.pluginRespirator().getSessionManager("Sone");
384 * Returns the node’s form password.
386 * @return The form password
388 public String getFormPassword() {
393 public Collection<Post> getNewPosts(@Nullable Sone currentSone) {
394 Set<Post> allNewPosts = ImmutableSet.<Post> builder()
395 .addAll(newPostNotification.getElements())
396 .addAll(localPostNotification.getElements())
398 return from(allNewPosts).filter(postVisibilityFilter.isVisible(currentSone)).toSet();
402 * Returns the replies that have been announced as new in the
403 * {@link #newReplyNotification}.
405 * @return The new replies
407 public Set<PostReply> getNewReplies() {
408 return ImmutableSet.<PostReply> builder().addAll(newReplyNotification.getElements()).addAll(localReplyNotification.getElements()).build();
412 public Collection<PostReply> getNewReplies(@Nullable Sone currentSone) {
413 Set<PostReply> allNewReplies = ImmutableSet.<PostReply>builder()
414 .addAll(newReplyNotification.getElements())
415 .addAll(localReplyNotification.getElements())
417 return from(allNewReplies).filter(replyVisibilityFilter.isVisible(currentSone)).toSet();
421 * Sets whether the current start of the plugin is the first start. It is
422 * considered a first start if the configuration file does not exist.
425 * {@code true} if no configuration file existed when Sone was
426 * loaded, {@code false} otherwise
428 public void setFirstStart(boolean firstStart) {
430 Template firstStartNotificationTemplate = loaders.loadTemplate("/templates/notify/firstStartNotification.html");
431 Notification firstStartNotification = new TemplateNotification("first-start-notification", firstStartNotificationTemplate);
432 notificationManager.addNotification(firstStartNotification);
437 * Sets whether Sone was started with a fresh configuration file.
440 * {@code true} if Sone was started with a fresh configuration,
441 * {@code false} if the existing configuration could be read
443 public void setNewConfig(boolean newConfig) {
444 if (newConfig && !hasFirstStartNotification()) {
445 Template configNotReadNotificationTemplate = loaders.loadTemplate("/templates/notify/configNotReadNotification.html");
446 Notification configNotReadNotification = new TemplateNotification("config-not-read-notification", configNotReadNotificationTemplate);
447 notificationManager.addNotification(configNotReadNotification);
456 * Returns whether the first start notification is currently displayed.
458 * @return {@code true} if the first-start notification is currently
459 * displayed, {@code false} otherwise
461 private boolean hasFirstStartNotification() {
462 return notificationManager.getNotification("first-start-notification") != null;
470 * Starts the web interface and registers all toadlets.
472 public void start() {
475 /* notification templates. */
476 Template startupNotificationTemplate = loaders.loadTemplate("/templates/notify/startupNotification.html");
478 final TemplateNotification startupNotification = new TemplateNotification("startup-notification", startupNotificationTemplate);
479 notificationManager.addNotification(startupNotification);
481 ticker.schedule(new Runnable() {
485 startupNotification.dismiss();
487 }, 2, TimeUnit.MINUTES);
489 Template wotMissingNotificationTemplate = loaders.loadTemplate("/templates/notify/wotMissingNotification.html");
490 final TemplateNotification wotMissingNotification = new TemplateNotification("wot-missing-notification", wotMissingNotificationTemplate);
491 ticker.scheduleAtFixedRate(new Runnable() {
494 @SuppressWarnings("synthetic-access")
496 if (getCore().getIdentityManager().isConnected()) {
497 wotMissingNotification.dismiss();
499 notificationManager.addNotification(wotMissingNotification);
503 }, 15, 15, TimeUnit.SECONDS);
507 * Stops the web interface and unregisters all toadlets.
510 pageToadletRegistry.unregisterToadlets();
511 ticker.shutdownNow();
519 * Register all toadlets.
521 private void registerToadlets() {
522 Template postTemplate = loaders.loadTemplate("/templates/include/viewPost.html");
523 Template replyTemplate = loaders.loadTemplate("/templates/include/viewReply.html");
524 Template openSearchTemplate = loaders.loadTemplate("/templates/xml/OpenSearch.xml");
526 pageToadletRegistry.addPage(new RedirectPage<FreenetRequest>("", "index.html"));
527 pageToadletRegistry.addPage(new IndexPage(this, loaders, templateRenderer, postVisibilityFilter));
528 pageToadletRegistry.addPage(new NewPage(this, loaders, templateRenderer));
529 pageToadletRegistry.addPage(new CreateSonePage(this, loaders, templateRenderer));
530 pageToadletRegistry.addPage(new KnownSonesPage(this, loaders, templateRenderer));
531 pageToadletRegistry.addPage(new EditProfilePage(this, loaders, templateRenderer));
532 pageToadletRegistry.addPage(new EditProfileFieldPage(this, loaders, templateRenderer));
533 pageToadletRegistry.addPage(new DeleteProfileFieldPage(this, loaders, templateRenderer));
534 pageToadletRegistry.addPage(new CreatePostPage(this, loaders, templateRenderer));
535 pageToadletRegistry.addPage(new CreateReplyPage(this, loaders, templateRenderer));
536 pageToadletRegistry.addPage(new ViewSonePage(this, loaders, templateRenderer));
537 pageToadletRegistry.addPage(new ViewPostPage(this, loaders, templateRenderer));
538 pageToadletRegistry.addPage(new LikePage(this, loaders, templateRenderer));
539 pageToadletRegistry.addPage(new UnlikePage(this, loaders, templateRenderer));
540 pageToadletRegistry.addPage(new DeletePostPage(this, loaders, templateRenderer));
541 pageToadletRegistry.addPage(new DeleteReplyPage(this, loaders, templateRenderer));
542 pageToadletRegistry.addPage(new LockSonePage(this, loaders, templateRenderer));
543 pageToadletRegistry.addPage(new UnlockSonePage(this, loaders, templateRenderer));
544 pageToadletRegistry.addPage(new FollowSonePage(this, loaders, templateRenderer));
545 pageToadletRegistry.addPage(new UnfollowSonePage(this, loaders, templateRenderer));
546 pageToadletRegistry.addPage(new ImageBrowserPage(this, loaders, templateRenderer));
547 pageToadletRegistry.addPage(new CreateAlbumPage(this, loaders, templateRenderer));
548 pageToadletRegistry.addPage(new EditAlbumPage(this, loaders, templateRenderer));
549 pageToadletRegistry.addPage(new DeleteAlbumPage(this, loaders, templateRenderer));
550 pageToadletRegistry.addPage(new UploadImagePage(this, loaders, templateRenderer));
551 pageToadletRegistry.addPage(new EditImagePage(this, loaders, templateRenderer));
552 pageToadletRegistry.addPage(new DeleteImagePage(this, loaders, templateRenderer));
553 pageToadletRegistry.addPage(new MarkAsKnownPage(this, loaders, templateRenderer));
554 pageToadletRegistry.addPage(new BookmarkPage(this, loaders, templateRenderer));
555 pageToadletRegistry.addPage(new UnbookmarkPage(this, loaders, templateRenderer));
556 pageToadletRegistry.addPage(new BookmarksPage(this, loaders, templateRenderer));
557 pageToadletRegistry.addPage(new SearchPage(this, loaders, templateRenderer));
558 pageToadletRegistry.addPage(new DeleteSonePage(this, loaders, templateRenderer));
559 pageToadletRegistry.addPage(new LoginPage(this, loaders, templateRenderer));
560 pageToadletRegistry.addPage(new LogoutPage(this, loaders, templateRenderer));
561 pageToadletRegistry.addPage(new OptionsPage(this, loaders, templateRenderer));
562 pageToadletRegistry.addPage(new RescuePage(this, loaders, templateRenderer));
563 pageToadletRegistry.addPage(new AboutPage(this, loaders, templateRenderer, new PluginVersion(SonePlugin.getPluginVersion()), new PluginYear(sonePlugin.getYear()), new PluginHomepage(sonePlugin.getHomepage())));
564 pageToadletRegistry.addPage(new InvalidPage(this, loaders, templateRenderer));
565 pageToadletRegistry.addPage(new NoPermissionPage(this, loaders, templateRenderer));
566 pageToadletRegistry.addPage(new EmptyImageTitlePage(this, loaders, templateRenderer));
567 pageToadletRegistry.addPage(new EmptyAlbumTitlePage(this, loaders, templateRenderer));
568 pageToadletRegistry.addPage(new DismissNotificationPage(this, loaders, templateRenderer));
569 pageToadletRegistry.addPage(new DebugPage(this, loaders, templateRenderer));
570 pageToadletRegistry.addDebugPage(new MetricsPage(this, loaders, templateRenderer, metricRegistry));
571 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("css/", "/static/css/", "text/css"));
572 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("javascript/", "/static/javascript/", "text/javascript"));
573 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("images/", "/static/images/", "image/png"));
574 pageToadletRegistry.addPage(new TemplatePage<FreenetRequest>("OpenSearch.xml", "application/opensearchdescription+xml", templateContextFactory, openSearchTemplate));
575 pageToadletRegistry.addPage(new GetImagePage(this));
576 pageToadletRegistry.addPage(new GetTranslationAjaxPage(this));
577 pageToadletRegistry.addPage(new GetStatusAjaxPage(this, elementLoader, timeTextConverter, l10nFilter, TimeZone.getDefault()));
578 pageToadletRegistry.addPage(new GetNotificationsAjaxPage(this));
579 pageToadletRegistry.addPage(new DismissNotificationAjaxPage(this));
580 pageToadletRegistry.addPage(new CreatePostAjaxPage(this));
581 pageToadletRegistry.addPage(new CreateReplyAjaxPage(this));
582 pageToadletRegistry.addPage(new GetReplyAjaxPage(this, replyTemplate));
583 pageToadletRegistry.addPage(new GetPostAjaxPage(this, postTemplate));
584 pageToadletRegistry.addPage(new GetLinkedElementAjaxPage(this, elementLoader, linkedElementRenderFilter));
585 pageToadletRegistry.addPage(new GetTimesAjaxPage(this, timeTextConverter, l10nFilter, TimeZone.getDefault()));
586 pageToadletRegistry.addPage(new MarkAsKnownAjaxPage(this));
587 pageToadletRegistry.addPage(new DeletePostAjaxPage(this));
588 pageToadletRegistry.addPage(new DeleteReplyAjaxPage(this));
589 pageToadletRegistry.addPage(new LockSoneAjaxPage(this));
590 pageToadletRegistry.addPage(new UnlockSoneAjaxPage(this));
591 pageToadletRegistry.addPage(new FollowSoneAjaxPage(this));
592 pageToadletRegistry.addPage(new UnfollowSoneAjaxPage(this));
593 pageToadletRegistry.addPage(new EditAlbumAjaxPage(this));
594 pageToadletRegistry.addPage(new EditImageAjaxPage(this, parserFilter, shortenFilter, renderFilter));
595 pageToadletRegistry.addPage(new LikeAjaxPage(this));
596 pageToadletRegistry.addPage(new UnlikeAjaxPage(this));
597 pageToadletRegistry.addPage(new GetLikesAjaxPage(this));
598 pageToadletRegistry.addPage(new BookmarkAjaxPage(this));
599 pageToadletRegistry.addPage(new UnbookmarkAjaxPage(this));
600 pageToadletRegistry.addPage(new EditProfileFieldAjaxPage(this));
601 pageToadletRegistry.addPage(new DeleteProfileFieldAjaxPage(this));
602 pageToadletRegistry.addPage(new MoveProfileFieldAjaxPage(this));
604 pageToadletRegistry.registerToadlets();
608 * Returns all {@link Sone#isLocal() local Sone}s that are referenced by
609 * {@link SonePart}s in the given text (after parsing it using
610 * {@link SoneTextParser}).
614 * @return All mentioned local Sones
616 private Collection<Sone> getMentionedSones(String text) {
617 /* we need no context to find mentioned Sones. */
618 Set<Sone> mentionedSones = new HashSet<>();
619 for (Part part : soneTextParser.parse(text, null)) {
620 if (part instanceof SonePart) {
621 mentionedSones.add(((SonePart) part).getSone());
624 return Collections2.filter(mentionedSones, Sone.LOCAL_SONE_FILTER);
628 * Returns the Sone insert notification for the given Sone. If no
629 * notification for the given Sone exists, a new notification is created and
633 * The Sone to get the insert notification for
634 * @return The Sone insert notification
636 private TemplateNotification getSoneInsertNotification(Sone sone) {
637 synchronized (soneInsertNotifications) {
638 TemplateNotification templateNotification = soneInsertNotifications.get(sone);
639 if (templateNotification == null) {
640 templateNotification = new TemplateNotification(loaders.loadTemplate("/templates/notify/soneInsertNotification.html"));
641 templateNotification.set("insertSone", sone);
642 soneInsertNotifications.put(sone, templateNotification);
644 return templateNotification;
648 private boolean localSoneMentionedInNewPostOrReply(Post post) {
649 if (!post.getSone().isLocal()) {
650 if (!getMentionedSones(post.getText()).isEmpty() && !post.isKnown()) {
654 for (PostReply postReply : getCore().getReplies(post.getId())) {
655 if (postReply.getSone().isLocal()) {
658 if (!getMentionedSones(postReply.getText()).isEmpty() && !postReply.isKnown()) {
670 * Notifies the web interface that a new {@link Post} was found.
672 * @param newPostFoundEvent
676 public void newPostFound(NewPostFoundEvent newPostFoundEvent) {
677 Post post = newPostFoundEvent.getPost();
678 boolean isLocal = post.getSone().isLocal();
680 localPostNotification.add(post);
682 if (!hasFirstStartNotification()) {
684 notificationManager.addNotification(localPostNotification);
686 if (!getMentionedSones(post.getText()).isEmpty() && !isLocal) {
687 mentionNotification.add(post);
688 notificationManager.addNotification(mentionNotification);
694 * Notifies the web interface that a new {@link PostReply} was found.
696 * @param newPostReplyFoundEvent
700 public void newReplyFound(NewPostReplyFoundEvent newPostReplyFoundEvent) {
701 PostReply reply = newPostReplyFoundEvent.getPostReply();
702 boolean isLocal = reply.getSone().isLocal();
704 localReplyNotification.add(reply);
706 newReplyNotification.add(reply);
708 if (!hasFirstStartNotification()) {
709 notificationManager.addNotification(isLocal ? localReplyNotification : newReplyNotification);
710 if (reply.getPost().isPresent() && localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
711 mentionNotification.add(reply.getPost().get());
712 notificationManager.addNotification(mentionNotification);
715 getCore().markReplyKnown(reply);
720 public void markPostKnown(MarkPostKnownEvent markPostKnownEvent) {
721 removePost(markPostKnownEvent.getPost());
725 public void markReplyKnown(MarkPostReplyKnownEvent markPostReplyKnownEvent) {
726 removeReply(markPostReplyKnownEvent.getPostReply());
730 public void postRemoved(PostRemovedEvent postRemovedEvent) {
731 removePost(postRemovedEvent.getPost());
734 private void removePost(Post post) {
735 newPostNotification.remove(post);
736 localPostNotification.remove(post);
737 if (!localSoneMentionedInNewPostOrReply(post)) {
738 mentionNotification.remove(post);
743 public void replyRemoved(PostReplyRemovedEvent postReplyRemovedEvent) {
744 removeReply(postReplyRemovedEvent.getPostReply());
747 private void removeReply(PostReply reply) {
748 newReplyNotification.remove(reply);
749 localReplyNotification.remove(reply);
750 if (reply.getPost().isPresent() && !localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
751 mentionNotification.remove(reply.getPost().get());
756 * Notifies the web interface that a {@link Sone} is being inserted.
758 * @param soneInsertingEvent
762 public void soneInserting(SoneInsertingEvent soneInsertingEvent) {
763 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertingEvent.getSone());
764 soneInsertNotification.set("soneStatus", "inserting");
765 if (soneInsertingEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
766 notificationManager.addNotification(soneInsertNotification);
771 * Notifies the web interface that a {@link Sone} was inserted.
773 * @param soneInsertedEvent
777 public void soneInserted(SoneInsertedEvent soneInsertedEvent) {
778 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertedEvent.getSone());
779 soneInsertNotification.set("soneStatus", "inserted");
780 soneInsertNotification.set("insertDuration", soneInsertedEvent.getInsertDuration() / 1000);
781 if (soneInsertedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
782 notificationManager.addNotification(soneInsertNotification);
787 * Notifies the web interface that a {@link Sone} insert was aborted.
789 * @param soneInsertAbortedEvent
793 public void soneInsertAborted(SoneInsertAbortedEvent soneInsertAbortedEvent) {
794 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertAbortedEvent.getSone());
795 soneInsertNotification.set("soneStatus", "insert-aborted");
796 soneInsertNotification.set("insert-error", soneInsertAbortedEvent.getCause());
797 if (soneInsertAbortedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
798 notificationManager.addNotification(soneInsertNotification);
803 * Notifies the web interface that a new Sone version was found.
805 * @param updateFoundEvent
809 public void updateFound(UpdateFoundEvent updateFoundEvent) {
810 newVersionNotification.set("latestVersion", updateFoundEvent.getVersion());
811 newVersionNotification.set("latestEdition", updateFoundEvent.getLatestEdition());
812 newVersionNotification.set("releaseTime", updateFoundEvent.getReleaseTime());
813 newVersionNotification.set("disruptive", updateFoundEvent.isDisruptive());
814 notificationManager.addNotification(newVersionNotification);
818 * Notifies the web interface that an image insert was started
820 * @param imageInsertStartedEvent
824 public void imageInsertStarted(ImageInsertStartedEvent imageInsertStartedEvent) {
825 insertingImagesNotification.add(imageInsertStartedEvent.getImage());
826 notificationManager.addNotification(insertingImagesNotification);
830 * Notifies the web interface that an {@link Image} insert was aborted.
832 * @param imageInsertAbortedEvent
836 public void imageInsertAborted(ImageInsertAbortedEvent imageInsertAbortedEvent) {
837 insertingImagesNotification.remove(imageInsertAbortedEvent.getImage());
841 * Notifies the web interface that an {@link Image} insert is finished.
843 * @param imageInsertFinishedEvent
847 public void imageInsertFinished(ImageInsertFinishedEvent imageInsertFinishedEvent) {
848 insertingImagesNotification.remove(imageInsertFinishedEvent.getImage());
849 insertedImagesNotification.add(imageInsertFinishedEvent.getImage());
850 notificationManager.addNotification(insertedImagesNotification);
854 * Notifies the web interface that an {@link Image} insert has failed.
856 * @param imageInsertFailedEvent
860 public void imageInsertFailed(ImageInsertFailedEvent imageInsertFailedEvent) {
861 insertingImagesNotification.remove(imageInsertFailedEvent.getImage());
862 imageInsertFailedNotification.add(imageInsertFailedEvent.getImage());
863 notificationManager.addNotification(imageInsertFailedNotification);
867 public void debugActivated(@Nonnull DebugActivatedEvent debugActivatedEvent) {
868 pageToadletRegistry.activateDebugMode();