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;
39 import net.pterodactylus.sone.core.Core;
40 import net.pterodactylus.sone.core.ElementLoader;
41 import net.pterodactylus.sone.core.event.*;
42 import net.pterodactylus.sone.data.Image;
43 import net.pterodactylus.sone.data.Post;
44 import net.pterodactylus.sone.data.PostReply;
45 import net.pterodactylus.sone.data.Sone;
46 import net.pterodactylus.sone.freenet.L10nFilter;
47 import net.pterodactylus.sone.freenet.Translation;
48 import net.pterodactylus.sone.main.Loaders;
49 import net.pterodactylus.sone.main.PluginHomepage;
50 import net.pterodactylus.sone.main.PluginVersion;
51 import net.pterodactylus.sone.main.PluginYear;
52 import net.pterodactylus.sone.main.SonePlugin;
53 import net.pterodactylus.sone.notify.ListNotification;
54 import net.pterodactylus.sone.notify.ListNotificationFilter;
55 import net.pterodactylus.sone.notify.PostVisibilityFilter;
56 import net.pterodactylus.sone.notify.ReplyVisibilityFilter;
57 import net.pterodactylus.sone.template.LinkedElementRenderFilter;
58 import net.pterodactylus.sone.template.ParserFilter;
59 import net.pterodactylus.sone.template.RenderFilter;
60 import net.pterodactylus.sone.template.ShortenFilter;
61 import net.pterodactylus.sone.text.Part;
62 import net.pterodactylus.sone.text.SonePart;
63 import net.pterodactylus.sone.text.SoneTextParser;
64 import net.pterodactylus.sone.text.TimeTextConverter;
65 import net.pterodactylus.sone.web.ajax.BookmarkAjaxPage;
66 import net.pterodactylus.sone.web.ajax.CreatePostAjaxPage;
67 import net.pterodactylus.sone.web.ajax.CreateReplyAjaxPage;
68 import net.pterodactylus.sone.web.ajax.DeletePostAjaxPage;
69 import net.pterodactylus.sone.web.ajax.DeleteProfileFieldAjaxPage;
70 import net.pterodactylus.sone.web.ajax.DeleteReplyAjaxPage;
71 import net.pterodactylus.sone.web.ajax.DismissNotificationAjaxPage;
72 import net.pterodactylus.sone.web.ajax.EditAlbumAjaxPage;
73 import net.pterodactylus.sone.web.ajax.EditImageAjaxPage;
74 import net.pterodactylus.sone.web.ajax.EditProfileFieldAjaxPage;
75 import net.pterodactylus.sone.web.ajax.FollowSoneAjaxPage;
76 import net.pterodactylus.sone.web.ajax.GetLikesAjaxPage;
77 import net.pterodactylus.sone.web.ajax.GetLinkedElementAjaxPage;
78 import net.pterodactylus.sone.web.ajax.GetNotificationsAjaxPage;
79 import net.pterodactylus.sone.web.ajax.GetPostAjaxPage;
80 import net.pterodactylus.sone.web.ajax.GetReplyAjaxPage;
81 import net.pterodactylus.sone.web.ajax.GetStatusAjaxPage;
82 import net.pterodactylus.sone.web.ajax.GetTimesAjaxPage;
83 import net.pterodactylus.sone.web.ajax.GetTranslationAjaxPage;
84 import net.pterodactylus.sone.web.ajax.LikeAjaxPage;
85 import net.pterodactylus.sone.web.ajax.LockSoneAjaxPage;
86 import net.pterodactylus.sone.web.ajax.MarkAsKnownAjaxPage;
87 import net.pterodactylus.sone.web.ajax.MoveProfileFieldAjaxPage;
88 import net.pterodactylus.sone.web.ajax.UnbookmarkAjaxPage;
89 import net.pterodactylus.sone.web.ajax.UnfollowSoneAjaxPage;
90 import net.pterodactylus.sone.web.ajax.UnlikeAjaxPage;
91 import net.pterodactylus.sone.web.ajax.UnlockSoneAjaxPage;
92 import net.pterodactylus.sone.web.page.FreenetRequest;
93 import net.pterodactylus.sone.web.page.TemplateRenderer;
94 import net.pterodactylus.sone.web.pages.*;
95 import net.pterodactylus.util.notify.Notification;
96 import net.pterodactylus.util.notify.NotificationManager;
97 import net.pterodactylus.util.notify.TemplateNotification;
98 import net.pterodactylus.util.template.Template;
99 import net.pterodactylus.util.template.TemplateContextFactory;
100 import net.pterodactylus.util.web.RedirectPage;
101 import net.pterodactylus.util.web.TemplatePage;
103 import freenet.clients.http.SessionManager;
104 import freenet.clients.http.SessionManager.Session;
105 import freenet.clients.http.ToadletContext;
107 import com.codahale.metrics.*;
108 import com.google.common.base.Optional;
109 import com.google.common.collect.Collections2;
110 import com.google.common.collect.ImmutableSet;
111 import com.google.common.eventbus.Subscribe;
112 import com.google.inject.Inject;
115 * Bundles functionality that a web interface of a Freenet plugin needs, e.g.
116 * references to l10n helpers.
118 public class WebInterface implements SessionProvider {
121 private static final Logger logger = getLogger(WebInterface.class.getName());
123 /** The loaders for templates, pages, and classpath providers. */
124 private final Loaders loaders;
126 /** The notification manager. */
127 private final NotificationManager notificationManager;
129 /** The Sone plugin. */
130 private final SonePlugin sonePlugin;
132 /** The form password. */
133 private final String formPassword;
135 /** The template context factory. */
136 private final TemplateContextFactory templateContextFactory;
137 private final TemplateRenderer templateRenderer;
139 /** The Sone text parser. */
140 private final SoneTextParser soneTextParser;
142 /** The parser filter. */
143 private final ParserFilter parserFilter;
144 private final ShortenFilter shortenFilter;
145 private final RenderFilter renderFilter;
147 private final ListNotificationFilter listNotificationFilter;
148 private final PostVisibilityFilter postVisibilityFilter;
149 private final ReplyVisibilityFilter replyVisibilityFilter;
151 private final ElementLoader elementLoader;
152 private final LinkedElementRenderFilter linkedElementRenderFilter;
153 private final TimeTextConverter timeTextConverter = new TimeTextConverter();
154 private final L10nFilter l10nFilter;
156 private final PageToadletRegistry pageToadletRegistry;
157 private final MetricRegistry metricRegistry;
158 private final Translation translation;
160 /** The “new Sone” notification. */
161 private final ListNotification<Sone> newSoneNotification;
163 /** The “new post” notification. */
164 private final ListNotification<Post> newPostNotification;
166 /** The “new reply” notification. */
167 private final ListNotification<PostReply> newReplyNotification;
169 /** The invisible “local post” notification. */
170 private final ListNotification<Post> localPostNotification;
172 /** The invisible “local reply” notification. */
173 private final ListNotification<PostReply> localReplyNotification;
175 /** The “you have been mentioned” notification. */
176 private final ListNotification<Post> mentionNotification;
178 /** Notifications for sone inserts. */
179 private final Map<Sone, TemplateNotification> soneInsertNotifications = new HashMap<>();
181 /** Sone locked notification ticker objects. */
182 private final Map<Sone, ScheduledFuture<?>> lockedSonesTickerObjects = Collections.synchronizedMap(new HashMap<Sone, ScheduledFuture<?>>());
184 /** The “Sone locked” notification. */
185 private final ListNotification<Sone> lockedSonesNotification;
187 /** The “new version” notification. */
188 private final TemplateNotification newVersionNotification;
190 /** The “inserting images” notification. */
191 private final ListNotification<Image> insertingImagesNotification;
193 /** The “inserted images” notification. */
194 private final ListNotification<Image> insertedImagesNotification;
196 /** The “image insert failed” notification. */
197 private final ListNotification<Image> imageInsertFailedNotification;
199 /** Scheduled executor for time-based notifications. */
200 private final ScheduledExecutorService ticker = Executors.newScheduledThreadPool(1);
203 public WebInterface(SonePlugin sonePlugin, Loaders loaders, ListNotificationFilter listNotificationFilter,
204 PostVisibilityFilter postVisibilityFilter, ReplyVisibilityFilter replyVisibilityFilter,
205 ElementLoader elementLoader, TemplateContextFactory templateContextFactory,
206 TemplateRenderer templateRenderer,
207 ParserFilter parserFilter, ShortenFilter shortenFilter,
208 RenderFilter renderFilter,
209 LinkedElementRenderFilter linkedElementRenderFilter,
210 PageToadletRegistry pageToadletRegistry, MetricRegistry metricRegistry, Translation translation, L10nFilter l10nFilter,
211 NotificationManager notificationManager) {
212 this.sonePlugin = sonePlugin;
213 this.loaders = loaders;
214 this.listNotificationFilter = listNotificationFilter;
215 this.postVisibilityFilter = postVisibilityFilter;
216 this.replyVisibilityFilter = replyVisibilityFilter;
217 this.elementLoader = elementLoader;
218 this.templateRenderer = templateRenderer;
219 this.parserFilter = parserFilter;
220 this.shortenFilter = shortenFilter;
221 this.renderFilter = renderFilter;
222 this.linkedElementRenderFilter = linkedElementRenderFilter;
223 this.pageToadletRegistry = pageToadletRegistry;
224 this.metricRegistry = metricRegistry;
225 this.l10nFilter = l10nFilter;
226 this.translation = translation;
227 this.notificationManager = notificationManager;
228 formPassword = sonePlugin.pluginRespirator().getToadletContainer().getFormPassword();
229 soneTextParser = new SoneTextParser(getCore(), getCore());
231 this.templateContextFactory = templateContextFactory;
232 templateContextFactory.addTemplateObject("webInterface", this);
233 templateContextFactory.addTemplateObject("formPassword", formPassword);
235 /* create notifications. */
236 Template newSoneNotificationTemplate = loaders.loadTemplate("/templates/notify/newSoneNotification.html");
237 newSoneNotification = new ListNotification<>("new-sone-notification", "sones", newSoneNotificationTemplate, false);
239 Template newPostNotificationTemplate = loaders.loadTemplate("/templates/notify/newPostNotification.html");
240 newPostNotification = new ListNotification<>("new-post-notification", "posts", newPostNotificationTemplate, false);
242 Template localPostNotificationTemplate = loaders.loadTemplate("/templates/notify/newPostNotification.html");
243 localPostNotification = new ListNotification<>("local-post-notification", "posts", localPostNotificationTemplate, false);
245 Template newReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
246 newReplyNotification = new ListNotification<>("new-reply-notification", "replies", newReplyNotificationTemplate, false);
248 Template localReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
249 localReplyNotification = new ListNotification<>("local-reply-notification", "replies", localReplyNotificationTemplate, false);
251 Template mentionNotificationTemplate = loaders.loadTemplate("/templates/notify/mentionNotification.html");
252 mentionNotification = new ListNotification<>("mention-notification", "posts", mentionNotificationTemplate, false);
254 Template lockedSonesTemplate = loaders.loadTemplate("/templates/notify/lockedSonesNotification.html");
255 lockedSonesNotification = new ListNotification<>("sones-locked-notification", "sones", lockedSonesTemplate);
257 Template newVersionTemplate = loaders.loadTemplate("/templates/notify/newVersionNotification.html");
258 newVersionNotification = new TemplateNotification("new-version-notification", newVersionTemplate);
260 Template insertingImagesTemplate = loaders.loadTemplate("/templates/notify/inserting-images-notification.html");
261 insertingImagesNotification = new ListNotification<>("inserting-images-notification", "images", insertingImagesTemplate);
263 Template insertedImagesTemplate = loaders.loadTemplate("/templates/notify/inserted-images-notification.html");
264 insertedImagesNotification = new ListNotification<>("inserted-images-notification", "images", insertedImagesTemplate);
266 Template imageInsertFailedTemplate = loaders.loadTemplate("/templates/notify/image-insert-failed-notification.html");
267 imageInsertFailedNotification = new ListNotification<>("image-insert-failed-notification", "images", imageInsertFailedTemplate);
275 * Returns the Sone core used by the Sone plugin.
277 * @return The Sone core
280 public Core getCore() {
281 return sonePlugin.core();
285 * Returns the template context factory of the web interface.
287 * @return The template context factory
289 public TemplateContextFactory getTemplateContextFactory() {
290 return templateContextFactory;
293 private Session getCurrentSessionWithoutCreation(ToadletContext toadletContenxt) {
294 return getSessionManager().useSession(toadletContenxt);
297 private Session getOrCreateCurrentSession(ToadletContext toadletContenxt) {
298 Session session = getCurrentSessionWithoutCreation(toadletContenxt);
299 if (session == null) {
300 session = getSessionManager().createSession(UUID.randomUUID().toString(), toadletContenxt);
305 public Sone getCurrentSoneCreatingSession(ToadletContext toadletContext) {
306 Collection<Sone> localSones = getCore().getLocalSones();
307 if (localSones.size() == 1) {
308 return localSones.iterator().next();
310 return getCurrentSone(getOrCreateCurrentSession(toadletContext));
313 public Sone getCurrentSoneWithoutCreatingSession(ToadletContext toadletContext) {
314 Collection<Sone> localSones = getCore().getLocalSones();
315 if (localSones.size() == 1) {
316 return localSones.iterator().next();
318 return getCurrentSone(getCurrentSessionWithoutCreation(toadletContext));
322 * Returns the currently logged in Sone.
326 * @return The currently logged in Sone, or {@code null} if no Sone is
327 * currently logged in
329 private Sone getCurrentSone(Session session) {
330 if (session == null) {
333 String soneId = (String) session.getAttribute("Sone.CurrentSone");
334 if (soneId == null) {
337 return getCore().getLocalSone(soneId);
342 public Sone getCurrentSone(@Nonnull ToadletContext toadletContext, boolean createSession) {
343 return createSession ? getCurrentSoneCreatingSession(toadletContext) : getCurrentSoneWithoutCreatingSession(toadletContext);
347 * Sets the currently logged in Sone.
349 * @param toadletContext
350 * The toadlet context
352 * The Sone to set as currently logged in
355 public void setCurrentSone(@Nonnull ToadletContext toadletContext, @Nullable Sone sone) {
356 Session session = getOrCreateCurrentSession(toadletContext);
358 session.removeAttribute("Sone.CurrentSone");
360 session.setAttribute("Sone.CurrentSone", sone.getId());
365 * Returns the notification manager.
367 * @return The notification manager
369 public NotificationManager getNotifications() {
370 return notificationManager;
374 public Optional<Notification> getNotification(@Nonnull String notificationId) {
375 return Optional.fromNullable(notificationManager.getNotification(notificationId));
379 public Collection<Notification> getNotifications(@Nullable Sone currentSone) {
380 return listNotificationFilter.filterNotifications(notificationManager.getNotifications(), currentSone);
383 public Translation getTranslation() {
388 * Returns the session manager of the node.
390 * @return The node’s session manager
392 public SessionManager getSessionManager() {
393 return sonePlugin.pluginRespirator().getSessionManager("Sone");
397 * Returns the node’s form password.
399 * @return The form password
401 public String getFormPassword() {
406 * Returns the posts that have been announced as new in the
407 * {@link #newPostNotification}.
409 * @return The new posts
411 public Set<Post> getNewPosts() {
412 return ImmutableSet.<Post> builder().addAll(newPostNotification.getElements()).addAll(localPostNotification.getElements()).build();
416 public Collection<Post> getNewPosts(@Nullable Sone currentSone) {
417 Set<Post> allNewPosts = ImmutableSet.<Post> builder()
418 .addAll(newPostNotification.getElements())
419 .addAll(localPostNotification.getElements())
421 return from(allNewPosts).filter(postVisibilityFilter.isVisible(currentSone)).toSet();
425 * Returns the replies that have been announced as new in the
426 * {@link #newReplyNotification}.
428 * @return The new replies
430 public Set<PostReply> getNewReplies() {
431 return ImmutableSet.<PostReply> builder().addAll(newReplyNotification.getElements()).addAll(localReplyNotification.getElements()).build();
435 public Collection<PostReply> getNewReplies(@Nullable Sone currentSone) {
436 Set<PostReply> allNewReplies = ImmutableSet.<PostReply>builder()
437 .addAll(newReplyNotification.getElements())
438 .addAll(localReplyNotification.getElements())
440 return from(allNewReplies).filter(replyVisibilityFilter.isVisible(currentSone)).toSet();
444 * Sets whether the current start of the plugin is the first start. It is
445 * considered a first start if the configuration file does not exist.
448 * {@code true} if no configuration file existed when Sone was
449 * loaded, {@code false} otherwise
451 public void setFirstStart(boolean firstStart) {
453 Template firstStartNotificationTemplate = loaders.loadTemplate("/templates/notify/firstStartNotification.html");
454 Notification firstStartNotification = new TemplateNotification("first-start-notification", firstStartNotificationTemplate);
455 notificationManager.addNotification(firstStartNotification);
460 * Sets whether Sone was started with a fresh configuration file.
463 * {@code true} if Sone was started with a fresh configuration,
464 * {@code false} if the existing configuration could be read
466 public void setNewConfig(boolean newConfig) {
467 if (newConfig && !hasFirstStartNotification()) {
468 Template configNotReadNotificationTemplate = loaders.loadTemplate("/templates/notify/configNotReadNotification.html");
469 Notification configNotReadNotification = new TemplateNotification("config-not-read-notification", configNotReadNotificationTemplate);
470 notificationManager.addNotification(configNotReadNotification);
479 * Returns whether the first start notification is currently displayed.
481 * @return {@code true} if the first-start notification is currently
482 * displayed, {@code false} otherwise
484 private boolean hasFirstStartNotification() {
485 return notificationManager.getNotification("first-start-notification") != null;
493 * Starts the web interface and registers all toadlets.
495 public void start() {
498 /* notification templates. */
499 Template startupNotificationTemplate = loaders.loadTemplate("/templates/notify/startupNotification.html");
501 final TemplateNotification startupNotification = new TemplateNotification("startup-notification", startupNotificationTemplate);
502 notificationManager.addNotification(startupNotification);
504 ticker.schedule(new Runnable() {
508 startupNotification.dismiss();
510 }, 2, TimeUnit.MINUTES);
512 Template wotMissingNotificationTemplate = loaders.loadTemplate("/templates/notify/wotMissingNotification.html");
513 final TemplateNotification wotMissingNotification = new TemplateNotification("wot-missing-notification", wotMissingNotificationTemplate);
514 ticker.scheduleAtFixedRate(new Runnable() {
517 @SuppressWarnings("synthetic-access")
519 if (getCore().getIdentityManager().isConnected()) {
520 wotMissingNotification.dismiss();
522 notificationManager.addNotification(wotMissingNotification);
526 }, 15, 15, TimeUnit.SECONDS);
530 * Stops the web interface and unregisters all toadlets.
533 pageToadletRegistry.unregisterToadlets();
534 ticker.shutdownNow();
542 * Register all toadlets.
544 private void registerToadlets() {
545 Template postTemplate = loaders.loadTemplate("/templates/include/viewPost.html");
546 Template replyTemplate = loaders.loadTemplate("/templates/include/viewReply.html");
547 Template openSearchTemplate = loaders.loadTemplate("/templates/xml/OpenSearch.xml");
549 pageToadletRegistry.addPage(new RedirectPage<FreenetRequest>("", "index.html"));
550 pageToadletRegistry.addPage(new IndexPage(this, loaders, templateRenderer, postVisibilityFilter));
551 pageToadletRegistry.addPage(new NewPage(this, loaders, templateRenderer));
552 pageToadletRegistry.addPage(new CreateSonePage(this, loaders, templateRenderer));
553 pageToadletRegistry.addPage(new KnownSonesPage(this, loaders, templateRenderer));
554 pageToadletRegistry.addPage(new EditProfilePage(this, loaders, templateRenderer));
555 pageToadletRegistry.addPage(new EditProfileFieldPage(this, loaders, templateRenderer));
556 pageToadletRegistry.addPage(new DeleteProfileFieldPage(this, loaders, templateRenderer));
557 pageToadletRegistry.addPage(new CreatePostPage(this, loaders, templateRenderer));
558 pageToadletRegistry.addPage(new CreateReplyPage(this, loaders, templateRenderer));
559 pageToadletRegistry.addPage(new ViewSonePage(this, loaders, templateRenderer));
560 pageToadletRegistry.addPage(new ViewPostPage(this, loaders, templateRenderer));
561 pageToadletRegistry.addPage(new LikePage(this, loaders, templateRenderer));
562 pageToadletRegistry.addPage(new UnlikePage(this, loaders, templateRenderer));
563 pageToadletRegistry.addPage(new DeletePostPage(this, loaders, templateRenderer));
564 pageToadletRegistry.addPage(new DeleteReplyPage(this, loaders, templateRenderer));
565 pageToadletRegistry.addPage(new LockSonePage(this, loaders, templateRenderer));
566 pageToadletRegistry.addPage(new UnlockSonePage(this, loaders, templateRenderer));
567 pageToadletRegistry.addPage(new FollowSonePage(this, loaders, templateRenderer));
568 pageToadletRegistry.addPage(new UnfollowSonePage(this, loaders, templateRenderer));
569 pageToadletRegistry.addPage(new ImageBrowserPage(this, loaders, templateRenderer));
570 pageToadletRegistry.addPage(new CreateAlbumPage(this, loaders, templateRenderer));
571 pageToadletRegistry.addPage(new EditAlbumPage(this, loaders, templateRenderer));
572 pageToadletRegistry.addPage(new DeleteAlbumPage(this, loaders, templateRenderer));
573 pageToadletRegistry.addPage(new UploadImagePage(this, loaders, templateRenderer));
574 pageToadletRegistry.addPage(new EditImagePage(this, loaders, templateRenderer));
575 pageToadletRegistry.addPage(new DeleteImagePage(this, loaders, templateRenderer));
576 pageToadletRegistry.addPage(new MarkAsKnownPage(this, loaders, templateRenderer));
577 pageToadletRegistry.addPage(new BookmarkPage(this, loaders, templateRenderer));
578 pageToadletRegistry.addPage(new UnbookmarkPage(this, loaders, templateRenderer));
579 pageToadletRegistry.addPage(new BookmarksPage(this, loaders, templateRenderer));
580 pageToadletRegistry.addPage(new SearchPage(this, loaders, templateRenderer));
581 pageToadletRegistry.addPage(new DeleteSonePage(this, loaders, templateRenderer));
582 pageToadletRegistry.addPage(new LoginPage(this, loaders, templateRenderer));
583 pageToadletRegistry.addPage(new LogoutPage(this, loaders, templateRenderer));
584 pageToadletRegistry.addPage(new OptionsPage(this, loaders, templateRenderer));
585 pageToadletRegistry.addPage(new RescuePage(this, loaders, templateRenderer));
586 pageToadletRegistry.addPage(new AboutPage(this, loaders, templateRenderer, new PluginVersion(SonePlugin.getPluginVersion()), new PluginYear(sonePlugin.getYear()), new PluginHomepage(sonePlugin.getHomepage())));
587 pageToadletRegistry.addPage(new InvalidPage(this, loaders, templateRenderer));
588 pageToadletRegistry.addPage(new NoPermissionPage(this, loaders, templateRenderer));
589 pageToadletRegistry.addPage(new EmptyImageTitlePage(this, loaders, templateRenderer));
590 pageToadletRegistry.addPage(new EmptyAlbumTitlePage(this, loaders, templateRenderer));
591 pageToadletRegistry.addPage(new DismissNotificationPage(this, loaders, templateRenderer));
592 pageToadletRegistry.addPage(new DebugPage(this, loaders, templateRenderer));
593 pageToadletRegistry.addDebugPage(new MetricsPage(this, loaders, templateRenderer, metricRegistry));
594 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("css/", "/static/css/", "text/css"));
595 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("javascript/", "/static/javascript/", "text/javascript"));
596 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("images/", "/static/images/", "image/png"));
597 pageToadletRegistry.addPage(new TemplatePage<FreenetRequest>("OpenSearch.xml", "application/opensearchdescription+xml", templateContextFactory, openSearchTemplate));
598 pageToadletRegistry.addPage(new GetImagePage(this));
599 pageToadletRegistry.addPage(new GetTranslationAjaxPage(this));
600 pageToadletRegistry.addPage(new GetStatusAjaxPage(this, elementLoader, timeTextConverter, l10nFilter, TimeZone.getDefault()));
601 pageToadletRegistry.addPage(new GetNotificationsAjaxPage(this));
602 pageToadletRegistry.addPage(new DismissNotificationAjaxPage(this));
603 pageToadletRegistry.addPage(new CreatePostAjaxPage(this));
604 pageToadletRegistry.addPage(new CreateReplyAjaxPage(this));
605 pageToadletRegistry.addPage(new GetReplyAjaxPage(this, replyTemplate));
606 pageToadletRegistry.addPage(new GetPostAjaxPage(this, postTemplate));
607 pageToadletRegistry.addPage(new GetLinkedElementAjaxPage(this, elementLoader, linkedElementRenderFilter));
608 pageToadletRegistry.addPage(new GetTimesAjaxPage(this, timeTextConverter, l10nFilter, TimeZone.getDefault()));
609 pageToadletRegistry.addPage(new MarkAsKnownAjaxPage(this));
610 pageToadletRegistry.addPage(new DeletePostAjaxPage(this));
611 pageToadletRegistry.addPage(new DeleteReplyAjaxPage(this));
612 pageToadletRegistry.addPage(new LockSoneAjaxPage(this));
613 pageToadletRegistry.addPage(new UnlockSoneAjaxPage(this));
614 pageToadletRegistry.addPage(new FollowSoneAjaxPage(this));
615 pageToadletRegistry.addPage(new UnfollowSoneAjaxPage(this));
616 pageToadletRegistry.addPage(new EditAlbumAjaxPage(this));
617 pageToadletRegistry.addPage(new EditImageAjaxPage(this, parserFilter, shortenFilter, renderFilter));
618 pageToadletRegistry.addPage(new LikeAjaxPage(this));
619 pageToadletRegistry.addPage(new UnlikeAjaxPage(this));
620 pageToadletRegistry.addPage(new GetLikesAjaxPage(this));
621 pageToadletRegistry.addPage(new BookmarkAjaxPage(this));
622 pageToadletRegistry.addPage(new UnbookmarkAjaxPage(this));
623 pageToadletRegistry.addPage(new EditProfileFieldAjaxPage(this));
624 pageToadletRegistry.addPage(new DeleteProfileFieldAjaxPage(this));
625 pageToadletRegistry.addPage(new MoveProfileFieldAjaxPage(this));
627 pageToadletRegistry.registerToadlets();
631 * Returns all {@link Sone#isLocal() local Sone}s that are referenced by
632 * {@link SonePart}s in the given text (after parsing it using
633 * {@link SoneTextParser}).
637 * @return All mentioned local Sones
639 private Collection<Sone> getMentionedSones(String text) {
640 /* we need no context to find mentioned Sones. */
641 Set<Sone> mentionedSones = new HashSet<>();
642 for (Part part : soneTextParser.parse(text, null)) {
643 if (part instanceof SonePart) {
644 mentionedSones.add(((SonePart) part).getSone());
647 return Collections2.filter(mentionedSones, Sone.LOCAL_SONE_FILTER);
651 * Returns the Sone insert notification for the given Sone. If no
652 * notification for the given Sone exists, a new notification is created and
656 * The Sone to get the insert notification for
657 * @return The Sone insert notification
659 private TemplateNotification getSoneInsertNotification(Sone sone) {
660 synchronized (soneInsertNotifications) {
661 TemplateNotification templateNotification = soneInsertNotifications.get(sone);
662 if (templateNotification == null) {
663 templateNotification = new TemplateNotification(loaders.loadTemplate("/templates/notify/soneInsertNotification.html"));
664 templateNotification.set("insertSone", sone);
665 soneInsertNotifications.put(sone, templateNotification);
667 return templateNotification;
671 private boolean localSoneMentionedInNewPostOrReply(Post post) {
672 if (!post.getSone().isLocal()) {
673 if (!getMentionedSones(post.getText()).isEmpty() && !post.isKnown()) {
677 for (PostReply postReply : getCore().getReplies(post.getId())) {
678 if (postReply.getSone().isLocal()) {
681 if (!getMentionedSones(postReply.getText()).isEmpty() && !postReply.isKnown()) {
693 * Notifies the web interface that a new {@link Sone} was found.
695 * @param newSoneFoundEvent
699 public void newSoneFound(NewSoneFoundEvent newSoneFoundEvent) {
700 newSoneNotification.add(newSoneFoundEvent.getSone());
701 if (!hasFirstStartNotification()) {
702 notificationManager.addNotification(newSoneNotification);
707 * Notifies the web interface that a new {@link Post} was found.
709 * @param newPostFoundEvent
713 public void newPostFound(NewPostFoundEvent newPostFoundEvent) {
714 Post post = newPostFoundEvent.getPost();
715 boolean isLocal = post.getSone().isLocal();
717 localPostNotification.add(post);
719 newPostNotification.add(post);
721 if (!hasFirstStartNotification()) {
722 notificationManager.addNotification(isLocal ? localPostNotification : newPostNotification);
723 if (!getMentionedSones(post.getText()).isEmpty() && !isLocal) {
724 mentionNotification.add(post);
725 notificationManager.addNotification(mentionNotification);
728 getCore().markPostKnown(post);
733 * Notifies the web interface that a new {@link PostReply} was found.
735 * @param newPostReplyFoundEvent
739 public void newReplyFound(NewPostReplyFoundEvent newPostReplyFoundEvent) {
740 PostReply reply = newPostReplyFoundEvent.getPostReply();
741 boolean isLocal = reply.getSone().isLocal();
743 localReplyNotification.add(reply);
745 newReplyNotification.add(reply);
747 if (!hasFirstStartNotification()) {
748 notificationManager.addNotification(isLocal ? localReplyNotification : newReplyNotification);
749 if (reply.getPost().isPresent() && localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
750 mentionNotification.add(reply.getPost().get());
751 notificationManager.addNotification(mentionNotification);
754 getCore().markReplyKnown(reply);
759 * Notifies the web interface that a {@link Sone} was marked as known.
761 * @param markSoneKnownEvent
765 public void markSoneKnown(MarkSoneKnownEvent markSoneKnownEvent) {
766 newSoneNotification.remove(markSoneKnownEvent.getSone());
770 public void markPostKnown(MarkPostKnownEvent markPostKnownEvent) {
771 removePost(markPostKnownEvent.getPost());
775 public void markReplyKnown(MarkPostReplyKnownEvent markPostReplyKnownEvent) {
776 removeReply(markPostReplyKnownEvent.getPostReply());
780 public void soneRemoved(SoneRemovedEvent soneRemovedEvent) {
781 newSoneNotification.remove(soneRemovedEvent.getSone());
785 public void postRemoved(PostRemovedEvent postRemovedEvent) {
786 removePost(postRemovedEvent.getPost());
789 private void removePost(Post post) {
790 newPostNotification.remove(post);
791 localPostNotification.remove(post);
792 if (!localSoneMentionedInNewPostOrReply(post)) {
793 mentionNotification.remove(post);
798 public void replyRemoved(PostReplyRemovedEvent postReplyRemovedEvent) {
799 removeReply(postReplyRemovedEvent.getPostReply());
802 private void removeReply(PostReply reply) {
803 newReplyNotification.remove(reply);
804 localReplyNotification.remove(reply);
805 if (reply.getPost().isPresent() && !localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
806 mentionNotification.remove(reply.getPost().get());
811 * Notifies the web interface that a Sone was locked.
813 * @param soneLockedEvent
817 public void soneLocked(SoneLockedEvent soneLockedEvent) {
818 final Sone sone = soneLockedEvent.getSone();
819 ScheduledFuture<?> tickerObject = ticker.schedule(new Runnable() {
822 @SuppressWarnings("synthetic-access")
824 lockedSonesNotification.add(sone);
825 notificationManager.addNotification(lockedSonesNotification);
827 }, 5, TimeUnit.MINUTES);
828 lockedSonesTickerObjects.put(sone, tickerObject);
832 * Notifies the web interface that a Sone was unlocked.
834 * @param soneUnlockedEvent
838 public void soneUnlocked(SoneUnlockedEvent soneUnlockedEvent) {
839 lockedSonesNotification.remove(soneUnlockedEvent.getSone());
840 lockedSonesTickerObjects.remove(soneUnlockedEvent.getSone()).cancel(false);
844 * Notifies the web interface that a {@link Sone} is being inserted.
846 * @param soneInsertingEvent
850 public void soneInserting(SoneInsertingEvent soneInsertingEvent) {
851 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertingEvent.getSone());
852 soneInsertNotification.set("soneStatus", "inserting");
853 if (soneInsertingEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
854 notificationManager.addNotification(soneInsertNotification);
859 * Notifies the web interface that a {@link Sone} was inserted.
861 * @param soneInsertedEvent
865 public void soneInserted(SoneInsertedEvent soneInsertedEvent) {
866 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertedEvent.getSone());
867 soneInsertNotification.set("soneStatus", "inserted");
868 soneInsertNotification.set("insertDuration", soneInsertedEvent.getInsertDuration() / 1000);
869 if (soneInsertedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
870 notificationManager.addNotification(soneInsertNotification);
875 * Notifies the web interface that a {@link Sone} insert was aborted.
877 * @param soneInsertAbortedEvent
881 public void soneInsertAborted(SoneInsertAbortedEvent soneInsertAbortedEvent) {
882 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertAbortedEvent.getSone());
883 soneInsertNotification.set("soneStatus", "insert-aborted");
884 soneInsertNotification.set("insert-error", soneInsertAbortedEvent.getCause());
885 if (soneInsertAbortedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
886 notificationManager.addNotification(soneInsertNotification);
891 * Notifies the web interface that a new Sone version was found.
893 * @param updateFoundEvent
897 public void updateFound(UpdateFoundEvent updateFoundEvent) {
898 newVersionNotification.set("latestVersion", updateFoundEvent.getVersion());
899 newVersionNotification.set("latestEdition", updateFoundEvent.getLatestEdition());
900 newVersionNotification.set("releaseTime", updateFoundEvent.getReleaseTime());
901 newVersionNotification.set("disruptive", updateFoundEvent.isDisruptive());
902 notificationManager.addNotification(newVersionNotification);
906 * Notifies the web interface that an image insert was started
908 * @param imageInsertStartedEvent
912 public void imageInsertStarted(ImageInsertStartedEvent imageInsertStartedEvent) {
913 insertingImagesNotification.add(imageInsertStartedEvent.getImage());
914 notificationManager.addNotification(insertingImagesNotification);
918 * Notifies the web interface that an {@link Image} insert was aborted.
920 * @param imageInsertAbortedEvent
924 public void imageInsertAborted(ImageInsertAbortedEvent imageInsertAbortedEvent) {
925 insertingImagesNotification.remove(imageInsertAbortedEvent.getImage());
929 * Notifies the web interface that an {@link Image} insert is finished.
931 * @param imageInsertFinishedEvent
935 public void imageInsertFinished(ImageInsertFinishedEvent imageInsertFinishedEvent) {
936 insertingImagesNotification.remove(imageInsertFinishedEvent.getImage());
937 insertedImagesNotification.add(imageInsertFinishedEvent.getImage());
938 notificationManager.addNotification(insertedImagesNotification);
942 * Notifies the web interface that an {@link Image} insert has failed.
944 * @param imageInsertFailedEvent
948 public void imageInsertFailed(ImageInsertFailedEvent imageInsertFailedEvent) {
949 insertingImagesNotification.remove(imageInsertFailedEvent.getImage());
950 imageInsertFailedNotification.add(imageInsertFailedEvent.getImage());
951 notificationManager.addNotification(imageInsertFailedNotification);
955 public void debugActivated(@Nonnull DebugActivatedEvent debugActivatedEvent) {
956 pageToadletRegistry.activateDebugMode();